mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-25 17:42:25 +01:00
* 增加Docker开发环境
* 更新Dockerfile构建文件,升级NodeJS依赖版本。 * 遵循ESLint重新格式化代码,修复部分警告 * 登录Token失效增加登出提示 * 网络请求问题增加错误错误提示 * 升级UI依赖库
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
<el-breadcrumb class="app-breadcrumb" separator="/">
|
||||
<transition-group name="breadcrumb">
|
||||
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
|
||||
<span v-if="item.redirect==='noredirect'||index==levelList.length-1"
|
||||
class="no-redirect">{{$t(item.meta.title) }}</span>
|
||||
<span
|
||||
v-if="item.redirect==='noredirect'||index==levelList.length-1"
|
||||
class="no-redirect"
|
||||
>{{ $t(item.meta.title) }}</span>
|
||||
<a v-else @click.prevent="handleLink(item)">{{ $t(item.meta.title) }}</a>
|
||||
</el-breadcrumb-item>
|
||||
</transition-group>
|
||||
@@ -11,64 +13,64 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
import pathToRegexp from 'path-to-regexp'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route () {
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
levelList: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getBreadcrumb()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.getBreadcrumb()
|
||||
},
|
||||
methods: {
|
||||
getBreadcrumb () {
|
||||
let matched = this.$route.matched.filter(item => item.name)
|
||||
|
||||
const first = matched[0]
|
||||
if (first && first.name !== 'Home') {
|
||||
matched = [{ path: '/home', meta: { title: 'Home' } }].concat(matched)
|
||||
}
|
||||
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
},
|
||||
pathCompile (path) {
|
||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||
const { params } = this.$route
|
||||
var toPath = pathToRegexp.compile(path)
|
||||
return toPath(params)
|
||||
},
|
||||
handleLink (item) {
|
||||
const { redirect } = item
|
||||
if (redirect) {
|
||||
this.$router.push(redirect)
|
||||
return
|
||||
}
|
||||
this.$router.push(this.getGoToPath(item))
|
||||
},
|
||||
getGoToPath (item) {
|
||||
if (item.path) {
|
||||
var path = item.path
|
||||
var startPos = path.indexOf(':')
|
||||
methods: {
|
||||
getBreadcrumb() {
|
||||
let matched = this.$route.matched.filter(item => item.name)
|
||||
|
||||
if (startPos !== -1) {
|
||||
var endPos = path.indexOf('/', startPos)
|
||||
var key = path.substring(startPos + 1, endPos)
|
||||
path = path.replace(':' + key, this.$route.params[key])
|
||||
return path
|
||||
const first = matched[0]
|
||||
if (first && first.name !== 'Home') {
|
||||
matched = [{ path: '/home', meta: { title: 'Home' }}].concat(matched)
|
||||
}
|
||||
}
|
||||
|
||||
return item.redirect || item.path
|
||||
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
|
||||
},
|
||||
pathCompile(path) {
|
||||
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
|
||||
const { params } = this.$route
|
||||
var toPath = pathToRegexp.compile(path)
|
||||
return toPath(params)
|
||||
},
|
||||
handleLink(item) {
|
||||
const { redirect } = item
|
||||
if (redirect) {
|
||||
this.$router.push(redirect)
|
||||
return
|
||||
}
|
||||
this.$router.push(this.getGoToPath(item))
|
||||
},
|
||||
getGoToPath(item) {
|
||||
if (item.path) {
|
||||
var path = item.path
|
||||
var startPos = path.indexOf(':')
|
||||
|
||||
if (startPos !== -1) {
|
||||
var endPos = path.indexOf('/', startPos)
|
||||
var key = path.substring(startPos + 1, endPos)
|
||||
path = path.replace(':' + key, this.$route.params[key])
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
return item.redirect || item.path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
|
||||
@@ -13,17 +13,22 @@
|
||||
width="580px"
|
||||
:before-close="beforeClose"
|
||||
>
|
||||
<div style="margin-bottom: 20px;">{{$t('Are you sure to run this spider?')}}</div>
|
||||
<el-form label-width="140px" :model="form" ref="form">
|
||||
<div style="margin-bottom: 20px;">{{ $t('Are you sure to run this spider?') }}</div>
|
||||
<el-form ref="form" label-width="140px" :model="form">
|
||||
<el-form-item :label="$t('Run Type')" prop="runType" required inline-message>
|
||||
<el-select v-model="form.runType" :placeholder="$t('Run Type')">
|
||||
<el-option value="all-nodes" :label="$t('All Nodes')"/>
|
||||
<el-option value="selected-nodes" :label="$t('Selected Nodes')"/>
|
||||
<el-option value="random" :label="$t('Random')"/>
|
||||
<el-option value="all-nodes" :label="$t('All Nodes')" />
|
||||
<el-option value="selected-nodes" :label="$t('Selected Nodes')" />
|
||||
<el-option value="random" :label="$t('Random')" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.runType === 'selected-nodes'" prop="nodeIds" :label="$t('Node')" required
|
||||
inline-message>
|
||||
<el-form-item
|
||||
v-if="form.runType === 'selected-nodes'"
|
||||
prop="nodeIds"
|
||||
:label="$t('Node')"
|
||||
required
|
||||
inline-message
|
||||
>
|
||||
<el-select v-model="form.nodeIds" :placeholder="$t('Node')" multiple clearable>
|
||||
<el-option
|
||||
v-for="op in nodeList"
|
||||
@@ -34,8 +39,13 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="spiderForm.is_scrapy && !multiple" :label="$t('Scrapy Spider')" prop="spider" required
|
||||
inline-message>
|
||||
<el-form-item
|
||||
v-if="spiderForm.is_scrapy && !multiple"
|
||||
:label="$t('Scrapy Spider')"
|
||||
prop="spider"
|
||||
required
|
||||
inline-message
|
||||
>
|
||||
<el-select v-model="form.spider" :placeholder="$t('Scrapy Spider')" :disabled="isLoading">
|
||||
<el-option
|
||||
v-for="s in spiderForm.spider_names"
|
||||
@@ -53,24 +63,24 @@
|
||||
inline-message
|
||||
>
|
||||
<el-select v-model="form.scrapy_log_level" :placeholder="$t('Scrapy Log Level')">
|
||||
<el-option value="INFO" label="INFO"/>
|
||||
<el-option value="DEBUG" label="DEBUG"/>
|
||||
<el-option value="WARN" label="WARN"/>
|
||||
<el-option value="ERROR" label="ERROR"/>
|
||||
<el-option value="INFO" label="INFO" />
|
||||
<el-option value="DEBUG" label="DEBUG" />
|
||||
<el-option value="WARN" label="WARN" />
|
||||
<el-option value="ERROR" label="ERROR" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="spiderForm.type === 'customized'" :label="$t('Parameters')" prop="param" inline-message>
|
||||
<template v-if="spiderForm.is_scrapy && !multiple">
|
||||
<el-input v-model="form.param" :placeholder="$t('Parameters')" class="param-input"/>
|
||||
<el-button type="primary" icon="el-icon-edit" class="param-btn" @click="onOpenParameters"/>
|
||||
<el-input v-model="form.param" :placeholder="$t('Parameters')" class="param-input" />
|
||||
<el-button type="primary" icon="el-icon-edit" class="param-btn" @click="onOpenParameters" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input v-model="form.param" :placeholder="$t('Parameters')"></el-input>
|
||||
<el-input v-model="form.param" :placeholder="$t('Parameters')" />
|
||||
</template>
|
||||
</el-form-item>
|
||||
<el-form-item class="checkbox-wrapper">
|
||||
<div>
|
||||
<el-checkbox v-model="isAllowDisclaimer"/>
|
||||
<el-checkbox v-model="isAllowDisclaimer" />
|
||||
<span v-if="lang === 'zh'" style="margin-left: 5px">
|
||||
我已阅读并同意
|
||||
<a href="javascript:" @click="onClickDisclaimer">
|
||||
@@ -86,19 +96,19 @@
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="!spiderForm.is_long_task && !multiple">
|
||||
<el-checkbox v-model="isRedirect"/>
|
||||
<span style="margin-left: 5px">{{$t('Redirect to task detail')}}</span>
|
||||
<el-checkbox v-model="isRedirect" />
|
||||
<span style="margin-left: 5px">{{ $t('Redirect to task detail') }}</span>
|
||||
</div>
|
||||
<div v-if="false">
|
||||
<el-checkbox v-model="isRetry"/>
|
||||
<span style="margin-left: 5px">{{$t('Retry (Maximum 5 Times)')}}</span>
|
||||
<el-checkbox v-model="isRetry" />
|
||||
<span style="margin-left: 5px">{{ $t('Retry (Maximum 5 Times)') }}</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button type="plain" size="small" @click="$emit('close')">{{$t('Cancel')}}</el-button>
|
||||
<el-button type="primary" size="small" @click="onConfirm" :disabled="isConfirmDisabled">
|
||||
{{$t('Confirm')}}
|
||||
<el-button type="plain" size="small" @click="$emit('close')">{{ $t('Cancel') }}</el-button>
|
||||
<el-button type="primary" size="small" :disabled="isConfirmDisabled" @click="onConfirm">
|
||||
{{ $t('Confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -106,222 +116,222 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import ParametersDialog from './ParametersDialog'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import ParametersDialog from './ParametersDialog'
|
||||
|
||||
export default {
|
||||
name: 'CrawlConfirmDialog',
|
||||
components: { ParametersDialog },
|
||||
props: {
|
||||
spiderId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
spiders: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
runType: 'random',
|
||||
nodeIds: undefined,
|
||||
spider: undefined,
|
||||
scrapy_log_level: 'INFO',
|
||||
param: '',
|
||||
nodeList: []
|
||||
export default {
|
||||
name: 'CrawlConfirmDialog',
|
||||
components: { ParametersDialog },
|
||||
props: {
|
||||
spiderId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isAllowDisclaimer: true,
|
||||
isRetry: false,
|
||||
isRedirect: true,
|
||||
isLoading: false,
|
||||
isParametersVisible: false,
|
||||
scrapySpidersNamesDict: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('setting', [
|
||||
'setting'
|
||||
]),
|
||||
...mapState('lang', [
|
||||
'lang'
|
||||
]),
|
||||
isConfirmDisabled () {
|
||||
if (this.isLoading) return true
|
||||
if (!this.isAllowDisclaimer) return true
|
||||
return false
|
||||
},
|
||||
scrapySpiders () {
|
||||
return this.spiders.filter(d => d.type === 'customized' && d.is_scrapy)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible (value) {
|
||||
if (value) {
|
||||
this.onOpen()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeClose () {
|
||||
this.$emit('close')
|
||||
},
|
||||
beforeParameterClose () {
|
||||
this.isParametersVisible = false
|
||||
},
|
||||
async fetchScrapySpiderName (id) {
|
||||
const res = await this.$request.get(`/spiders/${id}/scrapy/spiders`)
|
||||
this.scrapySpidersNamesDict[id] = res.data.data
|
||||
},
|
||||
onConfirm () {
|
||||
this.$refs['form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
|
||||
// 请求响应
|
||||
let res
|
||||
|
||||
if (!this.multiple) {
|
||||
// 运行单个爬虫
|
||||
|
||||
// 参数
|
||||
let param = this.form.param
|
||||
|
||||
// Scrapy爬虫特殊处理
|
||||
if (this.spiderForm.type === 'customized' && this.spiderForm.is_scrapy) {
|
||||
param = `${this.form.spider} --loglevel=${this.form.scrapy_log_level} ${this.form.param}`
|
||||
}
|
||||
|
||||
// 发起请求
|
||||
res = await this.$store.dispatch('spider/crawlSpider', {
|
||||
spiderId: this.spiderId,
|
||||
nodeIds: this.form.nodeIds,
|
||||
param,
|
||||
runType: this.form.runType
|
||||
})
|
||||
} else {
|
||||
// 运行多个爬虫
|
||||
|
||||
// 发起请求
|
||||
res = await this.$store.dispatch('spider/crawlSelectedSpiders', {
|
||||
nodeIds: this.form.nodeIds,
|
||||
runType: this.form.runType,
|
||||
taskParams: this.spiders.map(d => {
|
||||
// 参数
|
||||
let param = this.form.param
|
||||
|
||||
// Scrapy爬虫特殊处理
|
||||
if (d.type === 'customized' && d.is_scrapy) {
|
||||
param = `${this.scrapySpidersNamesDict[d._id] ? this.scrapySpidersNamesDict[d._id][0] : ''} --loglevel=${this.form.scrapy_log_level} ${this.form.param}`
|
||||
}
|
||||
|
||||
return {
|
||||
spider_id: d._id,
|
||||
param
|
||||
}
|
||||
})
|
||||
})
|
||||
spiders: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
|
||||
// 消息提示
|
||||
this.$message.success(this.$t('A task has been scheduled successfully'))
|
||||
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
runType: 'random',
|
||||
nodeIds: undefined,
|
||||
spider: undefined,
|
||||
scrapy_log_level: 'INFO',
|
||||
param: '',
|
||||
nodeList: []
|
||||
},
|
||||
isAllowDisclaimer: true,
|
||||
isRetry: false,
|
||||
isRedirect: true,
|
||||
isLoading: false,
|
||||
isParametersVisible: false,
|
||||
scrapySpidersNamesDict: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('setting', [
|
||||
'setting'
|
||||
]),
|
||||
...mapState('lang', [
|
||||
'lang'
|
||||
]),
|
||||
isConfirmDisabled() {
|
||||
if (this.isLoading) return true
|
||||
if (!this.isAllowDisclaimer) return true
|
||||
return false
|
||||
},
|
||||
scrapySpiders() {
|
||||
return this.spiders.filter(d => d.type === 'customized' && d.is_scrapy)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(value) {
|
||||
if (value) {
|
||||
this.onOpen()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeClose() {
|
||||
this.$emit('close')
|
||||
if (this.multiple) {
|
||||
this.$st.sendEv('爬虫确认', '确认批量运行', this.form.runType)
|
||||
} else {
|
||||
this.$st.sendEv('爬虫确认', '确认运行', this.form.runType)
|
||||
}
|
||||
},
|
||||
beforeParameterClose() {
|
||||
this.isParametersVisible = false
|
||||
},
|
||||
async fetchScrapySpiderName(id) {
|
||||
const res = await this.$request.get(`/spiders/${id}/scrapy/spiders`)
|
||||
this.scrapySpidersNamesDict[id] = res.data.data
|
||||
},
|
||||
onConfirm() {
|
||||
this.$refs['form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
|
||||
// 是否重定向
|
||||
if (
|
||||
this.isRedirect &&
|
||||
!this.spiderForm.is_long_task &&
|
||||
!this.multiple
|
||||
) {
|
||||
// 返回任务id
|
||||
const id = res.data.data[0]
|
||||
this.$router.push('/tasks/' + id)
|
||||
this.$st.sendEv('爬虫确认', '跳转到任务详情')
|
||||
}
|
||||
// 请求响应
|
||||
let res
|
||||
|
||||
this.$emit('confirm')
|
||||
})
|
||||
},
|
||||
onClickDisclaimer () {
|
||||
this.$router.push('/disclaimer')
|
||||
},
|
||||
async onOpen () {
|
||||
// 节点列表
|
||||
this.$request.get('/nodes', {}).then(response => {
|
||||
this.nodeList = response.data.data.map(d => {
|
||||
d.systemInfo = {
|
||||
os: '',
|
||||
arch: '',
|
||||
num_cpu: '',
|
||||
executables: []
|
||||
}
|
||||
return d
|
||||
})
|
||||
})
|
||||
if (!this.multiple) {
|
||||
// 运行单个爬虫
|
||||
|
||||
// 爬虫列表
|
||||
if (!this.multiple) {
|
||||
// 单个爬虫
|
||||
this.isLoading = true
|
||||
try {
|
||||
await this.$store.dispatch('spider/getSpiderData', this.spiderId)
|
||||
if (this.spiderForm.is_scrapy) {
|
||||
await this.$store.dispatch('spider/getSpiderScrapySpiders', this.spiderId)
|
||||
if (this.spiderForm.spider_names && this.spiderForm.spider_names.length > 0) {
|
||||
this.$set(this.form, 'spider', this.spiderForm.spider_names[0])
|
||||
// 参数
|
||||
let param = this.form.param
|
||||
|
||||
// Scrapy爬虫特殊处理
|
||||
if (this.spiderForm.type === 'customized' && this.spiderForm.is_scrapy) {
|
||||
param = `${this.form.spider} --loglevel=${this.form.scrapy_log_level} ${this.form.param}`
|
||||
}
|
||||
|
||||
// 发起请求
|
||||
res = await this.$store.dispatch('spider/crawlSpider', {
|
||||
spiderId: this.spiderId,
|
||||
nodeIds: this.form.nodeIds,
|
||||
param,
|
||||
runType: this.form.runType
|
||||
})
|
||||
} else {
|
||||
// 运行多个爬虫
|
||||
|
||||
// 发起请求
|
||||
res = await this.$store.dispatch('spider/crawlSelectedSpiders', {
|
||||
nodeIds: this.form.nodeIds,
|
||||
runType: this.form.runType,
|
||||
taskParams: this.spiders.map(d => {
|
||||
// 参数
|
||||
let param = this.form.param
|
||||
|
||||
// Scrapy爬虫特殊处理
|
||||
if (d.type === 'customized' && d.is_scrapy) {
|
||||
param = `${this.scrapySpidersNamesDict[d._id] ? this.scrapySpidersNamesDict[d._id][0] : ''} --loglevel=${this.form.scrapy_log_level} ${this.form.param}`
|
||||
}
|
||||
|
||||
return {
|
||||
spider_id: d._id,
|
||||
param
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 消息提示
|
||||
this.$message.success(this.$t('A task has been scheduled successfully'))
|
||||
|
||||
this.$emit('close')
|
||||
if (this.multiple) {
|
||||
this.$st.sendEv('爬虫确认', '确认批量运行', this.form.runType)
|
||||
} else {
|
||||
this.$st.sendEv('爬虫确认', '确认运行', this.form.runType)
|
||||
}
|
||||
|
||||
// 是否重定向
|
||||
if (
|
||||
this.isRedirect &&
|
||||
!this.spiderForm.is_long_task &&
|
||||
!this.multiple
|
||||
) {
|
||||
// 返回任务id
|
||||
const id = res.data.data[0]
|
||||
this.$router.push('/tasks/' + id)
|
||||
this.$st.sendEv('爬虫确认', '跳转到任务详情')
|
||||
}
|
||||
|
||||
this.$emit('confirm')
|
||||
})
|
||||
},
|
||||
onClickDisclaimer() {
|
||||
this.$router.push('/disclaimer')
|
||||
},
|
||||
async onOpen() {
|
||||
// 节点列表
|
||||
this.$request.get('/nodes', {}).then(response => {
|
||||
this.nodeList = response.data.data.map(d => {
|
||||
d.systemInfo = {
|
||||
os: '',
|
||||
arch: '',
|
||||
num_cpu: '',
|
||||
executables: []
|
||||
}
|
||||
return d
|
||||
})
|
||||
})
|
||||
|
||||
// 爬虫列表
|
||||
if (!this.multiple) {
|
||||
// 单个爬虫
|
||||
this.isLoading = true
|
||||
try {
|
||||
await this.$store.dispatch('spider/getSpiderData', this.spiderId)
|
||||
if (this.spiderForm.is_scrapy) {
|
||||
await this.$store.dispatch('spider/getSpiderScrapySpiders', this.spiderId)
|
||||
if (this.spiderForm.spider_names && this.spiderForm.spider_names.length > 0) {
|
||||
this.$set(this.form, 'spider', this.spiderForm.spider_names[0])
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
} else {
|
||||
// 多个爬虫
|
||||
this.isLoading = true
|
||||
try {
|
||||
// 遍历 Scrapy 爬虫列表
|
||||
await Promise.all(this.scrapySpiders.map(async d => {
|
||||
return this.fetchScrapySpiderName(d._id)
|
||||
}))
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
} else {
|
||||
// 多个爬虫
|
||||
this.isLoading = true
|
||||
try {
|
||||
// 遍历 Scrapy 爬虫列表
|
||||
await Promise.all(this.scrapySpiders.map(async d => {
|
||||
return this.fetchScrapySpiderName(d._id)
|
||||
}))
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
onOpenParameters() {
|
||||
this.isParametersVisible = true
|
||||
},
|
||||
onParametersConfirm(value) {
|
||||
this.form.param = value
|
||||
this.isParametersVisible = false
|
||||
},
|
||||
isNodeDisabled(node) {
|
||||
if (node.status !== 'online') return true
|
||||
if (node.is_master && this.setting.run_on_master === 'N') return true
|
||||
return false
|
||||
}
|
||||
},
|
||||
onOpenParameters () {
|
||||
this.isParametersVisible = true
|
||||
},
|
||||
onParametersConfirm (value) {
|
||||
this.form.param = value
|
||||
this.isParametersVisible = false
|
||||
},
|
||||
isNodeDisabled (node) {
|
||||
if (node.status !== 'online') return true
|
||||
if (node.is_master && this.setting.run_on_master === 'N') return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
class="deploy-dialog"
|
||||
:title="title"
|
||||
:visible.sync="dialogVisible"
|
||||
width="40%">
|
||||
width="40%"
|
||||
>
|
||||
<!--message-->
|
||||
<label>{{message}}</label>
|
||||
<label>{{ message }}</label>
|
||||
|
||||
<!--selection for node-->
|
||||
<el-select v-if="type === 'node'" v-model="activeSpider._id">
|
||||
<el-option v-for="op in spiderList" :key="op._id" :value="op._id" :label="op.name"></el-option>
|
||||
<el-option v-for="op in spiderList" :key="op._id" :value="op._id" :label="op.name" />
|
||||
</el-select>
|
||||
|
||||
<!--selection for spider-->
|
||||
<el-select v-else-if="type === 'spider'" v-model="activeNode._id">
|
||||
<el-option v-for="op in nodeList" :key="op._id" :value="op._id" :label="op.name"></el-option>
|
||||
<el-option v-for="op in nodeList" :key="op._id" :value="op._id" :label="op.name" />
|
||||
</el-select>
|
||||
|
||||
<!--action buttons-->
|
||||
@@ -29,132 +30,133 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DialogView',
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderList',
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('node', [
|
||||
'nodeList'
|
||||
]),
|
||||
...mapState('dialogView', [
|
||||
'dialogType'
|
||||
]),
|
||||
type () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'node'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'node'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'spider'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'spider'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
activeNode: {
|
||||
get () {
|
||||
return this.$store.state.spider.activeNode
|
||||
export default {
|
||||
name: 'DialogView',
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderList',
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('node', [
|
||||
'nodeList'
|
||||
]),
|
||||
...mapState('dialogView', [
|
||||
'dialogType'
|
||||
]),
|
||||
type() {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'node'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'node'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'spider'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'spider'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
set () {
|
||||
this.$store.commit('spider/SET_ACTIVE_NODE')
|
||||
}
|
||||
},
|
||||
activeSpider: {
|
||||
get () {
|
||||
return this.$store.state.node.activeSpider
|
||||
activeNode: {
|
||||
get() {
|
||||
return this.$store.state.spider.activeNode
|
||||
},
|
||||
set() {
|
||||
this.$store.commit('spider/SET_ACTIVE_NODE')
|
||||
}
|
||||
},
|
||||
set () {
|
||||
this.$store.commit('node/SET_ACTIVE_SPIDER')
|
||||
}
|
||||
},
|
||||
dialogVisible: {
|
||||
get () {
|
||||
return this.$store.state.dialogView.dialogVisible
|
||||
activeSpider: {
|
||||
get() {
|
||||
return this.$store.state.node.activeSpider
|
||||
},
|
||||
set() {
|
||||
this.$store.commit('node/SET_ACTIVE_SPIDER')
|
||||
}
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', value)
|
||||
dialogVisible: {
|
||||
get() {
|
||||
return this.$store.state.dialogView.dialogVisible
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', value)
|
||||
}
|
||||
},
|
||||
title() {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'Deploy'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'Run'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'Deploy'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'Run'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
message() {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'Please select spider you would like to deploy'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'Please select spider you would like to run'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'Please select node you would like to deploy'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'Please select node you would like to run'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
},
|
||||
title () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'Deploy'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'Run'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'Deploy'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'Run'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
mounted() {
|
||||
// if (!this.spiderList || !this.spiderList.length) this.$store.dispatch('spider/getSpiderList')
|
||||
if (!this.nodeList || !this.nodeList.length) this.$store.dispatch('node/getNodeList')
|
||||
},
|
||||
message () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return 'Please select spider you would like to deploy'
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return 'Please select spider you would like to run'
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
return 'Please select node you would like to deploy'
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
return 'Please select node you would like to run'
|
||||
} else {
|
||||
return ''
|
||||
methods: {
|
||||
onCancel() {
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
},
|
||||
onConfirm() {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
return
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
return
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
this.$store.dispatch('spider/deploySpider', {
|
||||
id: this.spiderForm._id,
|
||||
nodeId: this.activeNode._id
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`Spider "${this.spiderForm.name}" has been deployed on node "${this.activeNode._id}" successfully`)
|
||||
})
|
||||
.finally(() => {
|
||||
// get spider deploys
|
||||
this.$store.dispatch('spider/getDeployList', this.$route.params.id)
|
||||
|
||||
// close dialog
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
})
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
this.$store.dispatch('spider/crawlSpider', this.spiderForm._id)
|
||||
.then(() => {
|
||||
this.$message.success(`Spider "${this.spiderForm.name}" started to run on node "${this.activeNode._id}"`)
|
||||
})
|
||||
.finally(() => {
|
||||
// get spider tasks
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('spider/getTaskList', this.$route.params.id)
|
||||
}, 500)
|
||||
|
||||
// close dialog
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCancel () {
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
},
|
||||
onConfirm () {
|
||||
if (this.dialogType === 'nodeDeploy') {
|
||||
} else if (this.dialogType === 'nodeRun') {
|
||||
} else if (this.dialogType === 'spiderDeploy') {
|
||||
this.$store.dispatch('spider/deploySpider', {
|
||||
id: this.spiderForm._id,
|
||||
nodeId: this.activeNode._id
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(`Spider "${this.spiderForm.name}" has been deployed on node "${this.activeNode._id}" successfully`)
|
||||
})
|
||||
.finally(() => {
|
||||
// get spider deploys
|
||||
this.$store.dispatch('spider/getDeployList', this.$route.params.id)
|
||||
|
||||
// close dialog
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
})
|
||||
} else if (this.dialogType === 'spiderRun') {
|
||||
this.$store.dispatch('spider/crawlSpider', this.spiderForm._id)
|
||||
.then(() => {
|
||||
this.$message.success(`Spider "${this.spiderForm.name}" started to run on node "${this.activeNode._id}"`)
|
||||
})
|
||||
.finally(() => {
|
||||
// get spider tasks
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('spider/getTaskList', this.$route.params.id)
|
||||
}, 500)
|
||||
|
||||
// close dialog
|
||||
this.$store.commit('dialogView/SET_DIALOG_VISIBLE', false)
|
||||
})
|
||||
} else {
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// if (!this.spiderList || !this.spiderList.length) this.$store.dispatch('spider/getSpiderList')
|
||||
if (!this.nodeList || !this.nodeList.length) this.$store.dispatch('node/getNodeList')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
size="small"
|
||||
@click="onAdd"
|
||||
>
|
||||
{{$t('Add')}}
|
||||
{{ $t('Add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
@@ -68,7 +68,7 @@
|
||||
:label="$t('Parameter Value')"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.value" size="small" suffix-icon="el-icon-edit"/>
|
||||
<el-input v-model="scope.row.value" size="small" suffix-icon="el-icon-edit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -78,121 +78,121 @@
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<div class="action-btn-wrapper">
|
||||
<el-button type="danger" icon="el-icon-delete" size="mini" @click="onRemove(scope.$index)" circle/>
|
||||
<el-button type="danger" icon="el-icon-delete" size="mini" circle @click="onRemove(scope.$index)" />
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template slot="footer">
|
||||
<el-button type="plain" size="small" @click="$emit('close')">{{$t('Cancel')}}</el-button>
|
||||
<el-button type="plain" size="small" @click="$emit('close')">{{ $t('Cancel') }}</el-button>
|
||||
<el-button type="primary" size="small" @click="onConfirm">
|
||||
{{$t('Confirm')}}
|
||||
{{ $t('Confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ParametersDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
param: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
paramData: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible (value) {
|
||||
if (value) this.initParamData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeClose () {
|
||||
this.$emit('close')
|
||||
},
|
||||
initParamData () {
|
||||
const mArr = this.param.match(/((?:-[-a-zA-Z0-9] )?(?:\w+=)?\w+)/g)
|
||||
if (!mArr) {
|
||||
this.paramData = []
|
||||
this.paramData.push({ type: 'spider', name: '', value: '' })
|
||||
return
|
||||
export default {
|
||||
name: 'ParametersDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
param: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
this.paramData = []
|
||||
mArr.forEach(s => {
|
||||
s = s.trim()
|
||||
let d = {}
|
||||
const arr = s.split(' ')
|
||||
if (arr.length === 1) {
|
||||
d.type = 'other'
|
||||
d.value = s
|
||||
} else {
|
||||
const arr2 = arr[1].split('=')
|
||||
d.name = arr2[0]
|
||||
d.value = arr2[1]
|
||||
if (arr[0] === '-a') {
|
||||
d.type = 'spider'
|
||||
} else if (arr[0] === '-s') {
|
||||
d.type = 'setting'
|
||||
} else {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
paramData: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(value) {
|
||||
if (value) this.initParamData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
beforeClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
initParamData() {
|
||||
const mArr = this.param.match(/((?:-[-a-zA-Z0-9] )?(?:\w+=)?\w+)/g)
|
||||
if (!mArr) {
|
||||
this.paramData = []
|
||||
this.paramData.push({ type: 'spider', name: '', value: '' })
|
||||
return
|
||||
}
|
||||
this.paramData = []
|
||||
mArr.forEach(s => {
|
||||
s = s.trim()
|
||||
const d = {}
|
||||
const arr = s.split(' ')
|
||||
if (arr.length === 1) {
|
||||
d.type = 'other'
|
||||
d.value = s
|
||||
} else {
|
||||
const arr2 = arr[1].split('=')
|
||||
d.name = arr2[0]
|
||||
d.value = arr2[1]
|
||||
if (arr[0] === '-a') {
|
||||
d.type = 'spider'
|
||||
} else if (arr[0] === '-s') {
|
||||
d.type = 'setting'
|
||||
} else {
|
||||
d.type = 'other'
|
||||
d.value = s
|
||||
}
|
||||
}
|
||||
}
|
||||
this.paramData.push(d)
|
||||
})
|
||||
if (this.paramData.length === 0) {
|
||||
this.paramData.push({ type: 'spider', name: '', value: '' })
|
||||
}
|
||||
},
|
||||
onConfirm () {
|
||||
const param = this.paramData
|
||||
.filter(d => d.value)
|
||||
.map(d => {
|
||||
let s = ''
|
||||
if (d.type === 'setting') {
|
||||
s = `-s ${d.name}=${d.value}`
|
||||
} else if (d.type === 'spider') {
|
||||
s = `-a ${d.name}=${d.value}`
|
||||
} else if (d.type === 'other') {
|
||||
s = d.value
|
||||
}
|
||||
return s
|
||||
this.paramData.push(d)
|
||||
})
|
||||
.filter(s => !!s)
|
||||
.join(' ')
|
||||
this.$emit('confirm', param)
|
||||
},
|
||||
onRemove (index) {
|
||||
this.paramData.splice(index, 1)
|
||||
},
|
||||
onAdd () {
|
||||
this.paramData.push({ type: 'spider', name: '', value: '' })
|
||||
},
|
||||
querySearch (queryString, cb) {
|
||||
let data = this.$utils.scrapy.settingParamNames
|
||||
if (!queryString) {
|
||||
return cb(data.map(s => {
|
||||
if (this.paramData.length === 0) {
|
||||
this.paramData.push({ type: 'spider', name: '', value: '' })
|
||||
}
|
||||
},
|
||||
onConfirm() {
|
||||
const param = this.paramData
|
||||
.filter(d => d.value)
|
||||
.map(d => {
|
||||
let s = ''
|
||||
if (d.type === 'setting') {
|
||||
s = `-s ${d.name}=${d.value}`
|
||||
} else if (d.type === 'spider') {
|
||||
s = `-a ${d.name}=${d.value}`
|
||||
} else if (d.type === 'other') {
|
||||
s = d.value
|
||||
}
|
||||
return s
|
||||
})
|
||||
.filter(s => !!s)
|
||||
.join(' ')
|
||||
this.$emit('confirm', param)
|
||||
},
|
||||
onRemove(index) {
|
||||
this.paramData.splice(index, 1)
|
||||
},
|
||||
onAdd() {
|
||||
this.paramData.push({ type: 'spider', name: '', value: '' })
|
||||
},
|
||||
querySearch(queryString, cb) {
|
||||
let data = this.$utils.scrapy.settingParamNames
|
||||
if (!queryString) {
|
||||
return cb(data.map(s => {
|
||||
return { value: s, label: s }
|
||||
}))
|
||||
}
|
||||
data = data
|
||||
.filter(s => s.match(new RegExp(queryString, 'i')))
|
||||
.sort((a, b) => a < b ? 1 : -1)
|
||||
cb(data.map(s => {
|
||||
return { value: s, label: s }
|
||||
}))
|
||||
}
|
||||
data = data
|
||||
.filter(s => s.match(new RegExp(queryString, 'i')))
|
||||
.sort((a, b) => a < b ? 1 : -1)
|
||||
cb(data.map(s => {
|
||||
return { value: s, label: s }
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,153 +47,153 @@
|
||||
<div id="change-crontab">
|
||||
<div class="cron-wrapper">
|
||||
<label>
|
||||
{{$t('Cron Expression')}}:
|
||||
{{ $t('Cron Expression') }}:
|
||||
</label>
|
||||
<el-tag type="success" size="small">
|
||||
{{cron}}
|
||||
{{ cron }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i> {{text.Minutes.name}}</span>
|
||||
<span slot="label"><i class="el-icon-date" /> {{ text.Minutes.name }}</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="minute.cronEvery" label="1">{{text.Minutes.every}}</el-radio>
|
||||
<el-radio v-model="minute.cronEvery" label="1">{{ text.Minutes.every }}</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="minute.cronEvery" label="2">{{text.Minutes.interval[0]}}
|
||||
<el-input-number size="small" v-model="minute.incrementIncrement" :min="0" :max="59"></el-input-number>
|
||||
{{text.Minutes.interval[1]}}
|
||||
<el-input-number size="small" v-model="minute.incrementStart" :min="0" :max="59"></el-input-number>
|
||||
{{text.Minutes.interval[2]||''}}
|
||||
<el-radio v-model="minute.cronEvery" label="2">{{ text.Minutes.interval[0] }}
|
||||
<el-input-number v-model="minute.incrementIncrement" size="small" :min="0" :max="59" />
|
||||
{{ text.Minutes.interval[1] }}
|
||||
<el-input-number v-model="minute.incrementStart" size="small" :min="0" :max="59" />
|
||||
{{ text.Minutes.interval[2]||'' }}
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="minute.cronEvery" label="3">{{text.Minutes.specific}}
|
||||
<el-select size="small" multiple v-model="minute.specificSpecific">
|
||||
<el-option v-for="val in 60" :key="val" :value="(val-1).toString()" :label="val-1"></el-option>
|
||||
<el-radio v-model="minute.cronEvery" class="long" label="3">{{ text.Minutes.specific }}
|
||||
<el-select v-model="minute.specificSpecific" size="small" multiple>
|
||||
<el-option v-for="val in 60" :key="val" :value="(val-1).toString()" :label="val-1" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="minute.cronEvery" label="4">{{text.Minutes.cycle[0]}}
|
||||
<el-input-number size="small" v-model="minute.rangeStart" :min="0" :max="59"></el-input-number>
|
||||
{{text.Minutes.cycle[1]}}
|
||||
<el-input-number size="small" v-model="minute.rangeEnd" :min="0" :max="59"></el-input-number>
|
||||
{{text.Minutes.cycle[2]}}
|
||||
<el-radio v-model="minute.cronEvery" label="4">{{ text.Minutes.cycle[0] }}
|
||||
<el-input-number v-model="minute.rangeStart" size="small" :min="0" :max="59" />
|
||||
{{ text.Minutes.cycle[1] }}
|
||||
<el-input-number v-model="minute.rangeEnd" size="small" :min="0" :max="59" />
|
||||
{{ text.Minutes.cycle[2] }}
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i> {{text.Hours.name}}</span>
|
||||
<span slot="label"><i class="el-icon-date" /> {{ text.Hours.name }}</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="hour.cronEvery" label="1">{{text.Hours.every}}</el-radio>
|
||||
<el-radio v-model="hour.cronEvery" label="1">{{ text.Hours.every }}</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="hour.cronEvery" label="2">{{text.Hours.interval[0]}}
|
||||
<el-input-number size="small" v-model="hour.incrementIncrement" :min="0" :max="23"></el-input-number>
|
||||
{{text.Hours.interval[1]}}
|
||||
<el-input-number size="small" v-model="hour.incrementStart" :min="0" :max="23"></el-input-number>
|
||||
{{text.Hours.interval[2]}}
|
||||
<el-radio v-model="hour.cronEvery" label="2">{{ text.Hours.interval[0] }}
|
||||
<el-input-number v-model="hour.incrementIncrement" size="small" :min="0" :max="23" />
|
||||
{{ text.Hours.interval[1] }}
|
||||
<el-input-number v-model="hour.incrementStart" size="small" :min="0" :max="23" />
|
||||
{{ text.Hours.interval[2] }}
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="hour.cronEvery" label="3">{{text.Hours.specific}}
|
||||
<el-select size="small" multiple v-model="hour.specificSpecific">
|
||||
<el-option v-for="val in 24" :key="val" :value="(val-1).toString()" :label="val-1"></el-option>
|
||||
<el-radio v-model="hour.cronEvery" class="long" label="3">{{ text.Hours.specific }}
|
||||
<el-select v-model="hour.specificSpecific" size="small" multiple>
|
||||
<el-option v-for="val in 24" :key="val" :value="(val-1).toString()" :label="val-1" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="hour.cronEvery" label="4">{{text.Hours.cycle[0]}}
|
||||
<el-input-number size="small" v-model="hour.rangeStart" :min="0" :max="23"></el-input-number>
|
||||
{{text.Hours.cycle[1]}}
|
||||
<el-input-number size="small" v-model="hour.rangeEnd" :min="0" :max="23"></el-input-number>
|
||||
{{text.Hours.cycle[2]}}
|
||||
<el-radio v-model="hour.cronEvery" label="4">{{ text.Hours.cycle[0] }}
|
||||
<el-input-number v-model="hour.rangeStart" size="small" :min="0" :max="23" />
|
||||
{{ text.Hours.cycle[1] }}
|
||||
<el-input-number v-model="hour.rangeEnd" size="small" :min="0" :max="23" />
|
||||
{{ text.Hours.cycle[2] }}
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i> {{text.Day.name}}</span>
|
||||
<span slot="label"><i class="el-icon-date" /> {{ text.Day.name }}</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="1">{{text.Day.every}}</el-radio>
|
||||
<el-radio v-model="day.cronEvery" label="1">{{ text.Day.every }}</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="2">{{text.Day.intervalDay[0]}}
|
||||
<el-input-number size="small" v-model="day.incrementIncrement" :min="1" :max="31"></el-input-number>
|
||||
{{text.Day.intervalDay[1]}}
|
||||
<el-input-number size="small" v-model="day.incrementStart" :min="1" :max="31"></el-input-number>
|
||||
{{text.Day.intervalDay[2]}}
|
||||
<el-radio v-model="day.cronEvery" label="2">{{ text.Day.intervalDay[0] }}
|
||||
<el-input-number v-model="day.incrementIncrement" size="small" :min="1" :max="31" />
|
||||
{{ text.Day.intervalDay[1] }}
|
||||
<el-input-number v-model="day.incrementStart" size="small" :min="1" :max="31" />
|
||||
{{ text.Day.intervalDay[2] }}
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="day.cronEvery" label="3">{{text.Day.specificDay}}
|
||||
<el-select size="small" multiple v-model="day.specificSpecific">
|
||||
<el-option v-for="val in 31" :key="val" :value="val.toString()" :label="val"></el-option>
|
||||
<el-radio v-model="day.cronEvery" class="long" label="3">{{ text.Day.specificDay }}
|
||||
<el-select v-model="day.specificSpecific" size="small" multiple>
|
||||
<el-option v-for="val in 31" :key="val" :value="val.toString()" :label="val" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="4">{{text.Day.cycle[0]}}
|
||||
<el-input-number size="small" v-model="day.rangeStart" :min="1" :max="31"></el-input-number>
|
||||
{{text.Day.cycle[1]}}
|
||||
<el-input-number size="small" v-model="day.rangeEnd" :min="1" :max="31"></el-input-number>
|
||||
<el-radio v-model="day.cronEvery" label="4">{{ text.Day.cycle[0] }}
|
||||
<el-input-number v-model="day.rangeStart" size="small" :min="1" :max="31" />
|
||||
{{ text.Day.cycle[1] }}
|
||||
<el-input-number v-model="day.rangeEnd" size="small" :min="1" :max="31" />
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i> {{text.Month.name}}</span>
|
||||
<span slot="label"><i class="el-icon-date" /> {{ text.Month.name }}</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="month.cronEvery" label="1">{{text.Month.every}}</el-radio>
|
||||
<el-radio v-model="month.cronEvery" label="1">{{ text.Month.every }}</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="month.cronEvery" label="2">{{text.Month.interval[0]}}
|
||||
<el-input-number size="small" v-model="month.incrementIncrement" :min="0" :max="12"></el-input-number>
|
||||
{{text.Month.interval[1]}}
|
||||
<el-input-number size="small" v-model="month.incrementStart" :min="0" :max="12"></el-input-number>
|
||||
<el-radio v-model="month.cronEvery" label="2">{{ text.Month.interval[0] }}
|
||||
<el-input-number v-model="month.incrementIncrement" size="small" :min="0" :max="12" />
|
||||
{{ text.Month.interval[1] }}
|
||||
<el-input-number v-model="month.incrementStart" size="small" :min="0" :max="12" />
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="month.cronEvery" label="3">{{text.Month.specific}}
|
||||
<el-select size="small" multiple v-model="month.specificSpecific">
|
||||
<el-option v-for="val in 12" :key="val" :label="val" :value="val.toString()"></el-option>
|
||||
<el-radio v-model="month.cronEvery" class="long" label="3">{{ text.Month.specific }}
|
||||
<el-select v-model="month.specificSpecific" size="small" multiple>
|
||||
<el-option v-for="val in 12" :key="val" :label="val" :value="val.toString()" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="month.cronEvery" label="4">{{text.Month.cycle[0]}}
|
||||
<el-input-number size="small" v-model="month.rangeStart" :min="1" :max="12"></el-input-number>
|
||||
{{text.Month.cycle[1]}}
|
||||
<el-input-number size="small" v-model="month.rangeEnd" :min="1" :max="12"></el-input-number>
|
||||
{{text.Month.cycle[2]}}
|
||||
<el-radio v-model="month.cronEvery" label="4">{{ text.Month.cycle[0] }}
|
||||
<el-input-number v-model="month.rangeStart" size="small" :min="1" :max="12" />
|
||||
{{ text.Month.cycle[1] }}
|
||||
<el-input-number v-model="month.rangeEnd" size="small" :min="1" :max="12" />
|
||||
{{ text.Month.cycle[2] }}
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i> {{text.Week.name}}</span>
|
||||
<span slot="label"><i class="el-icon-date" /> {{ text.Week.name }}</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="week.cronEvery" label="1">{{text.Week.every}}</el-radio>
|
||||
<el-radio v-model="week.cronEvery" label="1">{{ text.Week.every }}</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="week.cronEvery" label="3">{{text.Week.specific}}
|
||||
<el-select size="small" multiple v-model="week.specificSpecific">
|
||||
<el-option v-for="i in 7" :key="i" :label="text.Week.list[i - 1]" :value="i.toString()"></el-option>
|
||||
<el-radio v-model="week.cronEvery" class="long" label="3">{{ text.Week.specific }}
|
||||
<el-select v-model="week.specificSpecific" size="small" multiple>
|
||||
<el-option v-for="i in 7" :key="i" :label="text.Week.list[i - 1]" :value="i.toString()" />
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="week.cronEvery" label="4">{{text.Week.cycle[0]}}
|
||||
<el-input-number size="small" v-model="week.rangeStart" :min="1" :max="7"></el-input-number>
|
||||
{{text.Week.cycle[1]}}
|
||||
<el-input-number size="small" v-model="week.rangeEnd" :min="1" :max="7"></el-input-number>
|
||||
<el-radio v-model="week.cronEvery" label="4">{{ text.Week.cycle[0] }}
|
||||
<el-input-number v-model="week.rangeStart" size="small" :min="1" :max="7" />
|
||||
{{ text.Week.cycle[1] }}
|
||||
<el-input-number v-model="week.rangeEnd" size="small" :min="1" :max="7" />
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -202,93 +202,85 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Language from './language/index'
|
||||
import Language from './language/index'
|
||||
|
||||
export default {
|
||||
name: 'VueCronLinux',
|
||||
props: ['data', 'i18n'],
|
||||
data () {
|
||||
return {
|
||||
second: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [0]
|
||||
},
|
||||
minute: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['0']
|
||||
},
|
||||
hour: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['0']
|
||||
},
|
||||
day: {
|
||||
cronEvery: '',
|
||||
incrementStart: '1',
|
||||
incrementIncrement: '1',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['1'],
|
||||
cronLastSpecificDomDay: 1,
|
||||
cronDaysBeforeEomMinus: '',
|
||||
cronDaysNearestWeekday: ''
|
||||
},
|
||||
month: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['1']
|
||||
},
|
||||
week: {
|
||||
cronEvery: '',
|
||||
incrementStart: '1',
|
||||
incrementIncrement: '1',
|
||||
specificSpecific: ['1'],
|
||||
cronNthDayDay: 1,
|
||||
cronNthDayNth: '1',
|
||||
rangeStart: '',
|
||||
rangeEnd: ''
|
||||
},
|
||||
output: {
|
||||
second: '',
|
||||
minute: '',
|
||||
hour: '',
|
||||
day: '',
|
||||
month: '',
|
||||
Week: '',
|
||||
year: ''
|
||||
export default {
|
||||
name: 'VueCronLinux',
|
||||
props: ['data', 'i18n'],
|
||||
data() {
|
||||
return {
|
||||
second: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [0]
|
||||
},
|
||||
minute: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['0']
|
||||
},
|
||||
hour: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['0']
|
||||
},
|
||||
day: {
|
||||
cronEvery: '',
|
||||
incrementStart: '1',
|
||||
incrementIncrement: '1',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['1'],
|
||||
cronLastSpecificDomDay: 1,
|
||||
cronDaysBeforeEomMinus: '',
|
||||
cronDaysNearestWeekday: ''
|
||||
},
|
||||
month: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: ['1']
|
||||
},
|
||||
week: {
|
||||
cronEvery: '',
|
||||
incrementStart: '1',
|
||||
incrementIncrement: '1',
|
||||
specificSpecific: ['1'],
|
||||
cronNthDayDay: 1,
|
||||
cronNthDayNth: '1',
|
||||
rangeStart: '',
|
||||
rangeEnd: ''
|
||||
},
|
||||
output: {
|
||||
second: '',
|
||||
minute: '',
|
||||
hour: '',
|
||||
day: '',
|
||||
month: '',
|
||||
Week: '',
|
||||
year: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data () {
|
||||
this.updateCronFromData()
|
||||
},
|
||||
cron () {
|
||||
this.$emit('change', this.cron)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
text () {
|
||||
return Language[this.i18n || 'cn']
|
||||
},
|
||||
minutesText () {
|
||||
let minutes = ''
|
||||
let cronEvery = this.minute.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
computed: {
|
||||
text() {
|
||||
return Language[this.i18n || 'cn']
|
||||
},
|
||||
minutesText() {
|
||||
let minutes = ''
|
||||
const cronEvery = this.minute.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
minutes = '*'
|
||||
break
|
||||
@@ -304,13 +296,13 @@ export default {
|
||||
case '4':
|
||||
minutes = this.minute.rangeStart + '-' + this.minute.rangeEnd
|
||||
break
|
||||
}
|
||||
return minutes
|
||||
},
|
||||
hoursText () {
|
||||
let hours = ''
|
||||
let cronEvery = this.hour.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
}
|
||||
return minutes
|
||||
},
|
||||
hoursText() {
|
||||
let hours = ''
|
||||
const cronEvery = this.hour.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
hours = '*'
|
||||
break
|
||||
@@ -326,13 +318,13 @@ export default {
|
||||
case '4':
|
||||
hours = this.hour.rangeStart + '-' + this.hour.rangeEnd
|
||||
break
|
||||
}
|
||||
return hours
|
||||
},
|
||||
daysText () {
|
||||
let days = ''
|
||||
let cronEvery = this.day.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
}
|
||||
return hours
|
||||
},
|
||||
daysText() {
|
||||
let days = ''
|
||||
const cronEvery = this.day.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
days = '*'
|
||||
break
|
||||
@@ -348,13 +340,13 @@ export default {
|
||||
case '4':
|
||||
days = this.day.rangeStart + '-' + this.day.rangeEnd
|
||||
break
|
||||
}
|
||||
return days
|
||||
},
|
||||
monthsText () {
|
||||
let months = ''
|
||||
let cronEvery = this.month.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
}
|
||||
return days
|
||||
},
|
||||
monthsText() {
|
||||
let months = ''
|
||||
const cronEvery = this.month.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
months = '*'
|
||||
break
|
||||
@@ -370,13 +362,13 @@ export default {
|
||||
case '4':
|
||||
months = this.month.rangeStart + '-' + this.month.rangeEnd
|
||||
break
|
||||
}
|
||||
return months
|
||||
},
|
||||
weeksText () {
|
||||
let weeks = ''
|
||||
let cronEvery = this.week.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
}
|
||||
return months
|
||||
},
|
||||
weeksText() {
|
||||
let weeks = ''
|
||||
const cronEvery = this.week.cronEvery
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
weeks = '*'
|
||||
break
|
||||
@@ -389,77 +381,85 @@ export default {
|
||||
case '4':
|
||||
weeks = this.week.rangeStart + '-' + this.week.rangeEnd
|
||||
break
|
||||
}
|
||||
return weeks
|
||||
},
|
||||
cron () {
|
||||
return [this.minutesText, this.hoursText, this.daysText, this.monthsText, this.weeksText]
|
||||
.filter(v => !!v)
|
||||
.join(' ')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
change () {
|
||||
this.$emit('change', this.cron)
|
||||
this.close()
|
||||
},
|
||||
close () {
|
||||
this.$emit('close')
|
||||
},
|
||||
submit () {
|
||||
if (!this.validate()) {
|
||||
this.$message.error(this.$t('Cron expression is invalid'))
|
||||
return false
|
||||
}
|
||||
this.$emit('submit', this.cron)
|
||||
return true
|
||||
},
|
||||
validate () {
|
||||
if (!this.minutesText) return false
|
||||
if (!this.hoursText) return false
|
||||
if (!this.daysText) return false
|
||||
if (!this.monthsText) return false
|
||||
if (!this.weeksText) return false
|
||||
return true
|
||||
},
|
||||
updateCronItem (key, value) {
|
||||
if (value === undefined) {
|
||||
this[key].cronEvery = '0'
|
||||
return
|
||||
}
|
||||
if (value.match(/^\*$/)) {
|
||||
this[key].cronEvery = '1'
|
||||
} else if (value.match(/\//)) {
|
||||
this[key].cronEvery = '2'
|
||||
this[key].incrementStart = value.split('/')[0]
|
||||
this[key].incrementIncrement = value.split('/')[1]
|
||||
} else if (value.match(/,|^\d+$/)) {
|
||||
this[key].cronEvery = '3'
|
||||
this[key].specificSpecific = value.split(',')
|
||||
} else if (value.match(/-/)) {
|
||||
this[key].cronEvery = '4'
|
||||
this[key].rangeStart = value.split('-')[0]
|
||||
this[key].rangeEnd = value.split('-')[1]
|
||||
} else {
|
||||
this[key].cronEvery = '0'
|
||||
}
|
||||
return weeks
|
||||
},
|
||||
cron() {
|
||||
return [this.minutesText, this.hoursText, this.daysText, this.monthsText, this.weeksText]
|
||||
.filter(v => !!v)
|
||||
.join(' ')
|
||||
}
|
||||
},
|
||||
updateCronFromData () {
|
||||
const arr = this.data.split(' ')
|
||||
const minute = arr[0]
|
||||
const hour = arr[1]
|
||||
const day = arr[2]
|
||||
const month = arr[3]
|
||||
const week = arr[4]
|
||||
watch: {
|
||||
data() {
|
||||
this.updateCronFromData()
|
||||
},
|
||||
cron() {
|
||||
this.$emit('change', this.cron)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.updateCronFromData()
|
||||
},
|
||||
methods: {
|
||||
change() {
|
||||
this.$emit('change', this.cron)
|
||||
this.close()
|
||||
},
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
submit() {
|
||||
if (!this.validate()) {
|
||||
this.$message.error(this.$t('Cron expression is invalid'))
|
||||
return false
|
||||
}
|
||||
this.$emit('submit', this.cron)
|
||||
return true
|
||||
},
|
||||
validate() {
|
||||
if (!this.minutesText) return false
|
||||
if (!this.hoursText) return false
|
||||
if (!this.daysText) return false
|
||||
if (!this.monthsText) return false
|
||||
if (!this.weeksText) return false
|
||||
return true
|
||||
},
|
||||
updateCronItem(key, value) {
|
||||
if (value === undefined) {
|
||||
this[key].cronEvery = '0'
|
||||
return
|
||||
}
|
||||
if (value.match(/^\*$/)) {
|
||||
this[key].cronEvery = '1'
|
||||
} else if (value.match(/\//)) {
|
||||
this[key].cronEvery = '2'
|
||||
this[key].incrementStart = value.split('/')[0]
|
||||
this[key].incrementIncrement = value.split('/')[1]
|
||||
} else if (value.match(/,|^\d+$/)) {
|
||||
this[key].cronEvery = '3'
|
||||
this[key].specificSpecific = value.split(',')
|
||||
} else if (value.match(/-/)) {
|
||||
this[key].cronEvery = '4'
|
||||
this[key].rangeStart = value.split('-')[0]
|
||||
this[key].rangeEnd = value.split('-')[1]
|
||||
} else {
|
||||
this[key].cronEvery = '0'
|
||||
}
|
||||
},
|
||||
updateCronFromData() {
|
||||
const arr = this.data.split(' ')
|
||||
const minute = arr[0]
|
||||
const hour = arr[1]
|
||||
const day = arr[2]
|
||||
const month = arr[3]
|
||||
const week = arr[4]
|
||||
|
||||
this.updateCronItem('minute', minute)
|
||||
this.updateCronItem('hour', hour)
|
||||
this.updateCronItem('day', day)
|
||||
this.updateCronItem('month', month)
|
||||
this.updateCronItem('week', week)
|
||||
this.updateCronItem('minute', minute)
|
||||
this.updateCronItem('hour', hour)
|
||||
this.updateCronItem('day', day)
|
||||
this.updateCronItem('month', month)
|
||||
this.updateCronItem('week', week)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.updateCronFromData()
|
||||
}
|
||||
}</script>
|
||||
}</script>
|
||||
|
||||
@@ -31,7 +31,9 @@ export default {
|
||||
lastWeekday: 'On the last weekday of the month',
|
||||
lastWeek: ['On the last', ' of the month'],
|
||||
beforeEndMonth: ['day(s) before the end of the month'],
|
||||
nearestWeekday: ['Nearest weekday (Monday to Friday) to the', 'of the month'],
|
||||
nearestWeekday: [
|
||||
'Nearest weekday (Monday to Friday) to the',
|
||||
'of the month'],
|
||||
someWeekday: ['On the', 'of the month'],
|
||||
cycle: ['From', 'to']
|
||||
},
|
||||
@@ -39,7 +41,14 @@ export default {
|
||||
name: 'Week',
|
||||
every: 'Every day',
|
||||
specific: 'Specific weekday (choose on or many)',
|
||||
list: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
|
||||
list: [
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
'Sunday'],
|
||||
cycle: ['From', 'to']
|
||||
},
|
||||
// Week:['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<el-tree
|
||||
:data="docData"
|
||||
ref="documentation-tree"
|
||||
:data="docData"
|
||||
node-key="fullUrl"
|
||||
>
|
||||
<span class="custom-tree-node" :class="[data.active ? 'active' : '', `level-${data.level}`]"
|
||||
slot-scope="{ node, data }">
|
||||
<span
|
||||
slot-scope="{ node, data }"
|
||||
class="custom-tree-node"
|
||||
:class="[data.active ? 'active' : '', `level-${data.level}`]"
|
||||
>
|
||||
<template v-if="data.level === 1 && data.children && data.children.length">
|
||||
<span>{{node.label}}</span>
|
||||
<span>{{ node.label }}</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span>
|
||||
<a :href="data.fullUrl" target="_blank" style="display: block" @click="onClickDocumentationLink">
|
||||
{{node.label}}
|
||||
{{ node.label }}
|
||||
</a>
|
||||
</span>
|
||||
</template>
|
||||
@@ -21,86 +24,86 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Documentation',
|
||||
data () {
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('doc', [
|
||||
'docData'
|
||||
]),
|
||||
pathLv1 () {
|
||||
if (this.$route.path === '/') return '/'
|
||||
const m = this.$route.path.match(/(^\/\w+)/)
|
||||
return m[1]
|
||||
},
|
||||
currentDoc () {
|
||||
// find current doc
|
||||
let currentDoc
|
||||
for (let i = 0; i < this.$utils.doc.docs.length; i++) {
|
||||
const doc = this.$utils.doc.docs[i]
|
||||
if (this.pathLv1 === doc.path) {
|
||||
currentDoc = doc
|
||||
break
|
||||
}
|
||||
export default {
|
||||
name: 'Documentation',
|
||||
data() {
|
||||
return {
|
||||
data: []
|
||||
}
|
||||
return currentDoc
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
pathLv1 () {
|
||||
},
|
||||
computed: {
|
||||
...mapState('doc', [
|
||||
'docData'
|
||||
]),
|
||||
pathLv1() {
|
||||
if (this.$route.path === '/') return '/'
|
||||
const m = this.$route.path.match(/(^\/\w+)/)
|
||||
return m[1]
|
||||
},
|
||||
currentDoc() {
|
||||
// find current doc
|
||||
let currentDoc
|
||||
for (let i = 0; i < this.$utils.doc.docs.length; i++) {
|
||||
const doc = this.$utils.doc.docs[i]
|
||||
if (this.pathLv1 === doc.path) {
|
||||
currentDoc = doc
|
||||
break
|
||||
}
|
||||
}
|
||||
return currentDoc
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
pathLv1() {
|
||||
this.update()
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
},
|
||||
mounted() {
|
||||
this.update()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isActiveNode (d) {
|
||||
// check match
|
||||
if (!this.currentDoc) return false
|
||||
return !!d.url.match(this.currentDoc.pattern)
|
||||
},
|
||||
update () {
|
||||
// expand related documentation list
|
||||
setTimeout(() => {
|
||||
this.docData.forEach(d => {
|
||||
// parent node
|
||||
const isActive = this.isActiveNode(d)
|
||||
const node = this.$refs['documentation-tree'].getNode(d)
|
||||
node.expanded = isActive
|
||||
this.$set(d, 'active', isActive)
|
||||
methods: {
|
||||
isActiveNode(d) {
|
||||
// check match
|
||||
if (!this.currentDoc) return false
|
||||
return !!d.url.match(this.currentDoc.pattern)
|
||||
},
|
||||
update() {
|
||||
// expand related documentation list
|
||||
setTimeout(() => {
|
||||
this.docData.forEach(d => {
|
||||
// parent node
|
||||
const isActive = this.isActiveNode(d)
|
||||
const node = this.$refs['documentation-tree'].getNode(d)
|
||||
node.expanded = isActive
|
||||
this.$set(d, 'active', isActive)
|
||||
|
||||
// child nodes
|
||||
d.children.forEach(c => {
|
||||
const node = this.$refs['documentation-tree'].getNode(c)
|
||||
const isActive = this.isActiveNode(c)
|
||||
if (!node.parent.expanded && isActive) {
|
||||
node.parent.expanded = true
|
||||
}
|
||||
this.$set(c, 'active', isActive)
|
||||
// child nodes
|
||||
d.children.forEach(c => {
|
||||
const node = this.$refs['documentation-tree'].getNode(c)
|
||||
const isActive = this.isActiveNode(c)
|
||||
if (!node.parent.expanded && isActive) {
|
||||
node.parent.expanded = true
|
||||
}
|
||||
this.$set(c, 'active', isActive)
|
||||
})
|
||||
})
|
||||
})
|
||||
}, 100)
|
||||
},
|
||||
async getDocumentationData () {
|
||||
// fetch api data
|
||||
await this.$store.dispatch('doc/getDocData')
|
||||
},
|
||||
onClickDocumentationLink () {
|
||||
this.$st.sendEv('全局', '点击右侧文档链接')
|
||||
}, 100)
|
||||
},
|
||||
async getDocumentationData() {
|
||||
// fetch api data
|
||||
await this.$store.dispatch('doc/getDocData')
|
||||
},
|
||||
onClickDocumentationLink() {
|
||||
this.$st.sendEv('全局', '点击右侧文档链接')
|
||||
}
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
},
|
||||
mounted () {
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.el-tree >>> .custom-tree-node.active {
|
||||
|
||||
@@ -2,25 +2,25 @@
|
||||
<div class="environment-list">
|
||||
<el-row>
|
||||
<div class="button-group">
|
||||
<el-button size="small" type="primary" @click="addEnv" icon="el-icon-plus">{{$t('Add Environment Variables')}}</el-button>
|
||||
<el-button size="small" type="success" @click="save">{{$t('Save')}}</el-button>
|
||||
<el-button size="small" type="primary" icon="el-icon-plus" @click="addEnv">{{ $t('Add Environment Variables') }}</el-button>
|
||||
<el-button size="small" type="success" @click="save">{{ $t('Save') }}</el-button>
|
||||
</div>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-table :data="spiderForm.envs">
|
||||
<el-table-column :label="$t('Variable')">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.name" :placeholder="$t('Variable')"></el-input>
|
||||
<el-input v-model="scope.row.name" :placeholder="$t('Variable')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Value')">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.value" :placeholder="$t('Value')"></el-input>
|
||||
<el-input v-model="scope.row.value" :placeholder="$t('Value')" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Action')">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteEnv(scope.$index)"></el-button>
|
||||
<el-button size="mini" icon="el-icon-delete" type="danger" @click="deleteEnv(scope.$index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -29,44 +29,44 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'EnvironmentList',
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
addEnv () {
|
||||
if (!this.spiderForm.envs) {
|
||||
this.$set(this.spiderForm, 'envs', [])
|
||||
export default {
|
||||
name: 'EnvironmentList',
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
addEnv() {
|
||||
if (!this.spiderForm.envs) {
|
||||
this.$set(this.spiderForm, 'envs', [])
|
||||
}
|
||||
this.spiderForm.envs.push({
|
||||
name: '',
|
||||
value: ''
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', '环境', '添加')
|
||||
},
|
||||
deleteEnv(index) {
|
||||
this.spiderForm.envs.splice(index, 1)
|
||||
this.$st.sendEv('爬虫详情', '环境', '删除')
|
||||
},
|
||||
save() {
|
||||
this.$store.dispatch('spider/editSpider')
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('Spider info has been saved successfully'))
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error)
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', '环境', '保存')
|
||||
}
|
||||
this.spiderForm.envs.push({
|
||||
name: '',
|
||||
value: ''
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', '环境', '添加')
|
||||
},
|
||||
deleteEnv (index) {
|
||||
this.spiderForm.envs.splice(index, 1)
|
||||
this.$st.sendEv('爬虫详情', '环境', '删除')
|
||||
},
|
||||
save () {
|
||||
this.$store.dispatch('spider/editSpider')
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('Spider info has been saved successfully'))
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error)
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', '环境', '保存')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,96 +1,96 @@
|
||||
<template>
|
||||
<div class="file-detail">
|
||||
<codemirror
|
||||
v-model="fileContent"
|
||||
class="file-content"
|
||||
:options="options"
|
||||
v-model="fileContent"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import { codemirror } from 'vue-codemirror-lite'
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import { codemirror } from 'vue-codemirror-lite'
|
||||
|
||||
import 'codemirror/lib/codemirror.js'
|
||||
import 'codemirror/lib/codemirror.js'
|
||||
|
||||
// language
|
||||
import 'codemirror/mode/python/python.js'
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/mode/go/go.js'
|
||||
import 'codemirror/mode/shell/shell.js'
|
||||
import 'codemirror/mode/markdown/markdown.js'
|
||||
import 'codemirror/mode/php/php.js'
|
||||
import 'codemirror/mode/yaml/yaml.js'
|
||||
// language
|
||||
import 'codemirror/mode/python/python.js'
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/mode/go/go.js'
|
||||
import 'codemirror/mode/shell/shell.js'
|
||||
import 'codemirror/mode/markdown/markdown.js'
|
||||
import 'codemirror/mode/php/php.js'
|
||||
import 'codemirror/mode/yaml/yaml.js'
|
||||
|
||||
export default {
|
||||
name: 'FileDetail',
|
||||
components: { codemirror },
|
||||
data () {
|
||||
return {
|
||||
internalFileContent: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapGetters('user', [
|
||||
'userInfo'
|
||||
]),
|
||||
fileContent: {
|
||||
get () {
|
||||
return this.$store.state.file.fileContent
|
||||
},
|
||||
set (value) {
|
||||
return this.$store.commit('file/SET_FILE_CONTENT', value)
|
||||
}
|
||||
},
|
||||
options () {
|
||||
export default {
|
||||
name: 'FileDetail',
|
||||
components: { codemirror },
|
||||
data() {
|
||||
return {
|
||||
mode: this.language,
|
||||
theme: 'darcula',
|
||||
styleActiveLine: true,
|
||||
smartIndent: true,
|
||||
indentUnit: 4,
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
matchBrackets: true,
|
||||
readOnly: this.isDisabled ? 'nocursor' : false
|
||||
internalFileContent: ''
|
||||
}
|
||||
},
|
||||
language () {
|
||||
const fileName = this.$store.state.file.currentPath
|
||||
if (!fileName) return ''
|
||||
if (fileName.match(/\.js$/)) {
|
||||
return 'text/javascript'
|
||||
} else if (fileName.match(/\.py$/)) {
|
||||
return 'text/x-python'
|
||||
} else if (fileName.match(/\.go$/)) {
|
||||
return 'text/x-go'
|
||||
} else if (fileName.match(/\.sh$/)) {
|
||||
return 'text/x-shell'
|
||||
} else if (fileName.match(/\.php$/)) {
|
||||
return 'text/x-php'
|
||||
} else if (fileName.match(/\.md$/)) {
|
||||
return 'text/x-markdown'
|
||||
} else if (fileName.match('Spiderfile')) {
|
||||
return 'text/x-yaml'
|
||||
} else {
|
||||
return 'text'
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapGetters('user', [
|
||||
'userInfo'
|
||||
]),
|
||||
fileContent: {
|
||||
get() {
|
||||
return this.$store.state.file.fileContent
|
||||
},
|
||||
set(value) {
|
||||
return this.$store.commit('file/SET_FILE_CONTENT', value)
|
||||
}
|
||||
},
|
||||
options() {
|
||||
return {
|
||||
mode: this.language,
|
||||
theme: 'darcula',
|
||||
styleActiveLine: true,
|
||||
smartIndent: true,
|
||||
indentUnit: 4,
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
matchBrackets: true,
|
||||
readOnly: this.isDisabled ? 'nocursor' : false
|
||||
}
|
||||
},
|
||||
language() {
|
||||
const fileName = this.$store.state.file.currentPath
|
||||
if (!fileName) return ''
|
||||
if (fileName.match(/\.js$/)) {
|
||||
return 'text/javascript'
|
||||
} else if (fileName.match(/\.py$/)) {
|
||||
return 'text/x-python'
|
||||
} else if (fileName.match(/\.go$/)) {
|
||||
return 'text/x-go'
|
||||
} else if (fileName.match(/\.sh$/)) {
|
||||
return 'text/x-shell'
|
||||
} else if (fileName.match(/\.php$/)) {
|
||||
return 'text/x-php'
|
||||
} else if (fileName.match(/\.md$/)) {
|
||||
return 'text/x-markdown'
|
||||
} else if (fileName.match('Spiderfile')) {
|
||||
return 'text/x-yaml'
|
||||
} else {
|
||||
return 'text'
|
||||
}
|
||||
},
|
||||
isDisabled() {
|
||||
return this.spiderForm.is_public && this.spiderForm.username !== this.userInfo.username && this.userInfo.role !== 'admin'
|
||||
}
|
||||
},
|
||||
isDisabled () {
|
||||
return this.spiderForm.is_public && this.spiderForm.username !== this.userInfo.username && this.userInfo.role !== 'admin'
|
||||
created() {
|
||||
this.internalFileContent = this.fileContent
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.internalFileContent = this.fileContent
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,30 +3,32 @@
|
||||
<el-dialog
|
||||
:title="$t('New Directory')"
|
||||
:visible.sync="dirDialogVisible"
|
||||
width="30%">
|
||||
width="30%"
|
||||
>
|
||||
<el-form>
|
||||
<el-form-item :label="$t('Enter new directory name')">
|
||||
<el-input v-model="name" :placeholder="$t('New directory name')"></el-input>
|
||||
<el-input v-model="name" :placeholder="$t('New directory name')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dirDialogVisible = false">{{$t('Cancel')}}</el-button>
|
||||
<el-button type="primary" @click="onAddDir">{{$t('Confirm')}}</el-button>
|
||||
<el-button @click="dirDialogVisible = false">{{ $t('Cancel') }}</el-button>
|
||||
<el-button type="primary" @click="onAddDir">{{ $t('Confirm') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
:title="$t('New File')"
|
||||
:visible.sync="fileDialogVisible"
|
||||
width="30%">
|
||||
width="30%"
|
||||
>
|
||||
<el-form>
|
||||
<el-form-item :label="$t('Enter new file name')">
|
||||
<el-input v-model="name" :placeholder="$t('New file name')"></el-input>
|
||||
<el-input v-model="name" :placeholder="$t('New file name')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="fileDialogVisible = false">{{$t('Cancel')}}</el-button>
|
||||
<el-button size="small" type="primary" @click="onAddFile">{{$t('Confirm')}}</el-button>
|
||||
<el-button size="small" @click="fileDialogVisible = false">{{ $t('Cancel') }}</el-button>
|
||||
<el-button size="small" type="primary" @click="onAddFile">{{ $t('Confirm') }}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
@@ -34,9 +36,9 @@
|
||||
class="file-tree-wrapper"
|
||||
>
|
||||
<el-tree
|
||||
ref="tree"
|
||||
class="tree"
|
||||
:data="computedFileTree"
|
||||
ref="tree"
|
||||
node-key="path"
|
||||
:highlight-current="true"
|
||||
:default-expanded-keys="expandedPaths"
|
||||
@@ -45,43 +47,64 @@
|
||||
@node-expand="onDirClick"
|
||||
@node-collapse="onDirClick"
|
||||
>
|
||||
<span class="custom-tree-node" slot-scope="{ node, data }">
|
||||
<el-popover v-model="isShowCreatePopoverDict[data.path]" trigger="manual" placement="right"
|
||||
popper-class="create-item-popover" :visible-arrow="false" @hide="onHideCreate(data)">
|
||||
<span slot-scope="{ node, data }" class="custom-tree-node">
|
||||
<el-popover
|
||||
v-model="isShowCreatePopoverDict[data.path]"
|
||||
trigger="manual"
|
||||
placement="right"
|
||||
popper-class="create-item-popover"
|
||||
:visible-arrow="false"
|
||||
@hide="onHideCreate(data)"
|
||||
>
|
||||
<ul class="action-item-list">
|
||||
<li class="action-item" @click="fileDialogVisible = true">
|
||||
<font-awesome-icon icon="file-alt" color="rgba(3,47,98,.5)"/>
|
||||
<span class="action-item-text">{{$t('Create File')}}</span>
|
||||
<font-awesome-icon icon="file-alt" color="rgba(3,47,98,.5)" />
|
||||
<span class="action-item-text">{{ $t('Create File') }}</span>
|
||||
</li>
|
||||
<li class="action-item" @click="dirDialogVisible = true">
|
||||
<font-awesome-icon :icon="['fa', 'folder']" color="rgba(3,47,98,.5)"/>
|
||||
<span class="action-item-text">{{$t('Create Directory')}}</span>
|
||||
<font-awesome-icon :icon="['fa', 'folder']" color="rgba(3,47,98,.5)" />
|
||||
<span class="action-item-text">{{ $t('Create Directory') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="action-item-list">
|
||||
<li class="action-item" @click="onClickRemoveNav(data)">
|
||||
<font-awesome-icon :icon="['fa', 'trash']" color="rgba(3,47,98,.5)"/>
|
||||
<span class="action-item-text">{{$t('Remove')}}</span>
|
||||
<font-awesome-icon :icon="['fa', 'trash']" color="rgba(3,47,98,.5)" />
|
||||
<span class="action-item-text">{{ $t('Remove') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<template slot="reference">
|
||||
<div>
|
||||
<span class="item-icon">
|
||||
<font-awesome-icon v-if="data.is_dir" :icon="['fa', 'folder']" color="rgba(3,47,98,.5)"/>
|
||||
<font-awesome-icon v-else-if="data.path.match(/\.py$/)" :icon="['fab','python']"
|
||||
color="rgba(3,47,98,.5)"/>
|
||||
<font-awesome-icon v-else-if="data.path.match(/\.js$/)" :icon="['fab','node-js']"
|
||||
color="rgba(3,47,98,.5)"/>
|
||||
<font-awesome-icon v-else-if="data.path.match(/\.(java|jar|class)$/)" :icon="['fab','java']"
|
||||
color="rgba(3,47,98,.5)"/>
|
||||
<font-awesome-icon v-else-if="data.path.match(/\.go$/)" :icon="['fab','go']"
|
||||
color="rgba(3,47,98,.5)"/>
|
||||
<font-awesome-icon v-else-if="data.path.match(/\.zip$/)" :icon="['fa','file-archive']"
|
||||
color="rgba(3,47,98,.5)"/>
|
||||
<font-awesome-icon v-else icon="file-alt" color="rgba(3,47,98,.5)"/>
|
||||
<font-awesome-icon v-if="data.is_dir" :icon="['fa', 'folder']" color="rgba(3,47,98,.5)" />
|
||||
<font-awesome-icon
|
||||
v-else-if="data.path.match(/\.py$/)"
|
||||
:icon="['fab','python']"
|
||||
color="rgba(3,47,98,.5)"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
v-else-if="data.path.match(/\.js$/)"
|
||||
:icon="['fab','node-js']"
|
||||
color="rgba(3,47,98,.5)"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
v-else-if="data.path.match(/\.(java|jar|class)$/)"
|
||||
:icon="['fab','java']"
|
||||
color="rgba(3,47,98,.5)"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
v-else-if="data.path.match(/\.go$/)"
|
||||
:icon="['fab','go']"
|
||||
color="rgba(3,47,98,.5)"
|
||||
/>
|
||||
<font-awesome-icon
|
||||
v-else-if="data.path.match(/\.zip$/)"
|
||||
:icon="['fa','file-archive']"
|
||||
color="rgba(3,47,98,.5)"
|
||||
/>
|
||||
<font-awesome-icon v-else icon="file-alt" color="rgba(3,47,98,.5)" />
|
||||
</span>
|
||||
<span class="item-name" :class="isActiveFile(data) ? 'active' : ''">
|
||||
{{data.name}}
|
||||
{{ data.name }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -91,28 +114,32 @@
|
||||
<div
|
||||
class="add-btn-wrapper"
|
||||
>
|
||||
<el-popover trigger="click" placement="right"
|
||||
popper-class="create-item-popover" :visible-arrow="false">
|
||||
<el-popover
|
||||
trigger="click"
|
||||
placement="right"
|
||||
popper-class="create-item-popover"
|
||||
:visible-arrow="false"
|
||||
>
|
||||
<ul class="action-item-list">
|
||||
<li class="action-item" @click="fileDialogVisible = true">
|
||||
<font-awesome-icon icon="file-alt" color="rgba(3,47,98,.5)"/>
|
||||
<span class="action-item-text">{{$t('Create File')}}</span>
|
||||
<font-awesome-icon icon="file-alt" color="rgba(3,47,98,.5)" />
|
||||
<span class="action-item-text">{{ $t('Create File') }}</span>
|
||||
</li>
|
||||
<li class="action-item" @click="dirDialogVisible = true">
|
||||
<font-awesome-icon :icon="['fa', 'folder']" color="rgba(3,47,98,.5)"/>
|
||||
<span class="action-item-text">{{$t('Create Directory')}}</span>
|
||||
<font-awesome-icon :icon="['fa', 'folder']" color="rgba(3,47,98,.5)" />
|
||||
<span class="action-item-text">{{ $t('Create Directory') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<el-button
|
||||
slot="reference"
|
||||
class="add-btn"
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
slot="reference"
|
||||
:disabled="isDisabled"
|
||||
@click="onEmptyClick"
|
||||
>
|
||||
{{$t('Add')}}
|
||||
{{ $t('Add') }}
|
||||
</el-button>
|
||||
</el-popover>
|
||||
</div>
|
||||
@@ -120,7 +147,7 @@
|
||||
|
||||
<div class="main-content">
|
||||
<div v-if="!showFile" class="file-list">
|
||||
{{$t('Please select a file or click the add button on the left.')}}
|
||||
{{ $t('Please select a file or click the add button on the left.') }}
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="top-part">
|
||||
@@ -128,336 +155,336 @@
|
||||
<div class="action-container">
|
||||
<el-popover v-model="isShowDelete" trigger="click">
|
||||
<el-button size="small" type="default" @click="() => this.isShowDelete = false">
|
||||
{{$t('Cancel')}}
|
||||
{{ $t('Cancel') }}
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="onFileDelete">
|
||||
{{$t('Confirm')}}
|
||||
{{ $t('Confirm') }}
|
||||
</el-button>
|
||||
<template slot="reference">
|
||||
<el-button type="danger" size="small" style="margin-right: 10px;" :disabled="isDisabled">
|
||||
<font-awesome-icon :icon="['fa', 'trash']"/>
|
||||
{{$t('Remove')}}
|
||||
<font-awesome-icon :icon="['fa', 'trash']" />
|
||||
{{ $t('Remove') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-popover v-model="isShowRename" trigger="click">
|
||||
<el-input v-model="name" :placeholder="$t('Name')" style="margin-bottom: 10px"/>
|
||||
<el-input v-model="name" :placeholder="$t('Name')" style="margin-bottom: 10px" />
|
||||
<div style="text-align: right">
|
||||
<el-button size="small" type="warning" @click="onRenameFile">
|
||||
{{$t('Confirm')}}
|
||||
{{ $t('Confirm') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<template slot="reference">
|
||||
<div>
|
||||
<el-button type="warning" size="small" style="margin-right: 10px;" :disabled="isDisabled" @click="onOpenRename">
|
||||
<font-awesome-icon :icon="['fa', 'redo']"/>
|
||||
{{$t('Rename')}}
|
||||
<font-awesome-icon :icon="['fa', 'redo']" />
|
||||
{{ $t('Rename') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-button type="success" size="small" style="margin-right: 10px;" :disabled="isDisabled" @click="onFileSave">
|
||||
<font-awesome-icon :icon="['fa', 'save']"/>
|
||||
{{$t('Save')}}
|
||||
<font-awesome-icon :icon="['fa', 'save']" />
|
||||
{{ $t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<!--./back-->
|
||||
|
||||
<!--file path-->
|
||||
<div class="file-path-container">
|
||||
<div class="file-path">{{currentPath}}</div>
|
||||
<div class="file-path">{{ currentPath }}</div>
|
||||
</div>
|
||||
<!--./file path-->
|
||||
</div>
|
||||
<file-detail/>
|
||||
<file-detail />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import FileDetail from './FileDetail'
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import FileDetail from './FileDetail'
|
||||
|
||||
export default {
|
||||
name: 'FileList',
|
||||
components: { FileDetail },
|
||||
data () {
|
||||
return {
|
||||
isEdit: false,
|
||||
showFile: false,
|
||||
name: '',
|
||||
isShowAdd: false,
|
||||
isShowDelete: false,
|
||||
isShowRename: false,
|
||||
isShowCreatePopoverDict: {},
|
||||
currentFilePath: '.',
|
||||
ignoreFileRegexList: [
|
||||
'__pycache__',
|
||||
'md5.txt',
|
||||
'.pyc',
|
||||
'.git'
|
||||
],
|
||||
activeFileNode: {},
|
||||
dirDialogVisible: false,
|
||||
fileDialogVisible: false,
|
||||
nodeExpandedDict: {},
|
||||
isShowDeleteNav: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'fileTree',
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('file', [
|
||||
'fileList'
|
||||
]),
|
||||
...mapGetters('user', [
|
||||
'userInfo'
|
||||
]),
|
||||
currentPath: {
|
||||
set (value) {
|
||||
this.$store.commit('file/SET_CURRENT_PATH', value)
|
||||
},
|
||||
get () {
|
||||
return this.$store.state.file.currentPath
|
||||
export default {
|
||||
name: 'FileList',
|
||||
components: { FileDetail },
|
||||
data() {
|
||||
return {
|
||||
isEdit: false,
|
||||
showFile: false,
|
||||
name: '',
|
||||
isShowAdd: false,
|
||||
isShowDelete: false,
|
||||
isShowRename: false,
|
||||
isShowCreatePopoverDict: {},
|
||||
currentFilePath: '.',
|
||||
ignoreFileRegexList: [
|
||||
'__pycache__',
|
||||
'md5.txt',
|
||||
'.pyc',
|
||||
'.git'
|
||||
],
|
||||
activeFileNode: {},
|
||||
dirDialogVisible: false,
|
||||
fileDialogVisible: false,
|
||||
nodeExpandedDict: {},
|
||||
isShowDeleteNav: false
|
||||
}
|
||||
},
|
||||
computedFileTree () {
|
||||
if (!this.fileTree || !this.fileTree.children) return []
|
||||
let nodes = this.sortFiles(this.fileTree.children)
|
||||
nodes = this.filterFiles(nodes)
|
||||
return nodes
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'fileTree',
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('file', [
|
||||
'fileList'
|
||||
]),
|
||||
...mapGetters('user', [
|
||||
'userInfo'
|
||||
]),
|
||||
currentPath: {
|
||||
set(value) {
|
||||
this.$store.commit('file/SET_CURRENT_PATH', value)
|
||||
},
|
||||
get() {
|
||||
return this.$store.state.file.currentPath
|
||||
}
|
||||
},
|
||||
computedFileTree() {
|
||||
if (!this.fileTree || !this.fileTree.children) return []
|
||||
let nodes = this.sortFiles(this.fileTree.children)
|
||||
nodes = this.filterFiles(nodes)
|
||||
return nodes
|
||||
},
|
||||
expandedPaths() {
|
||||
return Object.keys(this.nodeExpandedDict)
|
||||
.map(path => {
|
||||
return {
|
||||
path,
|
||||
expanded: this.nodeExpandedDict[path]
|
||||
}
|
||||
})
|
||||
.filter(d => d.expanded)
|
||||
.map(d => d.path)
|
||||
},
|
||||
isDisabled() {
|
||||
return this.spiderForm.is_public && this.spiderForm.username !== this.userInfo.username && this.userInfo.role !== 'admin'
|
||||
}
|
||||
},
|
||||
expandedPaths () {
|
||||
return Object.keys(this.nodeExpandedDict)
|
||||
.map(path => {
|
||||
return {
|
||||
path,
|
||||
expanded: this.nodeExpandedDict[path]
|
||||
}
|
||||
})
|
||||
.filter(d => d.expanded)
|
||||
.map(d => d.path)
|
||||
async created() {
|
||||
await this.getFileTree()
|
||||
},
|
||||
isDisabled () {
|
||||
return this.spiderForm.is_public && this.spiderForm.username !== this.userInfo.username && this.userInfo.role !== 'admin'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onEdit () {
|
||||
this.isEdit = true
|
||||
mounted() {
|
||||
this.listener = document.querySelector('body').addEventListener('click', ev => {
|
||||
this.isShowCreatePopoverDict = {}
|
||||
})
|
||||
},
|
||||
onItemClick (item) {
|
||||
if (item.is_dir) {
|
||||
// 目录
|
||||
this.$store.dispatch('file/getFileList', { path: item.path })
|
||||
} else {
|
||||
// 文件
|
||||
destroyed() {
|
||||
document.querySelector('body').removeEventListener('click', this.listener)
|
||||
},
|
||||
methods: {
|
||||
onEdit() {
|
||||
this.isEdit = true
|
||||
},
|
||||
onItemClick(item) {
|
||||
if (item.is_dir) {
|
||||
// 目录
|
||||
this.$store.dispatch('file/getFileList', { path: item.path })
|
||||
} else {
|
||||
// 文件
|
||||
this.showFile = true
|
||||
this.$store.commit('file/SET_FILE_CONTENT', '')
|
||||
this.$store.commit('file/SET_CURRENT_PATH', item.path)
|
||||
this.$store.dispatch('file/getFileContent', { path: item.path })
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', '文件', '点击')
|
||||
},
|
||||
async onFileSave() {
|
||||
await this.$store.dispatch('file/saveFileContent', { path: this.currentPath })
|
||||
this.$message.success(this.$t('Saved file successfully'))
|
||||
this.$st.sendEv('爬虫详情', '文件', '保存')
|
||||
},
|
||||
async onAddFile() {
|
||||
if (!this.name) {
|
||||
this.$message.error(this.$t('Name cannot be empty'))
|
||||
return
|
||||
}
|
||||
const arr = this.activeFileNode.path.split('/')
|
||||
if (this.activeFileNode.is_dir) {
|
||||
arr.push(this.name)
|
||||
} else {
|
||||
arr[arr.length - 1] = this.name
|
||||
}
|
||||
const path = arr.join('/')
|
||||
await this.$store.dispatch('file/addFile', { path })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.isShowAdd = false
|
||||
this.fileDialogVisible = false
|
||||
this.showFile = true
|
||||
this.$store.commit('file/SET_FILE_CONTENT', '')
|
||||
this.$store.commit('file/SET_CURRENT_PATH', item.path)
|
||||
this.$store.dispatch('file/getFileContent', { path: item.path })
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', '文件', '点击')
|
||||
},
|
||||
async onFileSave () {
|
||||
await this.$store.dispatch('file/saveFileContent', { path: this.currentPath })
|
||||
this.$message.success(this.$t('Saved file successfully'))
|
||||
this.$st.sendEv('爬虫详情', '文件', '保存')
|
||||
},
|
||||
async onAddFile () {
|
||||
if (!this.name) {
|
||||
this.$message.error(this.$t('Name cannot be empty'))
|
||||
return
|
||||
}
|
||||
const arr = this.activeFileNode.path.split('/')
|
||||
if (this.activeFileNode.is_dir) {
|
||||
arr.push(this.name)
|
||||
} else {
|
||||
arr[arr.length - 1] = this.name
|
||||
}
|
||||
const path = arr.join('/')
|
||||
await this.$store.dispatch('file/addFile', { path })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.isShowAdd = false
|
||||
this.fileDialogVisible = false
|
||||
this.showFile = true
|
||||
this.$store.commit('file/SET_FILE_CONTENT', '')
|
||||
this.$store.commit('file/SET_CURRENT_PATH', path)
|
||||
await this.$store.dispatch('file/getFileContent', { path })
|
||||
this.$st.sendEv('爬虫详情', '文件', '添加')
|
||||
},
|
||||
async onAddDir () {
|
||||
if (!this.name) {
|
||||
this.$message.error(this.$t('Name cannot be empty'))
|
||||
return
|
||||
}
|
||||
const arr = this.activeFileNode.path.split('/')
|
||||
if (this.activeFileNode.is_dir) {
|
||||
arr.push(this.name)
|
||||
} else {
|
||||
arr[arr.length - 1] = this.name
|
||||
}
|
||||
const path = arr.join('/')
|
||||
await this.$store.dispatch('file/addDir', { path })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.isShowAdd = false
|
||||
this.dirDialogVisible = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '添加')
|
||||
},
|
||||
async onFileDelete () {
|
||||
await this.$store.dispatch('file/deleteFile', { path: this.currentFilePath })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.$message.success(this.$t('Deleted successfully'))
|
||||
this.isShowDelete = false
|
||||
this.showFile = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '删除')
|
||||
},
|
||||
onOpenRename () {
|
||||
const arr = this.currentFilePath.split('/')
|
||||
this.name = arr[arr.length - 1]
|
||||
},
|
||||
async onRenameFile () {
|
||||
await this.$store.dispatch('file/renameFile', { path: this.currentFilePath, newPath: this.name })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
const arr = this.currentFilePath.split('/')
|
||||
arr[arr.length - 1] = this.name
|
||||
this.currentFilePath = arr.join('/')
|
||||
this.$store.commit('file/SET_CURRENT_PATH', this.currentFilePath)
|
||||
this.$message.success(this.$t('Renamed successfully'))
|
||||
this.isShowRename = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '重命名')
|
||||
},
|
||||
async getFileTree () {
|
||||
const arr = this.$route.path.split('/')
|
||||
const id = arr[arr.length - 1]
|
||||
await this.$store.dispatch('spider/getFileTree', { id })
|
||||
},
|
||||
async onFileClick (data) {
|
||||
if (data.is_dir) {
|
||||
return
|
||||
}
|
||||
this.currentFilePath = data.path
|
||||
this.onItemClick(data)
|
||||
},
|
||||
onDirClick (data, node) {
|
||||
const vm = this
|
||||
setTimeout(() => {
|
||||
vm.$set(vm.nodeExpandedDict, data.path, node.expanded)
|
||||
}, 0)
|
||||
},
|
||||
sortFiles (nodes) {
|
||||
nodes.forEach(node => {
|
||||
if (node.is_dir) {
|
||||
if (!node.children) node.children = []
|
||||
node.children = this.sortFiles(node.children)
|
||||
this.$store.commit('file/SET_CURRENT_PATH', path)
|
||||
await this.$store.dispatch('file/getFileContent', { path })
|
||||
this.$st.sendEv('爬虫详情', '文件', '添加')
|
||||
},
|
||||
async onAddDir() {
|
||||
if (!this.name) {
|
||||
this.$message.error(this.$t('Name cannot be empty'))
|
||||
return
|
||||
}
|
||||
})
|
||||
return nodes.sort((a, b) => {
|
||||
if ((a.is_dir && b.is_dir) || (!a.is_dir && !b.is_dir)) {
|
||||
return a.name > b.name ? 1 : -1
|
||||
const arr = this.activeFileNode.path.split('/')
|
||||
if (this.activeFileNode.is_dir) {
|
||||
arr.push(this.name)
|
||||
} else {
|
||||
return a.is_dir ? -1 : 1
|
||||
arr[arr.length - 1] = this.name
|
||||
}
|
||||
})
|
||||
},
|
||||
filterFiles (nodes) {
|
||||
return nodes.filter(node => {
|
||||
if (node.is_dir) {
|
||||
node.children = this.filterFiles(node.children)
|
||||
const path = arr.join('/')
|
||||
await this.$store.dispatch('file/addDir', { path })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.isShowAdd = false
|
||||
this.dirDialogVisible = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '添加')
|
||||
},
|
||||
async onFileDelete() {
|
||||
await this.$store.dispatch('file/deleteFile', { path: this.currentFilePath })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.$message.success(this.$t('Deleted successfully'))
|
||||
this.isShowDelete = false
|
||||
this.showFile = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '删除')
|
||||
},
|
||||
onOpenRename() {
|
||||
const arr = this.currentFilePath.split('/')
|
||||
this.name = arr[arr.length - 1]
|
||||
},
|
||||
async onRenameFile() {
|
||||
await this.$store.dispatch('file/renameFile', { path: this.currentFilePath, newPath: this.name })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
const arr = this.currentFilePath.split('/')
|
||||
arr[arr.length - 1] = this.name
|
||||
this.currentFilePath = arr.join('/')
|
||||
this.$store.commit('file/SET_CURRENT_PATH', this.currentFilePath)
|
||||
this.$message.success(this.$t('Renamed successfully'))
|
||||
this.isShowRename = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '重命名')
|
||||
},
|
||||
async getFileTree() {
|
||||
const arr = this.$route.path.split('/')
|
||||
const id = arr[arr.length - 1]
|
||||
await this.$store.dispatch('spider/getFileTree', { id })
|
||||
},
|
||||
async onFileClick(data) {
|
||||
if (data.is_dir) {
|
||||
return
|
||||
}
|
||||
for (let i = 0; i < this.ignoreFileRegexList.length; i++) {
|
||||
const regex = this.ignoreFileRegexList[i]
|
||||
if (node.name.match(regex)) {
|
||||
return false
|
||||
this.currentFilePath = data.path
|
||||
this.onItemClick(data)
|
||||
},
|
||||
onDirClick(data, node) {
|
||||
const vm = this
|
||||
setTimeout(() => {
|
||||
vm.$set(vm.nodeExpandedDict, data.path, node.expanded)
|
||||
}, 0)
|
||||
},
|
||||
sortFiles(nodes) {
|
||||
nodes.forEach(node => {
|
||||
if (node.is_dir) {
|
||||
if (!node.children) node.children = []
|
||||
node.children = this.sortFiles(node.children)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
},
|
||||
isActiveFile (node) {
|
||||
return node.path === this.currentFilePath
|
||||
},
|
||||
onFileRightClick (ev, data) {
|
||||
this.isShowCreatePopoverDict = {}
|
||||
this.$set(this.isShowCreatePopoverDict, data.path, true)
|
||||
this.activeFileNode = data
|
||||
this.$st.sendEv('爬虫详情', '文件', '右键点击导航栏')
|
||||
},
|
||||
onEmptyClick () {
|
||||
const data = { path: '' }
|
||||
this.isShowCreatePopoverDict = {}
|
||||
this.$set(this.isShowCreatePopoverDict, data.path, true)
|
||||
this.activeFileNode = data
|
||||
this.$st.sendEv('爬虫详情', '文件', '空白点击添加')
|
||||
},
|
||||
onHideCreate (data) {
|
||||
this.$set(this.isShowCreatePopoverDict, data.path, false)
|
||||
this.name = ''
|
||||
},
|
||||
onClickRemoveNav (data) {
|
||||
this.$confirm(this.$t('Are you sure to delete this file/directory?'), this.$t('Notification'), {
|
||||
confirmButtonText: this.$t('Confirm'),
|
||||
cancelButtonText: this.$t('Cancel'),
|
||||
confirmButtonClass: 'danger',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.onFileDeleteNav(data.path)
|
||||
})
|
||||
},
|
||||
async onFileDeleteNav (path) {
|
||||
await this.$store.dispatch('file/deleteFile', { path })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.$message.success(this.$t('Deleted successfully'))
|
||||
this.isShowDelete = false
|
||||
this.showFile = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '删除')
|
||||
},
|
||||
clickSpider (filepath) {
|
||||
const node = this.$refs['tree'].getNode(filepath)
|
||||
const data = node.data
|
||||
this.onFileClick(data)
|
||||
node.parent.expanded = true
|
||||
this.$set(this.nodeExpandedDict, node.parent.data.path, true)
|
||||
node.parent.parent.expanded = true
|
||||
this.$set(this.nodeExpandedDict, node.parent.parent.data.path, true)
|
||||
},
|
||||
clickPipeline () {
|
||||
const filename = 'pipelines.py'
|
||||
for (let i = 0; i < this.computedFileTree.length; i++) {
|
||||
const dataLv1 = this.computedFileTree[i]
|
||||
const nodeLv1 = this.$refs['tree'].getNode(dataLv1.path)
|
||||
if (dataLv1.is_dir) {
|
||||
for (let j = 0; j < dataLv1.children.length; j++) {
|
||||
const dataLv2 = dataLv1.children[j]
|
||||
if (dataLv2.path.match(filename)) {
|
||||
this.onFileClick(dataLv2)
|
||||
nodeLv1.expanded = true
|
||||
this.$set(this.nodeExpandedDict, dataLv1.path, true)
|
||||
return
|
||||
})
|
||||
return nodes.sort((a, b) => {
|
||||
if ((a.is_dir && b.is_dir) || (!a.is_dir && !b.is_dir)) {
|
||||
return a.name > b.name ? 1 : -1
|
||||
} else {
|
||||
return a.is_dir ? -1 : 1
|
||||
}
|
||||
})
|
||||
},
|
||||
filterFiles(nodes) {
|
||||
return nodes.filter(node => {
|
||||
if (node.is_dir) {
|
||||
node.children = this.filterFiles(node.children)
|
||||
}
|
||||
for (let i = 0; i < this.ignoreFileRegexList.length; i++) {
|
||||
const regex = this.ignoreFileRegexList[i]
|
||||
if (node.name.match(regex)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
},
|
||||
isActiveFile(node) {
|
||||
return node.path === this.currentFilePath
|
||||
},
|
||||
onFileRightClick(ev, data) {
|
||||
this.isShowCreatePopoverDict = {}
|
||||
this.$set(this.isShowCreatePopoverDict, data.path, true)
|
||||
this.activeFileNode = data
|
||||
this.$st.sendEv('爬虫详情', '文件', '右键点击导航栏')
|
||||
},
|
||||
onEmptyClick() {
|
||||
const data = { path: '' }
|
||||
this.isShowCreatePopoverDict = {}
|
||||
this.$set(this.isShowCreatePopoverDict, data.path, true)
|
||||
this.activeFileNode = data
|
||||
this.$st.sendEv('爬虫详情', '文件', '空白点击添加')
|
||||
},
|
||||
onHideCreate(data) {
|
||||
this.$set(this.isShowCreatePopoverDict, data.path, false)
|
||||
this.name = ''
|
||||
},
|
||||
onClickRemoveNav(data) {
|
||||
this.$confirm(this.$t('Are you sure to delete this file/directory?'), this.$t('Notification'), {
|
||||
confirmButtonText: this.$t('Confirm'),
|
||||
cancelButtonText: this.$t('Cancel'),
|
||||
confirmButtonClass: 'danger',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.onFileDeleteNav(data.path)
|
||||
})
|
||||
},
|
||||
async onFileDeleteNav(path) {
|
||||
await this.$store.dispatch('file/deleteFile', { path })
|
||||
await this.$store.dispatch('spider/getFileTree')
|
||||
this.$message.success(this.$t('Deleted successfully'))
|
||||
this.isShowDelete = false
|
||||
this.showFile = false
|
||||
this.$st.sendEv('爬虫详情', '文件', '删除')
|
||||
},
|
||||
clickSpider(filepath) {
|
||||
const node = this.$refs['tree'].getNode(filepath)
|
||||
const data = node.data
|
||||
this.onFileClick(data)
|
||||
node.parent.expanded = true
|
||||
this.$set(this.nodeExpandedDict, node.parent.data.path, true)
|
||||
node.parent.parent.expanded = true
|
||||
this.$set(this.nodeExpandedDict, node.parent.parent.data.path, true)
|
||||
},
|
||||
clickPipeline() {
|
||||
const filename = 'pipelines.py'
|
||||
for (let i = 0; i < this.computedFileTree.length; i++) {
|
||||
const dataLv1 = this.computedFileTree[i]
|
||||
const nodeLv1 = this.$refs['tree'].getNode(dataLv1.path)
|
||||
if (dataLv1.is_dir) {
|
||||
for (let j = 0; j < dataLv1.children.length; j++) {
|
||||
const dataLv2 = dataLv1.children[j]
|
||||
if (dataLv2.path.match(filename)) {
|
||||
this.onFileClick(dataLv2)
|
||||
nodeLv1.expanded = true
|
||||
this.$set(this.nodeExpandedDict, dataLv1.path, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
await this.getFileTree()
|
||||
},
|
||||
mounted () {
|
||||
this.listener = document.querySelector('body').addEventListener('click', ev => {
|
||||
this.isShowCreatePopoverDict = {}
|
||||
})
|
||||
},
|
||||
destroyed () {
|
||||
document.querySelector('body').removeEventListener('click', this.listener)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -7,26 +7,27 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="64"
|
||||
height="64"
|
||||
@click="toggleClick">
|
||||
@click="toggleClick"
|
||||
>
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
toggleClick: {
|
||||
type: Function,
|
||||
default: null
|
||||
export default {
|
||||
name: 'Hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
toggleClick: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,64 +1,65 @@
|
||||
<template>
|
||||
<div class="info-view">
|
||||
<el-row>
|
||||
<el-form label-width="150px"
|
||||
:model="nodeForm"
|
||||
ref="nodeForm"
|
||||
class="node-form"
|
||||
label-position="right">
|
||||
<el-form
|
||||
ref="nodeForm"
|
||||
label-width="150px"
|
||||
:model="nodeForm"
|
||||
class="node-form"
|
||||
label-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('Node Name')">
|
||||
<el-input v-model="nodeForm.name" :placeholder="$t('Node Name')" :disabled="isView"></el-input>
|
||||
<el-input v-model="nodeForm.name" :placeholder="$t('Node Name')" :disabled="isView" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Node IP')" prop="ip" required>
|
||||
<el-input v-model="nodeForm.ip" :placeholder="$t('Node IP')" disabled></el-input>
|
||||
<el-input v-model="nodeForm.ip" :placeholder="$t('Node IP')" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Node MAC')" prop="ip" required>
|
||||
<el-input v-model="nodeForm.mac" :placeholder="$t('Node MAC')" disabled></el-input>
|
||||
<el-input v-model="nodeForm.mac" :placeholder="$t('Node MAC')" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Description')">
|
||||
<el-input type="textarea" v-model="nodeForm.description" :placeholder="$t('Description')" :disabled="isView">
|
||||
</el-input>
|
||||
<el-input v-model="nodeForm.description" type="textarea" :placeholder="$t('Description')" :disabled="isView" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row class="button-container" v-if="!isView">
|
||||
<el-button size="small" type="success" @click="onSave">{{$t('Save')}}</el-button>
|
||||
<el-row v-if="!isView" class="button-container">
|
||||
<el-button size="small" type="success" @click="onSave">{{ $t('Save') }}</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NodeInfoView',
|
||||
props: {
|
||||
isView: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onSave () {
|
||||
this.$refs.nodeForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.$store.dispatch('node/editNode')
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('Node info has been saved successfully'))
|
||||
})
|
||||
}
|
||||
})
|
||||
this.$st.sendEv('节点详情', '概览', '保存')
|
||||
export default {
|
||||
name: 'NodeInfoView',
|
||||
props: {
|
||||
isView: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onSave() {
|
||||
this.$refs.nodeForm.validate(valid => {
|
||||
if (valid) {
|
||||
this.$store.dispatch('node/editNode')
|
||||
.then(() => {
|
||||
this.$message.success(this.$t('Node info has been saved successfully'))
|
||||
})
|
||||
}
|
||||
})
|
||||
this.$st.sendEv('节点详情', '概览', '保存')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -7,16 +7,18 @@
|
||||
/>
|
||||
|
||||
<el-row>
|
||||
<el-form label-width="150px"
|
||||
:model="spiderForm"
|
||||
ref="spiderForm"
|
||||
class="spider-form"
|
||||
label-position="right">
|
||||
<el-form
|
||||
ref="spiderForm"
|
||||
label-width="150px"
|
||||
:model="spiderForm"
|
||||
class="spider-form"
|
||||
label-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('Spider ID')">
|
||||
<el-input v-model="spiderForm._id" :placeholder="$t('Spider ID')" disabled></el-input>
|
||||
<el-input v-model="spiderForm._id" :placeholder="$t('Spider ID')" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Spider Name')">
|
||||
<el-input v-model="spiderForm.display_name" :placeholder="$t('Spider Name')" :disabled="isView || isPublic"/>
|
||||
<el-input v-model="spiderForm.display_name" :placeholder="$t('Spider Name')" :disabled="isView || isPublic" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Project')" prop="project_id" required>
|
||||
<el-select
|
||||
@@ -34,7 +36,7 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Source Folder')">
|
||||
<el-input v-model="spiderForm.src" :placeholder="$t('Source Folder')" disabled></el-input>
|
||||
<el-input v-model="spiderForm.src" :placeholder="$t('Source Folder')" disabled />
|
||||
</el-form-item>
|
||||
<template v-if="spiderForm.type === 'customized'">
|
||||
<el-form-item :label="$t('Execute Command')" prop="cmd" required :inline-message="true">
|
||||
@@ -54,14 +56,14 @@
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Spider Type')">
|
||||
<el-select v-model="spiderForm.type" :placeholder="$t('Spider Type')" :disabled="true" clearable>
|
||||
<el-option value="configurable" :label="$t('Configurable')"></el-option>
|
||||
<el-option value="customized" :label="$t('Customized')"></el-option>
|
||||
<el-option value="configurable" :label="$t('Configurable')" />
|
||||
<el-option value="customized" :label="$t('Customized')" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Remark')">
|
||||
<el-input
|
||||
type="textarea"
|
||||
v-model="spiderForm.remark"
|
||||
type="textarea"
|
||||
:placeholder="$t('Remark')"
|
||||
:disabled="isView || isPublic"
|
||||
/>
|
||||
@@ -96,8 +98,12 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item v-if="!isView" :label="$t('De-Duplication')" prop="dedup_field"
|
||||
:rules="dedupRules">
|
||||
<el-form-item
|
||||
v-if="!isView"
|
||||
:label="$t('De-Duplication')"
|
||||
prop="dedup_field"
|
||||
:rules="dedupRules"
|
||||
>
|
||||
<div style="display: flex; align-items: center; height: 40px">
|
||||
<el-switch
|
||||
v-model="spiderForm.is_dedup"
|
||||
@@ -112,8 +118,8 @@
|
||||
:disabled="isView || isPublic"
|
||||
style="margin-left: 20px; width: 180px"
|
||||
>
|
||||
<el-option value="overwrite" :label="$t('Overwrite')"/>
|
||||
<el-option value="ignore" :label="$t('Ignore')"/>
|
||||
<el-option value="overwrite" :label="$t('Overwrite')" />
|
||||
<el-option value="ignore" :label="$t('Ignore')" />
|
||||
</el-select>
|
||||
<el-input
|
||||
v-if="spiderForm.is_dedup"
|
||||
@@ -151,10 +157,16 @@
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row class="button-container" v-if="!isView">
|
||||
<el-button size="small" v-if="isShowRun && !isPublic" type="danger" @click="onCrawl"
|
||||
icon="el-icon-video-play" style="margin-right: 10px">
|
||||
{{$t('Run')}}
|
||||
<el-row v-if="!isView" class="button-container">
|
||||
<el-button
|
||||
v-if="isShowRun && !isPublic"
|
||||
size="small"
|
||||
type="danger"
|
||||
icon="el-icon-video-play"
|
||||
style="margin-right: 10px"
|
||||
@click="onCrawl"
|
||||
>
|
||||
{{ $t('Run') }}
|
||||
</el-button>
|
||||
<el-upload
|
||||
v-if="spiderForm.type === 'customized'"
|
||||
@@ -166,166 +178,166 @@
|
||||
:file-list="fileList"
|
||||
style="display:inline-block;margin-right:10px"
|
||||
>
|
||||
<el-button v-if="!isPublic" size="small" type="primary" icon="el-icon-upload" v-loading="uploadLoading">
|
||||
{{$t('Upload')}}
|
||||
<el-button v-if="!isPublic" v-loading="uploadLoading" size="small" type="primary" icon="el-icon-upload">
|
||||
{{ $t('Upload') }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-button v-if="!isPublic" size="small" type="success" @click="onSave" icon="el-icon-check">
|
||||
{{$t('Save')}}
|
||||
<el-button v-if="!isPublic" size="small" type="success" icon="el-icon-check" @click="onSave">
|
||||
{{ $t('Save') }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import CrawlConfirmDialog from '../Common/CrawlConfirmDialog'
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import CrawlConfirmDialog from '../Common/CrawlConfirmDialog'
|
||||
|
||||
export default {
|
||||
name: 'SpiderInfoView',
|
||||
components: { CrawlConfirmDialog },
|
||||
props: {
|
||||
isView: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const cronValidator = (rule, value, callback) => {
|
||||
let patArr = []
|
||||
for (let i = 0; i < 6; i++) {
|
||||
patArr.push('[/*,0-9]+')
|
||||
export default {
|
||||
name: 'SpiderInfoView',
|
||||
components: { CrawlConfirmDialog },
|
||||
props: {
|
||||
isView: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
const pat = '^' + patArr.join(' ') + '$'
|
||||
if (this.spiderForm.cron_enabled) {
|
||||
if (!value) {
|
||||
callback(new Error('cron cannot be empty'))
|
||||
} else if (!value.match(pat)) {
|
||||
callback(new Error('cron format is invalid'))
|
||||
},
|
||||
data() {
|
||||
const cronValidator = (rule, value, callback) => {
|
||||
const patArr = []
|
||||
for (let i = 0; i < 6; i++) {
|
||||
patArr.push('[/*,0-9]+')
|
||||
}
|
||||
const pat = '^' + patArr.join(' ') + '$'
|
||||
if (this.spiderForm.cron_enabled) {
|
||||
if (!value) {
|
||||
callback(new Error('cron cannot be empty'))
|
||||
} else if (!value.match(pat)) {
|
||||
callback(new Error('cron format is invalid'))
|
||||
}
|
||||
}
|
||||
callback()
|
||||
}
|
||||
callback()
|
||||
}
|
||||
const dedupValidator = (rule, value, callback) => {
|
||||
if (!this.spiderForm.is_dedup) {
|
||||
return callback()
|
||||
} else {
|
||||
if (value) {
|
||||
const dedupValidator = (rule, value, callback) => {
|
||||
if (!this.spiderForm.is_dedup) {
|
||||
return callback()
|
||||
} else {
|
||||
return callback(new Error('dedup field cannot be empty'))
|
||||
if (value) {
|
||||
return callback()
|
||||
} else {
|
||||
return callback(new Error('dedup field cannot be empty'))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
uploadLoading: false,
|
||||
fileList: [],
|
||||
crawlConfirmDialogVisible: false,
|
||||
cmdRule: [
|
||||
{ message: 'Execute Command should not be empty', required: true }
|
||||
],
|
||||
cronRules: [
|
||||
{ validator: cronValidator, trigger: 'blur' }
|
||||
],
|
||||
dedupRules: [
|
||||
{ validator: dedupValidator, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapGetters('user', [
|
||||
'userInfo',
|
||||
'token'
|
||||
]),
|
||||
...mapState('project', [
|
||||
'projectList'
|
||||
]),
|
||||
isConfigurable () {
|
||||
return this.spiderForm.type === 'configurable'
|
||||
},
|
||||
isShowRun () {
|
||||
if (this.spiderForm.type === 'customized') {
|
||||
return !!this.spiderForm.cmd
|
||||
} else {
|
||||
return true
|
||||
return {
|
||||
uploadLoading: false,
|
||||
fileList: [],
|
||||
crawlConfirmDialogVisible: false,
|
||||
cmdRule: [
|
||||
{ message: 'Execute Command should not be empty', required: true }
|
||||
],
|
||||
cronRules: [
|
||||
{ validator: cronValidator, trigger: 'blur' }
|
||||
],
|
||||
dedupRules: [
|
||||
{ validator: dedupValidator, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
},
|
||||
isPublic () {
|
||||
return this.spiderForm.is_public && this.spiderForm.username !== this.userInfo.username && this.userInfo.role !== 'admin'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCrawl () {
|
||||
this.crawlConfirmDialogVisible = true
|
||||
this.$st.sendEv('爬虫详情', '概览', '点击运行')
|
||||
},
|
||||
onSave () {
|
||||
this.$refs['spiderForm'].validate(async valid => {
|
||||
if (!valid) return
|
||||
const res = await this.$store.dispatch('spider/editSpider')
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Spider info has been saved successfully'))
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapGetters('user', [
|
||||
'userInfo',
|
||||
'token'
|
||||
]),
|
||||
...mapState('project', [
|
||||
'projectList'
|
||||
]),
|
||||
isConfigurable() {
|
||||
return this.spiderForm.type === 'configurable'
|
||||
},
|
||||
isShowRun() {
|
||||
if (this.spiderForm.type === 'customized') {
|
||||
return !!this.spiderForm.cmd
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
await this.$store.dispatch('spider/getSpiderData', this.$route.params.id)
|
||||
if (this.spiderForm.is_scrapy) {
|
||||
await this.$store.dispatch('spider/getSpiderScrapySpiders', this.$route.params.id)
|
||||
}
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', '概览', '保存')
|
||||
},
|
||||
isPublic() {
|
||||
return this.spiderForm.is_public && this.spiderForm.username !== this.userInfo.username && this.userInfo.role !== 'admin'
|
||||
}
|
||||
},
|
||||
fetchSiteSuggestions (keyword, callback) {
|
||||
this.$request.get('/sites', {
|
||||
keyword: keyword,
|
||||
page_num: 1,
|
||||
page_size: 100
|
||||
}).then(response => {
|
||||
const data = response.data.items.map(d => {
|
||||
d.value = `${d.name} | ${d.domain}`
|
||||
return d
|
||||
async created() {
|
||||
// fetch project list
|
||||
await this.$store.dispatch('project/getProjectList')
|
||||
|
||||
// 兼容项目ID
|
||||
if (!this.spiderForm.project_id) {
|
||||
this.$set(this.spiderForm, 'project_id', '000000000000000000000000')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onCrawl() {
|
||||
this.crawlConfirmDialogVisible = true
|
||||
this.$st.sendEv('爬虫详情', '概览', '点击运行')
|
||||
},
|
||||
onSave() {
|
||||
this.$refs['spiderForm'].validate(async valid => {
|
||||
if (!valid) return
|
||||
const res = await this.$store.dispatch('spider/editSpider')
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Spider info has been saved successfully'))
|
||||
}
|
||||
await this.$store.dispatch('spider/getSpiderData', this.$route.params.id)
|
||||
if (this.spiderForm.is_scrapy) {
|
||||
await this.$store.dispatch('spider/getSpiderScrapySpiders', this.$route.params.id)
|
||||
}
|
||||
})
|
||||
callback(data)
|
||||
})
|
||||
},
|
||||
onSiteSelect (item) {
|
||||
this.spiderForm.site = item._id
|
||||
},
|
||||
onUploadSuccess () {
|
||||
this.$store.dispatch('spider/getFileTree')
|
||||
this.$st.sendEv('爬虫详情', '概览', '保存')
|
||||
},
|
||||
fetchSiteSuggestions(keyword, callback) {
|
||||
this.$request.get('/sites', {
|
||||
keyword: keyword,
|
||||
page_num: 1,
|
||||
page_size: 100
|
||||
}).then(response => {
|
||||
const data = response.data.items.map(d => {
|
||||
d.value = `${d.name} | ${d.domain}`
|
||||
return d
|
||||
})
|
||||
callback(data)
|
||||
})
|
||||
},
|
||||
onSiteSelect(item) {
|
||||
this.spiderForm.site = item._id
|
||||
},
|
||||
onUploadSuccess() {
|
||||
this.$store.dispatch('spider/getFileTree')
|
||||
|
||||
this.uploadLoading = false
|
||||
this.uploadLoading = false
|
||||
|
||||
this.$message.success(this.$t('Uploaded spider files successfully'))
|
||||
},
|
||||
onUploadError () {
|
||||
this.uploadLoading = false
|
||||
},
|
||||
onIsScrapyChange (value) {
|
||||
if (value) {
|
||||
this.spiderForm.cmd = 'scrapy crawl'
|
||||
this.$message.success(this.$t('Uploaded spider files successfully'))
|
||||
},
|
||||
onUploadError() {
|
||||
this.uploadLoading = false
|
||||
},
|
||||
onIsScrapyChange(value) {
|
||||
if (value) {
|
||||
this.spiderForm.cmd = 'scrapy crawl'
|
||||
}
|
||||
},
|
||||
onIsDedupChange(value) {
|
||||
if (value && !this.spiderForm.dedup_method) {
|
||||
this.spiderForm.dedup_method = 'overwrite'
|
||||
}
|
||||
}
|
||||
},
|
||||
onIsDedupChange (value) {
|
||||
if (value && !this.spiderForm.dedup_method) {
|
||||
this.spiderForm.dedup_method = 'overwrite'
|
||||
}
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
// fetch project list
|
||||
await this.$store.dispatch('project/getProjectList')
|
||||
|
||||
// 兼容项目ID
|
||||
if (!this.spiderForm.project_id) {
|
||||
this.$set(this.spiderForm, 'project_id', '000000000000000000000000')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
<template>
|
||||
<div class="info-view">
|
||||
<el-row>
|
||||
<el-form label-width="150px"
|
||||
:model="taskForm"
|
||||
ref="nodeForm"
|
||||
class="node-form"
|
||||
label-position="right">
|
||||
<el-form
|
||||
ref="nodeForm"
|
||||
label-width="150px"
|
||||
:model="taskForm"
|
||||
class="node-form"
|
||||
label-position="right"
|
||||
>
|
||||
<el-form-item :label="$t('Task ID')">
|
||||
<el-input v-model="taskForm._id" placeholder="Task ID" disabled></el-input>
|
||||
<el-input v-model="taskForm._id" placeholder="Task ID" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Status')">
|
||||
<status-tag :status="taskForm.status"/>
|
||||
<status-tag :status="taskForm.status" />
|
||||
<el-badge
|
||||
v-if="taskForm.error_log_count > 0"
|
||||
:value="taskForm.error_log_count"
|
||||
style="margin-left:10px; cursor:pointer;"
|
||||
>
|
||||
<el-tag type="danger" @click="onClickLogWithErrors">
|
||||
<i class="el-icon-warning"></i>
|
||||
{{$t('Log with errors')}}
|
||||
<i class="el-icon-warning" />
|
||||
{{ $t('Log with errors') }}
|
||||
</el-tag>
|
||||
</el-badge>
|
||||
<el-tag
|
||||
@@ -26,42 +28,42 @@
|
||||
type="danger"
|
||||
style="margin-left: 10px"
|
||||
>
|
||||
<i class="el-icon-warning"></i>
|
||||
{{$t('Empty results')}}
|
||||
<i class="el-icon-warning" />
|
||||
{{ $t('Empty results') }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Log File Path')">
|
||||
<el-input v-model="taskForm.log_path" placeholder="Log File Path" disabled></el-input>
|
||||
<el-input v-model="taskForm.log_path" placeholder="Log File Path" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Parameters')">
|
||||
<el-input v-model="taskForm.param" placeholder="Parameters" disabled></el-input>
|
||||
<el-input v-model="taskForm.param" placeholder="Parameters" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Create Time')">
|
||||
<el-input :value="getTime(taskForm.create_ts)" placeholder="Create Time" disabled></el-input>
|
||||
<el-input :value="getTime(taskForm.create_ts)" placeholder="Create Time" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Start Time')">
|
||||
<el-input :value="getTime(taskForm.start_ts)" placeholder="Start Time" disabled></el-input>
|
||||
<el-input :value="getTime(taskForm.start_ts)" placeholder="Start Time" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Finish Time')">
|
||||
<el-input :value="getTime(taskForm.finish_ts)" placeholder="Finish Time" disabled></el-input>
|
||||
<el-input :value="getTime(taskForm.finish_ts)" placeholder="Finish Time" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Wait Duration (sec)')">
|
||||
<el-input :value="getWaitDuration(taskForm)" placeholder="Wait Duration" disabled></el-input>
|
||||
<el-input :value="getWaitDuration(taskForm)" placeholder="Wait Duration" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Runtime Duration (sec)')">
|
||||
<el-input :value="getRuntimeDuration(taskForm)" placeholder="Runtime Duration" disabled></el-input>
|
||||
<el-input :value="getRuntimeDuration(taskForm)" placeholder="Runtime Duration" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Total Duration (sec)')">
|
||||
<el-input :value="getTotalDuration(taskForm)" placeholder="Runtime Duration" disabled></el-input>
|
||||
<el-input :value="getTotalDuration(taskForm)" placeholder="Runtime Duration" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('Results Count')">
|
||||
<el-input v-model="taskForm.result_count" placeholder="Results Count" disabled></el-input>
|
||||
<el-input v-model="taskForm.result_count" placeholder="Results Count" disabled />
|
||||
</el-form-item>
|
||||
<!--<el-form-item :label="$t('Average Results Count per Second')">-->
|
||||
<!--<el-input v-model="taskForm.avg_num_results" placeholder="Average Results Count per Second" disabled>-->
|
||||
<!--</el-input>-->
|
||||
<!--</el-form-item>-->
|
||||
<el-form-item :label="$t('Error Message')" v-if="taskForm.status === 'error'">
|
||||
<el-form-item v-if="taskForm.status === 'error'" :label="$t('Error Message')">
|
||||
<div class="error-message">
|
||||
{{ taskForm.error }}
|
||||
</div>
|
||||
@@ -69,8 +71,8 @@
|
||||
</el-form>
|
||||
</el-row>
|
||||
<el-row class="button-container">
|
||||
<el-button v-if="isRunning" size="small" type="danger" @click="onStop" icon="el-icon-video-pause">
|
||||
{{$t('Stop')}}
|
||||
<el-button v-if="isRunning" size="small" type="danger" icon="el-icon-video-pause" @click="onStop">
|
||||
{{ $t('Stop') }}
|
||||
</el-button>
|
||||
<!--<el-button type="danger" @click="onRestart">Restart</el-button>-->
|
||||
</el-row>
|
||||
@@ -78,56 +80,56 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import StatusTag from '../Status/StatusTag'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import StatusTag from '../Status/StatusTag'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export default {
|
||||
name: 'NodeInfoView',
|
||||
components: { StatusTag },
|
||||
computed: {
|
||||
...mapState('task', [
|
||||
'taskForm',
|
||||
'taskLog',
|
||||
'errorLogData'
|
||||
]),
|
||||
isRunning () {
|
||||
return ['pending', 'running'].includes(this.taskForm.status)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onRestart () {
|
||||
export default {
|
||||
name: 'NodeInfoView',
|
||||
components: { StatusTag },
|
||||
computed: {
|
||||
...mapState('task', [
|
||||
'taskForm',
|
||||
'taskLog',
|
||||
'errorLogData'
|
||||
]),
|
||||
isRunning() {
|
||||
return ['pending', 'running'].includes(this.taskForm.status)
|
||||
}
|
||||
},
|
||||
onStop () {
|
||||
this.$store.dispatch('task/cancelTask', this.$route.params.id)
|
||||
.then(() => {
|
||||
this.$message.success(`Task "${this.$route.params.id}" has been sent signal to stop`)
|
||||
})
|
||||
},
|
||||
getTime (str) {
|
||||
if (!str || str.match('^0001')) return 'NA'
|
||||
return dayjs(str).format('YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
getWaitDuration (row) {
|
||||
if (!row.start_ts || row.start_ts.match('^0001')) return 'NA'
|
||||
return dayjs(row.start_ts).diff(row.create_ts, 'second')
|
||||
},
|
||||
getRuntimeDuration (row) {
|
||||
if (!row.finish_ts || row.finish_ts.match('^0001')) return 'NA'
|
||||
return dayjs(row.finish_ts).diff(row.start_ts, 'second')
|
||||
},
|
||||
getTotalDuration (row) {
|
||||
if (!row.finish_ts || row.finish_ts.match('^0001')) return 'NA'
|
||||
return dayjs(row.finish_ts).diff(row.create_ts, 'second')
|
||||
},
|
||||
onClickLogWithErrors () {
|
||||
this.$emit('click-log')
|
||||
this.$st.sendEv('任务详情', '概览', '点击日志错误')
|
||||
methods: {
|
||||
onRestart() {
|
||||
},
|
||||
onStop() {
|
||||
this.$store.dispatch('task/cancelTask', this.$route.params.id)
|
||||
.then(() => {
|
||||
this.$message.success(`Task "${this.$route.params.id}" has been sent signal to stop`)
|
||||
})
|
||||
},
|
||||
getTime(str) {
|
||||
if (!str || str.match('^0001')) return 'NA'
|
||||
return dayjs(str).format('YYYY-MM-DD HH:mm:ss')
|
||||
},
|
||||
getWaitDuration(row) {
|
||||
if (!row.start_ts || row.start_ts.match('^0001')) return 'NA'
|
||||
return dayjs(row.start_ts).diff(row.create_ts, 'second')
|
||||
},
|
||||
getRuntimeDuration(row) {
|
||||
if (!row.finish_ts || row.finish_ts.match('^0001')) return 'NA'
|
||||
return dayjs(row.finish_ts).diff(row.start_ts, 'second')
|
||||
},
|
||||
getTotalDuration(row) {
|
||||
if (!row.finish_ts || row.finish_ts.match('^0001')) return 'NA'
|
||||
return dayjs(row.finish_ts).diff(row.create_ts, 'second')
|
||||
},
|
||||
onClickLogWithErrors() {
|
||||
this.$emit('click-log')
|
||||
this.$st.sendEv('任务详情', '概览', '点击日志错误')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<el-form class="search-form" inline>
|
||||
<el-form-item>
|
||||
<el-autocomplete
|
||||
v-if="activeLang.executable_name === 'python'"
|
||||
v-model="depName"
|
||||
class="search-box"
|
||||
size="small"
|
||||
clearable
|
||||
v-if="activeLang.executable_name === 'python'"
|
||||
v-model="depName"
|
||||
style="width: 240px"
|
||||
:placeholder="$t('Search Dependencies')"
|
||||
:fetchSuggestions="fetchAllDepList"
|
||||
:fetch-suggestions="fetchAllDepList"
|
||||
:minlength="2"
|
||||
@select="onSearch"
|
||||
@clear="onSearch"
|
||||
@@ -23,20 +23,21 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small"
|
||||
icon="el-icon-search"
|
||||
type="success"
|
||||
@click="onSearch"
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-search"
|
||||
type="success"
|
||||
@click="onSearch"
|
||||
>
|
||||
{{$t('Search')}}
|
||||
{{ $t('Search') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="isShowInstalled" :label="$t('Show installed')" @change="onIsShowInstalledChange"/>
|
||||
<el-checkbox v-model="isShowInstalled" :label="$t('Show installed')" @change="onIsShowInstalledChange" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-tabs v-model="activeTab" @tab-click="onTabChange">
|
||||
<el-tab-pane v-for="lang in langList" :key="lang.name" :label="lang.name" :name="lang.executable_name"/>
|
||||
<el-tab-pane v-for="lang in langList" :key="lang.name" :label="lang.name" :name="lang.executable_name" />
|
||||
</el-tabs>
|
||||
<template v-if="activeLang.install_status === 'installed'">
|
||||
<template v-if="!['python', 'node'].includes(activeLang.executable_name)">
|
||||
@@ -46,16 +47,16 @@
|
||||
disabled
|
||||
type="success"
|
||||
>
|
||||
{{$t('Installed')}}
|
||||
{{ $t('Installed') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
height="calc(100vh - 280px)"
|
||||
:data="computedDepList"
|
||||
:empty-text="depName ? $t('No Data') : $t('Please search dependencies')"
|
||||
v-loading="loading"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
@@ -85,7 +86,7 @@
|
||||
type="primary"
|
||||
@click="onClickInstallDep(scope.row)"
|
||||
>
|
||||
{{$t('Install')}}
|
||||
{{ $t('Install') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
@@ -95,7 +96,7 @@
|
||||
type="danger"
|
||||
@click="onClickUninstallDep(scope.row)"
|
||||
>
|
||||
{{$t('Uninstall')}}
|
||||
{{ $t('Uninstall') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -109,7 +110,7 @@
|
||||
disabled
|
||||
type="warning"
|
||||
>
|
||||
{{$t('Installing')}}
|
||||
{{ $t('Installing') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -120,19 +121,19 @@
|
||||
disabled
|
||||
type="warning"
|
||||
>
|
||||
{{$t('Other language installing')}}
|
||||
{{ $t('Other language installing') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="activeLang.install_status === 'not-installed'">
|
||||
<div class="install-wrapper">
|
||||
<h4>{{$t('This language is not installed yet.')}}</h4>
|
||||
<h4>{{ $t('This language is not installed yet.') }}</h4>
|
||||
<el-button
|
||||
icon="el-icon-check"
|
||||
type="primary"
|
||||
@click="onClickInstallLang"
|
||||
>
|
||||
{{$t('Install')}}
|
||||
{{ $t('Install') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -140,208 +141,208 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NodeInstallation',
|
||||
data () {
|
||||
return {
|
||||
activeTab: '',
|
||||
langList: [],
|
||||
depName: '',
|
||||
depList: [],
|
||||
loading: false,
|
||||
isShowInstalled: true,
|
||||
installedDepList: [],
|
||||
depLoadingDict: {},
|
||||
isLoadingInstallLang: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
]),
|
||||
activeLang () {
|
||||
for (let i = 0; i < this.langList.length; i++) {
|
||||
if (this.langList[i].executable_name === this.activeTab) {
|
||||
return this.langList[i]
|
||||
export default {
|
||||
name: 'NodeInstallation',
|
||||
data() {
|
||||
return {
|
||||
activeTab: '',
|
||||
langList: [],
|
||||
depName: '',
|
||||
depList: [],
|
||||
loading: false,
|
||||
isShowInstalled: true,
|
||||
installedDepList: [],
|
||||
depLoadingDict: {},
|
||||
isLoadingInstallLang: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
]),
|
||||
activeLang() {
|
||||
for (let i = 0; i < this.langList.length; i++) {
|
||||
if (this.langList[i].executable_name === this.activeTab) {
|
||||
return this.langList[i]
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
activeLangName() {
|
||||
return this.activeLang.executable_name
|
||||
},
|
||||
computedDepList() {
|
||||
if (this.isShowInstalled) {
|
||||
return this.installedDepList
|
||||
} else {
|
||||
return this.depList
|
||||
}
|
||||
}
|
||||
return {}
|
||||
},
|
||||
activeLangName () {
|
||||
return this.activeLang.executable_name
|
||||
},
|
||||
computedDepList () {
|
||||
if (this.isShowInstalled) {
|
||||
return this.installedDepList
|
||||
} else {
|
||||
return this.depList
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getDepList () {
|
||||
this.loading = true
|
||||
this.depList = []
|
||||
const res = await this.$request.get(`/nodes/${this.nodeForm._id}/deps`, {
|
||||
lang: this.activeLang.executable_name,
|
||||
dep_name: this.depName
|
||||
})
|
||||
this.loading = false
|
||||
this.depList = res.data.data
|
||||
|
||||
if (this.activeLangName === 'python') {
|
||||
// 排序
|
||||
this.depList = this.depList.sort((a, b) => a.name > b.name ? 1 : -1)
|
||||
|
||||
// 异步获取python附加信息
|
||||
this.depList.map(async dep => {
|
||||
const resp = await this.$request.get(`/system/deps/${this.activeLang.executable_name}/${dep.name}/json`)
|
||||
if (resp) {
|
||||
dep.version = resp.data.data.version
|
||||
dep.description = resp.data.data.description
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
async getInstalledDepList () {
|
||||
if (this.activeLang.install_status !== 'installed') return
|
||||
if (!['Python', 'Node.js'].includes(this.activeLang.name)) return
|
||||
|
||||
this.loading = true
|
||||
this.installedDepList = []
|
||||
const res = await this.$request.get(`/nodes/${this.nodeForm._id}/deps/installed`, {
|
||||
lang: this.activeLang.executable_name
|
||||
})
|
||||
this.loading = false
|
||||
this.installedDepList = res.data.data
|
||||
},
|
||||
async fetchAllDepList (queryString, callback) {
|
||||
const res = await this.$request.get(`/system/deps/${this.activeLang.executable_name}`, {
|
||||
dep_name: queryString
|
||||
})
|
||||
callback(res.data.data ? res.data.data.map(d => {
|
||||
return { value: d, label: d }
|
||||
}) : [])
|
||||
},
|
||||
onSearch () {
|
||||
this.isShowInstalled = false
|
||||
this.getDepList()
|
||||
this.$st.sendEv('节点详情', '安装', '搜索依赖')
|
||||
},
|
||||
onIsShowInstalledChange (val) {
|
||||
if (val) {
|
||||
this.getInstalledDepList()
|
||||
} else {
|
||||
this.depName = ''
|
||||
this.depList = []
|
||||
}
|
||||
this.$st.sendEv('节点详情', '安装', '点击查看已安装')
|
||||
},
|
||||
async onClickInstallDep (dep) {
|
||||
const name = dep.name
|
||||
this.$set(this.depLoadingDict, name, true)
|
||||
async created() {
|
||||
const arr = this.$route.path.split('/')
|
||||
const id = arr[arr.length - 1]
|
||||
const data = await this.$request.post(`/nodes/${id}/deps/install`, {
|
||||
lang: this.activeLang.executable_name,
|
||||
dep_name: name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Installing dependency failed'),
|
||||
message: this.$t('The dependency installation is unsuccessful: ') + name
|
||||
})
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Installing dependency successful'),
|
||||
message: this.$t('You have successfully installed a dependency: ') + name
|
||||
})
|
||||
dep.installed = true
|
||||
}
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_dep'
|
||||
})
|
||||
this.$set(this.depLoadingDict, name, false)
|
||||
this.$st.sendEv('节点详情', '安装', '安装依赖')
|
||||
},
|
||||
async onClickUninstallDep (dep) {
|
||||
const name = dep.name
|
||||
this.$set(this.depLoadingDict, name, true)
|
||||
const arr = this.$route.path.split('/')
|
||||
const id = arr[arr.length - 1]
|
||||
const data = await this.$request.post(`/nodes/${id}/deps/uninstall`, {
|
||||
lang: this.activeLang.executable_name,
|
||||
dep_name: name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Uninstalling dependency failed'),
|
||||
message: this.$t('The dependency uninstallation is unsuccessful: ') + name
|
||||
})
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Uninstalling dependency successful'),
|
||||
message: this.$t('You have successfully uninstalled a dependency: ') + name
|
||||
})
|
||||
dep.installed = false
|
||||
}
|
||||
this.$set(this.depLoadingDict, name, false)
|
||||
this.$st.sendEv('节点详情', '安装', '卸载依赖')
|
||||
},
|
||||
getDepLoading (dep) {
|
||||
const name = dep.name
|
||||
if (this.depLoadingDict[name] === undefined) {
|
||||
return false
|
||||
}
|
||||
return this.depLoadingDict[name]
|
||||
},
|
||||
async onClickInstallLang () {
|
||||
this.isLoadingInstallLang = true
|
||||
const res = await this.$request.post(`/nodes/${this.nodeForm._id}/langs/install`, {
|
||||
lang: this.activeLang.executable_name
|
||||
})
|
||||
if (!res || res.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Installing language failed'),
|
||||
message: this.$t('The language installation is unsuccessful: ') + this.activeLang.name
|
||||
})
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Installing language successful'),
|
||||
message: this.$t('You have successfully installed a language: ') + this.activeLang.name
|
||||
})
|
||||
}
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_lang'
|
||||
})
|
||||
this.isLoadingInstallLang = false
|
||||
this.$st.sendEv('节点详情', '安装', '安装语言')
|
||||
},
|
||||
onTabChange () {
|
||||
if (this.isShowInstalled) {
|
||||
const res = await this.$request.get(`/nodes/${id}/langs`)
|
||||
this.langList = res.data.data
|
||||
this.activeTab = this.langList[0].executable_name || ''
|
||||
setTimeout(() => {
|
||||
this.getInstalledDepList()
|
||||
} else {
|
||||
this.depName = ''
|
||||
}, 100)
|
||||
},
|
||||
methods: {
|
||||
async getDepList() {
|
||||
this.loading = true
|
||||
this.depList = []
|
||||
const res = await this.$request.get(`/nodes/${this.nodeForm._id}/deps`, {
|
||||
lang: this.activeLang.executable_name,
|
||||
dep_name: this.depName
|
||||
})
|
||||
this.loading = false
|
||||
this.depList = res.data.data
|
||||
|
||||
if (this.activeLangName === 'python') {
|
||||
// 排序
|
||||
this.depList = this.depList.sort((a, b) => a.name > b.name ? 1 : -1)
|
||||
|
||||
// 异步获取python附加信息
|
||||
this.depList.map(async dep => {
|
||||
const resp = await this.$request.get(`/system/deps/${this.activeLang.executable_name}/${dep.name}/json`)
|
||||
if (resp) {
|
||||
dep.version = resp.data.data.version
|
||||
dep.description = resp.data.data.description
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
async getInstalledDepList() {
|
||||
if (this.activeLang.install_status !== 'installed') return
|
||||
if (!['Python', 'Node.js'].includes(this.activeLang.name)) return
|
||||
|
||||
this.loading = true
|
||||
this.installedDepList = []
|
||||
const res = await this.$request.get(`/nodes/${this.nodeForm._id}/deps/installed`, {
|
||||
lang: this.activeLang.executable_name
|
||||
})
|
||||
this.loading = false
|
||||
this.installedDepList = res.data.data
|
||||
},
|
||||
async fetchAllDepList(queryString, callback) {
|
||||
const res = await this.$request.get(`/system/deps/${this.activeLang.executable_name}`, {
|
||||
dep_name: queryString
|
||||
})
|
||||
callback(res.data.data ? res.data.data.map(d => {
|
||||
return { value: d, label: d }
|
||||
}) : [])
|
||||
},
|
||||
onSearch() {
|
||||
this.isShowInstalled = false
|
||||
this.getDepList()
|
||||
this.$st.sendEv('节点详情', '安装', '搜索依赖')
|
||||
},
|
||||
onIsShowInstalledChange(val) {
|
||||
if (val) {
|
||||
this.getInstalledDepList()
|
||||
} else {
|
||||
this.depName = ''
|
||||
this.depList = []
|
||||
}
|
||||
this.$st.sendEv('节点详情', '安装', '点击查看已安装')
|
||||
},
|
||||
async onClickInstallDep(dep) {
|
||||
const name = dep.name
|
||||
this.$set(this.depLoadingDict, name, true)
|
||||
const arr = this.$route.path.split('/')
|
||||
const id = arr[arr.length - 1]
|
||||
const data = await this.$request.post(`/nodes/${id}/deps/install`, {
|
||||
lang: this.activeLang.executable_name,
|
||||
dep_name: name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Installing dependency failed'),
|
||||
message: this.$t('The dependency installation is unsuccessful: ') + name
|
||||
})
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Installing dependency successful'),
|
||||
message: this.$t('You have successfully installed a dependency: ') + name
|
||||
})
|
||||
dep.installed = true
|
||||
}
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_dep'
|
||||
})
|
||||
this.$set(this.depLoadingDict, name, false)
|
||||
this.$st.sendEv('节点详情', '安装', '安装依赖')
|
||||
},
|
||||
async onClickUninstallDep(dep) {
|
||||
const name = dep.name
|
||||
this.$set(this.depLoadingDict, name, true)
|
||||
const arr = this.$route.path.split('/')
|
||||
const id = arr[arr.length - 1]
|
||||
const data = await this.$request.post(`/nodes/${id}/deps/uninstall`, {
|
||||
lang: this.activeLang.executable_name,
|
||||
dep_name: name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Uninstalling dependency failed'),
|
||||
message: this.$t('The dependency uninstallation is unsuccessful: ') + name
|
||||
})
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Uninstalling dependency successful'),
|
||||
message: this.$t('You have successfully uninstalled a dependency: ') + name
|
||||
})
|
||||
dep.installed = false
|
||||
}
|
||||
this.$set(this.depLoadingDict, name, false)
|
||||
this.$st.sendEv('节点详情', '安装', '卸载依赖')
|
||||
},
|
||||
getDepLoading(dep) {
|
||||
const name = dep.name
|
||||
if (this.depLoadingDict[name] === undefined) {
|
||||
return false
|
||||
}
|
||||
return this.depLoadingDict[name]
|
||||
},
|
||||
async onClickInstallLang() {
|
||||
this.isLoadingInstallLang = true
|
||||
const res = await this.$request.post(`/nodes/${this.nodeForm._id}/langs/install`, {
|
||||
lang: this.activeLang.executable_name
|
||||
})
|
||||
if (!res || res.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Installing language failed'),
|
||||
message: this.$t('The language installation is unsuccessful: ') + this.activeLang.name
|
||||
})
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Installing language successful'),
|
||||
message: this.$t('You have successfully installed a language: ') + this.activeLang.name
|
||||
})
|
||||
}
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_lang'
|
||||
})
|
||||
this.isLoadingInstallLang = false
|
||||
this.$st.sendEv('节点详情', '安装', '安装语言')
|
||||
},
|
||||
onTabChange() {
|
||||
if (this.isShowInstalled) {
|
||||
this.getInstalledDepList()
|
||||
} else {
|
||||
this.depName = ''
|
||||
this.depList = []
|
||||
}
|
||||
this.$st.sendEv('节点详情', '安装', '切换标签')
|
||||
}
|
||||
this.$st.sendEv('节点详情', '安装', '切换标签')
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
const arr = this.$route.path.split('/')
|
||||
const id = arr[arr.length - 1]
|
||||
const res = await this.$request.get(`/nodes/${id}/langs`)
|
||||
this.langList = res.data.data
|
||||
this.activeTab = this.langList[0].executable_name || ''
|
||||
setTimeout(() => {
|
||||
this.getInstalledDepList()
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
fixed
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="primary" v-if="scope.row.is_master">{{$t('Master')}}</el-tag>
|
||||
<el-tag type="warning" v-else>{{$t('Worker')}}</el-tag>
|
||||
<el-tag v-if="scope.row.is_master" type="primary">{{ $t('Master') }}</el-tag>
|
||||
<el-tag v-else type="warning">{{ $t('Worker') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -32,9 +32,9 @@
|
||||
fixed
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<el-tag type="info" v-if="scope.row.status === 'offline'">{{$t('Offline')}}</el-tag>
|
||||
<el-tag type="success" v-else-if="scope.row.status === 'online'">{{$t('Online')}}</el-tag>
|
||||
<el-tag type="danger" v-else>{{$t('Unavailable')}}</el-tag>
|
||||
<el-tag v-if="scope.row.status === 'offline'" type="info">{{ $t('Offline') }}</el-tag>
|
||||
<el-tag v-else-if="scope.row.status === 'online'" type="success">{{ $t('Online') }}</el-tag>
|
||||
<el-tag v-else type="danger">{{ $t('Unavailable') }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
@@ -45,23 +45,23 @@
|
||||
>
|
||||
<template slot="header" slot-scope="scope">
|
||||
<div class="header-with-action">
|
||||
<span>{{scope.column.label}}</span>
|
||||
<span>{{ scope.column.label }}</span>
|
||||
<el-button type="primary" size="mini" @click="onInstallLangAll(scope.column.label, $event)">
|
||||
{{$t('Install')}}
|
||||
{{ $t('Install') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<template v-if="getLangInstallStatus(scope.row._id, l.name) === 'installed'">
|
||||
<el-tag type="success">
|
||||
<i class="el-icon-check"></i>
|
||||
{{$t('Installed')}}
|
||||
<i class="el-icon-check" />
|
||||
{{ $t('Installed') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template v-else-if="getLangInstallStatus(scope.row._id, l.name) === 'installing'">
|
||||
<el-tag type="warning">
|
||||
<i class="el-icon-loading"></i>
|
||||
{{$t('Installing')}}
|
||||
<i class="el-icon-loading" />
|
||||
{{ $t('Installing') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template
|
||||
@@ -69,18 +69,18 @@
|
||||
>
|
||||
<div class="cell-with-action">
|
||||
<el-tag type="danger">
|
||||
<i class="el-icon-error"></i>
|
||||
{{$t('Not Installed')}}
|
||||
<i class="el-icon-error" />
|
||||
{{ $t('Not Installed') }}
|
||||
</el-tag>
|
||||
<el-button type="primary" size="mini" @click="onInstallLang(scope.row._id, scope.column.label, $event)">
|
||||
{{$t('Install')}}
|
||||
{{ $t('Install') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="getLangInstallStatus(scope.row._id, l.name) === 'na'">
|
||||
<el-tag type="info">
|
||||
<i class="el-icon-question"></i>
|
||||
{{$t('N/A')}}
|
||||
<i class="el-icon-question" />
|
||||
{{ $t('N/A') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
@@ -98,16 +98,17 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button size="small"
|
||||
icon="el-icon-search"
|
||||
type="success"
|
||||
@click="onSearch"
|
||||
<el-button
|
||||
size="small"
|
||||
icon="el-icon-search"
|
||||
type="success"
|
||||
@click="onSearch"
|
||||
>
|
||||
{{$t('Search')}}
|
||||
{{ $t('Search') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-checkbox v-model="isShowInstalled" :label="$t('Show installed')" @change="onIsShowInstalledChange"/>
|
||||
<el-checkbox v-model="isShowInstalled" :label="$t('Show installed')" @change="onIsShowInstalledChange" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-tabs v-model="activeLang">
|
||||
@@ -144,7 +145,7 @@
|
||||
size="mini"
|
||||
type="primary"
|
||||
>
|
||||
{{$t('Install')}}
|
||||
{{ $t('Install') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -156,7 +157,7 @@
|
||||
align="center"
|
||||
>
|
||||
<template slot="header" slot-scope="scope">
|
||||
{{scope.column.label}}
|
||||
{{ scope.column.label }}
|
||||
</template>
|
||||
<template slot-scope="scope">
|
||||
<div
|
||||
@@ -164,14 +165,14 @@
|
||||
class="cell-with-action"
|
||||
>
|
||||
<el-tag type="success">
|
||||
{{$t('Installed')}}
|
||||
{{ $t('Installed') }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="danger"
|
||||
@click="uninstallDep(n, scope.row)"
|
||||
>
|
||||
{{$t('Uninstall')}}
|
||||
{{ $t('Uninstall') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div
|
||||
@@ -179,8 +180,8 @@
|
||||
class="cell-with-action"
|
||||
>
|
||||
<el-tag type="warning">
|
||||
<i class="el-icon-loading"></i>
|
||||
{{$t('Installing')}}
|
||||
<i class="el-icon-loading" />
|
||||
{{ $t('Installing') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div
|
||||
@@ -188,14 +189,14 @@
|
||||
class="cell-with-action"
|
||||
>
|
||||
<el-tag type="danger">
|
||||
{{$t('Not Installed')}}
|
||||
{{ $t('Not Installed') }}
|
||||
</el-tag>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="installDep(n, scope.row)"
|
||||
>
|
||||
{{$t('Install')}}
|
||||
{{ $t('Install') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -207,242 +208,242 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'NodeInstallationMatrix',
|
||||
props: {
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
langs: [
|
||||
{ label: 'Python', name: 'python', hasDeps: true },
|
||||
{ label: 'Node.js', name: 'node', hasDeps: true },
|
||||
{ label: 'Java', name: 'java', hasDeps: false },
|
||||
{ label: '.Net Core', name: 'dotnet', hasDeps: false },
|
||||
{ label: 'PHP', name: 'php', hasDeps: false }
|
||||
],
|
||||
langsDataDict: {},
|
||||
handle: undefined,
|
||||
activeTabName: 'lang',
|
||||
depsDataDict: {},
|
||||
depsSet: new Set(),
|
||||
activeLang: 'python',
|
||||
isDepsLoading: false,
|
||||
depName: '',
|
||||
isShowInstalled: true,
|
||||
depList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeList'
|
||||
]),
|
||||
activeNodes () {
|
||||
return this.nodeList.filter(d => d.status === 'online')
|
||||
},
|
||||
computedDepsSet () {
|
||||
return Array.from(this.depsSet).map(d => {
|
||||
return {
|
||||
name: d
|
||||
}
|
||||
})
|
||||
},
|
||||
langsWithDeps () {
|
||||
return this.langs.filter(l => l.hasDeps)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeLang () {
|
||||
this.getDepsData()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async getLangsData () {
|
||||
await Promise.all(this.nodeList.map(async n => {
|
||||
if (n.status !== 'online') return
|
||||
const res = await this.$request.get(`/nodes/${n._id}/langs`)
|
||||
if (!res.data.data) return
|
||||
res.data.data.forEach(l => {
|
||||
const key = n._id + '|' + l.executable_name
|
||||
this.$set(this.langsDataDict, key, l)
|
||||
})
|
||||
}))
|
||||
},
|
||||
async getDepsData () {
|
||||
this.isDepsLoading = true
|
||||
this.depsDataDict = {}
|
||||
this.depsSet = new Set()
|
||||
const depsSet = new Set()
|
||||
await Promise.all(this.nodeList.map(async n => {
|
||||
if (n.status !== 'online') return
|
||||
const res = await this.$request.get(`/nodes/${n._id}/deps/installed`, { lang: this.activeLang })
|
||||
if (!res.data.data) return
|
||||
res.data.data.forEach(d => {
|
||||
depsSet.add(d.name)
|
||||
const key = n._id + '|' + d.name
|
||||
this.$set(this.depsDataDict, key, 'installed')
|
||||
})
|
||||
}))
|
||||
this.depsSet = depsSet
|
||||
this.isDepsLoading = false
|
||||
},
|
||||
getLang (nodeId, langName) {
|
||||
const key = nodeId + '|' + langName
|
||||
return this.langsDataDict[key]
|
||||
},
|
||||
getLangInstallStatus (nodeId, langName) {
|
||||
const lang = this.getLang(nodeId, langName)
|
||||
if (!lang || !lang.install_status) return 'na'
|
||||
return lang.install_status
|
||||
},
|
||||
getLangFromLabel (label) {
|
||||
for (let i = 0; i < this.langs.length; i++) {
|
||||
const lang = this.langs[i]
|
||||
if (lang.label === label) {
|
||||
return lang
|
||||
}
|
||||
export default {
|
||||
name: 'NodeInstallationMatrix',
|
||||
props: {
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
async onInstallLang (nodeId, langLabel, ev) {
|
||||
if (ev) {
|
||||
ev.stopPropagation()
|
||||
}
|
||||
const lang = this.getLangFromLabel(langLabel)
|
||||
this.$request.post(`/nodes/${nodeId}/langs/install`, {
|
||||
lang: lang.name
|
||||
})
|
||||
const key = nodeId + '|' + lang.name
|
||||
this.$set(this.langsDataDict[key], 'install_status', 'installing')
|
||||
setTimeout(() => {
|
||||
this.getLangsData()
|
||||
}, 1000)
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_lang'
|
||||
})
|
||||
this.$st.sendEv('节点列表', '安装', '安装语言')
|
||||
},
|
||||
async onInstallLangAll (langLabel, ev) {
|
||||
ev.stopPropagation()
|
||||
this.nodeList
|
||||
.filter(n => {
|
||||
if (n.status !== 'online') return false
|
||||
const lang = this.getLangFromLabel(langLabel)
|
||||
const key = n._id + '|' + lang.name
|
||||
if (!this.langsDataDict[key]) return false
|
||||
if (['installing', 'installed'].includes(this.langsDataDict[key].install_status)) return false
|
||||
return true
|
||||
})
|
||||
.forEach(n => {
|
||||
this.onInstallLang(n._id, langLabel, ev)
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.getLangsData()
|
||||
}, 1000)
|
||||
this.$st.sendEv('节点列表', '安装', '安装语言-所有节点')
|
||||
},
|
||||
onLangTableRowClick (row) {
|
||||
this.$router.push(`/nodes/${row._id}`)
|
||||
this.$st.sendEv('节点列表', '安装', '查看节点详情')
|
||||
},
|
||||
getDepStatus (node, dep) {
|
||||
const key = node._id + '|' + dep.name
|
||||
if (!this.depsDataDict[key]) {
|
||||
return 'uninstalled'
|
||||
} else {
|
||||
return this.depsDataDict[key]
|
||||
data() {
|
||||
return {
|
||||
langs: [
|
||||
{ label: 'Python', name: 'python', hasDeps: true },
|
||||
{ label: 'Node.js', name: 'node', hasDeps: true },
|
||||
{ label: 'Java', name: 'java', hasDeps: false },
|
||||
{ label: '.Net Core', name: 'dotnet', hasDeps: false },
|
||||
{ label: 'PHP', name: 'php', hasDeps: false }
|
||||
],
|
||||
langsDataDict: {},
|
||||
handle: undefined,
|
||||
activeTabName: 'lang',
|
||||
depsDataDict: {},
|
||||
depsSet: new Set(),
|
||||
activeLang: 'python',
|
||||
isDepsLoading: false,
|
||||
depName: '',
|
||||
isShowInstalled: true,
|
||||
depList: []
|
||||
}
|
||||
},
|
||||
async installDep (node, dep) {
|
||||
const key = node._id + '|' + dep.name
|
||||
this.$set(this.depsDataDict, key, 'installing')
|
||||
const data = await this.$request.post(`/nodes/${node._id}/deps/install`, {
|
||||
lang: this.activeLang,
|
||||
dep_name: dep.name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Installing dependency failed'),
|
||||
message: this.$t('The dependency installation is unsuccessful: ') + dep.name
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeList'
|
||||
]),
|
||||
activeNodes() {
|
||||
return this.nodeList.filter(d => d.status === 'online')
|
||||
},
|
||||
computedDepsSet() {
|
||||
return Array.from(this.depsSet).map(d => {
|
||||
return {
|
||||
name: d
|
||||
}
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'uninstalled')
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Installing dependency successful'),
|
||||
message: this.$t('You have successfully installed a dependency: ') + dep.name
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'installed')
|
||||
},
|
||||
langsWithDeps() {
|
||||
return this.langs.filter(l => l.hasDeps)
|
||||
}
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_dep'
|
||||
})
|
||||
this.$st.sendEv('节点列表', '安装', '安装依赖')
|
||||
},
|
||||
async uninstallDep (node, dep) {
|
||||
const key = node._id + '|' + dep.name
|
||||
this.$set(this.depsDataDict, key, 'installing')
|
||||
const data = await this.$request.post(`/nodes/${node._id}/deps/uninstall`, {
|
||||
lang: this.activeLang,
|
||||
dep_name: dep.name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Uninstalling dependency failed'),
|
||||
message: this.$t('The dependency uninstallation is unsuccessful: ') + dep.name
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'installed')
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Uninstalling dependency successful'),
|
||||
message: this.$t('You have successfully uninstalled a dependency: ') + dep.name
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'uninstalled')
|
||||
}
|
||||
this.$st.sendEv('节点列表', '安装', '卸载依赖')
|
||||
},
|
||||
onSearch () {
|
||||
this.isShowInstalled = false
|
||||
this.getDepList()
|
||||
this.$st.sendEv('节点列表', '安装', '搜索依赖')
|
||||
},
|
||||
async getDepList () {
|
||||
const masterNode = this.nodeList.filter(n => n.is_master)[0]
|
||||
this.depsSet = []
|
||||
this.isDepsLoading = true
|
||||
const res = await this.$request.get(`/nodes/${masterNode._id}/deps`, {
|
||||
lang: this.activeLang,
|
||||
dep_name: this.depName
|
||||
})
|
||||
this.isDepsLoading = false
|
||||
this.depsSet = new Set(res.data.data.map(d => d.name))
|
||||
},
|
||||
onIsShowInstalledChange (val) {
|
||||
if (val) {
|
||||
watch: {
|
||||
activeLang() {
|
||||
this.getDepsData()
|
||||
} else {
|
||||
this.depsSet = []
|
||||
}
|
||||
this.$st.sendEv('节点列表', '安装', '点击查看已安装')
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
setTimeout(() => {
|
||||
this.getLangsData()
|
||||
this.getDepsData()
|
||||
}, 1000)
|
||||
},
|
||||
async created() {
|
||||
setTimeout(() => {
|
||||
this.getLangsData()
|
||||
this.getDepsData()
|
||||
}, 1000)
|
||||
|
||||
this.handle = setInterval(() => {
|
||||
this.getLangsData()
|
||||
}, 10000)
|
||||
},
|
||||
destroyed () {
|
||||
clearInterval(this.handle)
|
||||
this.handle = setInterval(() => {
|
||||
this.getLangsData()
|
||||
}, 10000)
|
||||
},
|
||||
destroyed() {
|
||||
clearInterval(this.handle)
|
||||
},
|
||||
methods: {
|
||||
async getLangsData() {
|
||||
await Promise.all(this.nodeList.map(async n => {
|
||||
if (n.status !== 'online') return
|
||||
const res = await this.$request.get(`/nodes/${n._id}/langs`)
|
||||
if (!res.data.data) return
|
||||
res.data.data.forEach(l => {
|
||||
const key = n._id + '|' + l.executable_name
|
||||
this.$set(this.langsDataDict, key, l)
|
||||
})
|
||||
}))
|
||||
},
|
||||
async getDepsData() {
|
||||
this.isDepsLoading = true
|
||||
this.depsDataDict = {}
|
||||
this.depsSet = new Set()
|
||||
const depsSet = new Set()
|
||||
await Promise.all(this.nodeList.map(async n => {
|
||||
if (n.status !== 'online') return
|
||||
const res = await this.$request.get(`/nodes/${n._id}/deps/installed`, { lang: this.activeLang })
|
||||
if (!res.data.data) return
|
||||
res.data.data.forEach(d => {
|
||||
depsSet.add(d.name)
|
||||
const key = n._id + '|' + d.name
|
||||
this.$set(this.depsDataDict, key, 'installed')
|
||||
})
|
||||
}))
|
||||
this.depsSet = depsSet
|
||||
this.isDepsLoading = false
|
||||
},
|
||||
getLang(nodeId, langName) {
|
||||
const key = nodeId + '|' + langName
|
||||
return this.langsDataDict[key]
|
||||
},
|
||||
getLangInstallStatus(nodeId, langName) {
|
||||
const lang = this.getLang(nodeId, langName)
|
||||
if (!lang || !lang.install_status) return 'na'
|
||||
return lang.install_status
|
||||
},
|
||||
getLangFromLabel(label) {
|
||||
for (let i = 0; i < this.langs.length; i++) {
|
||||
const lang = this.langs[i]
|
||||
if (lang.label === label) {
|
||||
return lang
|
||||
}
|
||||
}
|
||||
},
|
||||
async onInstallLang(nodeId, langLabel, ev) {
|
||||
if (ev) {
|
||||
ev.stopPropagation()
|
||||
}
|
||||
const lang = this.getLangFromLabel(langLabel)
|
||||
this.$request.post(`/nodes/${nodeId}/langs/install`, {
|
||||
lang: lang.name
|
||||
})
|
||||
const key = nodeId + '|' + lang.name
|
||||
this.$set(this.langsDataDict[key], 'install_status', 'installing')
|
||||
setTimeout(() => {
|
||||
this.getLangsData()
|
||||
}, 1000)
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_lang'
|
||||
})
|
||||
this.$st.sendEv('节点列表', '安装', '安装语言')
|
||||
},
|
||||
async onInstallLangAll(langLabel, ev) {
|
||||
ev.stopPropagation()
|
||||
this.nodeList
|
||||
.filter(n => {
|
||||
if (n.status !== 'online') return false
|
||||
const lang = this.getLangFromLabel(langLabel)
|
||||
const key = n._id + '|' + lang.name
|
||||
if (!this.langsDataDict[key]) return false
|
||||
if (['installing', 'installed'].includes(this.langsDataDict[key].install_status)) return false
|
||||
return true
|
||||
})
|
||||
.forEach(n => {
|
||||
this.onInstallLang(n._id, langLabel, ev)
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.getLangsData()
|
||||
}, 1000)
|
||||
this.$st.sendEv('节点列表', '安装', '安装语言-所有节点')
|
||||
},
|
||||
onLangTableRowClick(row) {
|
||||
this.$router.push(`/nodes/${row._id}`)
|
||||
this.$st.sendEv('节点列表', '安装', '查看节点详情')
|
||||
},
|
||||
getDepStatus(node, dep) {
|
||||
const key = node._id + '|' + dep.name
|
||||
if (!this.depsDataDict[key]) {
|
||||
return 'uninstalled'
|
||||
} else {
|
||||
return this.depsDataDict[key]
|
||||
}
|
||||
},
|
||||
async installDep(node, dep) {
|
||||
const key = node._id + '|' + dep.name
|
||||
this.$set(this.depsDataDict, key, 'installing')
|
||||
const data = await this.$request.post(`/nodes/${node._id}/deps/install`, {
|
||||
lang: this.activeLang,
|
||||
dep_name: dep.name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Installing dependency failed'),
|
||||
message: this.$t('The dependency installation is unsuccessful: ') + dep.name
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'uninstalled')
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Installing dependency successful'),
|
||||
message: this.$t('You have successfully installed a dependency: ') + dep.name
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'installed')
|
||||
}
|
||||
this.$request.put('/actions', {
|
||||
type: 'install_dep'
|
||||
})
|
||||
this.$st.sendEv('节点列表', '安装', '安装依赖')
|
||||
},
|
||||
async uninstallDep(node, dep) {
|
||||
const key = node._id + '|' + dep.name
|
||||
this.$set(this.depsDataDict, key, 'installing')
|
||||
const data = await this.$request.post(`/nodes/${node._id}/deps/uninstall`, {
|
||||
lang: this.activeLang,
|
||||
dep_name: dep.name
|
||||
})
|
||||
if (!data || data.error) {
|
||||
this.$notify.error({
|
||||
title: this.$t('Uninstalling dependency failed'),
|
||||
message: this.$t('The dependency uninstallation is unsuccessful: ') + dep.name
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'installed')
|
||||
} else {
|
||||
this.$notify.success({
|
||||
title: this.$t('Uninstalling dependency successful'),
|
||||
message: this.$t('You have successfully uninstalled a dependency: ') + dep.name
|
||||
})
|
||||
this.$set(this.depsDataDict, key, 'uninstalled')
|
||||
}
|
||||
this.$st.sendEv('节点列表', '安装', '卸载依赖')
|
||||
},
|
||||
onSearch() {
|
||||
this.isShowInstalled = false
|
||||
this.getDepList()
|
||||
this.$st.sendEv('节点列表', '安装', '搜索依赖')
|
||||
},
|
||||
async getDepList() {
|
||||
const masterNode = this.nodeList.filter(n => n.is_master)[0]
|
||||
this.depsSet = []
|
||||
this.isDepsLoading = true
|
||||
const res = await this.$request.get(`/nodes/${masterNode._id}/deps`, {
|
||||
lang: this.activeLang,
|
||||
dep_name: this.depName
|
||||
})
|
||||
this.isDepsLoading = false
|
||||
this.depsSet = new Set(res.data.data.map(d => d.name))
|
||||
},
|
||||
onIsShowInstalledChange(val) {
|
||||
if (val) {
|
||||
this.getDepsData()
|
||||
} else {
|
||||
this.depsSet = []
|
||||
}
|
||||
this.$st.sendEv('节点列表', '安装', '点击查看已安装')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,184 +1,185 @@
|
||||
<template>
|
||||
<div id="network-chart"></div>
|
||||
<div id="network-chart"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import echarts from 'echarts'
|
||||
import echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: 'NodeNetwork',
|
||||
props: {
|
||||
activeTab: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeTab () {
|
||||
setTimeout(() => {
|
||||
this.render()
|
||||
}, 0)
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
masterNode () {
|
||||
const nodes = this.$store.state.node.nodeList
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].is_master) {
|
||||
return nodes[i]
|
||||
}
|
||||
export default {
|
||||
name: 'NodeNetwork',
|
||||
props: {
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
return {}
|
||||
},
|
||||
nodes () {
|
||||
let nodes = this.$store.state.node.nodeList
|
||||
nodes = nodes
|
||||
.filter(d => d.status !== 'offline')
|
||||
.map(d => {
|
||||
d.id = d._id
|
||||
d.x = Math.floor(100 * Math.random())
|
||||
d.y = Math.floor(100 * Math.random())
|
||||
d.itemStyle = {
|
||||
color: d.is_master ? '#409EFF' : '#e6a23c'
|
||||
data() {
|
||||
return {
|
||||
chart: undefined
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
masterNode() {
|
||||
const nodes = this.$store.state.node.nodeList
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i].is_master) {
|
||||
return nodes[i]
|
||||
}
|
||||
return d
|
||||
})
|
||||
|
||||
// mongodb
|
||||
nodes.push({
|
||||
id: 'mongodb',
|
||||
name: 'MongoDB',
|
||||
x: Math.floor(100 * Math.random()),
|
||||
y: Math.floor(100 * Math.random()),
|
||||
itemStyle: {
|
||||
color: '#67c23a'
|
||||
}
|
||||
})
|
||||
return {}
|
||||
},
|
||||
nodes() {
|
||||
let nodes = this.$store.state.node.nodeList
|
||||
nodes = nodes
|
||||
.filter(d => d.status !== 'offline')
|
||||
.map(d => {
|
||||
d.id = d._id
|
||||
d.x = Math.floor(100 * Math.random())
|
||||
d.y = Math.floor(100 * Math.random())
|
||||
d.itemStyle = {
|
||||
color: d.is_master ? '#409EFF' : '#e6a23c'
|
||||
}
|
||||
return d
|
||||
})
|
||||
|
||||
// redis
|
||||
nodes.push({
|
||||
id: 'redis',
|
||||
name: 'Redis',
|
||||
x: Math.floor(100 * Math.random()),
|
||||
y: Math.floor(100 * Math.random()),
|
||||
itemStyle: {
|
||||
color: '#f56c6c'
|
||||
}
|
||||
})
|
||||
|
||||
return nodes
|
||||
},
|
||||
links () {
|
||||
const links = []
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
if (this.nodes[i].status === 'offline') continue
|
||||
if (['redis', 'mongodb'].includes(this.nodes[i].id)) continue
|
||||
// mongodb
|
||||
links.push({
|
||||
source: this.nodes[i].id,
|
||||
target: 'mongodb',
|
||||
value: 10,
|
||||
lineStyle: {
|
||||
nodes.push({
|
||||
id: 'mongodb',
|
||||
name: 'MongoDB',
|
||||
x: Math.floor(100 * Math.random()),
|
||||
y: Math.floor(100 * Math.random()),
|
||||
itemStyle: {
|
||||
color: '#67c23a'
|
||||
}
|
||||
})
|
||||
|
||||
// redis
|
||||
links.push({
|
||||
source: this.nodes[i].id,
|
||||
target: 'redis',
|
||||
value: 10,
|
||||
lineStyle: {
|
||||
nodes.push({
|
||||
id: 'redis',
|
||||
name: 'Redis',
|
||||
x: Math.floor(100 * Math.random()),
|
||||
y: Math.floor(100 * Math.random()),
|
||||
itemStyle: {
|
||||
color: '#f56c6c'
|
||||
}
|
||||
})
|
||||
|
||||
if (this.masterNode.id === this.nodes[i].id) continue
|
||||
return nodes
|
||||
},
|
||||
links() {
|
||||
const links = []
|
||||
for (let i = 0; i < this.nodes.length; i++) {
|
||||
if (this.nodes[i].status === 'offline') continue
|
||||
if (['redis', 'mongodb'].includes(this.nodes[i].id)) continue
|
||||
// mongodb
|
||||
links.push({
|
||||
source: this.nodes[i].id,
|
||||
target: 'mongodb',
|
||||
value: 10,
|
||||
lineStyle: {
|
||||
color: '#67c23a'
|
||||
}
|
||||
})
|
||||
|
||||
// master
|
||||
// links.push({
|
||||
// source: this.masterNode.id,
|
||||
// target: this.nodes[i].id,
|
||||
// value: 0.5,
|
||||
// lineStyle: {
|
||||
// color: '#409EFF'
|
||||
// }
|
||||
// })
|
||||
// redis
|
||||
links.push({
|
||||
source: this.nodes[i].id,
|
||||
target: 'redis',
|
||||
value: 10,
|
||||
lineStyle: {
|
||||
color: '#f56c6c'
|
||||
}
|
||||
})
|
||||
|
||||
if (this.masterNode.id === this.nodes[i].id) continue
|
||||
|
||||
// master
|
||||
// links.push({
|
||||
// source: this.masterNode.id,
|
||||
// target: this.nodes[i].id,
|
||||
// value: 0.5,
|
||||
// lineStyle: {
|
||||
// color: '#409EFF'
|
||||
// }
|
||||
// })
|
||||
}
|
||||
return links
|
||||
}
|
||||
return links
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
render () {
|
||||
const option = {
|
||||
title: {
|
||||
text: this.$t('Node Network')
|
||||
},
|
||||
tooltip: {
|
||||
formatter: params => {
|
||||
if (!params.data.name) return
|
||||
let str = '<span style="margin-right:5px;display:inline-block;height:12px;width:12px;border-radius:6px;background:' + params.color + '"></span>'
|
||||
if (params.data.name) str += '<span>' + params.data.name + '</span><br>'
|
||||
if (params.data.ip) str += '<span>IP: ' + params.data.ip + '</span><br>'
|
||||
if (params.data.mac) str += '<span>MAC: ' + params.data.mac + '</span><br>'
|
||||
return str
|
||||
}
|
||||
},
|
||||
animationDurationUpdate: 1500,
|
||||
animationEasingUpdate: 'quinticInOut',
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
symbolSize: 50,
|
||||
roam: true,
|
||||
label: {
|
||||
normal: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
edgeSymbol: ['circle', 'arrow'],
|
||||
edgeSymbolSize: [4, 10],
|
||||
edgeLabel: {
|
||||
normal: {
|
||||
textStyle: {
|
||||
fontSize: 20
|
||||
},
|
||||
watch: {
|
||||
activeTab() {
|
||||
setTimeout(() => {
|
||||
this.render()
|
||||
}, 0)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.render()
|
||||
},
|
||||
methods: {
|
||||
render() {
|
||||
const option = {
|
||||
title: {
|
||||
text: this.$t('Node Network')
|
||||
},
|
||||
tooltip: {
|
||||
formatter: params => {
|
||||
if (!params.data.name) return
|
||||
let str = '<span style="margin-right:5px;display:inline-block;height:12px;width:12px;border-radius:6px;background:' + params.color + '"></span>'
|
||||
if (params.data.name) str += '<span>' + params.data.name + '</span><br>'
|
||||
if (params.data.ip) str += '<span>IP: ' + params.data.ip + '</span><br>'
|
||||
if (params.data.mac) str += '<span>MAC: ' + params.data.mac + '</span><br>'
|
||||
return str
|
||||
}
|
||||
},
|
||||
animationDurationUpdate: 1500,
|
||||
animationEasingUpdate: 'quinticInOut',
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
symbolSize: 50,
|
||||
roam: true,
|
||||
label: {
|
||||
normal: {
|
||||
show: true
|
||||
}
|
||||
},
|
||||
edgeSymbol: ['circle', 'arrow'],
|
||||
edgeSymbolSize: [4, 10],
|
||||
edgeLabel: {
|
||||
normal: {
|
||||
textStyle: {
|
||||
fontSize: 20
|
||||
}
|
||||
}
|
||||
},
|
||||
focusOneNodeAdjacency: true,
|
||||
force: {
|
||||
initLayout: 'force',
|
||||
repulsion: 30,
|
||||
gravity: 0.001,
|
||||
edgeLength: 30
|
||||
},
|
||||
draggable: true,
|
||||
data: this.nodes,
|
||||
links: this.links,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
opacity: 0.9,
|
||||
width: 2,
|
||||
curveness: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
focusOneNodeAdjacency: true,
|
||||
force: {
|
||||
initLayout: 'force',
|
||||
repulsion: 30,
|
||||
gravity: 0.001,
|
||||
edgeLength: 30
|
||||
},
|
||||
draggable: true,
|
||||
data: this.nodes,
|
||||
links: this.links,
|
||||
lineStyle: {
|
||||
normal: {
|
||||
opacity: 0.9,
|
||||
width: 2,
|
||||
curveness: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
this.chart = echarts.init(this.$el)
|
||||
this.chart.setOption(option)
|
||||
this.chart.resize()
|
||||
}
|
||||
this.chart = echarts.init(this.$el)
|
||||
this.chart.setOption(option)
|
||||
this.chart.resize()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.render()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,44 +3,44 @@
|
||||
<el-col :span="12">
|
||||
<!--last tasks-->
|
||||
<el-row class="latest-tasks-wrapper">
|
||||
<task-table-view :title="$t('Latest Tasks')"/>
|
||||
<task-table-view :title="$t('Latest Tasks')" />
|
||||
</el-row>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-row class="node-info-view-wrapper">
|
||||
<!--basic info-->
|
||||
<node-info-view/>
|
||||
<node-info-view />
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import TaskTableView from '../TableView/TaskTableView'
|
||||
import NodeInfoView from '../InfoView/NodeInfoView'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import TaskTableView from '../TableView/TaskTableView'
|
||||
import NodeInfoView from '../InfoView/NodeInfoView'
|
||||
|
||||
export default {
|
||||
name: 'NodeOverview',
|
||||
components: {
|
||||
NodeInfoView,
|
||||
TaskTableView
|
||||
},
|
||||
computed: {
|
||||
id () {
|
||||
return this.$route.params.id
|
||||
export default {
|
||||
name: 'NodeOverview',
|
||||
components: {
|
||||
NodeInfoView,
|
||||
TaskTableView
|
||||
},
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
])
|
||||
},
|
||||
methods: {},
|
||||
created () {
|
||||
computed: {
|
||||
id() {
|
||||
return this.$route.params.id
|
||||
},
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
])
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,50 +3,50 @@
|
||||
<el-col :span="12">
|
||||
<!--last tasks-->
|
||||
<el-row>
|
||||
<task-table-view :title="$t('Latest Tasks')"/>
|
||||
<task-table-view :title="$t('Latest Tasks')" />
|
||||
</el-row>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<!--basic info-->
|
||||
<spider-info-view/>
|
||||
<spider-info-view />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import TaskTableView from '../TableView/TaskTableView'
|
||||
import SpiderInfoView from '../InfoView/SpiderInfoView'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import TaskTableView from '../TableView/TaskTableView'
|
||||
import SpiderInfoView from '../InfoView/SpiderInfoView'
|
||||
|
||||
export default {
|
||||
name: 'SpiderOverview',
|
||||
components: {
|
||||
SpiderInfoView,
|
||||
TaskTableView
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
// spiderForm: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
id () {
|
||||
return this.$route.params.id
|
||||
export default {
|
||||
name: 'SpiderOverview',
|
||||
components: {
|
||||
SpiderInfoView,
|
||||
TaskTableView
|
||||
},
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
])
|
||||
},
|
||||
methods: {},
|
||||
created () {
|
||||
data() {
|
||||
return {
|
||||
// spiderForm: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
id() {
|
||||
return this.$route.params.id
|
||||
},
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
])
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
icon="el-icon-position"
|
||||
@click="onNavigateToSpider"
|
||||
>
|
||||
{{$t('Navigate to Spider')}}
|
||||
{{ $t('Navigate to Spider') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
@@ -15,30 +15,30 @@
|
||||
icon="el-icon-position"
|
||||
@click="onNavigateToNode"
|
||||
>
|
||||
{{$t('Navigate to Node')}}
|
||||
{{ $t('Navigate to Node') }}
|
||||
</el-button>
|
||||
</el-row>
|
||||
<el-row class="content">
|
||||
<el-col :span="12" style="padding-right: 20px;">
|
||||
<el-row class="task-info-overview-wrapper wrapper">
|
||||
<h4 class="title">{{$t('Task Info')}}</h4>
|
||||
<task-info-view @click-log="() => $emit('click-log')"/>
|
||||
<h4 class="title">{{ $t('Task Info') }}</h4>
|
||||
<task-info-view @click-log="() => $emit('click-log')" />
|
||||
</el-row>
|
||||
<el-row style="border-bottom:1px solid #e4e7ed;margin:0 0 20px 0;padding-bottom:20px;"/>
|
||||
<el-row style="border-bottom:1px solid #e4e7ed;margin:0 0 20px 0;padding-bottom:20px;" />
|
||||
</el-col>
|
||||
|
||||
<el-col :span="12">
|
||||
<el-row class="task-info-spider-wrapper wrapper">
|
||||
<h4 class="title spider-title" @click="onNavigateToSpider">
|
||||
<i class="fa fa-search" style="margin-right: 5px"></i>
|
||||
{{$t('Spider Info')}}</h4>
|
||||
<spider-info-view :is-view="true"/>
|
||||
<i class="fa fa-search" style="margin-right: 5px" />
|
||||
{{ $t('Spider Info') }}</h4>
|
||||
<spider-info-view :is-view="true" />
|
||||
</el-row>
|
||||
<el-row class="task-info-node-wrapper wrapper">
|
||||
<h4 class="title node-title" @click="onNavigateToNode">
|
||||
<i class="fa fa-search" style="margin-right: 5px"></i>
|
||||
{{$t('Node Info')}}</h4>
|
||||
<node-info-view :is-view="true"/>
|
||||
<i class="fa fa-search" style="margin-right: 5px" />
|
||||
{{ $t('Node Info') }}</h4>
|
||||
<node-info-view :is-view="true" />
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -46,41 +46,41 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import SpiderInfoView from '../InfoView/SpiderInfoView'
|
||||
import NodeInfoView from '../InfoView/NodeInfoView'
|
||||
import TaskInfoView from '../InfoView/TaskInfoView'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import SpiderInfoView from '../InfoView/SpiderInfoView'
|
||||
import NodeInfoView from '../InfoView/NodeInfoView'
|
||||
import TaskInfoView from '../InfoView/TaskInfoView'
|
||||
|
||||
export default {
|
||||
name: 'SpiderOverview',
|
||||
components: {
|
||||
NodeInfoView,
|
||||
SpiderInfoView,
|
||||
TaskInfoView
|
||||
},
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
]),
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onNavigateToSpider () {
|
||||
this.$router.push(`/spiders/${this.spiderForm._id}`)
|
||||
this.$st.sendEv('任务详情', '概览', '点击爬虫详情')
|
||||
export default {
|
||||
name: 'SpiderOverview',
|
||||
components: {
|
||||
NodeInfoView,
|
||||
SpiderInfoView,
|
||||
TaskInfoView
|
||||
},
|
||||
onNavigateToNode () {
|
||||
this.$router.push(`/nodes/${this.nodeForm._id}`)
|
||||
this.$st.sendEv('任务详情', '概览', '点击节点详情')
|
||||
computed: {
|
||||
...mapState('node', [
|
||||
'nodeForm'
|
||||
]),
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
])
|
||||
},
|
||||
created() {
|
||||
},
|
||||
methods: {
|
||||
onNavigateToSpider() {
|
||||
this.$router.push(`/spiders/${this.spiderForm._id}`)
|
||||
this.$st.sendEv('任务详情', '概览', '点击爬虫详情')
|
||||
},
|
||||
onNavigateToNode() {
|
||||
this.$router.push(`/nodes/${this.nodeForm._id}`)
|
||||
this.$st.sendEv('任务详情', '概览', '点击节点详情')
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -9,84 +9,85 @@
|
||||
:total="total"
|
||||
v-bind="$attrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"/>
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { scrollTo } from '@/utils/scrollTo'
|
||||
import { scrollTo } from '@/utils/scrollTo'
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:page', val)
|
||||
}
|
||||
},
|
||||
pageSize: {
|
||||
get() {
|
||||
return this.limit
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:limit', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit('pagination', { page: this.currentPage, limit: val })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [10, 20, 30, 50]
|
||||
}
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'total, sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('pagination', { page: val, limit: this.pageSize })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
computed: {
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:page', val)
|
||||
}
|
||||
},
|
||||
pageSize: {
|
||||
get() {
|
||||
return this.limit
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:limit', val)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit('pagination', { page: this.currentPage, limit: val })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit('pagination', { page: val, limit: this.pageSize })
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
|
||||
<div class="pan-info">
|
||||
<div class="pan-info-roles-container">
|
||||
<slot/>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<img :src="image" class="pan-thumb">
|
||||
@@ -10,27 +10,27 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PanThumb',
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
export default {
|
||||
name: 'PanThumb',
|
||||
props: {
|
||||
image: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import TaskList from '../../views/task/TaskList'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import TaskList from '../../views/task/TaskList'
|
||||
|
||||
export default {
|
||||
name: 'ScheduleTaskList',
|
||||
extends: TaskList,
|
||||
computed: {
|
||||
...mapState('task', [
|
||||
'filter'
|
||||
]),
|
||||
...mapState('schedule', [
|
||||
'scheduleForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
update () {
|
||||
this.isFilterSpiderDisabled = true
|
||||
this.$set(this.filter, 'spider_id', this.scheduleForm.spider_id)
|
||||
this.filter.schedule_id = this.scheduleForm._id
|
||||
this.$store.dispatch('task/getTaskList')
|
||||
export default {
|
||||
name: 'ScheduleTaskList',
|
||||
extends: TaskList,
|
||||
computed: {
|
||||
...mapState('task', [
|
||||
'filter'
|
||||
]),
|
||||
...mapState('schedule', [
|
||||
'scheduleForm'
|
||||
])
|
||||
},
|
||||
async created() {
|
||||
this.update()
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
this.isFilterSpiderDisabled = true
|
||||
this.$set(this.filter, 'spider_id', this.scheduleForm.spider_id)
|
||||
this.filter.schedule_id = this.scheduleForm._id
|
||||
this.$store.dispatch('task/getTaskList')
|
||||
}
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
icon="el-icon-plus"
|
||||
@click="onSettingsActiveParamAdd"
|
||||
>
|
||||
{{$t('Add')}}
|
||||
{{ $t('Add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
@@ -64,9 +64,9 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template slot="footer">
|
||||
<el-button type="plain" size="small" @click="onCloseDialog">{{$t('Cancel')}}</el-button>
|
||||
<el-button type="plain" size="small" @click="onCloseDialog">{{ $t('Cancel') }}</el-button>
|
||||
<el-button type="primary" size="small" @click="onSettingsConfirm">
|
||||
{{$t('Confirm')}}
|
||||
{{ $t('Confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -79,9 +79,9 @@
|
||||
width="480px"
|
||||
>
|
||||
<el-form
|
||||
ref="add-spider-form"
|
||||
:model="addSpiderForm"
|
||||
label-width="80px"
|
||||
ref="add-spider-form"
|
||||
inline-message
|
||||
>
|
||||
<el-form-item :label="$t('Name')" prop="name" required>
|
||||
@@ -100,15 +100,15 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button type="plain" size="small" @click="isAddSpiderVisible = false">{{$t('Cancel')}}</el-button>
|
||||
<el-button type="plain" size="small" @click="isAddSpiderVisible = false">{{ $t('Cancel') }}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="onAddSpiderConfirm"
|
||||
:icon="isAddSpiderLoading ? 'el-icon-loading' : ''"
|
||||
:disabled="isAddSpiderLoading"
|
||||
@click="onAddSpiderConfirm"
|
||||
>
|
||||
{{$t('Confirm')}}
|
||||
{{ $t('Confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -119,16 +119,7 @@
|
||||
>
|
||||
<!--settings-->
|
||||
<el-tab-pane :label="$t('Settings')" name="settings">
|
||||
<div v-if="!spiderScrapySettings || !spiderScrapySettings.length" class="settings">
|
||||
<span class="empty-text">
|
||||
{{$t('No data available')}}
|
||||
</span>
|
||||
<template v-if="spiderScrapyErrors.settings">
|
||||
<label class="errors-label">{{$t('Errors')}}:</label>
|
||||
<el-alert type="error" v-html="getScrapyErrors('settings')"/>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="settings">
|
||||
<div class="settings">
|
||||
<div class="top-action-wrapper">
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -136,10 +127,10 @@
|
||||
icon="el-icon-plus"
|
||||
@click="onSettingsAdd"
|
||||
>
|
||||
{{$t('Add Variable')}}
|
||||
{{ $t('Add Variable') }}
|
||||
</el-button>
|
||||
<el-button size="small" type="success" @click="onSettingsSave" icon="el-icon-check">
|
||||
{{$t('Save')}}
|
||||
<el-button size="small" type="success" icon="el-icon-check" @click="onSettingsSave">
|
||||
{{ $t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
@@ -188,8 +179,8 @@
|
||||
/>
|
||||
<el-input
|
||||
v-else-if="scope.row.type === 'number'"
|
||||
type="number"
|
||||
v-model="scope.row.value"
|
||||
type="number"
|
||||
size="small"
|
||||
suffix-icon="el-icon-edit"
|
||||
@change="scope.row.value = Number(scope.row.value)"
|
||||
@@ -208,7 +199,7 @@
|
||||
v-else
|
||||
style="margin-left: 10px;font-size: 12px"
|
||||
>
|
||||
{{JSON.stringify(scope.row.value)}}
|
||||
{{ JSON.stringify(scope.row.value) }}
|
||||
<el-button
|
||||
type="warning"
|
||||
size="mini"
|
||||
@@ -241,16 +232,7 @@
|
||||
|
||||
<!--spiders-->
|
||||
<el-tab-pane :label="$t('Spiders')" name="spiders">
|
||||
<div v-if="!spiderForm.spider_names || !spiderForm.spider_names.length" class="spiders">
|
||||
<span class="empty-text error">
|
||||
{{$t('No data available. Please check whether your spiders are missing dependencies or no spiders created.')}}
|
||||
</span>
|
||||
<template v-if="spiderScrapyErrors.spiders">
|
||||
<label class="errors-label">{{$t('Errors')}}:</label>
|
||||
<el-alert type="error" v-html="getScrapyErrors('spiders')"/>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="spiders">
|
||||
<div class="spiders">
|
||||
<div class="action-wrapper">
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -258,7 +240,7 @@
|
||||
icon="el-icon-plus"
|
||||
@click="onAddSpider"
|
||||
>
|
||||
{{$t('Add Spider')}}
|
||||
{{ $t('Add Spider') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<ul class="list">
|
||||
@@ -268,9 +250,9 @@
|
||||
class="item"
|
||||
@click="onClickSpider(s)"
|
||||
>
|
||||
<i class="el-icon-star-on"></i>
|
||||
{{s}}
|
||||
<i v-if="loadingDict[s]" class="el-icon-loading"></i>
|
||||
<i class="el-icon-star-on"/>
|
||||
{{ s }}
|
||||
<i v-if="loadingDict[s]" class="el-icon-loading"/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -279,16 +261,7 @@
|
||||
|
||||
<!--items-->
|
||||
<el-tab-pane label="Items" name="items">
|
||||
<div v-if="!spiderScrapyItems || !spiderScrapyItems.length" class="items">
|
||||
<span class="empty-text">
|
||||
{{$t('No data available')}}
|
||||
</span>
|
||||
<template v-if="spiderScrapyErrors.items">
|
||||
<label class="errors-label">{{$t('Errors')}}:</label>
|
||||
<el-alert type="error" v-html="getScrapyErrors('items')"/>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="items">
|
||||
<div class="items">
|
||||
<div class="action-wrapper">
|
||||
<el-button
|
||||
type="primary"
|
||||
@@ -296,75 +269,78 @@
|
||||
icon="el-icon-plus"
|
||||
@click="onAddItem"
|
||||
>
|
||||
{{$t('Add Item')}}
|
||||
{{ $t('Add Item') }}
|
||||
</el-button>
|
||||
<el-button size="small" type="success" @click="onItemsSave" icon="el-icon-check">
|
||||
{{$t('Save')}}
|
||||
<el-button size="small" type="success" icon="el-icon-check" @click="onItemsSave">
|
||||
{{ $t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-tree
|
||||
:data="spiderScrapyItems"
|
||||
default-expand-all
|
||||
>
|
||||
<span class="custom-tree-node" :class="`level-${data.level}`" slot-scope="{ node, data }">
|
||||
<template v-if="data.level === 1">
|
||||
<span v-if="!node.isEdit" class="label" @click="onItemLabelEdit(node, data, $event)">
|
||||
<i class="el-icon-star-on"></i>
|
||||
{{ data.label }}
|
||||
<i class="el-icon-edit"></i>
|
||||
</span>
|
||||
<el-input
|
||||
v-else
|
||||
:ref="`el-input-${data.id}`"
|
||||
:placeholder="$t('Item Name')"
|
||||
v-model="data.name"
|
||||
size="mini"
|
||||
@change="onItemChange(node, data, $event)"
|
||||
@blur="$set(node, 'isEdit', false)"
|
||||
/>
|
||||
<span>
|
||||
<el-button
|
||||
type="primary"
|
||||
<span slot-scope="{ node, data }" class="custom-tree-node" :class="`level-${data.level}`">
|
||||
<template v-if="data.level === 1">
|
||||
<span v-if="!node.isEdit" class="label" @click="onItemLabelEdit(node, data, $event)">
|
||||
<i class="el-icon-star-on"/>
|
||||
{{ data.label }}
|
||||
<i class="el-icon-edit"/>
|
||||
</span>
|
||||
<el-input
|
||||
v-else
|
||||
:ref="`el-input-${data.id}`"
|
||||
v-model="data.name"
|
||||
:placeholder="$t('Item Name')"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
@click="onAddItemField(data, $event)">
|
||||
{{$t('Add Field')}}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
@change="onItemChange(node, data, $event)"
|
||||
@blur="$set(node, 'isEdit', false)"
|
||||
/>
|
||||
<span>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
@click="onAddItemField(data, $event)"
|
||||
>
|
||||
{{ $t('Add Field') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
@click="removeItem(data, $event)"
|
||||
>
|
||||
{{ $t('Remove') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="data.level === 2">
|
||||
<span v-if="!node.isEdit" class="label" @click="onItemLabelEdit(node, data, $event)">
|
||||
<i class="el-icon-arrow-right"/>
|
||||
{{ node.label }}
|
||||
<i class="el-icon-edit"/>
|
||||
</span>
|
||||
<el-input
|
||||
v-else
|
||||
:ref="`el-input-${data.id}`"
|
||||
v-model="data.name"
|
||||
:placeholder="$t('Field Name')"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
@click="removeItem(data, $event)">
|
||||
{{$t('Remove')}}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="data.level === 2">
|
||||
<span v-if="!node.isEdit" class="label" @click="onItemLabelEdit(node, data, $event)">
|
||||
<i class="el-icon-arrow-right"></i>
|
||||
{{ node.label }}
|
||||
<i class="el-icon-edit"></i>
|
||||
</span>
|
||||
<el-input
|
||||
v-else
|
||||
:ref="`el-input-${data.id}`"
|
||||
:placeholder="$t('Field Name')"
|
||||
v-model="data.name"
|
||||
size="mini"
|
||||
@change="onItemFieldChange(node, data, $event)"
|
||||
@blur="node.isEdit = false"
|
||||
/>
|
||||
<span>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
@click="onRemoveItemField(node, data, $event)">
|
||||
{{$t('Remove')}}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</span>
|
||||
@change="onItemFieldChange(node, data, $event)"
|
||||
@blur="node.isEdit = false"
|
||||
/>
|
||||
<span>
|
||||
<el-button
|
||||
type="danger"
|
||||
size="mini"
|
||||
icon="el-icon-delete"
|
||||
@click="onRemoveItemField(node, data, $event)"
|
||||
>
|
||||
{{ $t('Remove') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</span>
|
||||
</el-tree>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
@@ -372,15 +348,6 @@
|
||||
|
||||
<!--pipelines-->
|
||||
<el-tab-pane label="Pipelines" name="pipelines">
|
||||
<div v-if="!spiderScrapyPipelines || !spiderScrapyPipelines.length" class="pipelines">
|
||||
<span class="empty-text">
|
||||
{{$t('No data available')}}
|
||||
</span>
|
||||
<template v-if="spiderScrapyErrors.pipelines">
|
||||
<label class="errors-label">{{$t('Errors')}}:</label>
|
||||
<el-alert type="error" v-html="getScrapyErrors('pipelines')"/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="pipelines">
|
||||
<ul class="list">
|
||||
<li
|
||||
@@ -389,8 +356,8 @@
|
||||
class="item"
|
||||
@click="$emit('click-pipeline')"
|
||||
>
|
||||
<i class="el-icon-star-on"></i>
|
||||
{{s}}
|
||||
<i class="el-icon-star-on"/>
|
||||
{{ s }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -401,300 +368,295 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'SpiderScrapy',
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm',
|
||||
'spiderScrapySettings',
|
||||
'spiderScrapyItems',
|
||||
'spiderScrapyPipelines',
|
||||
'spiderScrapyErrors'
|
||||
]),
|
||||
activeParamData () {
|
||||
if (this.activeParam.type === 'array') {
|
||||
return this.activeParam.value.map(s => {
|
||||
return { value: s }
|
||||
})
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
return Object.keys(this.activeParam.value).map(key => {
|
||||
return {
|
||||
key,
|
||||
value: this.activeParam.value[key]
|
||||
}
|
||||
})
|
||||
export default {
|
||||
name: 'SpiderScrapy',
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
activeParam: {},
|
||||
activeParamIndex: undefined,
|
||||
isAddSpiderVisible: false,
|
||||
addSpiderForm: {
|
||||
name: '',
|
||||
domain: '',
|
||||
template: 'basic'
|
||||
},
|
||||
isAddSpiderLoading: false,
|
||||
activeTabName: 'settings',
|
||||
loadingDict: {}
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
activeParam: {},
|
||||
activeParamIndex: undefined,
|
||||
isAddSpiderVisible: false,
|
||||
addSpiderForm: {
|
||||
name: '',
|
||||
domain: '',
|
||||
template: 'basic'
|
||||
},
|
||||
isAddSpiderLoading: false,
|
||||
activeTabName: 'settings',
|
||||
loadingDict: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpenDialog () {
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onCloseDialog () {
|
||||
this.dialogVisible = false
|
||||
},
|
||||
onSettingsConfirm () {
|
||||
if (this.activeParam.type === 'array') {
|
||||
this.activeParam.value = this.activeParamData.map(d => d.value)
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
const dict = {}
|
||||
this.activeParamData.forEach(d => {
|
||||
dict[d.key] = d.value
|
||||
})
|
||||
this.activeParam.value = dict
|
||||
}
|
||||
this.$set(this.spiderScrapySettings, this.activeParamIndex, JSON.parse(JSON.stringify(this.activeParam)))
|
||||
this.dialogVisible = false
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '确认编辑参数')
|
||||
},
|
||||
onSettingsEditParam (row, index) {
|
||||
this.activeParam = JSON.parse(JSON.stringify(row))
|
||||
this.activeParamIndex = index
|
||||
this.onOpenDialog()
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '点击编辑参数')
|
||||
},
|
||||
async onSettingsSave () {
|
||||
const res = await this.$store.dispatch('spider/saveSpiderScrapySettings', this.$route.params.id)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Saved successfully'))
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '保存设置')
|
||||
},
|
||||
onSettingsAdd () {
|
||||
const data = JSON.parse(JSON.stringify(this.spiderScrapySettings))
|
||||
data.push({
|
||||
key: '',
|
||||
value: '',
|
||||
type: 'string'
|
||||
})
|
||||
this.$store.commit('spider/SET_SPIDER_SCRAPY_SETTINGS', data)
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加参数')
|
||||
},
|
||||
onSettingsRemove (index) {
|
||||
const data = JSON.parse(JSON.stringify(this.spiderScrapySettings))
|
||||
data.splice(index, 1)
|
||||
this.$store.commit('spider/SET_SPIDER_SCRAPY_SETTINGS', data)
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除参数')
|
||||
},
|
||||
onSettingsActiveParamAdd () {
|
||||
if (this.activeParam.type === 'array') {
|
||||
this.activeParam.value.push('')
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
if (!this.activeParam.value) {
|
||||
this.activeParam.value = {}
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm',
|
||||
'spiderScrapySettings',
|
||||
'spiderScrapyItems',
|
||||
'spiderScrapyPipelines'
|
||||
]),
|
||||
activeParamData() {
|
||||
if (this.activeParam.type === 'array') {
|
||||
return this.activeParam.value.map(s => {
|
||||
return { value: s }
|
||||
})
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
return Object.keys(this.activeParam.value).map(key => {
|
||||
return {
|
||||
key,
|
||||
value: this.activeParam.value[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.$set(this.activeParam.value, '', 999)
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加参数中参数')
|
||||
},
|
||||
onSettingsActiveParamRemove (index) {
|
||||
if (this.activeParam.type === 'array') {
|
||||
this.activeParam.value.splice(index, 1)
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
const key = this.activeParamData[index].key
|
||||
const value = JSON.parse(JSON.stringify(this.activeParam.value))
|
||||
delete value[key]
|
||||
this.$set(this.activeParam, 'value', value)
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除参数中参数')
|
||||
},
|
||||
settingsKeysFetchSuggestions (queryString, cb) {
|
||||
const data = this.$utils.scrapy.settingParamNames
|
||||
.filter(s => {
|
||||
if (!queryString) return true
|
||||
return !!s.match(new RegExp(queryString, 'i'))
|
||||
})
|
||||
.map(s => {
|
||||
return {
|
||||
value: s,
|
||||
label: s
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a > b ? -1 : 1
|
||||
})
|
||||
cb(data)
|
||||
},
|
||||
onSettingsParamTypeChange (row) {
|
||||
if (row.type === 'number') {
|
||||
row.value = Number(row.value)
|
||||
return []
|
||||
}
|
||||
},
|
||||
onAddSpiderConfirm () {
|
||||
this.$refs['add-spider-form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
this.isAddSpiderLoading = true
|
||||
const res = await this.$store.dispatch('spider/addSpiderScrapySpider', {
|
||||
id: this.$route.params.id,
|
||||
form: this.addSpiderForm
|
||||
})
|
||||
methods: {
|
||||
onOpenDialog() {
|
||||
this.dialogVisible = true
|
||||
},
|
||||
onCloseDialog() {
|
||||
this.dialogVisible = false
|
||||
},
|
||||
onSettingsConfirm() {
|
||||
if (this.activeParam.type === 'array') {
|
||||
this.activeParam.value = this.activeParamData.map(d => d.value)
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
const dict = {}
|
||||
this.activeParamData.forEach(d => {
|
||||
dict[d.key] = d.value
|
||||
})
|
||||
this.activeParam.value = dict
|
||||
}
|
||||
this.$set(this.spiderScrapySettings, this.activeParamIndex, JSON.parse(JSON.stringify(this.activeParam)))
|
||||
this.dialogVisible = false
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '确认编辑参数')
|
||||
},
|
||||
onSettingsEditParam(row, index) {
|
||||
this.activeParam = JSON.parse(JSON.stringify(row))
|
||||
this.activeParamIndex = index
|
||||
this.onOpenDialog()
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '点击编辑参数')
|
||||
},
|
||||
async onSettingsSave() {
|
||||
const res = await this.$store.dispatch('spider/saveSpiderScrapySettings', this.$route.params.id)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Saved successfully'))
|
||||
}
|
||||
this.isAddSpiderVisible = false
|
||||
this.isAddSpiderLoading = false
|
||||
await this.$store.dispatch('spider/getSpiderScrapySpiders', this.$route.params.id)
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '确认添加爬虫')
|
||||
},
|
||||
onAddSpider () {
|
||||
this.addSpiderForm = {
|
||||
name: '',
|
||||
domain: '',
|
||||
template: 'basic'
|
||||
}
|
||||
this.isAddSpiderVisible = true
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加爬虫')
|
||||
},
|
||||
getMaxItemNodeId () {
|
||||
let max = 0
|
||||
this.spiderScrapyItems.forEach(d => {
|
||||
if (max < d.id) max = d.id
|
||||
d.children.forEach(f => {
|
||||
if (max < f.id) max = f.id
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '保存设置')
|
||||
},
|
||||
onSettingsAdd() {
|
||||
const data = JSON.parse(JSON.stringify(this.spiderScrapySettings))
|
||||
data.push({
|
||||
key: '',
|
||||
value: '',
|
||||
type: 'string'
|
||||
})
|
||||
})
|
||||
return max
|
||||
},
|
||||
onAddItem () {
|
||||
const maxId = this.getMaxItemNodeId()
|
||||
this.spiderScrapyItems.push({
|
||||
id: maxId + 1,
|
||||
label: `Item_${+new Date()}`,
|
||||
level: 1,
|
||||
children: [
|
||||
{
|
||||
id: maxId + 2,
|
||||
level: 2,
|
||||
label: `field_${+new Date()}`
|
||||
this.$store.commit('spider/SET_SPIDER_SCRAPY_SETTINGS', data)
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加参数')
|
||||
},
|
||||
onSettingsRemove(index) {
|
||||
const data = JSON.parse(JSON.stringify(this.spiderScrapySettings))
|
||||
data.splice(index, 1)
|
||||
this.$store.commit('spider/SET_SPIDER_SCRAPY_SETTINGS', data)
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除参数')
|
||||
},
|
||||
onSettingsActiveParamAdd() {
|
||||
if (this.activeParam.type === 'array') {
|
||||
this.activeParam.value.push('')
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
if (!this.activeParam.value) {
|
||||
this.activeParam.value = {}
|
||||
}
|
||||
]
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加Item')
|
||||
},
|
||||
removeItem (data, ev) {
|
||||
ev.stopPropagation()
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === data.id) {
|
||||
this.spiderScrapyItems.splice(i, 1)
|
||||
break
|
||||
this.$set(this.activeParam.value, '', 999)
|
||||
}
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除Item')
|
||||
},
|
||||
onAddItemField (data, ev) {
|
||||
ev.stopPropagation()
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === data.id) {
|
||||
item.children.push({
|
||||
id: this.getMaxItemNodeId() + 1,
|
||||
level: 2,
|
||||
label: `field_${+new Date()}`
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加参数中参数')
|
||||
},
|
||||
onSettingsActiveParamRemove(index) {
|
||||
if (this.activeParam.type === 'array') {
|
||||
this.activeParam.value.splice(index, 1)
|
||||
} else if (this.activeParam.type === 'object') {
|
||||
const key = this.activeParamData[index].key
|
||||
const value = JSON.parse(JSON.stringify(this.activeParam.value))
|
||||
delete value[key]
|
||||
this.$set(this.activeParam, 'value', value)
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除参数中参数')
|
||||
},
|
||||
settingsKeysFetchSuggestions(queryString, cb) {
|
||||
const data = this.$utils.scrapy.settingParamNames
|
||||
.filter(s => {
|
||||
if (!queryString) return true
|
||||
return !!s.match(new RegExp(queryString, 'i'))
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加Items字段')
|
||||
},
|
||||
onRemoveItemField (node, data, ev) {
|
||||
ev.stopPropagation()
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === node.parent.data.id) {
|
||||
for (let j = 0; j < item.children.length; j++) {
|
||||
const field = item.children[j]
|
||||
if (field.id === data.id) {
|
||||
item.children.splice(j, 1)
|
||||
break
|
||||
.map(s => {
|
||||
return {
|
||||
value: s,
|
||||
label: s
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a > b ? -1 : 1
|
||||
})
|
||||
cb(data)
|
||||
},
|
||||
onSettingsParamTypeChange(row) {
|
||||
if (row.type === 'number') {
|
||||
row.value = Number(row.value)
|
||||
}
|
||||
},
|
||||
onAddSpiderConfirm() {
|
||||
this.$refs['add-spider-form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
this.isAddSpiderLoading = true
|
||||
const res = await this.$store.dispatch('spider/addSpiderScrapySpider', {
|
||||
id: this.$route.params.id,
|
||||
form: this.addSpiderForm
|
||||
})
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Saved successfully'))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除Items字段')
|
||||
},
|
||||
onItemLabelEdit (node, data, ev) {
|
||||
ev.stopPropagation()
|
||||
this.$set(node, 'isEdit', true)
|
||||
this.$set(data, 'name', node.label)
|
||||
setTimeout(() => {
|
||||
this.$refs[`el-input-${data.id}`].focus()
|
||||
}, 0)
|
||||
},
|
||||
onItemChange (node, data, value) {
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === data.id) {
|
||||
item.label = value
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
onItemFieldChange (node, data, value) {
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === node.parent.data.id) {
|
||||
for (let j = 0; j < item.children.length; j++) {
|
||||
const field = item.children[j]
|
||||
if (field.id === data.id) {
|
||||
item.children[j].label = value
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
async onItemsSave () {
|
||||
const res = await this.$store.dispatch('spider/saveSpiderScrapyItems', this.$route.params.id)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Saved successfully'))
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '保存Items')
|
||||
},
|
||||
async onClickSpider (spiderName) {
|
||||
if (this.loadingDict[spiderName]) return
|
||||
this.$set(this.loadingDict, spiderName, true)
|
||||
try {
|
||||
const res = await this.$store.dispatch('spider/getSpiderScrapySpiderFilepath', {
|
||||
id: this.$route.params.id,
|
||||
spiderName
|
||||
this.isAddSpiderVisible = false
|
||||
this.isAddSpiderLoading = false
|
||||
await this.$store.dispatch('spider/getSpiderScrapySpiders', this.$route.params.id)
|
||||
})
|
||||
this.$emit('click-spider', res.data.data)
|
||||
} finally {
|
||||
this.$set(this.loadingDict, spiderName, false)
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '确认添加爬虫')
|
||||
},
|
||||
onAddSpider() {
|
||||
this.addSpiderForm = {
|
||||
name: '',
|
||||
domain: '',
|
||||
template: 'basic'
|
||||
}
|
||||
this.isAddSpiderVisible = true
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加爬虫')
|
||||
},
|
||||
getMaxItemNodeId() {
|
||||
let max = 0
|
||||
this.spiderScrapyItems.forEach(d => {
|
||||
if (max < d.id) max = d.id
|
||||
d.children.forEach(f => {
|
||||
if (max < f.id) max = f.id
|
||||
})
|
||||
})
|
||||
return max
|
||||
},
|
||||
onAddItem() {
|
||||
const maxId = this.getMaxItemNodeId()
|
||||
this.spiderScrapyItems.push({
|
||||
id: maxId + 1,
|
||||
label: `Item_${+new Date()}`,
|
||||
level: 1,
|
||||
children: [
|
||||
{
|
||||
id: maxId + 2,
|
||||
level: 2,
|
||||
label: `field_${+new Date()}`
|
||||
}
|
||||
]
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加Item')
|
||||
},
|
||||
removeItem(data, ev) {
|
||||
ev.stopPropagation()
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === data.id) {
|
||||
this.spiderScrapyItems.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除Item')
|
||||
},
|
||||
onAddItemField(data, ev) {
|
||||
ev.stopPropagation()
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === data.id) {
|
||||
item.children.push({
|
||||
id: this.getMaxItemNodeId() + 1,
|
||||
level: 2,
|
||||
label: `field_${+new Date()}`
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '添加Items字段')
|
||||
},
|
||||
onRemoveItemField(node, data, ev) {
|
||||
ev.stopPropagation()
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === node.parent.data.id) {
|
||||
for (let j = 0; j < item.children.length; j++) {
|
||||
const field = item.children[j]
|
||||
if (field.id === data.id) {
|
||||
item.children.splice(j, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '删除Items字段')
|
||||
},
|
||||
onItemLabelEdit(node, data, ev) {
|
||||
ev.stopPropagation()
|
||||
this.$set(node, 'isEdit', true)
|
||||
this.$set(data, 'name', node.label)
|
||||
setTimeout(() => {
|
||||
this.$refs[`el-input-${data.id}`].focus()
|
||||
}, 0)
|
||||
},
|
||||
onItemChange(node, data, value) {
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === data.id) {
|
||||
item.label = value
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
onItemFieldChange(node, data, value) {
|
||||
for (let i = 0; i < this.spiderScrapyItems.length; i++) {
|
||||
const item = this.spiderScrapyItems[i]
|
||||
if (item.id === node.parent.data.id) {
|
||||
for (let j = 0; j < item.children.length; j++) {
|
||||
const field = item.children[j]
|
||||
if (field.id === data.id) {
|
||||
item.children[j].label = value
|
||||
break
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
async onItemsSave() {
|
||||
const res = await this.$store.dispatch('spider/saveSpiderScrapyItems', this.$route.params.id)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Saved successfully'))
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '保存Items')
|
||||
},
|
||||
async onClickSpider(spiderName) {
|
||||
if (this.loadingDict[spiderName]) return
|
||||
this.$set(this.loadingDict, spiderName, true)
|
||||
try {
|
||||
const res = await this.$store.dispatch('spider/getSpiderScrapySpiderFilepath', {
|
||||
id: this.$route.params.id,
|
||||
spiderName
|
||||
})
|
||||
this.$emit('click-spider', res.data.data)
|
||||
} finally {
|
||||
this.$set(this.loadingDict, spiderName, false)
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '点击爬虫')
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Scrapy 设置', '点击爬虫')
|
||||
},
|
||||
getScrapyErrors (type) {
|
||||
if (!this.spiderScrapyErrors || !this.spiderScrapyErrors[type] || (typeof this.spiderScrapyErrors[type] !== 'string')) return ''
|
||||
return this.$utils.html.htmlEscape(this.spiderScrapyErrors[type]).split('\n').join('<br/>')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -821,19 +783,4 @@ export default {
|
||||
.items >>> .custom-tree-node .el-input {
|
||||
width: 240px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
display: block;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-text.error {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.errors-label {
|
||||
color: #f56c6c;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,38 +5,38 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import screenfull from 'screenfull'
|
||||
import screenfull from 'screenfull'
|
||||
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!screenfull.enabled) {
|
||||
this.$message({
|
||||
message: 'you browser can not work',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
export default {
|
||||
name: 'Screenfull',
|
||||
data() {
|
||||
return {
|
||||
isFullscreen: false
|
||||
}
|
||||
screenfull.toggle()
|
||||
},
|
||||
init() {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.on('change', () => {
|
||||
this.isFullscreen = screenfull.isFullscreen
|
||||
})
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
click() {
|
||||
if (!screenfull.enabled) {
|
||||
this.$message({
|
||||
message: 'you browser can not work',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
screenfull.toggle()
|
||||
},
|
||||
init() {
|
||||
if (screenfull.enabled) {
|
||||
screenfull.on('change', () => {
|
||||
this.isFullscreen = screenfull.isFullscreen
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
<template>
|
||||
<el-scrollbar ref="scrollContainer" :vertical="false" class="scroll-container" @wheel.native.prevent="handleScroll">
|
||||
<slot/>
|
||||
<slot />
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const tagAndTagSpacing = 4 // tagAndTagSpacing
|
||||
const tagAndTagSpacing = 4 // tagAndTagSpacing
|
||||
|
||||
export default {
|
||||
name: 'ScrollPane',
|
||||
data () {
|
||||
return {
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll (e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
||||
},
|
||||
moveToTarget (currentTag) {
|
||||
const $container = this.$refs.scrollContainer.$el
|
||||
const $containerWidth = $container.offsetWidth
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
const tagList = this.$parent.$refs.tag
|
||||
|
||||
let firstTag = null
|
||||
let lastTag = null
|
||||
|
||||
// find first tag and last tag
|
||||
if (tagList.length > 0) {
|
||||
firstTag = tagList[0]
|
||||
lastTag = tagList[tagList.length - 1]
|
||||
export default {
|
||||
name: 'ScrollPane',
|
||||
data() {
|
||||
return {
|
||||
left: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll(e) {
|
||||
const eventDelta = e.wheelDelta || -e.deltaY * 40
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollLeft + eventDelta / 4
|
||||
},
|
||||
moveToTarget(currentTag) {
|
||||
const $container = this.$refs.scrollContainer.$el
|
||||
const $containerWidth = $container.offsetWidth
|
||||
const $scrollWrapper = this.$refs.scrollContainer.$refs.wrap
|
||||
const tagList = this.$parent.$refs.tag
|
||||
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
||||
} else {
|
||||
// find preTag and nextTag
|
||||
const currentIndex = tagList.findIndex(item => item === currentTag)
|
||||
const prevTag = tagList[currentIndex - 1]
|
||||
const nextTag = tagList[currentIndex + 1]
|
||||
// the tag's offsetLeft after of nextTag
|
||||
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
|
||||
let firstTag = null
|
||||
let lastTag = null
|
||||
|
||||
// the tag's offsetLeft before of prevTag
|
||||
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
|
||||
// find first tag and last tag
|
||||
if (tagList.length > 0) {
|
||||
firstTag = tagList[0]
|
||||
lastTag = tagList[tagList.length - 1]
|
||||
}
|
||||
|
||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
||||
if (firstTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = 0
|
||||
} else if (lastTag === currentTag) {
|
||||
$scrollWrapper.scrollLeft = $scrollWrapper.scrollWidth - $containerWidth
|
||||
} else {
|
||||
// find preTag and nextTag
|
||||
const currentIndex = tagList.findIndex(item => item === currentTag)
|
||||
const prevTag = tagList[currentIndex - 1]
|
||||
const nextTag = tagList[currentIndex + 1]
|
||||
// the tag's offsetLeft after of nextTag
|
||||
const afterNextTagOffsetLeft = nextTag.$el.offsetLeft + nextTag.$el.offsetWidth + tagAndTagSpacing
|
||||
|
||||
// the tag's offsetLeft before of prevTag
|
||||
const beforePrevTagOffsetLeft = prevTag.$el.offsetLeft - tagAndTagSpacing
|
||||
|
||||
if (afterNextTagOffsetLeft > $scrollWrapper.scrollLeft + $containerWidth) {
|
||||
$scrollWrapper.scrollLeft = afterNextTagOffsetLeft - $containerWidth
|
||||
} else if (beforePrevTagOffsetLeft < $scrollWrapper.scrollLeft) {
|
||||
$scrollWrapper.scrollLeft = beforePrevTagOffsetLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
<template>
|
||||
<div class="log-item" :style="style" :class="`log-item-${index} ${active ? 'active' : ''}`">
|
||||
<div class="line-no">{{index}}</div>
|
||||
<div class="line-no">{{ index }}</div>
|
||||
<div class="line-content">
|
||||
<span v-if="isLogEnd" style="color: #E6A23C">
|
||||
<span class="loading-text">{{$t('Updating log...')}}</span>
|
||||
<i class="el-icon-loading"></i>
|
||||
<span class="loading-text">{{ $t('Updating log...') }}</span>
|
||||
<i class="el-icon-loading" />
|
||||
</span>
|
||||
<span v-else-if="isAnsi" v-html="dataHtml"></span>
|
||||
<span v-else v-html="dataHtml"></span>
|
||||
<span v-else-if="isAnsi" v-html="dataHtml" />
|
||||
<span v-else v-html="dataHtml" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'LogItem',
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
logItem: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
export default {
|
||||
name: 'LogItem',
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
logItem: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isAnsi: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
searchString: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isAnsi: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
searchString: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('user', [
|
||||
'userInfo'
|
||||
]),
|
||||
errorRegex () {
|
||||
if (!this.userInfo.setting.error_regex_pattern) {
|
||||
return this.$utils.log.errorRegex
|
||||
}
|
||||
console.log(this.userInfo.setting.error_regex_pattern)
|
||||
return new RegExp(this.userInfo.setting.error_regex_pattern, 'i')
|
||||
},
|
||||
dataHtml () {
|
||||
let html = this.data.replace(this.errorRegex, ' <span style="font-weight: bolder; text-decoration: underline">$1</span> ')
|
||||
if (!this.searchString) return html
|
||||
html = html.replace(new RegExp(`(${this.searchString})`, 'gi'), '<mark>$1</mark>')
|
||||
return html
|
||||
},
|
||||
style () {
|
||||
let color = ''
|
||||
if (this.data.match(this.errorRegex)) {
|
||||
color = '#F56C6C'
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
color
|
||||
}
|
||||
},
|
||||
isLogEnd () {
|
||||
return this.data === '###LOG_END###'
|
||||
computed: {
|
||||
...mapGetters('user', [
|
||||
'userInfo'
|
||||
]),
|
||||
errorRegex() {
|
||||
if (!this.userInfo.setting.error_regex_pattern) {
|
||||
return this.$utils.log.errorRegex
|
||||
}
|
||||
console.log(this.userInfo.setting.error_regex_pattern)
|
||||
return new RegExp(this.userInfo.setting.error_regex_pattern, 'i')
|
||||
},
|
||||
dataHtml() {
|
||||
let html = this.data.replace(this.errorRegex, ' <span style="font-weight: bolder; text-decoration: underline">$1</span> ')
|
||||
if (!this.searchString) return html
|
||||
html = html.replace(new RegExp(`(${this.searchString})`, 'gi'), '<mark>$1</mark>')
|
||||
return html
|
||||
},
|
||||
style() {
|
||||
let color = ''
|
||||
if (this.data.match(this.errorRegex)) {
|
||||
color = '#F56C6C'
|
||||
}
|
||||
return {
|
||||
color
|
||||
}
|
||||
},
|
||||
isLogEnd() {
|
||||
return this.data === '###LOG_END###'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
v-model="isLogAutoScroll"
|
||||
:inactive-text="$t('Auto-Scroll')"
|
||||
style="margin-right: 10px"
|
||||
>
|
||||
</el-switch>
|
||||
/>
|
||||
<!-- <el-switch-->
|
||||
<!-- v-model="isLogAutoFetch"-->
|
||||
<!-- :inactive-text="$t('Auto-Refresh')"-->
|
||||
@@ -28,7 +27,7 @@
|
||||
icon="el-icon-search"
|
||||
@click="onSearchLog"
|
||||
>
|
||||
{{$t('Search Log')}}
|
||||
{{ $t('Search Log') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="right">
|
||||
@@ -51,7 +50,7 @@
|
||||
icon="el-icon-warning-outline"
|
||||
@click="toggleErrors"
|
||||
>
|
||||
{{$t('Error Count')}}
|
||||
{{ $t('Error Count') }}
|
||||
</el-button>
|
||||
</el-badge>
|
||||
</div>
|
||||
@@ -63,8 +62,8 @@
|
||||
:class="isErrorsCollapsed ? 'errors-collapsed' : ''"
|
||||
>
|
||||
<virtual-list
|
||||
class="log-view"
|
||||
ref="log-view"
|
||||
class="log-view"
|
||||
:start="currentLogIndex - 1"
|
||||
:offset="0"
|
||||
:size="18"
|
||||
@@ -90,7 +89,7 @@
|
||||
@click="onClickError(item)"
|
||||
>
|
||||
<span class="line-content">
|
||||
{{item.msg}}
|
||||
{{ item.msg }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -100,198 +99,198 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import VirtualList from 'vue-virtual-scroll-list'
|
||||
import Convert from 'ansi-to-html'
|
||||
import hasAnsi from 'has-ansi'
|
||||
import {
|
||||
mapState,
|
||||
mapGetters
|
||||
} from 'vuex'
|
||||
import VirtualList from 'vue-virtual-scroll-list'
|
||||
import Convert from 'ansi-to-html'
|
||||
import hasAnsi from 'has-ansi'
|
||||
|
||||
import LogItem from './LogItem'
|
||||
import LogItem from './LogItem'
|
||||
|
||||
const convert = new Convert()
|
||||
export default {
|
||||
name: 'LogView',
|
||||
components: {
|
||||
VirtualList
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
item: LogItem,
|
||||
searchString: '',
|
||||
isScrolling: false,
|
||||
isScrolling2nd: false,
|
||||
errorRegex: this.$utils.log.errorRegex,
|
||||
currentOffset: 0,
|
||||
isErrorsCollapsed: true,
|
||||
isErrorCollapsing: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('task', [
|
||||
'taskForm',
|
||||
'taskLogTotal',
|
||||
'logKeyword',
|
||||
'isLogFetchLoading',
|
||||
'errorLogData'
|
||||
]),
|
||||
...mapGetters('task', [
|
||||
'logData'
|
||||
]),
|
||||
currentLogIndex: {
|
||||
get () {
|
||||
return this.$store.state.task.currentLogIndex
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_CURRENT_LOG_INDEX', value)
|
||||
const convert = new Convert()
|
||||
export default {
|
||||
name: 'LogView',
|
||||
components: {
|
||||
VirtualList
|
||||
},
|
||||
props: {
|
||||
data: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
logKeyword: {
|
||||
get () {
|
||||
return this.$store.state.task.logKeyword
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_LOG_KEYWORD', value)
|
||||
}
|
||||
},
|
||||
taskLogPage: {
|
||||
get () {
|
||||
return this.$store.state.task.taskLogPage
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_TASK_LOG_PAGE', value)
|
||||
}
|
||||
},
|
||||
taskLogPageSize: {
|
||||
get () {
|
||||
return this.$store.state.task.taskLogPageSize
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_TASK_LOG_PAGE_SIZE', value)
|
||||
}
|
||||
},
|
||||
isLogAutoScroll: {
|
||||
get () {
|
||||
return this.$store.state.task.isLogAutoScroll
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_IS_LOG_AUTO_SCROLL', value)
|
||||
}
|
||||
},
|
||||
isLogAutoFetch: {
|
||||
get () {
|
||||
return this.$store.state.task.isLogAutoFetch
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_IS_LOG_AUTO_FETCH', value)
|
||||
}
|
||||
},
|
||||
isLogFetchLoading: {
|
||||
get () {
|
||||
return this.$store.state.task.isLogFetchLoading
|
||||
},
|
||||
set (value) {
|
||||
this.$store.commit('task/SET_IS_LOG_FETCH_LOADING', value)
|
||||
}
|
||||
},
|
||||
filteredLogData () {
|
||||
return this.logData.filter(d => {
|
||||
if (!this.searchString) return true
|
||||
return !!d.data.toLowerCase().match(this.searchString.toLowerCase())
|
||||
})
|
||||
},
|
||||
remainSize () {
|
||||
const height = document.querySelector('body').clientHeight
|
||||
return (height - 240) / 18
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
taskLogPage () {
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '改变页数')
|
||||
},
|
||||
taskLogPageSize () {
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '改变日志每页条数')
|
||||
},
|
||||
isLogAutoScroll () {
|
||||
if (this.isLogAutoScroll) {
|
||||
this.$store.dispatch('task/getTaskLog', {
|
||||
id: this.$route.params.id,
|
||||
keyword: this.logKeyword
|
||||
}).then(() => {
|
||||
this.toBottom()
|
||||
})
|
||||
this.$st.sendEv('任务详情', '日志', '点击自动滚动')
|
||||
} else {
|
||||
this.$st.sendEv('任务详情', '日志', '取消自动滚动')
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getItemProps (index) {
|
||||
const logItem = this.filteredLogData[index]
|
||||
const isAnsi = hasAnsi(logItem.data)
|
||||
data() {
|
||||
return {
|
||||
// <item/> will render with itemProps.
|
||||
// https://vuejs.org/v2/guide/render-function.html#createElement-Arguments
|
||||
props: {
|
||||
index: logItem.index,
|
||||
logItem,
|
||||
data: isAnsi ? convert.toHtml(logItem.data) : logItem.data,
|
||||
searchString: this.logKeyword,
|
||||
active: logItem.active,
|
||||
isAnsi
|
||||
item: LogItem,
|
||||
searchString: '',
|
||||
isScrolling: false,
|
||||
isScrolling2nd: false,
|
||||
errorRegex: this.$utils.log.errorRegex,
|
||||
currentOffset: 0,
|
||||
isErrorsCollapsed: true,
|
||||
isErrorCollapsing: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('task', [
|
||||
'taskForm',
|
||||
'taskLogTotal',
|
||||
'logKeyword',
|
||||
'isLogFetchLoading',
|
||||
'errorLogData'
|
||||
]),
|
||||
...mapGetters('task', [
|
||||
'logData'
|
||||
]),
|
||||
currentLogIndex: {
|
||||
get() {
|
||||
return this.$store.state.task.currentLogIndex
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('task/SET_CURRENT_LOG_INDEX', value)
|
||||
}
|
||||
},
|
||||
logKeyword: {
|
||||
get() {
|
||||
return this.$store.state.task.logKeyword
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('task/SET_LOG_KEYWORD', value)
|
||||
}
|
||||
},
|
||||
taskLogPage: {
|
||||
get() {
|
||||
return this.$store.state.task.taskLogPage
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('task/SET_TASK_LOG_PAGE', value)
|
||||
}
|
||||
},
|
||||
taskLogPageSize: {
|
||||
get() {
|
||||
return this.$store.state.task.taskLogPageSize
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('task/SET_TASK_LOG_PAGE_SIZE', value)
|
||||
}
|
||||
},
|
||||
isLogAutoScroll: {
|
||||
get() {
|
||||
return this.$store.state.task.isLogAutoScroll
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('task/SET_IS_LOG_AUTO_SCROLL', value)
|
||||
}
|
||||
},
|
||||
isLogAutoFetch: {
|
||||
get() {
|
||||
return this.$store.state.task.isLogAutoFetch
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('task/SET_IS_LOG_AUTO_FETCH', value)
|
||||
}
|
||||
},
|
||||
isLogFetchLoading: {
|
||||
get() {
|
||||
return this.$store.state.task.isLogFetchLoading
|
||||
},
|
||||
set(value) {
|
||||
this.$store.commit('task/SET_IS_LOG_FETCH_LOADING', value)
|
||||
}
|
||||
},
|
||||
filteredLogData() {
|
||||
return this.logData.filter(d => {
|
||||
if (!this.searchString) return true
|
||||
return !!d.data.toLowerCase().match(this.searchString.toLowerCase())
|
||||
})
|
||||
},
|
||||
remainSize() {
|
||||
const height = document.querySelector('body').clientHeight
|
||||
return (height - 240) / 18
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
taskLogPage() {
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '改变页数')
|
||||
},
|
||||
taskLogPageSize() {
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '改变日志每页条数')
|
||||
},
|
||||
isLogAutoScroll() {
|
||||
if (this.isLogAutoScroll) {
|
||||
this.$store.dispatch('task/getTaskLog', {
|
||||
id: this.$route.params.id,
|
||||
keyword: this.logKeyword
|
||||
}).then(() => {
|
||||
this.toBottom()
|
||||
})
|
||||
this.$st.sendEv('任务详情', '日志', '点击自动滚动')
|
||||
} else {
|
||||
this.$st.sendEv('任务详情', '日志', '取消自动滚动')
|
||||
}
|
||||
}
|
||||
},
|
||||
onToBottom () {
|
||||
mounted() {
|
||||
this.currentLogIndex = 0
|
||||
this.handle = setInterval(() => {
|
||||
if (this.isLogAutoScroll) {
|
||||
this.toBottom()
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
onScroll () {
|
||||
destroyed() {
|
||||
clearInterval(this.handle)
|
||||
},
|
||||
toBottom () {
|
||||
this.$el.querySelector('.log-view').scrollTo({ top: 99999999 })
|
||||
},
|
||||
toggleErrors () {
|
||||
this.isErrorsCollapsed = !this.isErrorsCollapsed
|
||||
this.isErrorCollapsing = true
|
||||
setTimeout(() => {
|
||||
this.isErrorCollapsing = false
|
||||
}, 300)
|
||||
},
|
||||
async onClickError (item) {
|
||||
const page = Math.ceil(item.seq / this.taskLogPageSize)
|
||||
this.$store.commit('task/SET_LOG_KEYWORD', '')
|
||||
this.$store.commit('task/SET_TASK_LOG_PAGE', page)
|
||||
this.$store.commit('task/SET_IS_LOG_AUTO_SCROLL', false)
|
||||
this.$store.commit('task/SET_ACTIVE_ERROR_LOG_ITEM', item)
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '点击错误日志')
|
||||
},
|
||||
onSearchLog () {
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '搜索日志')
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.currentLogIndex = 0
|
||||
this.handle = setInterval(() => {
|
||||
if (this.isLogAutoScroll) {
|
||||
this.toBottom()
|
||||
methods: {
|
||||
getItemProps(index) {
|
||||
const logItem = this.filteredLogData[index]
|
||||
const isAnsi = hasAnsi(logItem.data)
|
||||
return {
|
||||
// <item/> will render with itemProps.
|
||||
// https://vuejs.org/v2/guide/render-function.html#createElement-Arguments
|
||||
props: {
|
||||
index: logItem.index,
|
||||
logItem,
|
||||
data: isAnsi ? convert.toHtml(logItem.data) : logItem.data,
|
||||
searchString: this.logKeyword,
|
||||
active: logItem.active,
|
||||
isAnsi
|
||||
}
|
||||
}
|
||||
},
|
||||
onToBottom() {
|
||||
},
|
||||
onScroll() {
|
||||
},
|
||||
toBottom() {
|
||||
this.$el.querySelector('.log-view').scrollTo({ top: 99999999 })
|
||||
},
|
||||
toggleErrors() {
|
||||
this.isErrorsCollapsed = !this.isErrorsCollapsed
|
||||
this.isErrorCollapsing = true
|
||||
setTimeout(() => {
|
||||
this.isErrorCollapsing = false
|
||||
}, 300)
|
||||
},
|
||||
async onClickError(item) {
|
||||
const page = Math.ceil(item.seq / this.taskLogPageSize)
|
||||
this.$store.commit('task/SET_LOG_KEYWORD', '')
|
||||
this.$store.commit('task/SET_TASK_LOG_PAGE', page)
|
||||
this.$store.commit('task/SET_IS_LOG_AUTO_SCROLL', false)
|
||||
this.$store.commit('task/SET_ACTIVE_ERROR_LOG_ITEM', item)
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '点击错误日志')
|
||||
},
|
||||
onSearchLog() {
|
||||
this.$emit('search')
|
||||
this.$st.sendEv('任务详情', '日志', '搜索日志')
|
||||
}
|
||||
}, 200)
|
||||
},
|
||||
destroyed () {
|
||||
clearInterval(this.handle)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
>
|
||||
<el-tab-pane :label="$t('Settings')" name="settings">
|
||||
<el-form
|
||||
ref="git-settings-form"
|
||||
class="git-settings-form"
|
||||
label-width="150px"
|
||||
:model="spiderForm"
|
||||
ref="git-settings-form"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$t('Git URL')"
|
||||
@@ -20,8 +20,7 @@
|
||||
v-model="spiderForm.git_url"
|
||||
:placeholder="$t('Git URL')"
|
||||
@blur="onGitUrlChange"
|
||||
>
|
||||
</el-input>
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('Has Credential')"
|
||||
@@ -41,8 +40,7 @@
|
||||
<el-input
|
||||
v-model="spiderForm.git_username"
|
||||
:placeholder="$t('Git Username')"
|
||||
>
|
||||
</el-input>
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="spiderForm.git_has_credential"
|
||||
@@ -53,8 +51,7 @@
|
||||
v-model="spiderForm.git_password"
|
||||
:placeholder="$t('Git Password')"
|
||||
type="password"
|
||||
>
|
||||
</el-input>
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('Git Branch')"
|
||||
@@ -111,7 +108,7 @@
|
||||
type="error"
|
||||
:closable="false"
|
||||
>
|
||||
{{spiderForm.git_sync_error}}
|
||||
{{ spiderForm.git_sync_error }}
|
||||
</el-alert>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
@@ -122,13 +119,13 @@
|
||||
type="info"
|
||||
:closable="false"
|
||||
>
|
||||
{{sshPublicKey}}
|
||||
{{ sshPublicKey }}
|
||||
</el-alert>
|
||||
<span class="copy" @click="copySshPublicKey">
|
||||
<i class="el-icon-copy-document"></i>
|
||||
{{$t('Copy')}}
|
||||
</span>
|
||||
<input id="ssh-public-key" v-model="sshPublicKey" v-show="true">
|
||||
<i class="el-icon-copy-document" />
|
||||
{{ $t('Copy') }}
|
||||
</span>
|
||||
<input v-show="true" id="ssh-public-key" v-model="sshPublicKey">
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="action-wrapper">
|
||||
@@ -139,7 +136,7 @@
|
||||
:icon="isGitResetLoading ? 'el-icon-loading' : 'el-icon-refresh-left'"
|
||||
@click="onReset"
|
||||
>
|
||||
{{$t('Reset')}}
|
||||
{{ $t('Reset') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
size="small"
|
||||
@@ -148,10 +145,10 @@
|
||||
:disabled="!spiderForm.git_url || isGitSyncLoading"
|
||||
@click="onSync"
|
||||
>
|
||||
{{$t('Sync')}}
|
||||
{{ $t('Sync') }}
|
||||
</el-button>
|
||||
<el-button size="small" type="success" @click="onSave" icon="el-icon-check">
|
||||
{{$t('Save')}}
|
||||
<el-button size="small" type="success" icon="el-icon-check" @click="onSave">
|
||||
{{ $t('Save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
@@ -168,10 +165,10 @@
|
||||
<div class="commit">
|
||||
<div class="row">
|
||||
<div class="message">
|
||||
{{c.message}}
|
||||
{{ c.message }}
|
||||
</div>
|
||||
<div class="author">
|
||||
{{c.author}} ({{c.email}})
|
||||
{{ c.author }} ({{ c.email }})
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 10px">
|
||||
@@ -181,7 +178,7 @@
|
||||
type="primary"
|
||||
size="mini"
|
||||
>
|
||||
<i class="fa fa-tag"></i>
|
||||
<i class="fa fa-tag" />
|
||||
HEAD
|
||||
</el-tag>
|
||||
<el-tag
|
||||
@@ -190,8 +187,8 @@
|
||||
:type="b.label === 'master' ? 'danger' : 'warning'"
|
||||
size="mini"
|
||||
>
|
||||
<i class="fa fa-tag"></i>
|
||||
{{b.label}}
|
||||
<i class="fa fa-tag" />
|
||||
{{ b.label }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="b in c.remote_branches"
|
||||
@@ -199,8 +196,8 @@
|
||||
type="info"
|
||||
size="mini"
|
||||
>
|
||||
<i class="fa fa-tag"></i>
|
||||
{{b.label}}
|
||||
<i class="fa fa-tag" />
|
||||
{{ b.label }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
v-for="t in c.tags"
|
||||
@@ -208,8 +205,8 @@
|
||||
type="success"
|
||||
size="mini"
|
||||
>
|
||||
<i class="fa fa-tag"></i>
|
||||
{{t.label}}
|
||||
<i class="fa fa-tag" />
|
||||
{{ t.label }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="actions">
|
||||
@@ -232,165 +229,165 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'GitSettings',
|
||||
data () {
|
||||
return {
|
||||
gitBranches: [],
|
||||
isGitBranchesLoading: false,
|
||||
isGitSyncLoading: false,
|
||||
isGitResetLoading: false,
|
||||
isGitCheckoutLoading: false,
|
||||
syncFrequencies: [
|
||||
{ label: '1m', value: '0 * * * * *' },
|
||||
{ label: '5m', value: '0 0/5 * * * *' },
|
||||
{ label: '15m', value: '0 0/15 * * * *' },
|
||||
{ label: '30m', value: '0 0/30 * * * *' },
|
||||
{ label: '1h', value: '0 0 * * * *' },
|
||||
{ label: '6h', value: '0 0 0/6 * * *' },
|
||||
{ label: '12h', value: '0 0 0/12 * * *' },
|
||||
{ label: '1d', value: '0 0 0 0 * *' }
|
||||
],
|
||||
sshPublicKey: '',
|
||||
activeTabName: 'settings',
|
||||
commits: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onSave () {
|
||||
this.$refs['git-settings-form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
const res = await this.$store.dispatch('spider/editSpider')
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Spider info has been saved successfully'))
|
||||
}
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '保存')
|
||||
},
|
||||
async onGitUrlChange () {
|
||||
if (!this.spiderForm.git_url) return
|
||||
this.isGitBranchesLoading = true
|
||||
try {
|
||||
const res = await this.$request.get('/git/branches', { url: this.spiderForm.git_url })
|
||||
this.gitBranches = res.data.data
|
||||
if (!this.spiderForm.git_branch && this.gitBranches.length > 0) {
|
||||
this.$set(this.spiderForm, 'git_branch', this.gitBranches[0])
|
||||
}
|
||||
} finally {
|
||||
this.isGitBranchesLoading = false
|
||||
export default {
|
||||
name: 'GitSettings',
|
||||
data() {
|
||||
return {
|
||||
gitBranches: [],
|
||||
isGitBranchesLoading: false,
|
||||
isGitSyncLoading: false,
|
||||
isGitResetLoading: false,
|
||||
isGitCheckoutLoading: false,
|
||||
syncFrequencies: [
|
||||
{ label: '1m', value: '0 * * * * *' },
|
||||
{ label: '5m', value: '0 0/5 * * * *' },
|
||||
{ label: '15m', value: '0 0/15 * * * *' },
|
||||
{ label: '30m', value: '0 0/30 * * * *' },
|
||||
{ label: '1h', value: '0 0 * * * *' },
|
||||
{ label: '6h', value: '0 0 0/6 * * *' },
|
||||
{ label: '12h', value: '0 0 0/12 * * *' },
|
||||
{ label: '1d', value: '0 0 0 0 * *' }
|
||||
],
|
||||
sshPublicKey: '',
|
||||
activeTabName: 'settings',
|
||||
commits: []
|
||||
}
|
||||
},
|
||||
async onSync () {
|
||||
this.isGitSyncLoading = true
|
||||
try {
|
||||
const res = await this.$request.post(`/spiders/${this.spiderForm._id}/git/sync`)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Git has been synchronized successfully'))
|
||||
}
|
||||
} finally {
|
||||
this.isGitSyncLoading = false
|
||||
await this.updateGit()
|
||||
await this.$store.dispatch('spider/getSpiderData', this.$route.params.id)
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
])
|
||||
},
|
||||
async created() {
|
||||
if (this.spiderForm.git_url) {
|
||||
this.onGitUrlChange()
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '同步')
|
||||
},
|
||||
onReset () {
|
||||
this.$confirm(
|
||||
this.$t('This would delete all files of the spider. Are you sure to continue?'),
|
||||
this.$t('Notification'),
|
||||
{
|
||||
confirmButtonText: this.$t('Confirm'),
|
||||
cancelButtonText: this.$t('Cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
this.isGitResetLoading = true
|
||||
try {
|
||||
const res = await this.$request.post(`/spiders/${this.spiderForm._id}/git/reset`)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Git has been reset successfully'))
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '确认重置')
|
||||
}
|
||||
} finally {
|
||||
this.isGitResetLoading = false
|
||||
// await this.updateGit()
|
||||
}
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '点击重置')
|
||||
},
|
||||
async getSshPublicKey () {
|
||||
const res = await this.$request.get('/git/public-key')
|
||||
this.sshPublicKey = res.data.data
|
||||
},
|
||||
copySshPublicKey () {
|
||||
const el = document.querySelector('#ssh-public-key')
|
||||
el.focus()
|
||||
el.setSelectionRange(0, this.sshPublicKey.length)
|
||||
document.execCommand('copy')
|
||||
this.$message.success(this.$t('SSH Public Key is copied to the clipboard'))
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '拷贝 SSH 公钥')
|
||||
},
|
||||
async getCommits () {
|
||||
const res = await this.$request.get('/git/commits', { spider_id: this.spiderForm._id })
|
||||
this.commits = res.data.data.map(d => {
|
||||
d.ts = dayjs(d.ts).format('YYYY-MM-DD HH:mm:ss')
|
||||
return d
|
||||
})
|
||||
},
|
||||
async checkout (c) {
|
||||
this.isGitCheckoutLoading = true
|
||||
try {
|
||||
const res = await this.$request.post('/git/checkout', { spider_id: this.spiderForm._id, hash: c.hash })
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Checkout success'))
|
||||
}
|
||||
} finally {
|
||||
this.isGitCheckoutLoading = false
|
||||
await this.getCommits()
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Git Log', 'Checkout')
|
||||
},
|
||||
async updateGit () {
|
||||
this.getSshPublicKey()
|
||||
this.getCommits()
|
||||
},
|
||||
getCommitType (c) {
|
||||
if (c.is_head) return 'primary'
|
||||
if (c.branches && c.branches.length) {
|
||||
if (c.branches.map(d => d.label).includes('master')) {
|
||||
return 'danger'
|
||||
} else {
|
||||
return 'warning'
|
||||
methods: {
|
||||
onSave() {
|
||||
this.$refs['git-settings-form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
const res = await this.$store.dispatch('spider/editSpider')
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Spider info has been saved successfully'))
|
||||
}
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '保存')
|
||||
},
|
||||
async onGitUrlChange() {
|
||||
if (!this.spiderForm.git_url) return
|
||||
this.isGitBranchesLoading = true
|
||||
try {
|
||||
const res = await this.$request.get('/git/branches', { url: this.spiderForm.git_url })
|
||||
this.gitBranches = res.data.data
|
||||
if (!this.spiderForm.git_branch && this.gitBranches.length > 0) {
|
||||
this.$set(this.spiderForm, 'git_branch', this.gitBranches[0])
|
||||
}
|
||||
} finally {
|
||||
this.isGitBranchesLoading = false
|
||||
}
|
||||
},
|
||||
async onSync() {
|
||||
this.isGitSyncLoading = true
|
||||
try {
|
||||
const res = await this.$request.post(`/spiders/${this.spiderForm._id}/git/sync`)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Git has been synchronized successfully'))
|
||||
}
|
||||
} finally {
|
||||
this.isGitSyncLoading = false
|
||||
await this.updateGit()
|
||||
await this.$store.dispatch('spider/getSpiderData', this.$route.params.id)
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '同步')
|
||||
},
|
||||
onReset() {
|
||||
this.$confirm(
|
||||
this.$t('This would delete all files of the spider. Are you sure to continue?'),
|
||||
this.$t('Notification'),
|
||||
{
|
||||
confirmButtonText: this.$t('Confirm'),
|
||||
cancelButtonText: this.$t('Cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async() => {
|
||||
this.isGitResetLoading = true
|
||||
try {
|
||||
const res = await this.$request.post(`/spiders/${this.spiderForm._id}/git/reset`)
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Git has been reset successfully'))
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '确认重置')
|
||||
}
|
||||
} finally {
|
||||
this.isGitResetLoading = false
|
||||
// await this.updateGit()
|
||||
}
|
||||
})
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '点击重置')
|
||||
},
|
||||
async getSshPublicKey() {
|
||||
const res = await this.$request.get('/git/public-key')
|
||||
this.sshPublicKey = res.data.data
|
||||
},
|
||||
copySshPublicKey() {
|
||||
const el = document.querySelector('#ssh-public-key')
|
||||
el.focus()
|
||||
el.setSelectionRange(0, this.sshPublicKey.length)
|
||||
document.execCommand('copy')
|
||||
this.$message.success(this.$t('SSH Public Key is copied to the clipboard'))
|
||||
this.$st.sendEv('爬虫详情', 'Git 设置', '拷贝 SSH 公钥')
|
||||
},
|
||||
async getCommits() {
|
||||
const res = await this.$request.get('/git/commits', { spider_id: this.spiderForm._id })
|
||||
this.commits = res.data.data.map(d => {
|
||||
d.ts = dayjs(d.ts).format('YYYY-MM-DD HH:mm:ss')
|
||||
return d
|
||||
})
|
||||
},
|
||||
async checkout(c) {
|
||||
this.isGitCheckoutLoading = true
|
||||
try {
|
||||
const res = await this.$request.post('/git/checkout', { spider_id: this.spiderForm._id, hash: c.hash })
|
||||
if (!res.data.error) {
|
||||
this.$message.success(this.$t('Checkout success'))
|
||||
}
|
||||
} finally {
|
||||
this.isGitCheckoutLoading = false
|
||||
await this.getCommits()
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', 'Git Log', 'Checkout')
|
||||
},
|
||||
async updateGit() {
|
||||
this.getCommits()
|
||||
},
|
||||
getCommitType(c) {
|
||||
if (c.is_head) return 'primary'
|
||||
if (c.branches && c.branches.length) {
|
||||
if (c.branches.map(d => d.label).includes('master')) {
|
||||
return 'danger'
|
||||
} else {
|
||||
return 'warning'
|
||||
}
|
||||
}
|
||||
if (c.tags && c.tags.length) {
|
||||
return 'success'
|
||||
}
|
||||
if (c.remote_branches && c.remote_branches.length) {
|
||||
return 'info'
|
||||
}
|
||||
},
|
||||
onChangeTab() {
|
||||
this.$st.sendEv('爬虫详情', 'Git 切换标签', this.activeTabName)
|
||||
}
|
||||
if (c.tags && c.tags.length) {
|
||||
return 'success'
|
||||
}
|
||||
if (c.remote_branches && c.remote_branches.length) {
|
||||
return 'info'
|
||||
}
|
||||
},
|
||||
onChangeTab () {
|
||||
this.$st.sendEv('爬虫详情', 'Git 切换标签', this.activeTabName)
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
if (this.spiderForm.git_url) {
|
||||
this.onGitUrlChange()
|
||||
}
|
||||
this.getSshPublicKey()
|
||||
this.getCommits()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -11,30 +11,30 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
export default {
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'vue'
|
||||
}
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'vue'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickTitle() {
|
||||
this.isActive = !this.isActive
|
||||
data() {
|
||||
return {
|
||||
isActive: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickTitle() {
|
||||
this.isActive = !this.isActive
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" >
|
||||
|
||||
@@ -5,51 +5,51 @@
|
||||
</div>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">{{
|
||||
item.label }}</el-dropdown-item>
|
||||
item.label }}</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sizeOptions: [
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Medium', value: 'medium' },
|
||||
{ label: 'Small', value: 'small' },
|
||||
{ label: 'Mini', value: 'mini' }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
size() {
|
||||
return this.$store.getters.size
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSetSize(size) {
|
||||
this.$ELEMENT.size = size
|
||||
this.$store.dispatch('setSize', size)
|
||||
this.refreshView()
|
||||
this.$message({
|
||||
message: 'Switch Size Success',
|
||||
type: 'success'
|
||||
})
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
sizeOptions: [
|
||||
{ label: 'Default', value: 'default' },
|
||||
{ label: 'Medium', value: 'medium' },
|
||||
{ label: 'Small', value: 'small' },
|
||||
{ label: 'Mini', value: 'mini' }
|
||||
]
|
||||
}
|
||||
},
|
||||
refreshView() {
|
||||
// In order to make the cached page re-rendered
|
||||
this.$store.dispatch('delAllCachedViews', this.$route)
|
||||
|
||||
const { fullPath } = this.$route
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
computed: {
|
||||
size() {
|
||||
return this.$store.getters.size
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleSetSize(size) {
|
||||
this.$ELEMENT.size = size
|
||||
this.$store.dispatch('setSize', size)
|
||||
this.refreshView()
|
||||
this.$message({
|
||||
message: 'Switch Size Success',
|
||||
type: 'success'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
refreshView() {
|
||||
// In order to make the cached page re-rendered
|
||||
this.$store.dispatch('delAllCachedViews', this.$route)
|
||||
|
||||
}
|
||||
const { fullPath } = this.$route
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$router.replace({
|
||||
path: '/redirect' + fullPath
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="copy-spider-dialog"
|
||||
ref="form"
|
||||
class="copy-spider-dialog"
|
||||
:title="$t('Copy Spider')"
|
||||
:visible="visible"
|
||||
width="580px"
|
||||
:before-close="onClose"
|
||||
>
|
||||
<el-form
|
||||
ref="form"
|
||||
label-width="160px"
|
||||
:model="form"
|
||||
ref="form"
|
||||
>
|
||||
<el-form-item
|
||||
:label="$t('New Spider Name')"
|
||||
required
|
||||
>
|
||||
<el-input v-model="form.name" :placeholder="$t('New Spider Name')"/>
|
||||
<el-input v-model="form.name" :placeholder="$t('New Spider Name')" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template slot="footer">
|
||||
<el-button type="plain" size="small" @click="$emit('close')">{{$t('Cancel')}}</el-button>
|
||||
<el-button type="plain" size="small" @click="$emit('close')">{{ $t('Cancel') }}</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
@@ -28,56 +28,56 @@
|
||||
:disabled="isLoading"
|
||||
@click="onConfirm"
|
||||
>
|
||||
{{$t('Confirm')}}
|
||||
{{ $t('Confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CopySpiderDialog',
|
||||
props: {
|
||||
spiderId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
form: {
|
||||
name: ''
|
||||
export default {
|
||||
name: 'CopySpiderDialog',
|
||||
props: {
|
||||
spiderId: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClose () {
|
||||
this.$emit('close')
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
onConfirm () {
|
||||
this.$refs['form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
try {
|
||||
this.isLoading = true
|
||||
const res = await this.$request.post(`/spiders/${this.spiderId}/copy`, this.form)
|
||||
if (!res.data.error) {
|
||||
this.$message.success('Copied successfully')
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
name: ''
|
||||
},
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
onConfirm() {
|
||||
this.$refs['form'].validate(async valid => {
|
||||
if (!valid) return
|
||||
try {
|
||||
this.isLoading = true
|
||||
const res = await this.$request.post(`/spiders/${this.spiderId}/copy`, this.form)
|
||||
if (!res.data.error) {
|
||||
this.$message.success('Copied successfully')
|
||||
}
|
||||
this.$emit('confirm')
|
||||
this.$emit('close')
|
||||
this.$st.sendEv('爬虫复制', '确认提交')
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
this.$emit('confirm')
|
||||
this.$emit('close')
|
||||
this.$st.sendEv('爬虫复制', '确认提交')
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,55 +1,56 @@
|
||||
<template>
|
||||
<el-card class="metric-card">
|
||||
<el-col :span="6" class="icon-col">
|
||||
<i :class="icon" :style="{color:color}"></i>
|
||||
<i :class="icon" :style="{color:color}" />
|
||||
</el-col>
|
||||
<el-col :span="18" class="text-col">
|
||||
<el-row>
|
||||
<label class="label">{{$t(label)}}</label>
|
||||
<label class="label">{{ $t(label) }}</label>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<div class="value">{{value}}</div>
|
||||
<div class="value">{{ value }}</div>
|
||||
</el-row>
|
||||
</el-col>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MetricCard',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
export default {
|
||||
name: 'MetricCard',
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
}
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
value: {
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
color () {
|
||||
if (this.type === 'primary') {
|
||||
return '#409EFF'
|
||||
} else if (this.type === 'warning') {
|
||||
return '#e6a23c'
|
||||
} else if (this.type === 'success') {
|
||||
return '#67c23a'
|
||||
} else if (this.type === 'danger') {
|
||||
return '#f56c6c'
|
||||
} else {
|
||||
return 'grey'
|
||||
computed: {
|
||||
color() {
|
||||
if (this.type === 'primary') {
|
||||
return '#409EFF'
|
||||
} else if (this.type === 'warning') {
|
||||
return '#e6a23c'
|
||||
} else if (this.type === 'success') {
|
||||
return '#67c23a'
|
||||
} else if (this.type === 'danger') {
|
||||
return '#f56c6c'
|
||||
} else {
|
||||
return 'grey'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,24 +1,32 @@
|
||||
<template>
|
||||
<div class="spider-stats" v-loading="loading">
|
||||
<div v-loading="loading" class="spider-stats">
|
||||
<!--overall stats-->
|
||||
<el-row>
|
||||
<div class="metric-list">
|
||||
<metric-card label="30-Day Tasks"
|
||||
icon="fa fa-play"
|
||||
:value="overviewStats.task_count"
|
||||
type="danger"/>
|
||||
<metric-card label="30-Day Results"
|
||||
icon="fa fa-table"
|
||||
:value="overviewStats.result_count"
|
||||
type="primary"/>
|
||||
<metric-card label="Success Rate"
|
||||
icon="fa fa-check"
|
||||
:value="getPercentStr(overviewStats.success_rate)"
|
||||
type="success"/>
|
||||
<metric-card label="Avg Duration (sec)"
|
||||
icon="fa fa-hourglass"
|
||||
:value="getDecimal(overviewStats.avg_runtime_duration)"
|
||||
type="warning"/>
|
||||
<metric-card
|
||||
label="30-Day Tasks"
|
||||
icon="fa fa-play"
|
||||
:value="overviewStats.task_count"
|
||||
type="danger"
|
||||
/>
|
||||
<metric-card
|
||||
label="30-Day Results"
|
||||
icon="fa fa-table"
|
||||
:value="overviewStats.result_count"
|
||||
type="primary"
|
||||
/>
|
||||
<metric-card
|
||||
label="Success Rate"
|
||||
icon="fa fa-check"
|
||||
:value="getPercentStr(overviewStats.success_rate)"
|
||||
type="success"
|
||||
/>
|
||||
<metric-card
|
||||
label="Avg Duration (sec)"
|
||||
icon="fa fa-hourglass"
|
||||
:value="getDecimal(overviewStats.avg_runtime_duration)"
|
||||
type="warning"
|
||||
/>
|
||||
</div>
|
||||
</el-row>
|
||||
<!--./overall stats-->
|
||||
@@ -26,8 +34,8 @@
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-card class="chart-wrapper">
|
||||
<h4>{{$t('Daily Tasks')}}</h4>
|
||||
<div id="task-line" class="chart"></div>
|
||||
<h4>{{ $t('Daily Tasks') }}</h4>
|
||||
<div id="task-line" class="chart" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -35,8 +43,8 @@
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-card class="chart-wrapper">
|
||||
<h4>{{$t('Daily Avg Duration (sec)')}}</h4>
|
||||
<div id="duration-line" class="chart"></div>
|
||||
<h4>{{ $t('Daily Avg Duration (sec)') }}</h4>
|
||||
<div id="duration-line" class="chart" />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -44,117 +52,120 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import MetricCard from './MetricCard'
|
||||
import echarts from 'echarts'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import MetricCard from './MetricCard'
|
||||
import echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: 'SpiderStats',
|
||||
components: { MetricCard },
|
||||
data () {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderTaskLine () {
|
||||
const chart = echarts.init(this.$el.querySelector('#task-line'))
|
||||
const option = {
|
||||
grid: {
|
||||
top: 20,
|
||||
bottom: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.dailyStats.map(d => d.date)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
data: this.dailyStats.map(d => d.task_count),
|
||||
areaStyle: {},
|
||||
smooth: true
|
||||
}],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
show: true
|
||||
}
|
||||
export default {
|
||||
name: 'SpiderStats',
|
||||
components: { MetricCard },
|
||||
data() {
|
||||
return {
|
||||
loading: false
|
||||
}
|
||||
chart.setOption(option)
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'overviewStats',
|
||||
'statusStats',
|
||||
'nodeStats',
|
||||
'dailyStats'
|
||||
])
|
||||
},
|
||||
|
||||
renderDurationLine () {
|
||||
const chart = echarts.init(this.$el.querySelector('#duration-line'))
|
||||
const option = {
|
||||
grid: {
|
||||
top: 20,
|
||||
bottom: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.dailyStats.map(d => d.date)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
data: this.dailyStats.map(d => d.avg_runtime_duration),
|
||||
areaStyle: {},
|
||||
smooth: true
|
||||
}],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
show: true
|
||||
mounted() {
|
||||
},
|
||||
|
||||
methods: {
|
||||
renderTaskLine() {
|
||||
const chart = echarts.init(this.$el.querySelector('#task-line'))
|
||||
const option = {
|
||||
grid: {
|
||||
top: 20,
|
||||
bottom: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.dailyStats.map(d => d.date)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
data: this.dailyStats.map(d => d.task_count),
|
||||
areaStyle: {},
|
||||
smooth: true
|
||||
}],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
show: true
|
||||
}
|
||||
}
|
||||
chart.setOption(option)
|
||||
},
|
||||
|
||||
renderDurationLine() {
|
||||
const chart = echarts.init(this.$el.querySelector('#duration-line'))
|
||||
const option = {
|
||||
grid: {
|
||||
top: 20,
|
||||
bottom: 40
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: this.dailyStats.map(d => d.date)
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [{
|
||||
type: 'line',
|
||||
data: this.dailyStats.map(d => d.avg_runtime_duration),
|
||||
areaStyle: {},
|
||||
smooth: true
|
||||
}],
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
show: true
|
||||
}
|
||||
}
|
||||
chart.setOption(option)
|
||||
},
|
||||
|
||||
render() {
|
||||
this.renderTaskLine()
|
||||
this.renderDurationLine()
|
||||
},
|
||||
|
||||
update() {
|
||||
this.loading = true
|
||||
this.$store.dispatch('spider/getSpiderStats')
|
||||
.then(() => {
|
||||
this.render()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(this.$t('An error happened when fetching the data'))
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
getPercentStr(value) {
|
||||
if (value === undefined) return 'NA'
|
||||
return (value * 100).toFixed(2) + '%'
|
||||
},
|
||||
|
||||
getDecimal(value) {
|
||||
if (value === undefined) return 'NA'
|
||||
return value.toFixed(2)
|
||||
}
|
||||
chart.setOption(option)
|
||||
},
|
||||
|
||||
render () {
|
||||
this.renderTaskLine()
|
||||
this.renderDurationLine()
|
||||
},
|
||||
|
||||
update () {
|
||||
this.loading = true
|
||||
this.$store.dispatch('spider/getSpiderStats')
|
||||
.then(() => {
|
||||
this.render()
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error(this.$t('An error happened when fetching the data'))
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
getPercentStr (value) {
|
||||
if (value === undefined) return 'NA'
|
||||
return (value * 100).toFixed(2) + '%'
|
||||
},
|
||||
|
||||
getDecimal (value) {
|
||||
if (value === undefined) return 'NA'
|
||||
return value.toFixed(2)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'overviewStats',
|
||||
'statusStats',
|
||||
'nodeStats',
|
||||
'dailyStats'
|
||||
])
|
||||
},
|
||||
mounted () {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
<template>
|
||||
<div class="legend">
|
||||
<el-tag type="primary" size="small">
|
||||
<i class="el-icon-loading"></i>
|
||||
{{$t('Pending')}}
|
||||
<i class="el-icon-loading" />
|
||||
{{ $t('Pending') }}
|
||||
</el-tag>
|
||||
<el-tag type="warning" size="small">
|
||||
<i class="el-icon-loading"></i>
|
||||
{{$t('Running')}}
|
||||
<i class="el-icon-loading" />
|
||||
{{ $t('Running') }}
|
||||
</el-tag>
|
||||
<el-tag type="success" size="small">
|
||||
<i class="el-icon-check"></i>
|
||||
{{$t('Finished')}}
|
||||
<i class="el-icon-check" />
|
||||
{{ $t('Finished') }}
|
||||
</el-tag>
|
||||
<el-tag type="danger" size="small">
|
||||
<i class="el-icon-error"></i>
|
||||
{{$t('Error')}}
|
||||
<i class="el-icon-error" />
|
||||
{{ $t('Error') }}
|
||||
</el-tag>
|
||||
<el-tag type="info" size="small">
|
||||
<i class="el-icon-video-pause"></i>
|
||||
{{$t('Cancelled')}}
|
||||
<i class="el-icon-video-pause" />
|
||||
{{ $t('Cancelled') }}
|
||||
</el-tag>
|
||||
<el-tag type="danger" size="small">
|
||||
<i class="el-icon-warning"></i>
|
||||
{{$t('Abnormal')}}
|
||||
<i class="el-icon-warning" />
|
||||
{{ $t('Abnormal') }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StatusLegend'
|
||||
}
|
||||
export default {
|
||||
name: 'StatusLegend'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,65 +1,65 @@
|
||||
<template>
|
||||
<el-tag :type="type" class="status-tag">
|
||||
<i :class="icon"></i>
|
||||
{{$t(label)}}
|
||||
<i :class="icon" />
|
||||
{{ $t(label) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StatusTag',
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
statusDict: {
|
||||
pending: { label: 'Pending', type: 'primary' },
|
||||
running: { label: 'Running', type: 'warning' },
|
||||
finished: { label: 'Finished', type: 'success' },
|
||||
error: { label: 'Error', type: 'danger' },
|
||||
cancelled: { label: 'Cancelled', type: 'info' },
|
||||
abnormal: { label: 'Abnormal', type: 'danger' }
|
||||
export default {
|
||||
name: 'StatusTag',
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
type () {
|
||||
const s = this.statusDict[this.status]
|
||||
if (s) {
|
||||
return s.type
|
||||
}
|
||||
return ''
|
||||
},
|
||||
label () {
|
||||
const s = this.statusDict[this.status]
|
||||
if (s) {
|
||||
return s.label
|
||||
data() {
|
||||
return {
|
||||
statusDict: {
|
||||
pending: { label: 'Pending', type: 'primary' },
|
||||
running: { label: 'Running', type: 'warning' },
|
||||
finished: { label: 'Finished', type: 'success' },
|
||||
error: { label: 'Error', type: 'danger' },
|
||||
cancelled: { label: 'Cancelled', type: 'info' },
|
||||
abnormal: { label: 'Abnormal', type: 'danger' }
|
||||
}
|
||||
}
|
||||
return 'NA'
|
||||
},
|
||||
icon () {
|
||||
if (this.status === 'finished') {
|
||||
return 'el-icon-check'
|
||||
} else if (this.status === 'pending') {
|
||||
return 'el-icon-loading'
|
||||
} else if (this.status === 'running') {
|
||||
return 'el-icon-loading'
|
||||
} else if (this.status === 'error') {
|
||||
return 'el-icon-error'
|
||||
} else if (this.status === 'cancelled') {
|
||||
return 'el-icon-video-pause'
|
||||
} else if (this.status === 'abnormal') {
|
||||
return 'el-icon-warning'
|
||||
} else {
|
||||
return 'el-icon-question'
|
||||
computed: {
|
||||
type() {
|
||||
const s = this.statusDict[this.status]
|
||||
if (s) {
|
||||
return s.type
|
||||
}
|
||||
return ''
|
||||
},
|
||||
label() {
|
||||
const s = this.statusDict[this.status]
|
||||
if (s) {
|
||||
return s.label
|
||||
}
|
||||
return 'NA'
|
||||
},
|
||||
icon() {
|
||||
if (this.status === 'finished') {
|
||||
return 'el-icon-check'
|
||||
} else if (this.status === 'pending') {
|
||||
return 'el-icon-loading'
|
||||
} else if (this.status === 'running') {
|
||||
return 'el-icon-loading'
|
||||
} else if (this.status === 'error') {
|
||||
return 'el-icon-error'
|
||||
} else if (this.status === 'cancelled') {
|
||||
return 'el-icon-video-pause'
|
||||
} else if (this.status === 'abnormal') {
|
||||
return 'el-icon-warning'
|
||||
} else {
|
||||
return 'el-icon-question'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -9,80 +9,80 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
position: '',
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
isSticky: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.height = this.$el.getBoundingClientRect().height
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
window.addEventListener('resize', this.handleReize)
|
||||
},
|
||||
activated() {
|
||||
this.handleScroll()
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
window.removeEventListener('resize', this.handleReize)
|
||||
},
|
||||
methods: {
|
||||
sticky() {
|
||||
if (this.active) {
|
||||
return
|
||||
export default {
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
stickyTop: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
zIndex: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
this.position = 'fixed'
|
||||
this.active = true
|
||||
this.width = this.width + 'px'
|
||||
this.isSticky = true
|
||||
},
|
||||
handleReset() {
|
||||
if (!this.active) {
|
||||
return
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
position: '',
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
isSticky: false
|
||||
}
|
||||
this.reset()
|
||||
},
|
||||
reset() {
|
||||
this.position = ''
|
||||
this.width = 'auto'
|
||||
this.active = false
|
||||
this.isSticky = false
|
||||
mounted() {
|
||||
this.height = this.$el.getBoundingClientRect().height
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
window.addEventListener('resize', this.handleReize)
|
||||
},
|
||||
handleScroll() {
|
||||
const width = this.$el.getBoundingClientRect().width
|
||||
this.width = width || 'auto'
|
||||
const offsetTop = this.$el.getBoundingClientRect().top
|
||||
if (offsetTop < this.stickyTop) {
|
||||
this.sticky()
|
||||
return
|
||||
}
|
||||
this.handleReset()
|
||||
activated() {
|
||||
this.handleScroll()
|
||||
},
|
||||
handleReize() {
|
||||
if (this.isSticky) {
|
||||
this.width = this.$el.getBoundingClientRect().width + 'px'
|
||||
destroyed() {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
window.removeEventListener('resize', this.handleReize)
|
||||
},
|
||||
methods: {
|
||||
sticky() {
|
||||
if (this.active) {
|
||||
return
|
||||
}
|
||||
this.position = 'fixed'
|
||||
this.active = true
|
||||
this.width = this.width + 'px'
|
||||
this.isSticky = true
|
||||
},
|
||||
handleReset() {
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
this.reset()
|
||||
},
|
||||
reset() {
|
||||
this.position = ''
|
||||
this.width = 'auto'
|
||||
this.active = false
|
||||
this.isSticky = false
|
||||
},
|
||||
handleScroll() {
|
||||
const width = this.$el.getBoundingClientRect().width
|
||||
this.width = width || 'auto'
|
||||
const offsetTop = this.$el.getBoundingClientRect().top
|
||||
if (offsetTop < this.stickyTop) {
|
||||
this.sticky()
|
||||
return
|
||||
}
|
||||
this.handleReset()
|
||||
},
|
||||
handleReize() {
|
||||
if (this.isSticky) {
|
||||
this.width = this.$el.getBoundingClientRect().width + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
<template>
|
||||
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
|
||||
<use :xlink:href="iconName"/>
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
export default {
|
||||
name: 'SvgIcon',
|
||||
props: {
|
||||
iconClass: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
className: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconName () {
|
||||
return `#icon-${this.iconClass}`
|
||||
},
|
||||
svgClass () {
|
||||
if (this.className) {
|
||||
return 'svg-icon ' + this.className
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
computed: {
|
||||
iconName() {
|
||||
return `#icon-${this.iconClass}`
|
||||
},
|
||||
svgClass() {
|
||||
if (this.className) {
|
||||
return 'svg-icon ' + this.className
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,57 +1,60 @@
|
||||
<template>
|
||||
<div class="deploy-table-view">
|
||||
<el-row class="title-wrapper">
|
||||
<h5 class="title">{{title}}</h5>
|
||||
<el-button type="success" plain class="small-btn" size="mini" icon="fa fa-refresh" @click="onRefresh"></el-button>
|
||||
<h5 class="title">{{ title }}</h5>
|
||||
<el-button type="success" plain class="small-btn" size="mini" icon="fa fa-refresh" @click="onRefresh" />
|
||||
</el-row>
|
||||
<el-table border height="240px" :data="deployList">
|
||||
<el-table-column property="version" label="Ver" width="40" align="center"></el-table-column>
|
||||
<el-table-column property="version" label="Ver" width="40" align="center" />
|
||||
<el-table-column property="node" label="Node" width="220" align="center">
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" @click="onClickNode(scope.row)">{{scope.row.node_id}}</a>
|
||||
<a class="a-tag" @click="onClickNode(scope.row)">{{ scope.row.node_id }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="spider_name" label="Spider" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" @click="onClickSpider(scope.row)">{{scope.row.spider_name}}</a>
|
||||
<a class="a-tag" @click="onClickSpider(scope.row)">{{ scope.row.spider_name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="finish_ts" label="Finish Time" width="auto" align="center"></el-table-column>
|
||||
<el-table-column property="finish_ts" label="Finish Time" width="auto" align="center" />
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DeployTableView',
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onClickSpider (row) {
|
||||
this.$router.push(`/spiders/${row.spider_id}`)
|
||||
export default {
|
||||
name: 'DeployTableView',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
onClickNode (row) {
|
||||
this.$router.push(`/nodes/${row.node_id}`)
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('deploy', [
|
||||
'deployList'
|
||||
])
|
||||
},
|
||||
onRefresh () {
|
||||
this.$store.dispatch('deploy/getDeployList', this.spiderForm._id)
|
||||
methods: {
|
||||
onClickSpider(row) {
|
||||
this.$router.push(`/spiders/${row.spider_id}`)
|
||||
},
|
||||
onClickNode(row) {
|
||||
this.$router.push(`/nodes/${row.node_id}`)
|
||||
},
|
||||
onRefresh() {
|
||||
this.$store.dispatch('deploy/getDeployList', this.spiderForm._id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
<template>
|
||||
<div class="fields-table-view">
|
||||
<el-row>
|
||||
<el-table :data="fields"
|
||||
class="table edit"
|
||||
:header-cell-style="{background:'rgb(48, 65, 86)',color:'white'}"
|
||||
:cell-style="getCellClassStyle"
|
||||
<el-table
|
||||
:data="fields"
|
||||
class="table edit"
|
||||
:header-cell-style="{background:'rgb(48, 65, 86)',color:'white'}"
|
||||
:cell-style="getCellClassStyle"
|
||||
>
|
||||
<el-table-column class-name="action" width="80px" align="right">
|
||||
<template slot-scope="scope">
|
||||
<i class="action-item el-icon-copy-document" @click="onCopyField(scope.row)"></i>
|
||||
<i class="action-item el-icon-remove-outline" @click="onRemoveField(scope.row)"></i>
|
||||
<i class="action-item el-icon-circle-plus-outline" @click="onAddField(scope.row)"></i>
|
||||
<i class="action-item el-icon-copy-document" @click="onCopyField(scope.row)" />
|
||||
<i class="action-item el-icon-remove-outline" @click="onRemoveField(scope.row)" />
|
||||
<i class="action-item el-icon-circle-plus-outline" @click="onAddField(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Field Name')" width="150px">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.name"
|
||||
:placeholder="$t('Field Name')"
|
||||
suffix-icon="el-icon-edit"
|
||||
@change="onNameChange(scope.row)"
|
||||
<el-input
|
||||
v-model="scope.row.name"
|
||||
:placeholder="$t('Field Name')"
|
||||
suffix-icon="el-icon-edit"
|
||||
@change="onNameChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -49,16 +51,14 @@
|
||||
v-model="scope.row.css"
|
||||
:placeholder="$t('CSS / XPath')"
|
||||
suffix-icon="el-icon-edit"
|
||||
>
|
||||
</el-input>
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-input
|
||||
v-model="scope.row.xpath"
|
||||
:placeholder="$t('CSS / XPath')"
|
||||
suffix-icon="el-icon-edit"
|
||||
>
|
||||
</el-input>
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -69,7 +69,7 @@
|
||||
:class="!isShowAttr(scope.row) ? 'active' : 'inactive'"
|
||||
type="success"
|
||||
>
|
||||
{{$t('Text')}}
|
||||
{{ $t('Text') }}
|
||||
</el-tag>
|
||||
</span>
|
||||
<span class="button-selector-item" @click="onClickIsAttribute(scope.row, true)">
|
||||
@@ -77,7 +77,7 @@
|
||||
:class="isShowAttr(scope.row) ? 'active' : 'inactive'"
|
||||
type="primary"
|
||||
>
|
||||
{{$t('Attribute')}}
|
||||
{{ $t('Attribute') }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</template>
|
||||
@@ -106,14 +106,14 @@
|
||||
:class="!scope.row.next_stage ? 'disabled' : ''"
|
||||
@change="onChangeNextStage(scope.row)"
|
||||
>
|
||||
<el-option :label="$t('No Next Stage')" value=""/>
|
||||
<el-option v-for="n in filteredStageNames" :key="n" :label="n" :value="n"/>
|
||||
<el-option :label="$t('No Next Stage')" value="" />
|
||||
<el-option v-for="n in filteredStageNames" :key="n" :label="n" :value="n" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Remark')" width="auto" min-width="120px">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.remark" :placeholder="$t('Remark')" suffix-icon="el-icon-edit"/>
|
||||
<el-input v-model="scope.row.remark" :placeholder="$t('Remark')" suffix-icon="el-icon-edit" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -122,144 +122,144 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'FieldsTableView',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'list'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
stageNames: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
filteredStageNames () {
|
||||
return this.stageNames.filter(n => n !== this.stage.name)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onNameChange (row) {
|
||||
if (this.fields.filter(d => d.name === row.name).length > 1) {
|
||||
this.$message.error(this.$t(`Duplicated field names for ${row.name}`))
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', '配置', '更改字段')
|
||||
},
|
||||
onClickSelectorType (row, selectorType) {
|
||||
this.$st.sendEv('爬虫详情', '配置', `点击字段选择器类别-${selectorType}`)
|
||||
if (selectorType === 'css') {
|
||||
if (row.xpath) this.$set(row, 'xpath', '')
|
||||
if (!row.css) this.$set(row, 'css', 'body')
|
||||
} else {
|
||||
if (row.css) this.$set(row, 'css', '')
|
||||
if (!row.xpath) this.$set(row, 'xpath', '//body')
|
||||
}
|
||||
},
|
||||
onClickIsAttribute (row, isAttribute) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '设置字段属性')
|
||||
if (!isAttribute) {
|
||||
// 文本
|
||||
if (row.attr) this.$set(row, 'attr', '')
|
||||
} else {
|
||||
// 属性
|
||||
if (!row.attr) this.$set(row, 'attr', 'href')
|
||||
}
|
||||
this.$set(row, 'isAttrChange', false)
|
||||
},
|
||||
onCopyField (row) {
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
if (row.name === this.fields[i].name) {
|
||||
this.fields.splice(i, 0, JSON.parse(JSON.stringify(row)))
|
||||
break
|
||||
export default {
|
||||
name: 'FieldsTableView',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'list'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
stage: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
stageNames: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
fields: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
onRemoveField (row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '删除字段')
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
if (row.name === this.fields[i].name) {
|
||||
this.fields.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.fields.length === 0) {
|
||||
this.fields.push({
|
||||
xpath: '//body',
|
||||
next_stage: ''
|
||||
})
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
filteredStageNames() {
|
||||
return this.stageNames.filter(n => n !== this.stage.name)
|
||||
}
|
||||
},
|
||||
onAddField (row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '添加字段')
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
if (row.name === this.fields[i].name) {
|
||||
this.fields.splice(i + 1, 0, {
|
||||
name: `field_${Math.floor(new Date().getTime()).toString()}`,
|
||||
methods: {
|
||||
onNameChange(row) {
|
||||
if (this.fields.filter(d => d.name === row.name).length > 1) {
|
||||
this.$message.error(this.$t(`Duplicated field names for ${row.name}`))
|
||||
}
|
||||
this.$st.sendEv('爬虫详情', '配置', '更改字段')
|
||||
},
|
||||
onClickSelectorType(row, selectorType) {
|
||||
this.$st.sendEv('爬虫详情', '配置', `点击字段选择器类别-${selectorType}`)
|
||||
if (selectorType === 'css') {
|
||||
if (row.xpath) this.$set(row, 'xpath', '')
|
||||
if (!row.css) this.$set(row, 'css', 'body')
|
||||
} else {
|
||||
if (row.css) this.$set(row, 'css', '')
|
||||
if (!row.xpath) this.$set(row, 'xpath', '//body')
|
||||
}
|
||||
},
|
||||
onClickIsAttribute(row, isAttribute) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '设置字段属性')
|
||||
if (!isAttribute) {
|
||||
// 文本
|
||||
if (row.attr) this.$set(row, 'attr', '')
|
||||
} else {
|
||||
// 属性
|
||||
if (!row.attr) this.$set(row, 'attr', 'href')
|
||||
}
|
||||
this.$set(row, 'isAttrChange', false)
|
||||
},
|
||||
onCopyField(row) {
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
if (row.name === this.fields[i].name) {
|
||||
this.fields.splice(i, 0, JSON.parse(JSON.stringify(row)))
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
onRemoveField(row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '删除字段')
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
if (row.name === this.fields[i].name) {
|
||||
this.fields.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
if (this.fields.length === 0) {
|
||||
this.fields.push({
|
||||
xpath: '//body',
|
||||
next_stage: ''
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
getCellClassStyle ({ row, columnIndex }) {
|
||||
if (columnIndex === 1) {
|
||||
// 字段名称
|
||||
if (!row.name) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
},
|
||||
onAddField(row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '添加字段')
|
||||
for (let i = 0; i < this.fields.length; i++) {
|
||||
if (row.name === this.fields[i].name) {
|
||||
this.fields.splice(i + 1, 0, {
|
||||
name: `field_${Math.floor(new Date().getTime()).toString()}`,
|
||||
xpath: '//body',
|
||||
next_stage: ''
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (columnIndex === 3) {
|
||||
// 选择器
|
||||
if (!row.css && !row.xpath) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
},
|
||||
getCellClassStyle({ row, columnIndex }) {
|
||||
if (columnIndex === 1) {
|
||||
// 字段名称
|
||||
if (!row.name) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
}
|
||||
}
|
||||
} else if (columnIndex === 3) {
|
||||
// 选择器
|
||||
if (!row.css && !row.xpath) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onChangeNextStage(row) {
|
||||
this.fields.forEach(f => {
|
||||
if (f.name !== row.name) {
|
||||
this.$set(f, 'next_stage', '')
|
||||
}
|
||||
})
|
||||
},
|
||||
onAttrChange(row) {
|
||||
this.$set(row, 'isAttrChange', !row.attr)
|
||||
},
|
||||
isShowAttr(row) {
|
||||
return (row.attr || row.isAttrChange)
|
||||
}
|
||||
},
|
||||
onChangeNextStage (row) {
|
||||
this.fields.forEach(f => {
|
||||
if (f.name !== row.name) {
|
||||
this.$set(f, 'next_stage', '')
|
||||
}
|
||||
})
|
||||
},
|
||||
onAttrChange (row) {
|
||||
this.$set(row, 'isAttrChange', !row.attr)
|
||||
},
|
||||
isShowAttr (row) {
|
||||
return (row.attr || row.isAttrChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -3,18 +3,19 @@
|
||||
<el-table
|
||||
:data="filteredData"
|
||||
:header-cell-style="{background:'rgb(48, 65, 86)',color:'white'}"
|
||||
border>
|
||||
border
|
||||
>
|
||||
<template v-for="col in columns">
|
||||
<el-table-column :key="col" :label="col" :property="col" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-popover trigger="hover" :content="getString(scope.row[col])" popper-class="cell-popover">
|
||||
<div v-if="isUrl(scope.row[col])" slot="reference" class="wrapper">
|
||||
<a :href="getString(scope.row[col])" target="_blank" style="color: #409eff">
|
||||
{{getString(scope.row[col])}}
|
||||
{{ getString(scope.row[col]) }}
|
||||
</a>
|
||||
</div>
|
||||
<div v-else slot="reference" class="wrapper">
|
||||
{{getString(scope.row[col])}}
|
||||
{{ getString(scope.row[col]) }}
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
@@ -23,72 +24,72 @@
|
||||
</el-table>
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
@current-change="onPageChange"
|
||||
@size-change="onPageChange"
|
||||
:current-page.sync="pageNum"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size.sync="pageSize"
|
||||
layout="sizes, prev, pager, next"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
:total="total"
|
||||
@current-change="onPageChange"
|
||||
@size-change="onPageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'GeneralTableView',
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
pageNum: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
export default {
|
||||
name: 'GeneralTableView',
|
||||
props: {
|
||||
pageNum: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
total: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
columns: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
filteredData() {
|
||||
return this.data
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredData () {
|
||||
return this.data
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isUrl (value) {
|
||||
if (!value) return false
|
||||
if (!value.match) return false
|
||||
return !!value.match(/^https?:\/\//)
|
||||
},
|
||||
onPageChange () {
|
||||
this.$emit('page-change', { pageNum: this.pageNum, pageSize: this.pageSize })
|
||||
},
|
||||
getString (value) {
|
||||
if (value === undefined) return ''
|
||||
const str = JSON.stringify(value)
|
||||
if (str.match(/^"(.*)"$/)) return str.match(/^"(.*)"$/)[1]
|
||||
return str
|
||||
methods: {
|
||||
isUrl(value) {
|
||||
if (!value) return false
|
||||
if (!value.match) return false
|
||||
return !!value.match(/^https?:\/\//)
|
||||
},
|
||||
onPageChange() {
|
||||
this.$emit('page-change', { pageNum: this.pageNum, pageSize: this.pageSize })
|
||||
},
|
||||
getString(value) {
|
||||
if (value === undefined) return ''
|
||||
const str = JSON.stringify(value)
|
||||
if (str.match(/^"(.*)"$/)) return str.match(/^"(.*)"$/)[1]
|
||||
return str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<template>
|
||||
<div class="setting-list-table-view">
|
||||
<el-row>
|
||||
<el-table :data="list"
|
||||
class="table edit"
|
||||
:header-cell-style="{background:'rgb(48, 65, 86)',color:'white'}"
|
||||
:cell-style="getCellClassStyle"
|
||||
<el-table
|
||||
:data="list"
|
||||
class="table edit"
|
||||
:header-cell-style="{background:'rgb(48, 65, 86)',color:'white'}"
|
||||
:cell-style="getCellClassStyle"
|
||||
>
|
||||
<el-table-column class-name="action" width="80px" align="right">
|
||||
<template slot-scope="scope">
|
||||
<!-- <i class="action-item el-icon-copy-document" @click="onCopyField(scope.row)"></i>-->
|
||||
<i class="action-item el-icon-remove-outline" @click="onRemoveField(scope.row)"></i>
|
||||
<i class="action-item el-icon-circle-plus-outline" @click="onAddField(scope.row)"></i>
|
||||
<i class="action-item el-icon-remove-outline" @click="onRemoveField(scope.row)" />
|
||||
<i class="action-item el-icon-circle-plus-outline" @click="onAddField(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Name')" width="240px">
|
||||
@@ -39,114 +40,114 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'SettingFieldsTableView',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'list'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
stageNames: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
list () {
|
||||
const list = []
|
||||
for (let name in this.spiderForm.config.settings) {
|
||||
if (this.spiderForm.config.settings.hasOwnProperty(name)) {
|
||||
const value = this.spiderForm.config.settings[name]
|
||||
list.push({ name, value })
|
||||
export default {
|
||||
name: 'SettingFieldsTableView',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'list'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
stageNames: {
|
||||
type: Array,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange (row) {
|
||||
if (this.list.filter(d => d.name === row.name).length > 1) {
|
||||
this.$message.error(this.$t(`Duplicated field names for ${row.name}`))
|
||||
}
|
||||
this.$store.commit('spider/SET_SPIDER_FORM_CONFIG_SETTINGS', this.list)
|
||||
},
|
||||
onRemoveField (row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '删除设置')
|
||||
const list = JSON.parse(JSON.stringify(this.list))
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (row.name === list[i].name) {
|
||||
list.splice(i, 1)
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
list() {
|
||||
const list = []
|
||||
for (const name in this.spiderForm.config.settings) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.spiderForm.config.settings, name)) {
|
||||
const value = this.spiderForm.config.settings[name]
|
||||
list.push({ name, value })
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
if (list.length === 0) {
|
||||
list.push({
|
||||
name: `VARIABLE_NAME_${Math.floor(new Date().getTime())}`,
|
||||
value: `VARIABLE_VALUE_${Math.floor(new Date().getTime())}`
|
||||
},
|
||||
created() {
|
||||
if (this.list.length === 0) {
|
||||
this.$store.commit(
|
||||
'spider/SET_SPIDER_FORM_CONFIG_SETTING_ITEM',
|
||||
'VARIABLE_NAME_' + Math.floor(new Date().getTime()),
|
||||
'VARIABLE_VALUE_' + Math.floor(new Date().getTime())
|
||||
)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(row) {
|
||||
if (this.list.filter(d => d.name === row.name).length > 1) {
|
||||
this.$message.error(this.$t(`Duplicated field names for ${row.name}`))
|
||||
}
|
||||
this.$store.commit('spider/SET_SPIDER_FORM_CONFIG_SETTINGS', this.list)
|
||||
},
|
||||
onRemoveField(row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '删除设置')
|
||||
const list = JSON.parse(JSON.stringify(this.list))
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (row.name === list[i].name) {
|
||||
list.splice(i, 1)
|
||||
}
|
||||
}
|
||||
if (list.length === 0) {
|
||||
list.push({
|
||||
name: `VARIABLE_NAME_${Math.floor(new Date().getTime())}`,
|
||||
value: `VARIABLE_VALUE_${Math.floor(new Date().getTime())}`
|
||||
})
|
||||
}
|
||||
this.$store.commit('spider/SET_SPIDER_FORM_CONFIG_SETTINGS', list)
|
||||
},
|
||||
onAddField(row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '添加设置')
|
||||
const list = JSON.parse(JSON.stringify(this.list))
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (row.name === list[i].name) {
|
||||
const name = 'VARIABLE_NAME_' + Math.floor(new Date().getTime())
|
||||
const value = 'VARIABLE_VALUE_' + Math.floor(new Date().getTime())
|
||||
list.push({ name, value })
|
||||
break
|
||||
}
|
||||
}
|
||||
this.$store.commit('spider/SET_SPIDER_FORM_CONFIG_SETTINGS', list)
|
||||
},
|
||||
getCellClassStyle({ row, columnIndex }) {
|
||||
if (columnIndex === 1) {
|
||||
// 字段名称
|
||||
if (!row.name) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
}
|
||||
}
|
||||
} else if (columnIndex === 3) {
|
||||
// 选择器
|
||||
if (!row.css && !row.xpath) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onChangeNextStage(row) {
|
||||
this.list.forEach(f => {
|
||||
if (f.name !== row.name) {
|
||||
this.$set(f, 'next_stage', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
this.$store.commit('spider/SET_SPIDER_FORM_CONFIG_SETTINGS', list)
|
||||
},
|
||||
onAddField (row) {
|
||||
this.$st.sendEv('爬虫详情', '配置', '添加设置')
|
||||
const list = JSON.parse(JSON.stringify(this.list))
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (row.name === list[i].name) {
|
||||
const name = 'VARIABLE_NAME_' + Math.floor(new Date().getTime())
|
||||
const value = 'VARIABLE_VALUE_' + Math.floor(new Date().getTime())
|
||||
list.push({ name, value })
|
||||
break
|
||||
}
|
||||
}
|
||||
this.$store.commit('spider/SET_SPIDER_FORM_CONFIG_SETTINGS', list)
|
||||
},
|
||||
getCellClassStyle ({ row, columnIndex }) {
|
||||
if (columnIndex === 1) {
|
||||
// 字段名称
|
||||
if (!row.name) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
}
|
||||
}
|
||||
} else if (columnIndex === 3) {
|
||||
// 选择器
|
||||
if (!row.css && !row.xpath) {
|
||||
return {
|
||||
'border': '1px solid red'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onChangeNextStage (row) {
|
||||
this.list.forEach(f => {
|
||||
if (f.name !== row.name) {
|
||||
this.$set(f, 'next_stage', '')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if (this.list.length === 0) {
|
||||
this.$store.commit(
|
||||
'spider/SET_SPIDER_FORM_CONFIG_SETTING_ITEM',
|
||||
'VARIABLE_NAME_' + Math.floor(new Date().getTime()),
|
||||
'VARIABLE_VALUE_' + Math.floor(new Date().getTime())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="task-table-view">
|
||||
<el-row class="title-wrapper">
|
||||
<h5 class="title">{{title}}</h5>
|
||||
<el-button type="success" plain class="small-btn" size="mini" icon="fa fa-refresh" @click="onRefresh"></el-button>
|
||||
<h5 class="title">{{ title }}</h5>
|
||||
<el-button type="success" plain class="small-btn" size="mini" icon="fa fa-refresh" @click="onRefresh" />
|
||||
</el-row>
|
||||
<el-table
|
||||
:data="taskList"
|
||||
@@ -13,36 +13,38 @@
|
||||
>
|
||||
<el-table-column property="node" :label="$t('Node')" width="120" align="left">
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" @click="onClickNode(scope.row)">{{scope.row.node_name}}</a>
|
||||
<a class="a-tag" @click="onClickNode(scope.row)">{{ scope.row.node_name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="spider_name" :label="$t('Spider')" width="120" align="left">
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" @click="onClickSpider(scope.row)">{{scope.row.spider_name}}</a>
|
||||
<a class="a-tag" @click="onClickSpider(scope.row)">{{ scope.row.spider_name }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="param" :label="$t('Parameters')" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.param}}</span>
|
||||
<span>{{ scope.row.param }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column property="result_count" :label="$t('Results Count')" width="60" align="right">
|
||||
<template slot-scope="scope">
|
||||
<span>{{scope.row.result_count}}</span>
|
||||
<span>{{ scope.row.result_count }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('Status')"
|
||||
align="left"
|
||||
width="100">
|
||||
<el-table-column
|
||||
:label="$t('Status')"
|
||||
align="left"
|
||||
width="100"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<status-tag :status="scope.row.status"/>
|
||||
<status-tag :status="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!--<el-table-column property="create_ts" label="Create Time" width="auto" align="center"></el-table-column>-->
|
||||
<el-table-column property="create_ts" :label="$t('Create Time')" width="150" align="left">
|
||||
<template slot-scope="scope">
|
||||
<a href="javascript:" class="a-tag" @click="onClickTask(scope.row)">
|
||||
{{getTime(scope.row.create_ts).format('YYYY-MM-DD HH:mm:ss')}}
|
||||
{{ getTime(scope.row.create_ts).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -52,85 +54,89 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import StatusTag from '../Status/StatusTag'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import dayjs from 'dayjs'
|
||||
import StatusTag from '../Status/StatusTag'
|
||||
|
||||
export default {
|
||||
name: 'TaskTableView',
|
||||
components: { StatusTag },
|
||||
data () {
|
||||
return {
|
||||
// setInterval handle
|
||||
handle: undefined,
|
||||
// 防抖
|
||||
clicked: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
title: String
|
||||
},
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('task', [
|
||||
'taskList'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
onClickSpider (row) {
|
||||
this.clicked = true
|
||||
setTimeout(() => {
|
||||
this.clicked = false
|
||||
}, 100)
|
||||
this.$router.push(`/spiders/${row.spider_id}`)
|
||||
if (this.$route.path.match(/\/nodes\//)) {
|
||||
this.$st.sendEv('节点详情', '概览', '查看爬虫')
|
||||
export default {
|
||||
name: 'TaskTableView',
|
||||
components: { StatusTag },
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
onClickNode (row) {
|
||||
this.clicked = true
|
||||
setTimeout(() => {
|
||||
this.clicked = false
|
||||
}, 100)
|
||||
this.$router.push(`/nodes/${row.node_id}`)
|
||||
if (this.$route.path.match(/\/spiders\//)) {
|
||||
this.$st.sendEv('爬虫详情', '概览', '查看节点')
|
||||
data() {
|
||||
return {
|
||||
// setInterval handle
|
||||
handle: undefined,
|
||||
// 防抖
|
||||
clicked: false
|
||||
|
||||
}
|
||||
},
|
||||
onClickTask (row) {
|
||||
if (!this.clicked) {
|
||||
this.$router.push(`/tasks/${row._id}`)
|
||||
computed: {
|
||||
...mapState('spider', [
|
||||
'spiderForm'
|
||||
]),
|
||||
...mapState('task', [
|
||||
'taskList'
|
||||
])
|
||||
},
|
||||
mounted() {
|
||||
this.handle = setInterval(() => {
|
||||
this.onRefresh()
|
||||
}, 5000)
|
||||
},
|
||||
destroyed() {
|
||||
clearInterval(this.handle)
|
||||
},
|
||||
methods: {
|
||||
onClickSpider(row) {
|
||||
this.clicked = true
|
||||
setTimeout(() => {
|
||||
this.clicked = false
|
||||
}, 100)
|
||||
this.$router.push(`/spiders/${row.spider_id}`)
|
||||
if (this.$route.path.match(/\/nodes\//)) {
|
||||
this.$st.sendEv('节点详情', '概览', '查看任务')
|
||||
} else if (this.$route.path.match(/\/spiders\//)) {
|
||||
this.$st.sendEv('爬虫详情', '概览', '查看任务')
|
||||
this.$st.sendEv('节点详情', '概览', '查看爬虫')
|
||||
}
|
||||
},
|
||||
onClickNode(row) {
|
||||
this.clicked = true
|
||||
setTimeout(() => {
|
||||
this.clicked = false
|
||||
}, 100)
|
||||
this.$router.push(`/nodes/${row.node_id}`)
|
||||
if (this.$route.path.match(/\/spiders\//)) {
|
||||
this.$st.sendEv('爬虫详情', '概览', '查看节点')
|
||||
}
|
||||
},
|
||||
onClickTask(row) {
|
||||
if (!this.clicked) {
|
||||
this.$router.push(`/tasks/${row._id}`)
|
||||
if (this.$route.path.match(/\/nodes\//)) {
|
||||
this.$st.sendEv('节点详情', '概览', '查看任务')
|
||||
} else if (this.$route.path.match(/\/spiders\//)) {
|
||||
this.$st.sendEv('爬虫详情', '概览', '查看任务')
|
||||
}
|
||||
}
|
||||
},
|
||||
onRefresh() {
|
||||
if (this.$route.path.split('/')[1] === 'spiders') {
|
||||
this.$store.dispatch('spider/getTaskList', this.$route.params.id)
|
||||
} else if (this.$route.path.split('/')[1] === 'nodes') {
|
||||
this.$store.dispatch('node/getTaskList', this.$route.params.id)
|
||||
}
|
||||
},
|
||||
getTime(str) {
|
||||
return dayjs(str)
|
||||
}
|
||||
},
|
||||
onRefresh () {
|
||||
if (this.$route.path.split('/')[1] === 'spiders') {
|
||||
this.$store.dispatch('spider/getTaskList', this.$route.params.id)
|
||||
} else if (this.$route.path.split('/')[1] === 'nodes') {
|
||||
this.$store.dispatch('node/getTaskList', this.$route.params.id)
|
||||
}
|
||||
},
|
||||
getTime (str) {
|
||||
return dayjs(str)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.handle = setInterval(() => {
|
||||
this.onRefresh()
|
||||
}, 5000)
|
||||
},
|
||||
destroyed () {
|
||||
clearInterval(this.handle)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -2,102 +2,120 @@
|
||||
<el-color-picker
|
||||
v-model="theme"
|
||||
class="theme-picker"
|
||||
popper-class="theme-picker-dropdown"/>
|
||||
popper-class="theme-picker-dropdown"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
const version = require('element-ui/package.json').version // element-ui version from node_modules
|
||||
const ORIGINAL_THEME = '#409EFF' // default color
|
||||
const version = require('element-ui/package.json').version // element-ui version from node_modules
|
||||
const ORIGINAL_THEME = '#409EFF' // default color
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chalk: '', // content of theme-chalk css
|
||||
theme: ORIGINAL_THEME
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
theme(val) {
|
||||
const oldVal = this.theme
|
||||
if (typeof val !== 'string') return
|
||||
const themeCluster = this.getThemeCluster(val.replace('#', ''))
|
||||
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
|
||||
console.log(themeCluster, originalCluster)
|
||||
const getHandler = (variable, id) => {
|
||||
return () => {
|
||||
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
|
||||
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chalk: '', // content of theme-chalk css
|
||||
theme: ORIGINAL_THEME
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
theme(val) {
|
||||
const oldVal = this.theme
|
||||
if (typeof val !== 'string') return
|
||||
const themeCluster = this.getThemeCluster(val.replace('#', ''))
|
||||
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
|
||||
console.log(themeCluster, originalCluster)
|
||||
const getHandler = (variable, id) => {
|
||||
return () => {
|
||||
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
|
||||
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
|
||||
|
||||
let styleTag = document.getElementById(id)
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style')
|
||||
styleTag.setAttribute('id', id)
|
||||
document.head.appendChild(styleTag)
|
||||
let styleTag = document.getElementById(id)
|
||||
if (!styleTag) {
|
||||
styleTag = document.createElement('style')
|
||||
styleTag.setAttribute('id', id)
|
||||
document.head.appendChild(styleTag)
|
||||
}
|
||||
styleTag.innerText = newStyle
|
||||
}
|
||||
styleTag.innerText = newStyle
|
||||
}
|
||||
}
|
||||
|
||||
const chalkHandler = getHandler('chalk', 'chalk-style')
|
||||
const chalkHandler = getHandler('chalk', 'chalk-style')
|
||||
|
||||
if (!this.chalk) {
|
||||
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
|
||||
this.getCSSString(url, chalkHandler, 'chalk')
|
||||
} else {
|
||||
chalkHandler()
|
||||
}
|
||||
|
||||
const styles = [].slice.call(document.querySelectorAll('style'))
|
||||
.filter(style => {
|
||||
const text = style.innerText
|
||||
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
|
||||
})
|
||||
styles.forEach(style => {
|
||||
const { innerText } = style
|
||||
if (typeof innerText !== 'string') return
|
||||
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
|
||||
})
|
||||
this.$message({
|
||||
message: '换肤成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateStyle(style, oldCluster, newCluster) {
|
||||
let newStyle = style
|
||||
oldCluster.forEach((color, index) => {
|
||||
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
|
||||
})
|
||||
return newStyle
|
||||
},
|
||||
|
||||
getCSSString(url, callback, variable) {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
|
||||
callback()
|
||||
}
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
},
|
||||
|
||||
getThemeCluster(theme) {
|
||||
const tintColor = (color, tint) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
if (tint === 0) { // when primary color is in its rgb space
|
||||
return [red, green, blue].join(',')
|
||||
if (!this.chalk) {
|
||||
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
|
||||
this.getCSSString(url, chalkHandler, 'chalk')
|
||||
} else {
|
||||
red += Math.round(tint * (255 - red))
|
||||
green += Math.round(tint * (255 - green))
|
||||
blue += Math.round(tint * (255 - blue))
|
||||
chalkHandler()
|
||||
}
|
||||
|
||||
const styles = [].slice.call(document.querySelectorAll('style'))
|
||||
.filter(style => {
|
||||
const text = style.innerText
|
||||
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
|
||||
})
|
||||
styles.forEach(style => {
|
||||
const { innerText } = style
|
||||
if (typeof innerText !== 'string') return
|
||||
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
|
||||
})
|
||||
this.$message({
|
||||
message: '换肤成功',
|
||||
type: 'success'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateStyle(style, oldCluster, newCluster) {
|
||||
let newStyle = style
|
||||
oldCluster.forEach((color, index) => {
|
||||
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
|
||||
})
|
||||
return newStyle
|
||||
},
|
||||
|
||||
getCSSString(url, callback, variable) {
|
||||
const xhr = new XMLHttpRequest()
|
||||
xhr.onreadystatechange = () => {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
|
||||
callback()
|
||||
}
|
||||
}
|
||||
xhr.open('GET', url)
|
||||
xhr.send()
|
||||
},
|
||||
|
||||
getThemeCluster(theme) {
|
||||
const tintColor = (color, tint) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
if (tint === 0) { // when primary color is in its rgb space
|
||||
return [red, green, blue].join(',')
|
||||
} else {
|
||||
red += Math.round(tint * (255 - red))
|
||||
green += Math.round(tint * (255 - green))
|
||||
blue += Math.round(tint * (255 - blue))
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
}
|
||||
|
||||
const shadeColor = (color, shade) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
red = Math.round((1 - shade) * red)
|
||||
green = Math.round((1 - shade) * green)
|
||||
blue = Math.round((1 - shade) * blue)
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
@@ -105,33 +123,16 @@ export default {
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
|
||||
const clusters = [theme]
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
|
||||
}
|
||||
clusters.push(shadeColor(theme, 0.1))
|
||||
return clusters
|
||||
}
|
||||
|
||||
const shadeColor = (color, shade) => {
|
||||
let red = parseInt(color.slice(0, 2), 16)
|
||||
let green = parseInt(color.slice(2, 4), 16)
|
||||
let blue = parseInt(color.slice(4, 6), 16)
|
||||
|
||||
red = Math.round((1 - shade) * red)
|
||||
green = Math.round((1 - shade) * green)
|
||||
blue = Math.round((1 - shade) * blue)
|
||||
|
||||
red = red.toString(16)
|
||||
green = green.toString(16)
|
||||
blue = blue.toString(16)
|
||||
|
||||
return `#${red}${green}${blue}`
|
||||
}
|
||||
|
||||
const clusters = [theme]
|
||||
for (let i = 0; i <= 9; i++) {
|
||||
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
|
||||
}
|
||||
clusters.push(shadeColor(theme, 0.1))
|
||||
return clusters
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
:before-upload="beforeUpload"
|
||||
class="editor-slide-upload"
|
||||
action="https://httpbin.org/post"
|
||||
list-type="picture-card">
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
@@ -24,73 +25,73 @@
|
||||
<script>
|
||||
// import { getToken } from 'api/qiniu'
|
||||
|
||||
export default {
|
||||
name: 'EditorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#1890ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
listObj: {},
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkAllSuccess() {
|
||||
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
|
||||
},
|
||||
handleSubmit() {
|
||||
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
|
||||
if (!this.checkAllSuccess()) {
|
||||
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
|
||||
return
|
||||
export default {
|
||||
name: 'EditorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#1890ff'
|
||||
}
|
||||
this.$emit('successCBK', arr)
|
||||
this.listObj = {}
|
||||
this.fileList = []
|
||||
this.dialogVisible = false
|
||||
},
|
||||
handleSuccess(response, file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
this.listObj[objKeyArr[i]].url = response.files.file
|
||||
this.listObj[objKeyArr[i]].hasSuccess = true
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
listObj: {},
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkAllSuccess() {
|
||||
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
|
||||
},
|
||||
handleSubmit() {
|
||||
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
|
||||
if (!this.checkAllSuccess()) {
|
||||
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
handleRemove(file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
delete this.listObj[objKeyArr[i]]
|
||||
return
|
||||
this.$emit('successCBK', arr)
|
||||
this.listObj = {}
|
||||
this.fileList = []
|
||||
this.dialogVisible = false
|
||||
},
|
||||
handleSuccess(response, file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
this.listObj[objKeyArr[i]].url = response.files.file
|
||||
this.listObj[objKeyArr[i]].hasSuccess = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const _self = this
|
||||
const _URL = window.URL || window.webkitURL
|
||||
const fileName = file.uid
|
||||
this.listObj[fileName] = {}
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = _URL.createObjectURL(file)
|
||||
img.onload = function() {
|
||||
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
|
||||
},
|
||||
handleRemove(file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
delete this.listObj[objKeyArr[i]]
|
||||
return
|
||||
}
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const _self = this
|
||||
const _URL = window.URL || window.webkitURL
|
||||
const fileName = file.uid
|
||||
this.listObj[fileName] = {}
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = _URL.createObjectURL(file)
|
||||
img.onload = function() {
|
||||
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
:before-upload="beforeUpload"
|
||||
class="editor-slide-upload"
|
||||
action="https://httpbin.org/post"
|
||||
list-type="picture-card">
|
||||
list-type="picture-card"
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
</el-upload>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
@@ -24,73 +25,73 @@
|
||||
<script>
|
||||
// import { getToken } from 'api/qiniu'
|
||||
|
||||
export default {
|
||||
name: 'EditorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#1890ff'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
listObj: {},
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkAllSuccess() {
|
||||
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
|
||||
},
|
||||
handleSubmit() {
|
||||
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
|
||||
if (!this.checkAllSuccess()) {
|
||||
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
|
||||
return
|
||||
export default {
|
||||
name: 'EditorSlideUpload',
|
||||
props: {
|
||||
color: {
|
||||
type: String,
|
||||
default: '#1890ff'
|
||||
}
|
||||
this.$emit('successCBK', arr)
|
||||
this.listObj = {}
|
||||
this.fileList = []
|
||||
this.dialogVisible = false
|
||||
},
|
||||
handleSuccess(response, file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
this.listObj[objKeyArr[i]].url = response.files.file
|
||||
this.listObj[objKeyArr[i]].hasSuccess = true
|
||||
data() {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
listObj: {},
|
||||
fileList: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkAllSuccess() {
|
||||
return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
|
||||
},
|
||||
handleSubmit() {
|
||||
const arr = Object.keys(this.listObj).map(v => this.listObj[v])
|
||||
if (!this.checkAllSuccess()) {
|
||||
this.$message('请等待所有图片上传成功 或 出现了网络问题,请刷新页面重新上传!')
|
||||
return
|
||||
}
|
||||
}
|
||||
},
|
||||
handleRemove(file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
delete this.listObj[objKeyArr[i]]
|
||||
return
|
||||
this.$emit('successCBK', arr)
|
||||
this.listObj = {}
|
||||
this.fileList = []
|
||||
this.dialogVisible = false
|
||||
},
|
||||
handleSuccess(response, file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
this.listObj[objKeyArr[i]].url = response.files.file
|
||||
this.listObj[objKeyArr[i]].hasSuccess = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const _self = this
|
||||
const _URL = window.URL || window.webkitURL
|
||||
const fileName = file.uid
|
||||
this.listObj[fileName] = {}
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = _URL.createObjectURL(file)
|
||||
img.onload = function() {
|
||||
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
|
||||
},
|
||||
handleRemove(file) {
|
||||
const uid = file.uid
|
||||
const objKeyArr = Object.keys(this.listObj)
|
||||
for (let i = 0, len = objKeyArr.length; i < len; i++) {
|
||||
if (this.listObj[objKeyArr[i]].uid === uid) {
|
||||
delete this.listObj[objKeyArr[i]]
|
||||
return
|
||||
}
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
},
|
||||
beforeUpload(file) {
|
||||
const _self = this
|
||||
const _URL = window.URL || window.webkitURL
|
||||
const fileName = file.uid
|
||||
this.listObj[fileName] = {}
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image()
|
||||
img.src = _URL.createObjectURL(file)
|
||||
img.onload = function() {
|
||||
_self.listObj[fileName] = { hasSuccess: false, uid: file.uid, width: this.width, height: this.height }
|
||||
}
|
||||
resolve(true)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
|
||||
@@ -1,126 +1,126 @@
|
||||
<template>
|
||||
<div :class="{fullscreen:fullscreen}" class="tinymce-container editor-container">
|
||||
<textarea :id="tinymceId" class="tinymce-textarea"/>
|
||||
<textarea :id="tinymceId" class="tinymce-textarea" />
|
||||
<div class="editor-custom-btn-container">
|
||||
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK"/>
|
||||
<editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import editorImage from './components/editorImage'
|
||||
import plugins from './plugins'
|
||||
import toolbar from './toolbar'
|
||||
import editorImage from './components/editorImage'
|
||||
import plugins from './plugins'
|
||||
import toolbar from './toolbar'
|
||||
|
||||
export default {
|
||||
name: 'Tinymce',
|
||||
components: { editorImage },
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
toolbar: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
menubar: {
|
||||
type: String,
|
||||
default: 'file edit insert view format table'
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 360
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasChange: false,
|
||||
hasInit: false,
|
||||
tinymceId: this.id,
|
||||
fullscreen: false,
|
||||
languageTypeList: {
|
||||
'en': 'en',
|
||||
'zh': 'zh_CN'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
language() {
|
||||
return this.languageTypeList[this.$store.getters.language]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() =>
|
||||
window.tinymce.get(this.tinymceId).setContent(val || ''))
|
||||
}
|
||||
},
|
||||
language() {
|
||||
this.destroyTinymce()
|
||||
this.$nextTick(() => this.initTinymce())
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initTinymce()
|
||||
},
|
||||
activated() {
|
||||
this.initTinymce()
|
||||
},
|
||||
deactivated() {
|
||||
this.destroyTinymce()
|
||||
},
|
||||
destroyed() {
|
||||
this.destroyTinymce()
|
||||
},
|
||||
methods: {
|
||||
initTinymce() {
|
||||
const _this = this
|
||||
window.tinymce.init({
|
||||
language: this.language,
|
||||
selector: `#${this.tinymceId}`,
|
||||
height: this.height,
|
||||
body_class: 'panel-body ',
|
||||
object_resizing: false,
|
||||
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
|
||||
menubar: this.menubar,
|
||||
plugins: plugins,
|
||||
end_container_on_empty_block: true,
|
||||
powerpaste_word_import: 'clean',
|
||||
code_dialog_height: 450,
|
||||
code_dialog_width: 1000,
|
||||
advlist_bullet_styles: 'square',
|
||||
advlist_number_styles: 'default',
|
||||
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
|
||||
default_link_target: '_blank',
|
||||
link_title: false,
|
||||
nonbreaking_force_tab: true, // inserting nonbreaking space need Nonbreaking Space Plugin
|
||||
init_instance_callback: editor => {
|
||||
if (_this.value) {
|
||||
editor.setContent(_this.value)
|
||||
}
|
||||
_this.hasInit = true
|
||||
editor.on('NodeChange Change KeyUp SetContent', () => {
|
||||
this.hasChange = true
|
||||
this.$emit('input', editor.getContent())
|
||||
})
|
||||
},
|
||||
setup(editor) {
|
||||
editor.on('FullscreenStateChanged', (e) => {
|
||||
_this.fullscreen = e.state
|
||||
})
|
||||
export default {
|
||||
name: 'Tinymce',
|
||||
components: { editorImage },
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: function() {
|
||||
return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
toolbar: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
menubar: {
|
||||
type: String,
|
||||
default: 'file edit insert view format table'
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 360
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasChange: false,
|
||||
hasInit: false,
|
||||
tinymceId: this.id,
|
||||
fullscreen: false,
|
||||
languageTypeList: {
|
||||
'en': 'en',
|
||||
'zh': 'zh_CN'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
language() {
|
||||
return this.languageTypeList[this.$store.getters.language]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
if (!this.hasChange && this.hasInit) {
|
||||
this.$nextTick(() =>
|
||||
window.tinymce.get(this.tinymceId).setContent(val || ''))
|
||||
}
|
||||
},
|
||||
language() {
|
||||
this.destroyTinymce()
|
||||
this.$nextTick(() => this.initTinymce())
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initTinymce()
|
||||
},
|
||||
activated() {
|
||||
this.initTinymce()
|
||||
},
|
||||
deactivated() {
|
||||
this.destroyTinymce()
|
||||
},
|
||||
destroyed() {
|
||||
this.destroyTinymce()
|
||||
},
|
||||
methods: {
|
||||
initTinymce() {
|
||||
const _this = this
|
||||
window.tinymce.init({
|
||||
language: this.language,
|
||||
selector: `#${this.tinymceId}`,
|
||||
height: this.height,
|
||||
body_class: 'panel-body ',
|
||||
object_resizing: false,
|
||||
toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,
|
||||
menubar: this.menubar,
|
||||
plugins: plugins,
|
||||
end_container_on_empty_block: true,
|
||||
powerpaste_word_import: 'clean',
|
||||
code_dialog_height: 450,
|
||||
code_dialog_width: 1000,
|
||||
advlist_bullet_styles: 'square',
|
||||
advlist_number_styles: 'default',
|
||||
imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
|
||||
default_link_target: '_blank',
|
||||
link_title: false,
|
||||
nonbreaking_force_tab: true, // inserting nonbreaking space need Nonbreaking Space Plugin
|
||||
init_instance_callback: editor => {
|
||||
if (_this.value) {
|
||||
editor.setContent(_this.value)
|
||||
}
|
||||
_this.hasInit = true
|
||||
editor.on('NodeChange Change KeyUp SetContent', () => {
|
||||
this.hasChange = true
|
||||
this.$emit('input', editor.getContent())
|
||||
})
|
||||
},
|
||||
setup(editor) {
|
||||
editor.on('FullscreenStateChanged', (e) => {
|
||||
_this.fullscreen = e.state
|
||||
})
|
||||
}
|
||||
// 整合七牛上传
|
||||
// images_dataimg_filter(img) {
|
||||
// setTimeout(() => {
|
||||
@@ -154,32 +154,32 @@ export default {
|
||||
// console.log(err);
|
||||
// });
|
||||
// },
|
||||
})
|
||||
},
|
||||
destroyTinymce() {
|
||||
const tinymce = window.tinymce.get(this.tinymceId)
|
||||
if (this.fullscreen) {
|
||||
tinymce.execCommand('mceFullScreen')
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyTinymce() {
|
||||
const tinymce = window.tinymce.get(this.tinymceId)
|
||||
if (this.fullscreen) {
|
||||
tinymce.execCommand('mceFullScreen')
|
||||
}
|
||||
|
||||
if (tinymce) {
|
||||
tinymce.destroy()
|
||||
if (tinymce) {
|
||||
tinymce.destroy()
|
||||
}
|
||||
},
|
||||
setContent(value) {
|
||||
window.tinymce.get(this.tinymceId).setContent(value)
|
||||
},
|
||||
getContent() {
|
||||
window.tinymce.get(this.tinymceId).getContent()
|
||||
},
|
||||
imageSuccessCBK(arr) {
|
||||
const _this = this
|
||||
arr.forEach(v => {
|
||||
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
|
||||
})
|
||||
}
|
||||
},
|
||||
setContent(value) {
|
||||
window.tinymce.get(this.tinymceId).setContent(value)
|
||||
},
|
||||
getContent() {
|
||||
window.tinymce.get(this.tinymceId).getContent()
|
||||
},
|
||||
imageSuccessCBK(arr) {
|
||||
const _this = this
|
||||
arr.forEach(v => {
|
||||
window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Here is a list of the toolbar
|
||||
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
|
||||
|
||||
const toolbar = ['searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample', 'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
|
||||
const toolbar = [
|
||||
'searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample',
|
||||
'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen']
|
||||
|
||||
export default toolbar
|
||||
|
||||
@@ -9,111 +9,111 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import XLSX from 'xlsx'
|
||||
import XLSX from 'xlsx'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
export default {
|
||||
props: {
|
||||
beforeUpload: Function, // eslint-disable-line
|
||||
onSuccess: Function// eslint-disable-line
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
excelData: {
|
||||
header: null,
|
||||
results: null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
generateData({ header, results }) {
|
||||
this.excelData.header = header
|
||||
this.excelData.results = results
|
||||
this.onSuccess && this.onSuccess(this.excelData)
|
||||
},
|
||||
handleDrop(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
const files = e.dataTransfer.files
|
||||
if (files.length !== 1) {
|
||||
this.$message.error('Only support uploading one file!')
|
||||
return
|
||||
}
|
||||
const rawFile = files[0] // only use files[0]
|
||||
|
||||
if (!this.isExcel(rawFile)) {
|
||||
this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
|
||||
return false
|
||||
}
|
||||
this.upload(rawFile)
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
handleDragover(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
},
|
||||
handleUpload() {
|
||||
this.$refs['excel-upload-input'].click()
|
||||
},
|
||||
handleClick(e) {
|
||||
const files = e.target.files
|
||||
const rawFile = files[0] // only use files[0]
|
||||
if (!rawFile) return
|
||||
this.upload(rawFile)
|
||||
},
|
||||
upload(rawFile) {
|
||||
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
|
||||
|
||||
if (!this.beforeUpload) {
|
||||
this.readerData(rawFile)
|
||||
return
|
||||
}
|
||||
const before = this.beforeUpload(rawFile)
|
||||
if (before) {
|
||||
this.readerData(rawFile)
|
||||
}
|
||||
},
|
||||
readerData(rawFile) {
|
||||
this.loading = true
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = e => {
|
||||
const data = e.target.result
|
||||
const workbook = XLSX.read(data, { type: 'array' })
|
||||
const firstSheetName = workbook.SheetNames[0]
|
||||
const worksheet = workbook.Sheets[firstSheetName]
|
||||
const header = this.getHeaderRow(worksheet)
|
||||
const results = XLSX.utils.sheet_to_json(worksheet)
|
||||
this.generateData({ header, results })
|
||||
this.loading = false
|
||||
resolve()
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
excelData: {
|
||||
header: null,
|
||||
results: null
|
||||
}
|
||||
reader.readAsArrayBuffer(rawFile)
|
||||
})
|
||||
},
|
||||
getHeaderRow(sheet) {
|
||||
const headers = []
|
||||
const range = XLSX.utils.decode_range(sheet['!ref'])
|
||||
let C
|
||||
const R = range.s.r
|
||||
/* start in the first row */
|
||||
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
|
||||
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
|
||||
/* find the cell in the first row */
|
||||
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
|
||||
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
|
||||
headers.push(hdr)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
isExcel(file) {
|
||||
return /\.(xlsx|xls|csv)$/.test(file.name)
|
||||
methods: {
|
||||
generateData({ header, results }) {
|
||||
this.excelData.header = header
|
||||
this.excelData.results = results
|
||||
this.onSuccess && this.onSuccess(this.excelData)
|
||||
},
|
||||
handleDrop(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
if (this.loading) return
|
||||
const files = e.dataTransfer.files
|
||||
if (files.length !== 1) {
|
||||
this.$message.error('Only support uploading one file!')
|
||||
return
|
||||
}
|
||||
const rawFile = files[0] // only use files[0]
|
||||
|
||||
if (!this.isExcel(rawFile)) {
|
||||
this.$message.error('Only supports upload .xlsx, .xls, .csv suffix files')
|
||||
return false
|
||||
}
|
||||
this.upload(rawFile)
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
},
|
||||
handleDragover(e) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
},
|
||||
handleUpload() {
|
||||
this.$refs['excel-upload-input'].click()
|
||||
},
|
||||
handleClick(e) {
|
||||
const files = e.target.files
|
||||
const rawFile = files[0] // only use files[0]
|
||||
if (!rawFile) return
|
||||
this.upload(rawFile)
|
||||
},
|
||||
upload(rawFile) {
|
||||
this.$refs['excel-upload-input'].value = null // fix can't select the same excel
|
||||
|
||||
if (!this.beforeUpload) {
|
||||
this.readerData(rawFile)
|
||||
return
|
||||
}
|
||||
const before = this.beforeUpload(rawFile)
|
||||
if (before) {
|
||||
this.readerData(rawFile)
|
||||
}
|
||||
},
|
||||
readerData(rawFile) {
|
||||
this.loading = true
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = e => {
|
||||
const data = e.target.result
|
||||
const workbook = XLSX.read(data, { type: 'array' })
|
||||
const firstSheetName = workbook.SheetNames[0]
|
||||
const worksheet = workbook.Sheets[firstSheetName]
|
||||
const header = this.getHeaderRow(worksheet)
|
||||
const results = XLSX.utils.sheet_to_json(worksheet)
|
||||
this.generateData({ header, results })
|
||||
this.loading = false
|
||||
resolve()
|
||||
}
|
||||
reader.readAsArrayBuffer(rawFile)
|
||||
})
|
||||
},
|
||||
getHeaderRow(sheet) {
|
||||
const headers = []
|
||||
const range = XLSX.utils.decode_range(sheet['!ref'])
|
||||
let C
|
||||
const R = range.s.r
|
||||
/* start in the first row */
|
||||
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
|
||||
const cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })]
|
||||
/* find the cell in the first row */
|
||||
let hdr = 'UNKNOWN ' + C // <-- replace with your desired default
|
||||
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
|
||||
headers.push(hdr)
|
||||
}
|
||||
return headers
|
||||
},
|
||||
isExcel(file) {
|
||||
return /\.(xlsx|xls|csv)$/.test(file.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user