mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-22 17:31:03 +01:00
optimized file upload
This commit is contained in:
@@ -37,7 +37,7 @@ RUN chmod 777 /tmp \
|
||||
&& ln -s /usr/bin/python3 /usr/local/bin/python
|
||||
|
||||
# install seaweedfs
|
||||
RUN wget https://github.com/chrislusf/seaweedfs/releases/download/2.48/linux_amd64.tar.gz \
|
||||
RUN wget https://github.com/chrislusf/seaweedfs/releases/download/2.59/linux_amd64.tar.gz \
|
||||
&& tar -zxf linux_amd64.tar.gz \
|
||||
&& cp weed /usr/local/bin
|
||||
|
||||
@@ -46,9 +46,6 @@ RUN pip install scrapy pymongo bs4 requests crawlab-sdk
|
||||
|
||||
# add files
|
||||
COPY ./backend/conf /app/backend/conf
|
||||
#COPY ./backend/data /app/backend/data
|
||||
#COPY ./backend/scripts /app/backend/scripts
|
||||
#COPY ./backend/template /app/backend/template
|
||||
COPY ./nginx /app/nginx
|
||||
COPY ./docker_init.sh /app/docker_init.sh
|
||||
|
||||
|
||||
@@ -37,15 +37,16 @@ RUN chmod 777 /tmp \
|
||||
&& ln -s /usr/bin/pip3 /usr/local/bin/pip \
|
||||
&& ln -s /usr/bin/python3 /usr/local/bin/python
|
||||
|
||||
# install seaweedfs
|
||||
RUN wget https://github.com/chrislusf/seaweedfs/releases/download/2.59/linux_amd64.tar.gz \
|
||||
&& tar -zxf linux_amd64.tar.gz \
|
||||
&& cp weed /usr/local/bin
|
||||
|
||||
# install backend
|
||||
RUN pip install scrapy pymongo bs4 requests crawlab-sdk
|
||||
|
||||
# add files
|
||||
COPY ./backend/conf /app/backend/conf
|
||||
#COPY ./backend/data /app/backend/data
|
||||
#COPY ./backend/scripts /app/backend/scripts
|
||||
#COPY ./backend/template /app/backend/template
|
||||
COPY ./nginx /app/nginx
|
||||
COPY ./docker_init.sh /app/docker_init.sh
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/crawlab-team/crawlab-core/interfaces"
|
||||
"github.com/crawlab-team/crawlab-core/middlewares"
|
||||
"github.com/crawlab-team/crawlab-core/routes"
|
||||
"github.com/crawlab-team/crawlab-db/mongo"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/viper"
|
||||
"net"
|
||||
@@ -25,9 +24,6 @@ type Api struct {
|
||||
}
|
||||
|
||||
func (app *Api) Init() {
|
||||
// initialize mongo
|
||||
_ = initModule("mongo", mongo.InitMongo)
|
||||
|
||||
// initialize controllers
|
||||
_ = initModule("controllers", controllers.InitControllers)
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@@ -33,20 +33,23 @@ func init() {
|
||||
|
||||
func initConfig() {
|
||||
if cfgFile != "" {
|
||||
// Use config file from the flag.
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
// Find home directory.
|
||||
home, err := homedir.Dir()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Search config in home directory with name ".cobra" (without extension).
|
||||
viper.AddConfigPath(home)
|
||||
viper.SetConfigName(".cobra")
|
||||
viper.AddConfigPath("./conf")
|
||||
viper.SetConfigName("config")
|
||||
}
|
||||
|
||||
// file format as yaml
|
||||
viper.SetConfigType("yaml")
|
||||
|
||||
// auto load env
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// env prefix as CRAWLAB
|
||||
viper.SetEnvPrefix("CRAWLAB")
|
||||
replacer := strings.NewReplacer(".", "_")
|
||||
viper.SetEnvKeyReplacer(replacer)
|
||||
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
fmt.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@ module crawlab
|
||||
|
||||
go 1.15
|
||||
|
||||
replace (
|
||||
github.com/crawlab-team/crawlab-core => /Users/marvzhang/projects/crawlab-team/crawlab-core
|
||||
github.com/crawlab-team/goseaweedfs => /Users/marvzhang/projects/crawlab-team/goseaweedfs
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/apex/log v1.9.0
|
||||
github.com/crawlab-team/crawlab-core v0.6.0-beta.20210716
|
||||
github.com/crawlab-team/crawlab-db v0.1.0
|
||||
github.com/crawlab-team/crawlab-core v0.6.0-beta.20210716.1817
|
||||
github.com/crawlab-team/go-trace v0.1.0
|
||||
github.com/crawlab-team/goseaweedfs v0.2.0
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/spf13/cobra v1.1.3
|
||||
github.com/spf13/viper v1.7.1
|
||||
go.mongodb.org/mongo-driver v1.6.0 // indirect
|
||||
|
||||
@@ -29,6 +29,7 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSi
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -70,11 +71,11 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/crawlab-team/crawlab-core v0.0.1/go.mod h1:6dJHMvrmIJbfYHhYNeGZkGOLEBvur+yGiFzLCRXx92k=
|
||||
github.com/crawlab-team/crawlab-core v0.6.0-beta.20210716 h1:9P/XryMK4yyIcxLpeI2f0RNqDw3lF6HjgH5DUzywxUk=
|
||||
github.com/crawlab-team/crawlab-core v0.6.0-beta.20210716/go.mod h1:OXO0hN3YwKN0hnJoa6bJ/DGWZjE4XzVAv+pfmCzOmds=
|
||||
github.com/crawlab-team/crawlab-core v0.6.0-beta.20210716.1817 h1:YWLhmiDxvDEFjk+n1D5eJUv4I7bGbqb4nvu/zgXRhB0=
|
||||
github.com/crawlab-team/crawlab-core v0.6.0-beta.20210716.1817/go.mod h1:okTuM1EtQNk6Rl81GSW3rS5U6q4GbzVc3FsNGlvc7r8=
|
||||
github.com/crawlab-team/crawlab-db v0.0.2/go.mod h1:o7o4rbcyAWlFGHg9VS7V7tM/GqRq+N2mnAXO71cZA78=
|
||||
github.com/crawlab-team/crawlab-db v0.1.0 h1:6dTVNb5+7cDkH8fkKOkFALk8laWNOuorYm3ZEKsvFLI=
|
||||
github.com/crawlab-team/crawlab-db v0.1.0/go.mod h1:t0VidSjXKzQgACqNSQV5wusXncFtL6lGEiQTbLfNR04=
|
||||
github.com/crawlab-team/crawlab-db v0.1.1 h1:156h2fbbFKXAHs1mxprqRFC8zs2nrdyaG9JKG7patVw=
|
||||
github.com/crawlab-team/crawlab-db v0.1.1/go.mod h1:t0VidSjXKzQgACqNSQV5wusXncFtL6lGEiQTbLfNR04=
|
||||
github.com/crawlab-team/crawlab-fs v0.0.0/go.mod h1:k2VXprQspLAmbgO5sSpqMjg/xP4iKDkW4RyTWY8eTZM=
|
||||
github.com/crawlab-team/crawlab-fs v0.1.0 h1:iKSJJY4Wvea8Qss+zC/tLiZ371VeV75Z3cuqlsxydzY=
|
||||
github.com/crawlab-team/crawlab-fs v0.1.0/go.mod h1:dOE0TeWPDz9krwzt1H72rjj0Fn/aHe53yn7GoOZHD0s=
|
||||
|
||||
@@ -4,7 +4,8 @@ services:
|
||||
image: crawlabteam/crawlab:latest
|
||||
container_name: crawlab_master
|
||||
environment:
|
||||
CRAWLAB_MONGO_HOST: 'mongo'
|
||||
CRAWLAB_SERVER_MASTER: Y
|
||||
CRAWLAB_MONGO_HOST: mongo
|
||||
ports:
|
||||
- "8080:8080" # frontend port mapping 前端端口映射
|
||||
depends_on:
|
||||
|
||||
@@ -51,4 +51,8 @@ Host *
|
||||
EOF
|
||||
|
||||
# start backend
|
||||
crawlab-server
|
||||
if [ "${CRAWLAB_SERVER_MASTER}" = "Y" ];
|
||||
crawlab-server master
|
||||
then
|
||||
crawlab-server worker
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<div :style="style" class="pie-chart">
|
||||
<div v-if="isEmpty" class="empty-placeholder">
|
||||
No Data Available
|
||||
</div>
|
||||
<div ref="elRef" class="echarts-element"></div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -46,8 +49,16 @@ export default defineComponent({
|
||||
const elRef = ref<HTMLDivElement>();
|
||||
const chart = ref<ECharts>();
|
||||
|
||||
const isEmpty = computed<boolean>(() => {
|
||||
const {config} = props;
|
||||
const {data} = config;
|
||||
if (!data) return true;
|
||||
return data.length === 0;
|
||||
|
||||
});
|
||||
|
||||
const getSeriesData = (data: StatsResult[], key?: string) => {
|
||||
const {valueKey, labelKey, config} = props;
|
||||
const {valueKey, labelKey} = props;
|
||||
const _valueKey = !key ? valueKey : key;
|
||||
|
||||
if (_valueKey) {
|
||||
@@ -69,7 +80,7 @@ export default defineComponent({
|
||||
|
||||
const seriesItem = {
|
||||
type: 'pie',
|
||||
data: getSeriesData(data),
|
||||
data: getSeriesData(data || []),
|
||||
radius: ['40%', '70%'],
|
||||
alignTo: 'labelLine',
|
||||
} as EChartSeries;
|
||||
@@ -119,6 +130,7 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
return {
|
||||
isEmpty,
|
||||
style,
|
||||
elRef,
|
||||
render,
|
||||
@@ -128,7 +140,22 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/variables";
|
||||
|
||||
.pie-chart {
|
||||
position: relative;
|
||||
|
||||
.empty-placeholder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.echarts-element {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
175
frontend/src/components/file/FileUpload.vue
Normal file
175
frontend/src/components/file/FileUpload.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div class="file-upload">
|
||||
<div class="mode-select">
|
||||
<el-radio-group v-model="internalMode" @change="onModeChange">
|
||||
<el-radio
|
||||
v-for="{value, label} in modeOptions"
|
||||
:key="value"
|
||||
:label="value"
|
||||
>
|
||||
{{ label }}
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
|
||||
<template v-if="mode === FILE_UPLOAD_MODE_FILES">
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:on-change="onFileChange"
|
||||
:http-request="() => {}"
|
||||
drag
|
||||
multiple
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">Drag files here, or <em>click to upload</em></div>
|
||||
</el-upload>
|
||||
<input v-bind="getInputProps()">
|
||||
</template>
|
||||
<template v-else-if="mode === FILE_UPLOAD_MODE_DIR">
|
||||
<div class="folder-upload">
|
||||
<Button @click="open">
|
||||
<i class="fa fa-folder"></i>
|
||||
Click to Select Folder to Upload
|
||||
</Button>
|
||||
<template v-if="!!dirInfo?.dirName && dirInfo?.fileCount">
|
||||
<Tag
|
||||
type="primary"
|
||||
class="info-tag"
|
||||
:label="dirInfo?.dirName"
|
||||
:icon="['fa', 'folder']"
|
||||
tooltip="Folder Name"
|
||||
/>
|
||||
<Tag
|
||||
type="success"
|
||||
class="info-tag"
|
||||
:label="dirInfo?.fileCount"
|
||||
:icon="['fa', 'hashtag']"
|
||||
tooltip="Files Count"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<input v-bind="getInputProps()" webkitdirectory>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, onBeforeMount, ref, watch} from 'vue';
|
||||
import {FILE_UPLOAD_MODE_DIR, FILE_UPLOAD_MODE_FILES} from '@/constants/file';
|
||||
import {ElUpload} from 'element-plus/lib/el-upload/src/upload.type';
|
||||
import {UploadFile} from 'element-plus/packages/upload/src/upload.type';
|
||||
import Button from '@/components/button/Button.vue';
|
||||
import Tag from '@/components/tag/Tag.vue';
|
||||
import {plainClone} from '@/utils/object';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FileUpload',
|
||||
components: {
|
||||
Tag,
|
||||
Button,
|
||||
},
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
},
|
||||
getInputProps: {
|
||||
type: Function,
|
||||
},
|
||||
open: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'mode-change',
|
||||
'files-change',
|
||||
],
|
||||
setup(props: FileUploadProps, {emit}) {
|
||||
const modeOptions: FileUploadModeOption[] = [
|
||||
{
|
||||
label: 'Files',
|
||||
value: FILE_UPLOAD_MODE_FILES,
|
||||
},
|
||||
{
|
||||
label: 'Folder',
|
||||
value: FILE_UPLOAD_MODE_DIR,
|
||||
},
|
||||
];
|
||||
const internalMode = ref<string>();
|
||||
|
||||
const uploadRef = ref<ElUpload>();
|
||||
|
||||
const dirPath = ref<string>();
|
||||
|
||||
watch(() => props.mode, () => {
|
||||
internalMode.value = props.mode;
|
||||
uploadRef.value?.clearFiles();
|
||||
});
|
||||
|
||||
const onFileChange = (file: UploadFile, fileList: UploadFile[]) => {
|
||||
emit('files-change', fileList.map(f => f.raw));
|
||||
};
|
||||
|
||||
const clearFiles = () => {
|
||||
uploadRef.value?.clearFiles();
|
||||
};
|
||||
|
||||
const onModeChange = (mode: string) => {
|
||||
emit('mode-change', mode);
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
const {mode} = props;
|
||||
internalMode.value = mode;
|
||||
});
|
||||
|
||||
const dirInfo = ref<FileUploadDirInfo>();
|
||||
|
||||
const setDirInfo = (info: FileUploadDirInfo) => {
|
||||
console.debug(info);
|
||||
dirInfo.value = plainClone(info);
|
||||
};
|
||||
|
||||
return {
|
||||
uploadRef,
|
||||
FILE_UPLOAD_MODE_FILES,
|
||||
FILE_UPLOAD_MODE_DIR,
|
||||
modeOptions,
|
||||
internalMode,
|
||||
dirPath,
|
||||
onFileChange,
|
||||
clearFiles,
|
||||
onModeChange,
|
||||
dirInfo,
|
||||
setDirInfo,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.file-upload {
|
||||
.mode-select {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-upload {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.folder-upload {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.file-upload >>> .el-upload,
|
||||
.file-upload >>> .el-upload .el-upload-dragger {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.file-upload >>> .folder-upload .info-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -15,8 +15,16 @@
|
||||
<!-- ./Input -->
|
||||
|
||||
<!-- Button -->
|
||||
<Button v-if="buttonLabel" :disabled="disabled" :size="size" :type="buttonType" class="button" no-margin>
|
||||
<Icon v-if="buttonIcon" :icon="buttonIcon"/>
|
||||
<Button
|
||||
v-if="buttonLabel"
|
||||
:disabled="disabled"
|
||||
:size="size"
|
||||
:type="buttonType"
|
||||
class="button"
|
||||
no-margin
|
||||
@click="onClick"
|
||||
>
|
||||
<Icon v-if="buttonIcon" :icon="buttonIcon" />
|
||||
{{ buttonLabel }}
|
||||
</Button>
|
||||
<template v-else-if="buttonIcon">
|
||||
@@ -27,6 +35,7 @@
|
||||
:size="size"
|
||||
:type="buttonType"
|
||||
class="button"
|
||||
@click="onClick"
|
||||
/>
|
||||
<IconButton
|
||||
v-else
|
||||
@@ -35,6 +44,7 @@
|
||||
:size="size"
|
||||
:type="buttonType"
|
||||
class="button"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
<!-- ./Button -->
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
export const FILE_ROOT = '~';
|
||||
|
||||
export const FILE_UPLOAD_MODE_FILES = 'files';
|
||||
export const FILE_UPLOAD_MODE_DIR = 'dir';
|
||||
|
||||
15
frontend/src/interfaces/components/file/FileUpload.d.ts
vendored
Normal file
15
frontend/src/interfaces/components/file/FileUpload.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
interface FileUploadProps {
|
||||
mode?: string;
|
||||
getInputProps?: Function;
|
||||
open?: Function;
|
||||
}
|
||||
|
||||
interface FileUploadModeOption {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface FileUploadDirInfo {
|
||||
dirName: string;
|
||||
fileCount: number;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router';
|
||||
import {createRouter, createWebHashHistory, RouteRecordRaw} from 'vue-router';
|
||||
import login from '@/router/login';
|
||||
import home from '@/router/home';
|
||||
import node from '@/router/node';
|
||||
@@ -44,7 +44,7 @@ export const menuItems: MenuItem[] = [
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
history: createWebHashHistory(process.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
import axios, {AxiosRequestConfig} from 'axios';
|
||||
import {ElMessageBox} from 'element-plus';
|
||||
import router from '@/router';
|
||||
|
||||
// TODO: request interception
|
||||
|
||||
// TODO: response interception
|
||||
let msgBoxVisible = false;
|
||||
axios.interceptors.response.use(res => {
|
||||
return res;
|
||||
}, err => {
|
||||
const status = err?.response?.status;
|
||||
if (status === 401) {
|
||||
if (msgBoxVisible) return;
|
||||
msgBoxVisible = true;
|
||||
ElMessageBox.confirm('You seem to have been logged-out, try to login again?', 'Unauthorized', {type: 'warning'})
|
||||
.then(_ => router.push('/login'))
|
||||
.finally(() => {
|
||||
msgBoxVisible = false;
|
||||
});
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
const useRequest = () => {
|
||||
// implementation
|
||||
|
||||
@@ -228,7 +228,7 @@ export default defineComponent({
|
||||
// TODO: filter by date range?
|
||||
const {start, end} = dateRange.value;
|
||||
const res = await get(`/stats/daily`);
|
||||
dailyConfig.value.data = spanDateRange(start, end, res.data, 'date');
|
||||
dailyConfig.value.data = spanDateRange(start, end, res.data || [], 'date');
|
||||
};
|
||||
|
||||
const getTasks = async () => {
|
||||
|
||||
@@ -94,12 +94,13 @@
|
||||
<img alt="github-stars" src="https://img.shields.io/github/stars/crawlab-team/crawlab?logo=github">
|
||||
</a>
|
||||
</div>
|
||||
<div class="lang">
|
||||
<!-- TODO: implement -->
|
||||
<div v-if="false" class="lang">
|
||||
<span :class="lang==='zh'?'active':''" @click="setLang('zh')">中文</span>
|
||||
|
|
||||
<span :class="lang==='en'?'active':''" @click="setLang('en')">English</span>
|
||||
</div>
|
||||
<div class="documentation">
|
||||
<div v-if="false" class="documentation">
|
||||
<a href="https://docs.crawlab.cn" target="_blank">{{ $t('Documentation') }}</a>
|
||||
</div>
|
||||
<div class="mobile-warning" v-if="isShowMobileWarning">
|
||||
@@ -129,8 +130,6 @@ const {
|
||||
export default defineComponent({
|
||||
name: 'Login',
|
||||
setup() {
|
||||
const {tm} = useI18n();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const router = useRouter();
|
||||
@@ -150,7 +149,7 @@ export default defineComponent({
|
||||
|
||||
const validateUsername = (rule: any, value: any, callback: any) => {
|
||||
if (!isValidUsername(value)) {
|
||||
callback(new Error(tm('Please enter the correct username')));
|
||||
callback(new Error('Please enter the correct username'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -158,7 +157,7 @@ export default defineComponent({
|
||||
|
||||
const validatePass = (rule: any, value: any, callback: any) => {
|
||||
if (value.length < 5) {
|
||||
callback(new Error(tm('Password length should be no shorter than 5')));
|
||||
callback(new Error('Password length should be no shorter than 5'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
@@ -167,7 +166,7 @@ export default defineComponent({
|
||||
const validateConfirmPass = (rule: any, value: any, callback: any) => {
|
||||
if (!isSignup.value) return callback();
|
||||
if (value !== loginForm.value.password) {
|
||||
callback(new Error(tm('Two passwords must be the same')));
|
||||
callback(new Error('Two passwords must be the same'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
<FaIconButton :icon="['far', 'star']" plain tooltip="Favorite" type="warning"/>
|
||||
</NavActionItem>
|
||||
</NavActionGroup>
|
||||
<NavActionGroup>
|
||||
<!--TODO: implement-->
|
||||
<NavActionGroup v-if="false">
|
||||
<NavActionFaIcon :icon="['fab', 'git-alt']"/>
|
||||
<NavActionItem>
|
||||
<FaIconButton :icon="['fa', 'upload']" tooltip="Upload File" type="primary"/>
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
<template>
|
||||
<NavActionGroup>
|
||||
<NavActionFaIcon :icon="['fa', 'laptop-code']" tooltip="File Editor Actions"/>
|
||||
<NavActionFaIcon :icon="['fa', 'laptop-code']" tooltip="File Editor Actions" />
|
||||
<NavActionItem>
|
||||
<FaIconButton :icon="['fa', 'upload']" tooltip="Upload Files" type="primary" @click="onOpenFiles"/>
|
||||
<input v-bind="getInputProps()">
|
||||
<FaIconButton :icon="['fa', 'cog']" tooltip="File Editor Settings" type="info" @click="onOpenFilesSettings"/>
|
||||
<FaIconButton :icon="['fa', 'upload']" tooltip="Upload Files" type="primary" @click="onClickUpload" />
|
||||
<FaIconButton :icon="['fa', 'cog']" tooltip="File Editor Settings" type="info" @click="onOpenFilesSettings" />
|
||||
</NavActionItem>
|
||||
</NavActionGroup>
|
||||
|
||||
<Dialog
|
||||
:visible="fileUploadVisible"
|
||||
title="Files Upload"
|
||||
:confirm-loading="confirmLoading"
|
||||
:confirm-disabled="confirmDisabled"
|
||||
@close="onUploadClose"
|
||||
@confirm="onUploadConfirm"
|
||||
>
|
||||
<FileUpload
|
||||
ref="fileUploadRef"
|
||||
:mode="mode"
|
||||
:get-input-props="getInputProps"
|
||||
:open="open"
|
||||
@mode-change="onModeChange"
|
||||
@files-change="onFilesChange"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {computed, defineComponent} from 'vue';
|
||||
import {computed, defineComponent, ref} from 'vue';
|
||||
import {useStore} from 'vuex';
|
||||
import NavActionGroup from '@/components/nav/NavActionGroup.vue';
|
||||
import NavActionItem from '@/components/nav/NavActionItem.vue';
|
||||
@@ -19,10 +36,16 @@ import NavActionFaIcon from '@/components/nav/NavActionFaIcon.vue';
|
||||
import {useDropzone} from 'vue3-dropzone';
|
||||
import useSpiderService from '@/services/spider/spiderService';
|
||||
import {useRoute} from 'vue-router';
|
||||
import FileUpload from '@/components/file/FileUpload.vue';
|
||||
import Dialog from '@/components/dialog/Dialog.vue';
|
||||
import {ElMessage} from 'element-plus';
|
||||
import {FILE_UPLOAD_MODE_DIR, FILE_UPLOAD_MODE_FILES} from '@/constants/file';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SpiderDetailActionsFiles',
|
||||
components: {
|
||||
Dialog,
|
||||
FileUpload,
|
||||
NavActionFaIcon,
|
||||
FaIconButton,
|
||||
NavActionGroup,
|
||||
@@ -36,33 +59,101 @@ export default defineComponent({
|
||||
const storeNamespace = 'file';
|
||||
const store = useStore();
|
||||
|
||||
const id = computed<string>(() => route.params.id as string);
|
||||
|
||||
const {
|
||||
listRootDir,
|
||||
saveFileBinary,
|
||||
} = useSpiderService(store);
|
||||
|
||||
const mode = ref<string>(FILE_UPLOAD_MODE_FILES);
|
||||
const files = ref<File[]>();
|
||||
|
||||
const id = computed<string>(() => route.params.id as string);
|
||||
|
||||
const fileUploadRef = ref<typeof FileUpload>();
|
||||
|
||||
const confirmLoading = ref<boolean>(false);
|
||||
const confirmDisabled = computed<boolean>(() => !files.value?.length);
|
||||
|
||||
const onOpenFilesSettings = () => {
|
||||
store.commit(`${storeNamespace}/setEditorSettingsDialogVisible`, true);
|
||||
};
|
||||
|
||||
const uploadFiles = async () => {
|
||||
if (!files.value) return;
|
||||
await Promise.all(files.value.map(f => {
|
||||
return saveFileBinary(id.value, f.name, f as File);
|
||||
}));
|
||||
await listRootDir(id.value);
|
||||
};
|
||||
|
||||
const {
|
||||
getInputProps,
|
||||
open: onOpenFiles,
|
||||
open,
|
||||
} = useDropzone({
|
||||
onDrop: async (files: InputFile[]) => {
|
||||
await Promise.all(files.map(f => {
|
||||
return saveFileBinary(id.value, f.path as string, f as File);
|
||||
}));
|
||||
await listRootDir(id.value);
|
||||
onDrop: async (fileList: InputFile[]) => {
|
||||
if (mode.value === FILE_UPLOAD_MODE_DIR) {
|
||||
if (!fileList.length) return;
|
||||
const f = fileList[0];
|
||||
const dirName = f.path?.split('/')[0];
|
||||
const fileCount = fileList.length;
|
||||
const dirInfo = {
|
||||
dirName,
|
||||
fileCount,
|
||||
} as FileUploadDirInfo;
|
||||
console.debug(fileList, dirInfo);
|
||||
fileUploadRef.value?.setDirInfo(dirInfo);
|
||||
}
|
||||
files.value = fileList as File[];
|
||||
},
|
||||
});
|
||||
|
||||
const fileUploadVisible = ref<boolean>(false);
|
||||
|
||||
const onClickUpload = () => {
|
||||
fileUploadVisible.value = true;
|
||||
};
|
||||
|
||||
const onModeChange = (value: string) => {
|
||||
mode.value = value;
|
||||
};
|
||||
|
||||
const onFilesChange = (fileList: File[]) => {
|
||||
files.value = fileList;
|
||||
};
|
||||
|
||||
const onUploadConfirm = async () => {
|
||||
confirmLoading.value = true;
|
||||
try {
|
||||
await uploadFiles();
|
||||
await ElMessage.success('Uploaded successfully');
|
||||
} catch (e) {
|
||||
await ElMessage.error(e);
|
||||
} finally {
|
||||
confirmLoading.value = false;
|
||||
fileUploadVisible.value = false;
|
||||
fileUploadRef.value?.clearFiles();
|
||||
}
|
||||
};
|
||||
|
||||
const onUploadClose = () => {
|
||||
fileUploadVisible.value = false;
|
||||
};
|
||||
|
||||
return {
|
||||
fileUploadRef,
|
||||
confirmLoading,
|
||||
confirmDisabled,
|
||||
onOpenFilesSettings,
|
||||
getInputProps,
|
||||
onOpenFiles,
|
||||
open,
|
||||
fileUploadVisible,
|
||||
onClickUpload,
|
||||
onUploadClose,
|
||||
onUploadConfirm,
|
||||
mode,
|
||||
files,
|
||||
onModeChange,
|
||||
onFilesChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user