mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-21 17:21:09 +01:00
Merge pull request #789 from yaziming/update_dependencies
Update dependencies
This commit is contained in:
@@ -3,4 +3,11 @@ logs
|
||||
*.log
|
||||
dist/
|
||||
**/node_modules/
|
||||
|
||||
**/tmp/
|
||||
**/vendor/
|
||||
.github/
|
||||
.devops
|
||||
k8s
|
||||
workspace
|
||||
.git/
|
||||
|
||||
22
Dockerfile
22
Dockerfile
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.13 AS backend-build
|
||||
FROM golang:latest AS backend-build
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
@@ -8,16 +8,16 @@ ENV GOPROXY https://goproxy.io
|
||||
|
||||
RUN go install -v ./...
|
||||
|
||||
FROM node:8.16.0-alpine AS frontend-build
|
||||
FROM node:latest AS frontend-build
|
||||
|
||||
ADD ./frontend /app
|
||||
WORKDIR /app
|
||||
|
||||
# install frontend
|
||||
RUN npm config set unsafe-perm true
|
||||
RUN npm install -g yarn && yarn install
|
||||
#RUN npm config set unsafe-perm true
|
||||
#RUN npm install -g yarn && yarn install
|
||||
|
||||
RUN npm run build:prod
|
||||
RUN yarn install && yarn run build:prod
|
||||
|
||||
# images
|
||||
FROM ubuntu:latest
|
||||
@@ -31,19 +31,21 @@ ENV CRAWLAB_IS_DOCKER Y
|
||||
# install packages
|
||||
RUN chmod 777 /tmp \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y curl git net-tools iputils-ping ntp ntpdate python3 python3-pip nginx wget \
|
||||
&& apt-get install -y curl git net-tools iputils-ping ntp ntpdate python3 python3-pip nginx wget dumb-init \
|
||||
&& ln -s /usr/bin/pip3 /usr/local/bin/pip \
|
||||
&& ln -s /usr/bin/python3 /usr/local/bin/python
|
||||
|
||||
# install dumb-init
|
||||
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
|
||||
RUN chmod +x /usr/local/bin/dumb-init
|
||||
|
||||
# install backend
|
||||
RUN pip install scrapy pymongo bs4 requests crawlab-sdk scrapy-splash
|
||||
|
||||
# add files
|
||||
ADD . /app
|
||||
COPY ./backend/conf /app/backend/conf
|
||||
COPY ./backend/data /app/backend/data
|
||||
COPY ./backend/scripts /app/backend/scripts
|
||||
COPY ./backend/template /app/backend/template
|
||||
COPY ./nginx /app/nginx
|
||||
COPY ./docker_init.sh /app/docker_init.sh
|
||||
|
||||
# copy backend files
|
||||
RUN mkdir -p /opt/bin
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
FROM golang:1.13 AS backend-build
|
||||
FROM golang:latest AS backend-build
|
||||
|
||||
WORKDIR /go/src/app
|
||||
COPY ./backend .
|
||||
|
||||
ENV GO111MODULE on
|
||||
ENV GOPROXY https://goproxy.io
|
||||
ENV GOPROXY https://goproxy.cn
|
||||
|
||||
RUN go install -v ./...
|
||||
|
||||
FROM node:8.16.0-alpine AS frontend-build
|
||||
FROM node:latest AS frontend-build
|
||||
|
||||
ADD ./frontend /app
|
||||
WORKDIR /app
|
||||
|
||||
# install frontend
|
||||
RUN npm config set unsafe-perm true
|
||||
RUN npm install -g cnpm --registry=https://registry.npm.taobao.org && cnpm install
|
||||
|
||||
RUN npm run build:prod
|
||||
RUN yarn config set registry https://registry.npm.taobao.org && \
|
||||
yarn install && yarn run build:prod
|
||||
|
||||
# images
|
||||
FROM ubuntu:latest
|
||||
@@ -28,22 +26,27 @@ ENV DEBIAN_FRONTEND noninteractive
|
||||
# set CRAWLAB_IS_DOCKER
|
||||
ENV CRAWLAB_IS_DOCKER Y
|
||||
|
||||
|
||||
|
||||
# install packages
|
||||
RUN chmod 777 /tmp \
|
||||
&& sed -i 's/archive.ubuntu.com/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y curl git net-tools iputils-ping ntp ntpdate python3 python3-pip nginx wget \
|
||||
&& apt-get install -y curl git net-tools iputils-ping ntp ntpdate python3 python3-pip nginx wget dumb-init\
|
||||
&& ln -s /usr/bin/pip3 /usr/local/bin/pip \
|
||||
&& ln -s /usr/bin/python3 /usr/local/bin/python
|
||||
|
||||
# install dumb-init
|
||||
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
|
||||
RUN chmod +x /usr/local/bin/dumb-init
|
||||
|
||||
# install backend
|
||||
RUN pip install scrapy pymongo bs4 requests crawlab-sdk scrapy-splash -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
|
||||
# add files
|
||||
ADD . /app
|
||||
COPY ./backend/conf /app/backend/conf
|
||||
COPY ./backend/data /app/backend/data
|
||||
COPY ./backend/scripts /app/backend/scripts
|
||||
COPY ./backend/template /app/backend/template
|
||||
COPY ./nginx /app/nginx
|
||||
COPY ./docker_init.sh /app/docker_init.sh
|
||||
|
||||
# copy backend files
|
||||
RUN mkdir -p /opt/bin
|
||||
|
||||
47
backend/.air.conf
Normal file
47
backend/.air.conf
Normal file
@@ -0,0 +1,47 @@
|
||||
# Config file for [Air](https://github.com/cosmtrek/air) in TOML format
|
||||
|
||||
# Working directory
|
||||
# . or absolute path, please note that the directories following must be under root.
|
||||
root = "."
|
||||
tmp_dir = "/tmp"
|
||||
|
||||
[build]
|
||||
# Just plain old shell command. You could use `make` as well.
|
||||
cmd = "go build -o ../tmp/main ./ "
|
||||
# Binary file yields from `cmd`.
|
||||
bin = "../tmp/main"
|
||||
# Customize binary.
|
||||
full_bin = "../tmp/main start"
|
||||
# Watch these filename extensions.
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
# Ignore these filename extensions or directories.
|
||||
exclude_dir = ["assets", "tmp", "vendor", "frontend/node_modules"]
|
||||
# Watch these directories if you specified.
|
||||
include_dir = []
|
||||
# Exclude files.
|
||||
exclude_file = []
|
||||
# This log file places in your tmp_dir.
|
||||
log = "air.log"
|
||||
# It's not necessary to trigger build each time file changes if it's too frequent.
|
||||
delay = 1000 # ms
|
||||
# Stop running old binary when build errors occur.
|
||||
stop_on_error = true
|
||||
# Send Interrupt signal before killing process (windows does not support this feature)
|
||||
send_interrupt = false
|
||||
# Delay after sending Interrupt signal
|
||||
kill_delay = 500 # ms
|
||||
|
||||
[log]
|
||||
# Show log time
|
||||
time = false
|
||||
|
||||
[color]
|
||||
# Customize each part's color. If no color found, use the raw app log.
|
||||
main = "magenta"
|
||||
watcher = "cyan"
|
||||
build = "yellow"
|
||||
runner = "green"
|
||||
|
||||
[misc]
|
||||
# Delete tmp directory on exit
|
||||
clean_on_exit = true
|
||||
85
docker-compose.local.yml
Normal file
85
docker-compose.local.yml
Normal file
@@ -0,0 +1,85 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
master:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile.local
|
||||
container_name: local_master
|
||||
environment:
|
||||
# CRAWLAB_API_ADDRESS: "https://<your_api_ip>:<your_api_port>" # backend API address 后端 API 地址. 适用于 https 或者源码部署
|
||||
CRAWLAB_SERVER_MASTER: "Y" # whether to be master node 是否为主节点,主节点为 Y,工作节点为 N
|
||||
CRAWLAB_MONGO_HOST: "mongo" # MongoDB host address MongoDB 的地址,在 docker compose 网络中,直接引用服务名称
|
||||
# CRAWLAB_MONGO_PORT: "27017" # MongoDB port MongoDB 的端口
|
||||
# CRAWLAB_MONGO_DB: "crawlab_test" # MongoDB database MongoDB 的数据库
|
||||
# CRAWLAB_MONGO_USERNAME: "username" # MongoDB username MongoDB 的用户名
|
||||
# CRAWLAB_MONGO_PASSWORD: "password" # MongoDB password MongoDB 的密码
|
||||
# CRAWLAB_MONGO_AUTHSOURCE: "admin" # MongoDB auth source MongoDB 的验证源
|
||||
CRAWLAB_REDIS_ADDRESS: "redis" # Redis host address Redis 的地址,在 docker compose 网络中,直接引用服务名称
|
||||
# CRAWLAB_REDIS_PORT: "6379" # Redis port Redis 的端口
|
||||
# CRAWLAB_REDIS_DATABASE: "1" # Redis database Redis 的数据库
|
||||
# CRAWLAB_REDIS_PASSWORD: "password" # Redis password Redis 的密码
|
||||
# CRAWLAB_LOG_LEVEL: "info" # log level 日志级别. 默认为 info
|
||||
# CRAWLAB_LOG_ISDELETEPERIODICALLY: "N" # whether to periodically delete log files 是否周期性删除日志文件. 默认不删除
|
||||
# CRAWLAB_LOG_DELETEFREQUENCY: "@hourly" # frequency of deleting log files 删除日志文件的频率. 默认为每小时
|
||||
# CRAWLAB_TASK_WORKERS: 8 # number of task executors 任务执行器个数(并行执行任务数)
|
||||
# CRAWLAB_SERVER_REGISTER_TYPE: "mac" # node register type 节点注册方式. 默认为 mac 地址,也可设置为 ip(防止 mac 地址冲突)
|
||||
# CRAWLAB_SERVER_REGISTER_IP: "127.0.0.1" # node register ip 节点注册IP. 节点唯一识别号,只有当 CRAWLAB_SERVER_REGISTER_TYPE 为 "ip" 时才生效
|
||||
# CRAWLAB_SERVER_LANG_NODE: "Y" # whether to pre-install Node.js 预安装 Node.js 语言环境
|
||||
# CRAWLAB_SERVER_LANG_JAVA: "Y" # whether to pre-install Java 预安装 Java 语言环境
|
||||
# CRAWLAB_SERVER_LANG_DOTNET: "Y" # whether to pre-install .Net core 预安装 .Net Core 语言环境
|
||||
# CRAWLAB_SERVER_LANG_PHP: "Y" # whether to pre-install PHP 预安装 PHP 语言环境
|
||||
# CRAWLAB_SETTING_ALLOWREGISTER: "N" # whether to allow user registration 是否允许用户注册
|
||||
# CRAWLAB_SETTING_ENABLETUTORIAL: "N" # whether to enable tutorial 是否启用教程
|
||||
# CRAWLAB_SETTING_RUNONMASTER: "N" # whether to run on master node 是否在主节点上运行任务
|
||||
# CRAWLAB_SETTING_DEMOSPIDERS: "Y" # whether to init demo spiders 是否使用Demo爬虫
|
||||
# CRAWLAB_SETTING_CHECKSCRAPY: "Y" # whether to automatically check if the spider is scrapy 是否自动检测爬虫为scrapy
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SERVER: smtp.exmaple.com # STMP server address STMP 服务器地址
|
||||
# CRAWLAB_NOTIFICATION_MAIL_PORT: 465 # STMP server port STMP 服务器端口
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SENDEREMAIL: admin@exmaple.com # sender email 发送者邮箱
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SENDERIDENTITY: admin@exmaple.com # sender ID 发送者 ID
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SMTP_USER: username # SMTP username SMTP 用户名
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SMTP_PASSWORD: password # SMTP password SMTP 密码
|
||||
ports:
|
||||
- "8080:8080" # frontend port mapping 前端端口映射
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
# volumes:
|
||||
# - "/var/crawlab/log:/var/logs/crawlab" # log persistent 日志持久化
|
||||
worker:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: Dockerfile.local
|
||||
container_name: local_master
|
||||
environment:
|
||||
CRAWLAB_SERVER_MASTER: "N"
|
||||
CRAWLAB_MONGO_HOST: "mongo"
|
||||
CRAWLAB_REDIS_ADDRESS: "redis"
|
||||
depends_on:
|
||||
- mongo
|
||||
- redis
|
||||
# environment:
|
||||
# MONGO_INITDB_ROOT_USERNAME: username
|
||||
# MONGO_INITDB_ROOT_PASSWORD: password
|
||||
# volumes:
|
||||
# - "/var/crawlab/log:/var/logs/crawlab" # log persistent 日志持久化
|
||||
mongo:
|
||||
image: mongo:latest
|
||||
restart: always
|
||||
# volumes:
|
||||
# - "/opt/crawlab/mongo/data/db:/data/db" # make data persistent 持久化
|
||||
# ports:
|
||||
# - "27017:27017" # expose port to host machine 暴露接口到宿主机
|
||||
redis:
|
||||
image: redis:latest
|
||||
restart: always
|
||||
# command: redis-server --requirepass "password" # set redis password 设置 Redis 密码
|
||||
# volumes:
|
||||
# - "/opt/crawlab/redis/data:/data" # make data persistent 持久化
|
||||
# ports:
|
||||
# - "6379:6379" # expose port to host machine 暴露接口到宿主机
|
||||
# splash: # use Splash to run spiders on dynamic pages
|
||||
# image: scrapinghub/splash
|
||||
# container_name: splash
|
||||
# ports:
|
||||
# - "8050:8050"
|
||||
@@ -1,6 +1,6 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
master:
|
||||
master:
|
||||
image: tikazyq/crawlab:latest
|
||||
container_name: master
|
||||
environment:
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SENDERIDENTITY: admin@exmaple.com # sender ID 发送者 ID
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SMTP_USER: username # SMTP username SMTP 用户名
|
||||
# CRAWLAB_NOTIFICATION_MAIL_SMTP_PASSWORD: password # SMTP password SMTP 密码
|
||||
ports:
|
||||
ports:
|
||||
- "8080:8080" # frontend port mapping 前端端口映射
|
||||
depends_on:
|
||||
- mongo
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
@@ -1,20 +1,254 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard'
|
||||
extends: ['plugin:vue/recommended', 'eslint:recommended'],
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'*.vue'
|
||||
],
|
||||
rules: {
|
||||
indent: 'off'
|
||||
}
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.{j,t}s?(x)',
|
||||
'**/tests/unit/**/*.spec.{j,t}s?(x)'
|
||||
],
|
||||
env: {
|
||||
jest: true
|
||||
}
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
|
||||
'vue/max-attributes-per-line': [
|
||||
2, {
|
||||
'singleline': 10,
|
||||
'multiline': {
|
||||
'max': 1,
|
||||
'allowFirstLine': false
|
||||
}
|
||||
}],
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/multiline-html-element-content-newline': 'off',
|
||||
'vue/name-property-casing': ['error', 'PascalCase'],
|
||||
'vue/no-v-html': 'off',
|
||||
'vue/no-template-shadow': 'off',
|
||||
'vue/this-in-template': 'off',
|
||||
'vue/script-indent': [
|
||||
'error', 2, {
|
||||
'baseIndent': 1,
|
||||
'switchCase': 0,
|
||||
'ignores': []
|
||||
}],
|
||||
'accessor-pairs': 2,
|
||||
'arrow-spacing': [
|
||||
2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'block-spacing': [2, 'always'],
|
||||
'brace-style': [
|
||||
2, '1tbs', {
|
||||
'allowSingleLine': true
|
||||
}],
|
||||
'camelcase': [
|
||||
0, {
|
||||
'properties': 'always'
|
||||
}],
|
||||
'comma-dangle': [2, 'never'],
|
||||
'comma-spacing': [
|
||||
2, {
|
||||
'before': false,
|
||||
'after': true
|
||||
}],
|
||||
'comma-style': [2, 'last'],
|
||||
'constructor-super': 2,
|
||||
'curly': [2, 'multi-line'],
|
||||
'dot-location': [2, 'property'],
|
||||
'eol-last': 2,
|
||||
'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
|
||||
'generator-star-spacing': [
|
||||
2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'handle-callback-err': [2, '^(err|error)$'],
|
||||
'indent': [
|
||||
2, 2, {
|
||||
'SwitchCase': 1
|
||||
}],
|
||||
'jsx-quotes': [2, 'prefer-single'],
|
||||
'key-spacing': [
|
||||
2, {
|
||||
'beforeColon': false,
|
||||
'afterColon': true
|
||||
}],
|
||||
'keyword-spacing': [
|
||||
2, {
|
||||
'before': true,
|
||||
'after': true
|
||||
}],
|
||||
'new-cap': [
|
||||
2, {
|
||||
'newIsCap': true,
|
||||
'capIsNew': false
|
||||
}],
|
||||
'new-parens': 2,
|
||||
'no-array-constructor': 2,
|
||||
'no-caller': 2,
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
'no-class-assign': 2,
|
||||
'no-cond-assign': 2,
|
||||
'no-const-assign': 2,
|
||||
'no-control-regex': 0,
|
||||
'no-delete-var': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-class-members': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-empty-character-class': 2,
|
||||
'no-empty-pattern': 2,
|
||||
'no-eval': 2,
|
||||
'no-ex-assign': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-parens': [2, 'functions'],
|
||||
'no-fallthrough': 2,
|
||||
'no-floating-decimal': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-implied-eval': 2,
|
||||
'no-inner-declarations': [2, 'functions'],
|
||||
'no-invalid-regexp': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-iterator': 2,
|
||||
'no-label-var': 2,
|
||||
'no-labels': [
|
||||
2, {
|
||||
'allowLoop': false,
|
||||
'allowSwitch': false
|
||||
}],
|
||||
'no-lone-blocks': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multi-spaces': 2,
|
||||
'no-multi-str': 2,
|
||||
'no-multiple-empty-lines': [
|
||||
2, {
|
||||
'max': 1
|
||||
}],
|
||||
'no-native-reassign': 2,
|
||||
'no-negated-in-lhs': 2,
|
||||
'no-new-object': 2,
|
||||
'no-new-require': 2,
|
||||
'no-new-symbol': 2,
|
||||
'no-new-wrappers': 2,
|
||||
'no-obj-calls': 2,
|
||||
'no-octal': 2,
|
||||
'no-octal-escape': 2,
|
||||
'no-path-concat': 2,
|
||||
'no-proto': 2,
|
||||
'no-redeclare': 2,
|
||||
'no-regex-spaces': 2,
|
||||
'no-return-assign': [2, 'except-parens'],
|
||||
'no-self-assign': 2,
|
||||
'no-self-compare': 2,
|
||||
'no-sequences': 2,
|
||||
'no-shadow-restricted-names': 2,
|
||||
'no-spaced-func': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-this-before-super': 2,
|
||||
'no-throw-literal': 2,
|
||||
'no-trailing-spaces': 2,
|
||||
'no-undef': 2,
|
||||
'no-undef-init': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unmodified-loop-condition': 2,
|
||||
'no-unneeded-ternary': [
|
||||
2, {
|
||||
'defaultAssignment': false
|
||||
}],
|
||||
'no-unreachable': 2,
|
||||
'no-unsafe-finally': 2,
|
||||
'no-unused-vars': [
|
||||
2, {
|
||||
'vars': 'all',
|
||||
'args': 'none'
|
||||
}],
|
||||
'no-useless-call': 2,
|
||||
'no-useless-computed-key': 2,
|
||||
'no-useless-constructor': 2,
|
||||
'no-useless-escape': 0,
|
||||
'no-whitespace-before-property': 2,
|
||||
'no-with': 2,
|
||||
'one-var': [
|
||||
2, {
|
||||
'initialized': 'never'
|
||||
}],
|
||||
'operator-linebreak': [
|
||||
2, 'after', {
|
||||
'overrides': {
|
||||
'?': 'before',
|
||||
':': 'before'
|
||||
}
|
||||
}],
|
||||
'padded-blocks': [2, 'never'],
|
||||
'quotes': [
|
||||
2, 'single', {
|
||||
'avoidEscape': true,
|
||||
'allowTemplateLiterals': true
|
||||
}],
|
||||
'semi': [2, 'never'],
|
||||
'semi-spacing': [
|
||||
2, {
|
||||
'before': false,
|
||||
'after': true
|
||||
}],
|
||||
'space-before-blocks': [2, 'always'],
|
||||
'space-before-function-paren': [2, 'never'],
|
||||
'space-in-parens': [2, 'never'],
|
||||
'space-infix-ops': 2,
|
||||
'space-unary-ops': [
|
||||
2, {
|
||||
'words': true,
|
||||
'nonwords': false
|
||||
}],
|
||||
'spaced-comment': [
|
||||
2, 'always', {
|
||||
'markers': [
|
||||
'global',
|
||||
'globals',
|
||||
'eslint',
|
||||
'eslint-disable',
|
||||
'*package',
|
||||
'!',
|
||||
',']
|
||||
}],
|
||||
'template-curly-spacing': [2, 'never'],
|
||||
'use-isnan': 2,
|
||||
'valid-typeof': 2,
|
||||
'wrap-iife': [2, 'any'],
|
||||
'yield-star-spacing': [2, 'both'],
|
||||
'yoda': [2, 'never'],
|
||||
'prefer-const': 2,
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
|
||||
'object-curly-spacing': [
|
||||
2, 'always', {
|
||||
objectsInObjects: false
|
||||
}],
|
||||
'array-bracket-spacing': [2, 'never']
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
parser: 'babel-eslint',
|
||||
sourceType: 'module'
|
||||
},
|
||||
globals: {
|
||||
'_hmt': 1
|
||||
'_hmt': 'readonly'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,19 +5,25 @@ module.exports = {
|
||||
'json',
|
||||
'vue'
|
||||
],
|
||||
|
||||
transform: {
|
||||
'^.+\\.vue$': 'vue-jest',
|
||||
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
|
||||
'^.+\\.jsx?$': 'babel-jest'
|
||||
},
|
||||
|
||||
moduleNameMapper: {
|
||||
'^@/(.*)$': '<rootDir>/src/$1'
|
||||
},
|
||||
|
||||
snapshotSerializers: [
|
||||
'jest-serializer-vue'
|
||||
],
|
||||
|
||||
testMatch: [
|
||||
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
|
||||
],
|
||||
testURL: 'http://localhost/'
|
||||
|
||||
testURL: 'http://localhost/',
|
||||
preset: '@vue/cli-plugin-unit-jest'
|
||||
}
|
||||
|
||||
9
frontend/jsconfig.json
Normal file
9
frontend/jsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -4,60 +4,64 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --ip=0.0.0.0 --mode=development",
|
||||
"serve:prod": "vue-cli-service serve --mode=production --ip=0.0.0.0",
|
||||
"config": "vue ui",
|
||||
"test:unit": "vue-cli-service test:unit",
|
||||
"lint": "vue-cli-service lint",
|
||||
"build:dev": "vue-cli-service build --mode development",
|
||||
"build:prod": "vue-cli-service build --mode production",
|
||||
"lint": "vue-cli-service lint",
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
"config": "vue ui",
|
||||
"serve:prod": "vue-cli-service serve --mode=production --ip=0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.19",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.9.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.9.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.9.0",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.6",
|
||||
"@tinymce/tinymce-vue": "^2.0.0",
|
||||
"ansi-to-html": "^0.6.13",
|
||||
"axios": "0.18.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.13.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.13.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/vue-fontawesome": "^0.1.9",
|
||||
"@tinymce/tinymce-vue": "^3.2.2",
|
||||
"ansi-to-html": "^0.6.14",
|
||||
"axios": "^0.19.2",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"dayjs": "^1.8.6",
|
||||
"echarts": "^4.1.0",
|
||||
"element-ui": "2.13.0",
|
||||
"core-js": "^3.6.5",
|
||||
"cross-env": "^7.0.2",
|
||||
"dayjs": "^1.8.28",
|
||||
"echarts": "^4.8.0",
|
||||
"element-ui": "^2.13.2",
|
||||
"font-awesome": "^4.7.0",
|
||||
"github-markdown-css": "^3.0.1",
|
||||
"js-cookie": "2.2.0",
|
||||
"normalize.css": "7.0.0",
|
||||
"nprogress": "0.2.0",
|
||||
"github-markdown-css": "^4.0.0",
|
||||
"js-cookie": "^2.2.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"npm": "^6.14.5",
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"showdown": "^1.9.1",
|
||||
"vcrontab": "^0.3.3",
|
||||
"vue": "^2.5.22",
|
||||
"vue-ba": "^1.2.5",
|
||||
"vcrontab": "^0.3.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-ba": "^1.2.8",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-codemirror-lite": "^1.0.4",
|
||||
"vue-github-button": "^1.1.2",
|
||||
"vue-i18n": "^8.9.0",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-tour": "^1.2.0",
|
||||
"vue-virtual-scroll-list": "^1.3.9",
|
||||
"vuex": "^3.0.1"
|
||||
"vue-github-button": "^1.2.0",
|
||||
"vue-i18n": "^8.18.1",
|
||||
"vue-router": "^3.3.2",
|
||||
"vue-tour": "^1.4.0",
|
||||
"vue-virtual-scroll-list": "^2.2.6",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.4.0",
|
||||
"@vue/cli-plugin-eslint": "^3.4.0",
|
||||
"@vue/cli-plugin-unit-jest": "^3.4.0",
|
||||
"@vue/cli-service": "^3.4.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.20",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"node-sass": "^4.9.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"vue-template-compiler": "^2.5.21"
|
||||
"@babel/core": "^7.10.2",
|
||||
"@babel/register": "^7.10.1",
|
||||
"@vue/cli-plugin-babel": "~4.4.0",
|
||||
"@vue/cli-plugin-eslint": "^4.4.1",
|
||||
"@vue/cli-plugin-unit-jest": "~4.4.0",
|
||||
"@vue/cli-service": "^4.4.1",
|
||||
"@vue/test-utils": "^1.0.3",
|
||||
"autoprefixer": "^9.5.1",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.0.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +1,67 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<dialog-view/>
|
||||
<router-view/>
|
||||
<dialog-view />
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import DialogView from './components/Common/DialogView'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import DialogView from './components/Common/DialogView'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
data () {
|
||||
return {
|
||||
msgPopup: undefined
|
||||
}
|
||||
},
|
||||
components: {
|
||||
DialogView
|
||||
},
|
||||
computed: {
|
||||
...mapState('setting', ['setting']),
|
||||
useStats () {
|
||||
return localStorage.getItem('useStats')
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
DialogView
|
||||
},
|
||||
uid () {
|
||||
return localStorage.getItem('uid')
|
||||
data() {
|
||||
return {
|
||||
msgPopup: undefined
|
||||
}
|
||||
},
|
||||
sid () {
|
||||
return sessionStorage.getItem('sid')
|
||||
}
|
||||
},
|
||||
methods: {},
|
||||
async mounted () {
|
||||
// set uid if first visit
|
||||
if (this.uid === undefined || this.uid === null) {
|
||||
localStorage.setItem('uid', this.$utils.encrypt.UUID())
|
||||
}
|
||||
computed: {
|
||||
...mapState('setting', ['setting']),
|
||||
useStats() {
|
||||
return localStorage.getItem('useStats')
|
||||
},
|
||||
uid() {
|
||||
return localStorage.getItem('uid')
|
||||
},
|
||||
sid() {
|
||||
return sessionStorage.getItem('sid')
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
// set uid if first visit
|
||||
if (this.uid === undefined || this.uid === null) {
|
||||
localStorage.setItem('uid', this.$utils.encrypt.UUID())
|
||||
}
|
||||
|
||||
// set session id if starting a session
|
||||
if (this.sid === undefined || this.sid === null) {
|
||||
sessionStorage.setItem('sid', this.$utils.encrypt.UUID())
|
||||
}
|
||||
// set session id if starting a session
|
||||
if (this.sid === undefined || this.sid === null) {
|
||||
sessionStorage.setItem('sid', this.$utils.encrypt.UUID())
|
||||
}
|
||||
|
||||
// get latest version
|
||||
await this.$store.dispatch('version/getLatestRelease')
|
||||
// get latest version
|
||||
await this.$store.dispatch('version/getLatestRelease')
|
||||
if (getToken()) {
|
||||
// get user info
|
||||
await this.$store.dispatch('user/getInfo')
|
||||
// remove loading-placeholder
|
||||
const elLoading = document.querySelector('#loading-placeholder')
|
||||
elLoading.remove()
|
||||
|
||||
// get user info
|
||||
await this.$store.dispatch('user/getInfo')
|
||||
|
||||
// remove loading-placeholder
|
||||
const elLoading = document.querySelector('#loading-placeholder')
|
||||
elLoading.remove()
|
||||
|
||||
// send visit event
|
||||
await this.$request.put('/actions', {
|
||||
type: 'visit'
|
||||
})
|
||||
// send visit event
|
||||
await this.$request.put('/actions', {
|
||||
type: 'visit'
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,72 +1,27 @@
|
||||
import axios from 'axios'
|
||||
import router from '../router'
|
||||
import { Message } from 'element-ui'
|
||||
import service from '@/utils/request'
|
||||
|
||||
// 根据 VUE_APP_BASE_URL 生成 baseUrl
|
||||
let baseUrl = process.env.VUE_APP_BASE_URL ? process.env.VUE_APP_BASE_URL : 'http://localhost:8000'
|
||||
if (!baseUrl.match(/^https?/i)) {
|
||||
baseUrl = `${window.location.protocol}//${window.location.host}${process.env.VUE_APP_BASE_URL}`
|
||||
}
|
||||
|
||||
// 如果 Docker 中设置了 CRAWLAB_API_ADDRESS 这个环境变量,则会将 baseUrl 覆盖
|
||||
const CRAWLAB_API_ADDRESS = '###CRAWLAB_API_ADDRESS###'
|
||||
if (!CRAWLAB_API_ADDRESS.match('CRAWLAB_API_ADDRESS')) {
|
||||
baseUrl = CRAWLAB_API_ADDRESS
|
||||
}
|
||||
|
||||
const request = (method, path, params, data, others = {}) => {
|
||||
const url = baseUrl + path
|
||||
const headers = {
|
||||
'Authorization': window.localStorage.getItem('token')
|
||||
}
|
||||
return axios({
|
||||
method,
|
||||
url,
|
||||
params,
|
||||
data,
|
||||
headers,
|
||||
...others
|
||||
}).then((response) => {
|
||||
if (response.status === 200) {
|
||||
return Promise.resolve(response)
|
||||
}
|
||||
return Promise.reject(response)
|
||||
}).catch((e) => {
|
||||
let response = e.response
|
||||
if (!response) {
|
||||
return e
|
||||
}
|
||||
if (response.status === 400) {
|
||||
Message.error(response.data.error)
|
||||
}
|
||||
if (response.status === 401 && router.currentRoute.path !== '/login') {
|
||||
router.push('/login')
|
||||
}
|
||||
if (response.status === 500) {
|
||||
Message.error(response.data.error)
|
||||
}
|
||||
return response
|
||||
const get = (path, params) => {
|
||||
return service.get(path, {
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
const get = (path, params) => {
|
||||
return request('GET', path, params)
|
||||
}
|
||||
|
||||
const post = (path, data) => {
|
||||
return request('POST', path, {}, data)
|
||||
return service.post(path, data)
|
||||
}
|
||||
|
||||
const put = (path, data) => {
|
||||
return request('PUT', path, {}, data)
|
||||
return service.put(path, data)
|
||||
}
|
||||
|
||||
const del = (path, data) => {
|
||||
return request('DELETE', path, {}, data)
|
||||
return service.delete(path, {
|
||||
params: data
|
||||
})
|
||||
}
|
||||
const request = service.request
|
||||
|
||||
export default {
|
||||
baseUrl,
|
||||
request,
|
||||
get,
|
||||
post,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="150" cy="150" r="150" fill="#409eff">
|
||||
</circle>
|
||||
<circle cx="150" cy="150" r="110" fill="#fff">
|
||||
</circle>
|
||||
<circle cx="150" cy="150" r="70" fill="#409eff">
|
||||
</circle>
|
||||
<path d="
|
||||
<circle cx="150" cy="150" r="150" fill="#409eff">
|
||||
</circle>
|
||||
<circle cx="150" cy="150" r="110" fill="#fff">
|
||||
</circle>
|
||||
<circle cx="150" cy="150" r="70" fill="#409eff">
|
||||
</circle>
|
||||
<path d="
|
||||
M 150,150
|
||||
L 280,225
|
||||
A 150,150 90 0 0 280,75
|
||||
" fill="#409eff">
|
||||
</path>
|
||||
</path>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 377 B |
@@ -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>
|
||||
|
||||
@@ -6,5 +6,11 @@ You cannot add nodes directly on the web interface in Crawlab.
|
||||
Adding a node is quite simple. The only thing you have to do is to run a Crawlab service on your target machine.
|
||||
|
||||
For details, please refer to the [Multi-Node Deployment Documentation](https://docs.crawlab.cn/Installation/MultiNode.html).
|
||||
`
|
||||
`,
|
||||
auth: {
|
||||
login_expired_message: 'You have been logged out, you can cancel to stay on this page, or log in again',
|
||||
login_expired_title: 'Confirm logout',
|
||||
login_expired_confirm: 'Confirm',
|
||||
login_expired_cancel: 'Cancel'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ export default {
|
||||
Running: '进行中',
|
||||
Finished: '已完成',
|
||||
Error: '错误',
|
||||
Errors: '错误',
|
||||
NA: '未知',
|
||||
Cancelled: '已取消',
|
||||
Abnormal: '异常',
|
||||
@@ -417,8 +416,6 @@ export default {
|
||||
'Disclaimer': '免责声明',
|
||||
'Please search dependencies': '请搜索依赖',
|
||||
'No Data': '暂无数据',
|
||||
'No data available': '暂无数据',
|
||||
'No data available. Please check whether your spiders are missing dependencies or no spiders created.': '暂无数据。请检查您的爬虫是否缺少依赖,或者没有创建爬虫。',
|
||||
'Show installed': '查看已安装',
|
||||
'Installing dependency successful': '安装依赖成功',
|
||||
'Installing dependency failed': '安装依赖失败',
|
||||
@@ -541,7 +538,12 @@ export default {
|
||||
// Cron Format: [second] [minute] [hour] [day of month] [month] [day of week]
|
||||
cron_format: 'Cron 格式: [秒] [分] [小时] [日] [月] [周]'
|
||||
},
|
||||
|
||||
auth: {
|
||||
login_expired_message: '您已注销,可以取消以保留在该页面上,或者再次登录',
|
||||
login_expired_title: '确认登出',
|
||||
login_expired_confirm: '确认',
|
||||
login_expired_cancel: '取消'
|
||||
},
|
||||
// 内容
|
||||
addNodeInstruction: `
|
||||
您不能在 Crawlab 的 Web 界面直接添加节点。
|
||||
|
||||
@@ -13,7 +13,11 @@ import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons'
|
||||
import { fas } from '@fortawesome/free-solid-svg-icons'
|
||||
import { far } from '@fortawesome/free-regular-svg-icons'
|
||||
import { FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText } from '@fortawesome/vue-fontawesome'
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeLayers,
|
||||
FontAwesomeLayersText
|
||||
} from '@fortawesome/vue-fontawesome'
|
||||
|
||||
import 'codemirror/lib/codemirror.js'
|
||||
import { codemirror } from 'vue-codemirror-lite'
|
||||
@@ -57,10 +61,10 @@ Vue.config.productionTip = false
|
||||
// 百度统计
|
||||
if (localStorage.getItem('useStats') !== '0') {
|
||||
window._hmt = window._hmt || [];
|
||||
(function () {
|
||||
let hm = document.createElement('script')
|
||||
(function() {
|
||||
const hm = document.createElement('script')
|
||||
hm.src = 'https://hm.baidu.com/hm.js?c35e3a563a06caee2524902c81975add'
|
||||
let s = document.getElementsByTagName('script')[0]
|
||||
const s = document.getElementsByTagName('script')[0]
|
||||
s.parentNode.insertBefore(hm, s)
|
||||
})()
|
||||
}
|
||||
|
||||
@@ -27,8 +27,16 @@ Vue.use(Router)
|
||||
}
|
||||
**/
|
||||
export const constantRouterMap = [
|
||||
{ path: '/login', component: () => import('../views/login/index'), hidden: true },
|
||||
{ path: '/signup', component: () => import('../views/login/index'), hidden: true },
|
||||
{
|
||||
path: '/login',
|
||||
component: () => import('../views/login/index'),
|
||||
hidden: true
|
||||
},
|
||||
{
|
||||
path: '/signup',
|
||||
component: () => import('../views/login/index'),
|
||||
hidden: true
|
||||
},
|
||||
{ path: '/404', component: () => import('../views/404'), hidden: true },
|
||||
{ path: '/', redirect: '/home' },
|
||||
|
||||
@@ -180,7 +188,6 @@ export const constantRouterMap = [
|
||||
title: 'Disclaimer',
|
||||
icon: 'fa fa-exclamation-triangle'
|
||||
},
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -200,7 +207,6 @@ export const constantRouterMap = [
|
||||
title: 'ChallengeList',
|
||||
icon: 'fa fa-flash'
|
||||
},
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -220,7 +226,6 @@ export const constantRouterMap = [
|
||||
title: 'Feedback',
|
||||
icon: 'fa fa-commenting-o'
|
||||
},
|
||||
hidden: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
@@ -300,7 +305,7 @@ router.beforeEach((to, from, next) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(async (to, from, next) => {
|
||||
router.afterEach(async(to, from, next) => {
|
||||
if (to.path) {
|
||||
await store.dispatch('setting/getSetting')
|
||||
const res = await request.get('/version')
|
||||
|
||||
@@ -31,10 +31,10 @@ const app = {
|
||||
ToggleSideBar: ({ commit }) => {
|
||||
commit('TOGGLE_SIDEBAR')
|
||||
},
|
||||
CloseSideBar ({ commit }, { withoutAnimation }) {
|
||||
CloseSideBar({ commit }, { withoutAnimation }) {
|
||||
commit('CLOSE_SIDEBAR', withoutAnimation)
|
||||
},
|
||||
ToggleDevice ({ commit }, device) {
|
||||
ToggleDevice({ commit }, device) {
|
||||
commit('TOGGLE_DEVICE', device)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_DEPLOY_LIST (state, value) {
|
||||
SET_DEPLOY_LIST(state, value) {
|
||||
state.deployList = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getDeployList ({ state, commit }) {
|
||||
getDeployList({ state, commit }) {
|
||||
request.get('/deploys')
|
||||
.then(response => {
|
||||
commit('SET_DEPLOY_LIST', response.data.items.map(d => {
|
||||
|
||||
@@ -6,10 +6,10 @@ const dialogView = {
|
||||
},
|
||||
getters: {},
|
||||
mutations: {
|
||||
SET_DIALOG_TYPE (state, value) {
|
||||
SET_DIALOG_TYPE(state, value) {
|
||||
state.dialogType = value
|
||||
},
|
||||
SET_DIALOG_VISIBLE (state, value) {
|
||||
SET_DIALOG_VISIBLE(state, value) {
|
||||
state.dialogVisible = value
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,13 +7,13 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_DOC_DATA (state, value) {
|
||||
SET_DOC_DATA(state, value) {
|
||||
state.docData = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
async getDocData ({ commit }) {
|
||||
async getDocData({ commit }) {
|
||||
const res = await request.get('/docs')
|
||||
|
||||
const data = JSON.parse(res.data.data.string)
|
||||
@@ -22,8 +22,8 @@ const actions = {
|
||||
const cache = {}
|
||||
|
||||
// iterate paths
|
||||
for (let path in data) {
|
||||
if (data.hasOwnProperty(path)) {
|
||||
for (const path in data) {
|
||||
if (Object.prototype.hasOwnProperty.call(data, path)) {
|
||||
const d = data[path]
|
||||
if (path.match(/\/$/)) {
|
||||
cache[path] = d
|
||||
|
||||
@@ -9,19 +9,20 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_CURRENT_PATH (state, value) {
|
||||
SET_CURRENT_PATH(state, value) {
|
||||
state.currentPath = value
|
||||
},
|
||||
SET_FILE_LIST (state, value) {
|
||||
|
||||
SET_FILE_LIST(state, value) {
|
||||
state.fileList = value
|
||||
},
|
||||
SET_FILE_CONTENT (state, value) {
|
||||
SET_FILE_CONTENT(state, value) {
|
||||
state.fileContent = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getFileList ({ commit, rootState }, payload) {
|
||||
getFileList({ commit, rootState }, payload) {
|
||||
const { path } = payload
|
||||
const spiderId = rootState.spider.spiderForm._id
|
||||
commit('SET_CURRENT_PATH', path)
|
||||
@@ -36,7 +37,7 @@ const actions = {
|
||||
)
|
||||
})
|
||||
},
|
||||
getFileContent ({ commit, rootState }, payload) {
|
||||
getFileContent({ commit, rootState }, payload) {
|
||||
const { path } = payload
|
||||
const spiderId = rootState.spider.spiderForm._id
|
||||
return request.get(`/spiders/${spiderId}/file`, { path })
|
||||
@@ -44,30 +45,32 @@ const actions = {
|
||||
commit('SET_FILE_CONTENT', response.data.data)
|
||||
})
|
||||
},
|
||||
saveFileContent ({ state, rootState }, payload) {
|
||||
saveFileContent({ state, rootState }, payload) {
|
||||
const { path } = payload
|
||||
const spiderId = rootState.spider.spiderForm._id
|
||||
return request.post(`/spiders/${spiderId}/file`, { path, content: state.fileContent })
|
||||
return request.post(`/spiders/${spiderId}/file`,
|
||||
{ path, content: state.fileContent })
|
||||
},
|
||||
addFile ({ rootState }, payload) {
|
||||
addFile({ rootState }, payload) {
|
||||
const { path } = payload
|
||||
const spiderId = rootState.spider.spiderForm._id
|
||||
return request.put(`/spiders/${spiderId}/file`, { path })
|
||||
},
|
||||
addDir ({ rootState }, payload) {
|
||||
addDir({ rootState }, payload) {
|
||||
const { path } = payload
|
||||
const spiderId = rootState.spider.spiderForm._id
|
||||
return request.put(`/spiders/${spiderId}/dir`, { path })
|
||||
},
|
||||
deleteFile ({ rootState }, payload) {
|
||||
deleteFile({ rootState }, payload) {
|
||||
const { path } = payload
|
||||
const spiderId = rootState.spider.spiderForm._id
|
||||
return request.delete(`/spiders/${spiderId}/file`, { path })
|
||||
},
|
||||
renameFile ({ rootState }, payload) {
|
||||
renameFile({ rootState }, payload) {
|
||||
const { path, newPath } = payload
|
||||
const spiderId = rootState.spider.spiderForm._id
|
||||
return request.post(`/spiders/${spiderId}/file/rename`, { path, new_path: newPath })
|
||||
return request.post(`/spiders/${spiderId}/file/rename`,
|
||||
{ path, new_path: newPath })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const state = {
|
||||
}
|
||||
|
||||
const getters = {
|
||||
lang (state) {
|
||||
lang(state) {
|
||||
if (state.lang === 'en') {
|
||||
return 'English'
|
||||
} else if (state.lang === 'zh') {
|
||||
@@ -15,7 +15,7 @@ const getters = {
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_LANG (state, value) {
|
||||
SET_LANG(state, value) {
|
||||
state.lang = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,16 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_NODE_FORM (state, value) {
|
||||
SET_NODE_FORM(state, value) {
|
||||
state.nodeForm = value
|
||||
},
|
||||
SET_NODE_LIST (state, value) {
|
||||
SET_NODE_LIST(state, value) {
|
||||
state.nodeList = value
|
||||
},
|
||||
SET_ACTIVE_SPIDER (state, value) {
|
||||
SET_ACTIVE_SPIDER(state, value) {
|
||||
state.activeSpider = value
|
||||
},
|
||||
SET_NODE_SYSTEM_INFO (state, payload) {
|
||||
SET_NODE_SYSTEM_INFO(state, payload) {
|
||||
const { id, systemInfo } = payload
|
||||
for (let i = 0; i < state.nodeList.length; i++) {
|
||||
if (state.nodeList[i]._id === id) {
|
||||
@@ -33,54 +33,48 @@ const mutations = {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getNodeList ({ state, commit }) {
|
||||
request.get('/nodes', {})
|
||||
.then(response => {
|
||||
commit('SET_NODE_LIST', response.data.data.map(d => {
|
||||
d.systemInfo = {
|
||||
os: '',
|
||||
arch: '',
|
||||
num_cpu: '',
|
||||
executables: []
|
||||
}
|
||||
return d
|
||||
}))
|
||||
})
|
||||
},
|
||||
editNode ({ state, dispatch }) {
|
||||
request.post(`/nodes/${state.nodeForm._id}`, state.nodeForm)
|
||||
.then(() => {
|
||||
dispatch('getNodeList')
|
||||
})
|
||||
},
|
||||
deleteNode ({ state, dispatch }, id) {
|
||||
request.delete(`/nodes/${id}`)
|
||||
.then(() => {
|
||||
dispatch('getNodeList')
|
||||
})
|
||||
},
|
||||
getNodeData ({ state, commit }, id) {
|
||||
request.get(`/nodes/${id}`)
|
||||
.then(response => {
|
||||
commit('SET_NODE_FORM', response.data.data)
|
||||
})
|
||||
},
|
||||
getTaskList ({ state, commit }, id) {
|
||||
return request.get(`/nodes/${id}/tasks`)
|
||||
.then(response => {
|
||||
if (response.data.data) {
|
||||
commit('task/SET_TASK_LIST',
|
||||
response.data.data.map(d => d)
|
||||
.sort((a, b) => a.create_ts < b.create_ts ? 1 : -1),
|
||||
{ root: true })
|
||||
getNodeList({ state, commit }) {
|
||||
request.get('/nodes', {}).then(response => {
|
||||
commit('SET_NODE_LIST', response.data.data.map(d => {
|
||||
d.systemInfo = {
|
||||
os: '',
|
||||
arch: '',
|
||||
num_cpu: '',
|
||||
executables: []
|
||||
}
|
||||
})
|
||||
return d
|
||||
}))
|
||||
})
|
||||
},
|
||||
getNodeSystemInfo ({ state, commit }, id) {
|
||||
return request.get(`/nodes/${id}/system`)
|
||||
.then(response => {
|
||||
commit('SET_NODE_SYSTEM_INFO', { id, systemInfo: response.data.data })
|
||||
})
|
||||
editNode({ state, dispatch }) {
|
||||
request.post(`/nodes/${state.nodeForm._id}`, state.nodeForm).then(() => {
|
||||
dispatch('getNodeList')
|
||||
})
|
||||
},
|
||||
deleteNode({ state, dispatch }, id) {
|
||||
request.delete(`/nodes/${id}`).then(() => {
|
||||
dispatch('getNodeList')
|
||||
})
|
||||
},
|
||||
getNodeData({ state, commit }, id) {
|
||||
request.get(`/nodes/${id}`).then(response => {
|
||||
commit('SET_NODE_FORM', response.data.data)
|
||||
})
|
||||
},
|
||||
getTaskList({ state, commit }, id) {
|
||||
return request.get(`/nodes/${id}/tasks`).then(response => {
|
||||
if (response.data.data) {
|
||||
commit('task/SET_TASK_LIST',
|
||||
response.data.data.map(d => d)
|
||||
.sort((a, b) => a.create_ts < b.create_ts ? 1 : -1),
|
||||
{ root: true })
|
||||
}
|
||||
})
|
||||
},
|
||||
getNodeSystemInfo({ state, commit }, id) {
|
||||
return request.get(`/nodes/${id}/system`).then(response => {
|
||||
commit('SET_NODE_SYSTEM_INFO', { id, systemInfo: response.data.data })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ const mutations = {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getProjectList ({ state, commit }, payload) {
|
||||
getProjectList({ state, commit }, payload) {
|
||||
return request.get('/projects', payload)
|
||||
.then(response => {
|
||||
if (response.data.data) {
|
||||
@@ -32,7 +32,7 @@ const actions = {
|
||||
}
|
||||
})
|
||||
},
|
||||
getProjectTags ({ state, commit }) {
|
||||
getProjectTags({ state, commit }) {
|
||||
return request.get('/projects/tags')
|
||||
.then(response => {
|
||||
if (response.data.data) {
|
||||
@@ -40,13 +40,13 @@ const actions = {
|
||||
}
|
||||
})
|
||||
},
|
||||
addProject ({ state }) {
|
||||
addProject({ state }) {
|
||||
return request.put('/projects', state.projectForm)
|
||||
},
|
||||
editProject ({ state }, id) {
|
||||
editProject({ state }, id) {
|
||||
return request.post(`/projects/${id}`, state.projectForm)
|
||||
},
|
||||
removeProject ({ state }, id) {
|
||||
removeProject({ state }, id) {
|
||||
return request.delete(`/projects/${id}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import request from '../../api/request'
|
||||
|
||||
const state = {
|
||||
scheduleList: [],
|
||||
scheduleForm: {
|
||||
@@ -9,16 +10,16 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_SCHEDULE_LIST (state, value) {
|
||||
SET_SCHEDULE_LIST(state, value) {
|
||||
state.scheduleList = value
|
||||
},
|
||||
SET_SCHEDULE_FORM (state, value) {
|
||||
SET_SCHEDULE_FORM(state, value) {
|
||||
state.scheduleForm = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getScheduleList ({ state, commit }) {
|
||||
getScheduleList({ state, commit }) {
|
||||
request.get('/schedules')
|
||||
.then(response => {
|
||||
if (response.data.data) {
|
||||
@@ -31,19 +32,19 @@ const actions = {
|
||||
}
|
||||
})
|
||||
},
|
||||
addSchedule ({ state }) {
|
||||
addSchedule({ state }) {
|
||||
request.put('/schedules', state.scheduleForm)
|
||||
},
|
||||
editSchedule ({ state }, id) {
|
||||
editSchedule({ state }, id) {
|
||||
request.post(`/schedules/${id}`, state.scheduleForm)
|
||||
},
|
||||
removeSchedule ({ state }, id) {
|
||||
removeSchedule({ state }, id) {
|
||||
request.delete(`/schedules/${id}`)
|
||||
},
|
||||
enableSchedule ({ state, dispatch }, id) {
|
||||
enableSchedule({ state, dispatch }, id) {
|
||||
return request.post(`/schedules/${id}/enable`)
|
||||
},
|
||||
disableSchedule ({ state, dispatch }, id) {
|
||||
disableSchedule({ state, dispatch }, id) {
|
||||
return request.post(`/schedules/${id}/disable`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_SETTING (state, value) {
|
||||
SET_SETTING(state, value) {
|
||||
state.setting = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
async getSetting ({ commit }) {
|
||||
async getSetting({ commit }) {
|
||||
const res = await request.get('/setting')
|
||||
commit('SET_SETTING', res.data.data)
|
||||
|
||||
|
||||
@@ -26,37 +26,37 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_KEYWORD (state, value) {
|
||||
SET_KEYWORD(state, value) {
|
||||
state.keyword = value
|
||||
},
|
||||
SET_SITE_LIST (state, value) {
|
||||
SET_SITE_LIST(state, value) {
|
||||
state.siteList = value
|
||||
},
|
||||
SET_PAGE_NUM (state, value) {
|
||||
SET_PAGE_NUM(state, value) {
|
||||
state.pageNum = value
|
||||
},
|
||||
SET_PAGE_SIZE (state, value) {
|
||||
SET_PAGE_SIZE(state, value) {
|
||||
state.pageSize = value
|
||||
},
|
||||
SET_TOTAL_COUNT (state, value) {
|
||||
SET_TOTAL_COUNT(state, value) {
|
||||
state.totalCount = value
|
||||
},
|
||||
SET_MAIN_CATEGORY_LIST (state, value) {
|
||||
SET_MAIN_CATEGORY_LIST(state, value) {
|
||||
state.mainCategoryList = value
|
||||
},
|
||||
SET_CATEGORY_LIST (state, value) {
|
||||
SET_CATEGORY_LIST(state, value) {
|
||||
state.categoryList = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
editSite ({ state, dispatch }, payload) {
|
||||
editSite({ state, dispatch }, payload) {
|
||||
const { id, category } = payload
|
||||
return request.post(`/sites/${id}`, {
|
||||
category
|
||||
})
|
||||
},
|
||||
getSiteList ({ state, commit }) {
|
||||
getSiteList({ state, commit }) {
|
||||
return request.get('/sites', {
|
||||
page_num: state.pageNum,
|
||||
page_size: state.pageSize,
|
||||
@@ -71,13 +71,13 @@ const actions = {
|
||||
commit('SET_TOTAL_COUNT', response.data.total_count)
|
||||
})
|
||||
},
|
||||
getMainCategoryList ({ state, commit }) {
|
||||
getMainCategoryList({ state, commit }) {
|
||||
return request.get('/sites/get/get_main_category_list')
|
||||
.then(response => {
|
||||
commit('SET_MAIN_CATEGORY_LIST', response.data.items)
|
||||
})
|
||||
},
|
||||
getCategoryList ({ state, commit }) {
|
||||
getCategoryList({ state, commit }) {
|
||||
return request.get('/sites/get/get_category_list', {
|
||||
'main_category': state.filter.mainCategory || undefined
|
||||
})
|
||||
|
||||
@@ -19,9 +19,6 @@ const state = {
|
||||
// spider scrapy pipelines
|
||||
spiderScrapyPipelines: [],
|
||||
|
||||
// scrapy errors
|
||||
spiderScrapyErrors: {},
|
||||
|
||||
// node to deploy/run
|
||||
activeNode: {},
|
||||
|
||||
@@ -62,110 +59,94 @@ const state = {
|
||||
const getters = {}
|
||||
|
||||
const mutations = {
|
||||
SET_SPIDER_TOTAL (state, value) {
|
||||
SET_SPIDER_TOTAL(state, value) {
|
||||
state.spiderTotal = value
|
||||
},
|
||||
SET_SPIDER_FORM (state, value) {
|
||||
SET_SPIDER_FORM(state, value) {
|
||||
state.spiderForm = value
|
||||
},
|
||||
SET_SPIDER_LIST (state, value) {
|
||||
SET_SPIDER_LIST(state, value) {
|
||||
state.spiderList = value
|
||||
},
|
||||
SET_ACTIVE_NODE (state, value) {
|
||||
SET_ACTIVE_NODE(state, value) {
|
||||
state.activeNode = value
|
||||
},
|
||||
SET_IMPORT_FORM (state, value) {
|
||||
SET_IMPORT_FORM(state, value) {
|
||||
state.importForm = value
|
||||
},
|
||||
SET_OVERVIEW_STATS (state, value) {
|
||||
SET_OVERVIEW_STATS(state, value) {
|
||||
state.overviewStats = value
|
||||
},
|
||||
SET_STATUS_STATS (state, value) {
|
||||
SET_STATUS_STATS(state, value) {
|
||||
state.statusStats = value
|
||||
},
|
||||
SET_DAILY_STATS (state, value) {
|
||||
SET_DAILY_STATS(state, value) {
|
||||
state.dailyStats = value
|
||||
},
|
||||
SET_NODE_STATS (state, value) {
|
||||
SET_NODE_STATS(state, value) {
|
||||
state.nodeStats = value
|
||||
},
|
||||
SET_FILTER_SITE (state, value) {
|
||||
SET_FILTER_SITE(state, value) {
|
||||
state.filterSite = value
|
||||
},
|
||||
SET_PREVIEW_CRAWL_DATA (state, value) {
|
||||
SET_PREVIEW_CRAWL_DATA(state, value) {
|
||||
state.previewCrawlData = value
|
||||
},
|
||||
SET_SPIDER_FORM_CONFIG_SETTINGS (state, payload) {
|
||||
SET_SPIDER_FORM_CONFIG_SETTINGS(state, payload) {
|
||||
const settings = {}
|
||||
payload.forEach(row => {
|
||||
settings[row.name] = row.value
|
||||
})
|
||||
Vue.set(state.spiderForm.config, 'settings', settings)
|
||||
},
|
||||
SET_TEMPLATE_LIST (state, value) {
|
||||
SET_TEMPLATE_LIST(state, value) {
|
||||
state.templateList = value
|
||||
},
|
||||
SET_FILE_TREE (state, value) {
|
||||
SET_FILE_TREE(state, value) {
|
||||
state.fileTree = value
|
||||
},
|
||||
SET_SPIDER_SCRAPY_SETTINGS (state, value) {
|
||||
SET_SPIDER_SCRAPY_SETTINGS(state, value) {
|
||||
state.spiderScrapySettings = value
|
||||
},
|
||||
SET_SPIDER_SCRAPY_ITEMS (state, value) {
|
||||
SET_SPIDER_SCRAPY_ITEMS(state, value) {
|
||||
state.spiderScrapyItems = value
|
||||
},
|
||||
SET_SPIDER_SCRAPY_PIPELINES (state, value) {
|
||||
SET_SPIDER_SCRAPY_PIPELINES(state, value) {
|
||||
state.spiderScrapyPipelines = value
|
||||
},
|
||||
SET_CONFIG_LIST_TS (state, value) {
|
||||
SET_CONFIG_LIST_TS(state, value) {
|
||||
state.configListTs = value
|
||||
},
|
||||
SET_SPIDER_SCRAPY_ERRORS (state, value) {
|
||||
for (let key in value) {
|
||||
if (value.hasOwnProperty(key)) {
|
||||
Vue.set(state.spiderScrapyErrors, key, value[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getSpiderList ({ state, commit }, params = {}) {
|
||||
getSpiderList({ state, commit }, params = {}) {
|
||||
return request.get('/spiders', params)
|
||||
.then(response => {
|
||||
commit('SET_SPIDER_LIST', response.data.data.list)
|
||||
commit('SET_SPIDER_TOTAL', response.data.data.total)
|
||||
})
|
||||
},
|
||||
editSpider ({ state, dispatch }) {
|
||||
editSpider({ state, dispatch }) {
|
||||
return request.post(`/spiders/${state.spiderForm._id}`, state.spiderForm)
|
||||
},
|
||||
deleteSpider ({ state, dispatch }, id) {
|
||||
deleteSpider({ state, dispatch }, id) {
|
||||
return request.delete(`/spiders/${id}`)
|
||||
},
|
||||
getSpiderData ({ state, commit }, id) {
|
||||
getSpiderData({ state, commit }, id) {
|
||||
return request.get(`/spiders/${id}`)
|
||||
.then(response => {
|
||||
let data = response.data.data
|
||||
const data = response.data.data
|
||||
commit('SET_SPIDER_FORM', data)
|
||||
})
|
||||
},
|
||||
async getSpiderScrapySpiders ({ state, commit }, id) {
|
||||
async getSpiderScrapySpiders({ state, commit }, id) {
|
||||
const res = await request.get(`/spiders/${id}/scrapy/spiders`)
|
||||
if (res.data.error) {
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { spiders: res.data.error })
|
||||
return
|
||||
}
|
||||
state.spiderForm.spider_names = res.data.data
|
||||
commit('SET_SPIDER_FORM', state.spiderForm)
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { spiders: '' })
|
||||
},
|
||||
async getSpiderScrapySettings ({ state, commit }, id) {
|
||||
async getSpiderScrapySettings({ state, commit }, id) {
|
||||
const res = await request.get(`/spiders/${id}/scrapy/settings`)
|
||||
if (res.data.error) {
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { settings: res.data.error })
|
||||
return
|
||||
}
|
||||
commit('SET_SPIDER_SCRAPY_SETTINGS', res.data.data.map(d => {
|
||||
const key = d.key
|
||||
const value = d.value
|
||||
@@ -183,17 +164,13 @@ const actions = {
|
||||
type
|
||||
}
|
||||
}))
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { settings: '' })
|
||||
},
|
||||
async saveSpiderScrapySettings ({ state }, id) {
|
||||
return request.post(`/spiders/${id}/scrapy/settings`, state.spiderScrapySettings)
|
||||
async saveSpiderScrapySettings({ state }, id) {
|
||||
return request.post(`/spiders/${id}/scrapy/settings`,
|
||||
state.spiderScrapySettings)
|
||||
},
|
||||
async getSpiderScrapyItems ({ state, commit }, id) {
|
||||
async getSpiderScrapyItems({ state, commit }, id) {
|
||||
const res = await request.get(`/spiders/${id}/scrapy/items`)
|
||||
if (res.data.error) {
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { items: res.data.error })
|
||||
return
|
||||
}
|
||||
let nodeId = 0
|
||||
commit('SET_SPIDER_SCRAPY_ITEMS', res.data.data.map(d => {
|
||||
d.id = nodeId++
|
||||
@@ -210,36 +187,33 @@ const actions = {
|
||||
})
|
||||
return d
|
||||
}))
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { items: '' })
|
||||
},
|
||||
async saveSpiderScrapyItems ({ state }, id) {
|
||||
return request.post(`/spiders/${id}/scrapy/items`, state.spiderScrapyItems.map(d => {
|
||||
d.name = d.label
|
||||
d.fields = d.children.map(f => f.label)
|
||||
return d
|
||||
}))
|
||||
async saveSpiderScrapyItems({ state }, id) {
|
||||
return request.post(`/spiders/${id}/scrapy/items`,
|
||||
state.spiderScrapyItems.map(d => {
|
||||
d.name = d.label
|
||||
d.fields = d.children.map(f => f.label)
|
||||
return d
|
||||
}))
|
||||
},
|
||||
async getSpiderScrapyPipelines ({ state, commit }, id) {
|
||||
async getSpiderScrapyPipelines({ state, commit }, id) {
|
||||
const res = await request.get(`/spiders/${id}/scrapy/pipelines`)
|
||||
if (res.data.error) {
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { pipelines: res.data.error })
|
||||
return
|
||||
}
|
||||
commit('SET_SPIDER_SCRAPY_PIPELINES', res.data.data)
|
||||
commit('SET_SPIDER_SCRAPY_ERRORS', { pipelines: '' })
|
||||
},
|
||||
async saveSpiderScrapyPipelines ({ state }, id) {
|
||||
return request.post(`/spiders/${id}/scrapy/pipelines`, state.spiderScrapyPipelines)
|
||||
async saveSpiderScrapyPipelines({ state }, id) {
|
||||
return request.post(`/spiders/${id}/scrapy/pipelines`,
|
||||
state.spiderScrapyPipelines)
|
||||
},
|
||||
async getSpiderScrapySpiderFilepath ({ state, commit }, payload) {
|
||||
async getSpiderScrapySpiderFilepath({ state, commit }, payload) {
|
||||
const { id, spiderName } = payload
|
||||
return request.get(`/spiders/${id}/scrapy/spider/filepath`, { spider_name: spiderName })
|
||||
return request.get(`/spiders/${id}/scrapy/spider/filepath`,
|
||||
{ spider_name: spiderName })
|
||||
},
|
||||
addSpiderScrapySpider ({ state }, payload) {
|
||||
addSpiderScrapySpider({ state }, payload) {
|
||||
const { id, form } = payload
|
||||
return request.put(`/spiders/${id}/scrapy/spiders`, form)
|
||||
},
|
||||
crawlSpider ({ state, dispatch }, payload) {
|
||||
crawlSpider({ state, dispatch }, payload) {
|
||||
const { spiderId, runType, nodeIds, param } = payload
|
||||
return request.put(`/tasks`, {
|
||||
spider_id: spiderId,
|
||||
@@ -248,7 +222,7 @@ const actions = {
|
||||
param: param
|
||||
})
|
||||
},
|
||||
crawlSelectedSpiders ({ state, dispatch }, payload) {
|
||||
crawlSelectedSpiders({ state, dispatch }, payload) {
|
||||
const { taskParams, runType, nodeIds } = payload
|
||||
return request.post(`/spiders-run`, {
|
||||
task_params: taskParams,
|
||||
@@ -256,7 +230,7 @@ const actions = {
|
||||
node_ids: nodeIds
|
||||
})
|
||||
},
|
||||
getTaskList ({ state, commit }, id) {
|
||||
getTaskList({ state, commit }, id) {
|
||||
return request.get(`/spiders/${id}/tasks`)
|
||||
.then(response => {
|
||||
commit('task/SET_TASK_LIST',
|
||||
@@ -266,18 +240,18 @@ const actions = {
|
||||
{ root: true })
|
||||
})
|
||||
},
|
||||
getDir ({ state, commit }, path) {
|
||||
getDir({ state, commit }, path) {
|
||||
const id = state.spiderForm._id
|
||||
return request.get(`/spiders/${id}/dir`)
|
||||
.then(response => {
|
||||
commit('')
|
||||
})
|
||||
},
|
||||
importGithub ({ state }) {
|
||||
importGithub({ state }) {
|
||||
const url = state.importForm.url
|
||||
return request.post('/spiders/import/github', { url })
|
||||
},
|
||||
getSpiderStats ({ state, commit }) {
|
||||
getSpiderStats({ state, commit }) {
|
||||
return request.get(`/spiders/${state.spiderForm._id}/stats`)
|
||||
.then(response => {
|
||||
commit('SET_OVERVIEW_STATS', response.data.data.overview)
|
||||
@@ -286,33 +260,35 @@ const actions = {
|
||||
// commit('SET_NODE_STATS', response.data.task_count_by_node)
|
||||
})
|
||||
},
|
||||
getPreviewCrawlData ({ state, commit }) {
|
||||
getPreviewCrawlData({ state, commit }) {
|
||||
return request.post(`/spiders/${state.spiderForm._id}/preview_crawl`)
|
||||
.then(response => {
|
||||
commit('SET_PREVIEW_CRAWL_DATA', response.data.items)
|
||||
})
|
||||
},
|
||||
extractFields ({ state, commit }) {
|
||||
extractFields({ state, commit }) {
|
||||
return request.post(`/spiders/${state.spiderForm._id}/extract_fields`)
|
||||
},
|
||||
postConfigSpiderConfig ({ state }) {
|
||||
return request.post(`/config_spiders/${state.spiderForm._id}/config`, state.spiderForm.config)
|
||||
postConfigSpiderConfig({ state }) {
|
||||
return request.post(`/config_spiders/${state.spiderForm._id}/config`,
|
||||
state.spiderForm.config)
|
||||
},
|
||||
saveConfigSpiderSpiderfile ({ state, rootState }) {
|
||||
saveConfigSpiderSpiderfile({ state, rootState }) {
|
||||
const content = rootState.file.fileContent
|
||||
return request.post(`/config_spiders/${state.spiderForm._id}/spiderfile`, { content })
|
||||
return request.post(`/config_spiders/${state.spiderForm._id}/spiderfile`,
|
||||
{ content })
|
||||
},
|
||||
addConfigSpider ({ state }) {
|
||||
addConfigSpider({ state }) {
|
||||
return request.put(`/config_spiders`, state.spiderForm)
|
||||
},
|
||||
addSpider ({ state }) {
|
||||
addSpider({ state }) {
|
||||
return request.put(`/spiders`, state.spiderForm)
|
||||
},
|
||||
async getTemplateList ({ state, commit }) {
|
||||
async getTemplateList({ state, commit }) {
|
||||
const res = await request.get(`/config_spiders_templates`)
|
||||
commit('SET_TEMPLATE_LIST', res.data.data)
|
||||
},
|
||||
async getScheduleList ({ state, commit }, payload) {
|
||||
async getScheduleList({ state, commit }, payload) {
|
||||
const { id } = payload
|
||||
const res = await request.get(`/spiders/${id}/schedules`)
|
||||
let data = res.data.data
|
||||
@@ -326,7 +302,7 @@ const actions = {
|
||||
}
|
||||
commit('schedule/SET_SCHEDULE_LIST', data, { root: true })
|
||||
},
|
||||
async getFileTree ({ state, commit }, payload) {
|
||||
async getFileTree({ state, commit }, payload) {
|
||||
const id = payload ? payload.id : state.spiderForm._id
|
||||
const res = await request.get(`/spiders/${id}/file/tree`)
|
||||
commit('SET_FILE_TREE', res.data.data)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const state = {}
|
||||
const getters = {
|
||||
useStats () {
|
||||
useStats() {
|
||||
return localStorage.getItem('useStats')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,18 +72,18 @@ const tagsView = {
|
||||
|
||||
},
|
||||
actions: {
|
||||
addView ({ dispatch }, view) {
|
||||
addView({ dispatch }, view) {
|
||||
dispatch('addVisitedView', view)
|
||||
dispatch('addCachedView', view)
|
||||
},
|
||||
addVisitedView ({ commit }, view) {
|
||||
addVisitedView({ commit }, view) {
|
||||
commit('ADD_VISITED_VIEW', view)
|
||||
},
|
||||
addCachedView ({ commit }, view) {
|
||||
addCachedView({ commit }, view) {
|
||||
commit('ADD_CACHED_VIEW', view)
|
||||
},
|
||||
|
||||
delView ({ dispatch, state }, view) {
|
||||
delView({ dispatch, state }, view) {
|
||||
return new Promise(resolve => {
|
||||
dispatch('delVisitedView', view)
|
||||
dispatch('delCachedView', view)
|
||||
@@ -93,20 +93,20 @@ const tagsView = {
|
||||
})
|
||||
})
|
||||
},
|
||||
delVisitedView ({ commit, state }, view) {
|
||||
delVisitedView({ commit, state }, view) {
|
||||
return new Promise(resolve => {
|
||||
commit('DEL_VISITED_VIEW', view)
|
||||
resolve([...state.visitedViews])
|
||||
})
|
||||
},
|
||||
delCachedView ({ commit, state }, view) {
|
||||
delCachedView({ commit, state }, view) {
|
||||
return new Promise(resolve => {
|
||||
commit('DEL_CACHED_VIEW', view)
|
||||
resolve([...state.cachedViews])
|
||||
})
|
||||
},
|
||||
|
||||
delOthersViews ({ dispatch, state }, view) {
|
||||
delOthersViews({ dispatch, state }, view) {
|
||||
return new Promise(resolve => {
|
||||
dispatch('delOthersVisitedViews', view)
|
||||
dispatch('delOthersCachedViews', view)
|
||||
@@ -116,20 +116,20 @@ const tagsView = {
|
||||
})
|
||||
})
|
||||
},
|
||||
delOthersVisitedViews ({ commit, state }, view) {
|
||||
delOthersVisitedViews({ commit, state }, view) {
|
||||
return new Promise(resolve => {
|
||||
commit('DEL_OTHERS_VISITED_VIEWS', view)
|
||||
resolve([...state.visitedViews])
|
||||
})
|
||||
},
|
||||
delOthersCachedViews ({ commit, state }, view) {
|
||||
delOthersCachedViews({ commit, state }, view) {
|
||||
return new Promise(resolve => {
|
||||
commit('DEL_OTHERS_CACHED_VIEWS', view)
|
||||
resolve([...state.cachedViews])
|
||||
})
|
||||
},
|
||||
|
||||
delAllViews ({ dispatch, state }, view) {
|
||||
delAllViews({ dispatch, state }, view) {
|
||||
return new Promise(resolve => {
|
||||
dispatch('delAllVisitedViews', view)
|
||||
dispatch('delAllCachedViews', view)
|
||||
@@ -139,20 +139,20 @@ const tagsView = {
|
||||
})
|
||||
})
|
||||
},
|
||||
delAllVisitedViews ({ commit, state }) {
|
||||
delAllVisitedViews({ commit, state }) {
|
||||
return new Promise(resolve => {
|
||||
commit('DEL_ALL_VISITED_VIEWS')
|
||||
resolve([...state.visitedViews])
|
||||
})
|
||||
},
|
||||
delAllCachedViews ({ commit, state }) {
|
||||
delAllCachedViews({ commit, state }) {
|
||||
return new Promise(resolve => {
|
||||
commit('DEL_ALL_CACHED_VIEWS')
|
||||
resolve([...state.cachedViews])
|
||||
})
|
||||
},
|
||||
|
||||
updateVisitedView ({ commit }, view) {
|
||||
updateVisitedView({ commit }, view) {
|
||||
commit('UPDATE_VISITED_VIEW', view)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,20 +36,20 @@ const state = {
|
||||
}
|
||||
|
||||
const getters = {
|
||||
taskResultsColumns (state) {
|
||||
taskResultsColumns(state) {
|
||||
if (!state.taskResultsData || !state.taskResultsData.length) {
|
||||
return []
|
||||
}
|
||||
const keys = []
|
||||
const item = state.taskResultsData[0]
|
||||
for (const key in item) {
|
||||
if (item.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(item, key)) {
|
||||
keys.push(key)
|
||||
}
|
||||
}
|
||||
return keys
|
||||
},
|
||||
logData (state) {
|
||||
logData(state) {
|
||||
const data = state.taskLog
|
||||
.map((d, i) => {
|
||||
return {
|
||||
@@ -71,7 +71,7 @@ const getters = {
|
||||
}
|
||||
return data
|
||||
},
|
||||
errorLogData (state, getters) {
|
||||
errorLogData(state, getters) {
|
||||
const idxList = getters.logData.map(d => d._id)
|
||||
return state.errorLogData.map(d => {
|
||||
const idx = idxList.indexOf(d._id)
|
||||
@@ -82,82 +82,82 @@ const getters = {
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
SET_TASK_FORM (state, value) {
|
||||
SET_TASK_FORM(state, value) {
|
||||
state.taskForm = value
|
||||
},
|
||||
SET_TASK_LIST (state, value) {
|
||||
SET_TASK_LIST(state, value) {
|
||||
state.taskList = value
|
||||
},
|
||||
SET_TASK_LOG (state, value) {
|
||||
SET_TASK_LOG(state, value) {
|
||||
state.taskLog = value
|
||||
},
|
||||
SET_TASK_LOG_TOTAL (state, value) {
|
||||
SET_TASK_LOG_TOTAL(state, value) {
|
||||
state.taskLogTotal = value
|
||||
},
|
||||
SET_CURRENT_LOG_INDEX (state, value) {
|
||||
SET_CURRENT_LOG_INDEX(state, value) {
|
||||
state.currentLogIndex = value
|
||||
},
|
||||
SET_TASK_RESULTS_DATA (state, value) {
|
||||
SET_TASK_RESULTS_DATA(state, value) {
|
||||
state.taskResultsData = value
|
||||
},
|
||||
SET_TASK_RESULTS_COLUMNS (state, value) {
|
||||
SET_TASK_RESULTS_COLUMNS(state, value) {
|
||||
state.taskResultsColumns = value
|
||||
},
|
||||
SET_PAGE_NUM (state, value) {
|
||||
SET_PAGE_NUM(state, value) {
|
||||
state.pageNum = value
|
||||
},
|
||||
SET_PAGE_SIZE (state, value) {
|
||||
SET_PAGE_SIZE(state, value) {
|
||||
state.pageSize = value
|
||||
},
|
||||
SET_TASK_LIST_TOTAL_COUNT (state, value) {
|
||||
SET_TASK_LIST_TOTAL_COUNT(state, value) {
|
||||
state.taskListTotalCount = value
|
||||
},
|
||||
SET_RESULTS_PAGE_NUM (state, value) {
|
||||
SET_RESULTS_PAGE_NUM(state, value) {
|
||||
state.resultsPageNum = value
|
||||
},
|
||||
SET_RESULTS_PAGE_SIZE (state, value) {
|
||||
SET_RESULTS_PAGE_SIZE(state, value) {
|
||||
state.resultsPageSize = value
|
||||
},
|
||||
SET_TASK_RESULTS_TOTAL_COUNT (state, value) {
|
||||
SET_TASK_RESULTS_TOTAL_COUNT(state, value) {
|
||||
state.taskResultsTotalCount = value
|
||||
},
|
||||
SET_LOG_KEYWORD (state, value) {
|
||||
SET_LOG_KEYWORD(state, value) {
|
||||
state.logKeyword = value
|
||||
},
|
||||
SET_ERROR_LOG_DATA (state, value) {
|
||||
SET_ERROR_LOG_DATA(state, value) {
|
||||
state.errorLogData = value
|
||||
},
|
||||
SET_TASK_LOG_PAGE (state, value) {
|
||||
SET_TASK_LOG_PAGE(state, value) {
|
||||
state.taskLogPage = value
|
||||
},
|
||||
SET_TASK_LOG_PAGE_SIZE (state, value) {
|
||||
SET_TASK_LOG_PAGE_SIZE(state, value) {
|
||||
state.taskLogPageSize = value
|
||||
},
|
||||
SET_IS_LOG_AUTO_SCROLL (state, value) {
|
||||
SET_IS_LOG_AUTO_SCROLL(state, value) {
|
||||
state.isLogAutoScroll = value
|
||||
},
|
||||
SET_IS_LOG_AUTO_FETCH (state, value) {
|
||||
SET_IS_LOG_AUTO_FETCH(state, value) {
|
||||
state.isLogAutoFetch = value
|
||||
},
|
||||
SET_IS_LOG_FETCH_LOADING (state, value) {
|
||||
SET_IS_LOG_FETCH_LOADING(state, value) {
|
||||
state.isLogFetchLoading = value
|
||||
},
|
||||
SET_ACTIVE_ERROR_LOG_ITEM (state, value) {
|
||||
SET_ACTIVE_ERROR_LOG_ITEM(state, value) {
|
||||
state.activeErrorLogItem = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
getTaskData ({ state, dispatch, commit }, id) {
|
||||
getTaskData({ state, dispatch, commit }, id) {
|
||||
return request.get(`/tasks/${id}`)
|
||||
.then(response => {
|
||||
let data = response.data.data
|
||||
const data = response.data.data
|
||||
commit('SET_TASK_FORM', data)
|
||||
dispatch('spider/getSpiderData', data.spider_id, { root: true })
|
||||
dispatch('node/getNodeData', data.node_id, { root: true })
|
||||
})
|
||||
},
|
||||
getTaskList ({ state, commit }) {
|
||||
getTaskList({ state, commit }) {
|
||||
return request.get('/tasks', {
|
||||
page_num: state.pageNum,
|
||||
page_size: state.pageSize,
|
||||
@@ -171,24 +171,24 @@ const actions = {
|
||||
commit('SET_TASK_LIST_TOTAL_COUNT', response.data.total)
|
||||
})
|
||||
},
|
||||
deleteTask ({ state, dispatch }, id) {
|
||||
deleteTask({ state, dispatch }, id) {
|
||||
return request.delete(`/tasks/${id}`)
|
||||
.then(() => {
|
||||
dispatch('getTaskList')
|
||||
})
|
||||
},
|
||||
deleteTaskMultiple ({ state }, ids) {
|
||||
deleteTaskMultiple({ state }, ids) {
|
||||
return request.delete(`/tasks`, {
|
||||
ids: ids
|
||||
})
|
||||
},
|
||||
restartTask ({ state, dispatch }, id) {
|
||||
restartTask({ state, dispatch }, id) {
|
||||
return request.post(`/tasks/${id}/restart`)
|
||||
.then(() => {
|
||||
dispatch('getTaskList')
|
||||
})
|
||||
},
|
||||
getTaskLog ({ state, commit }, { id, keyword }) {
|
||||
getTaskLog({ state, commit }, { id, keyword }) {
|
||||
return request.get(`/tasks/${id}/log`, {
|
||||
keyword,
|
||||
page_num: state.taskLogPage,
|
||||
@@ -199,18 +199,20 @@ const actions = {
|
||||
commit('SET_TASK_LOG_TOTAL', response.data.total || 0)
|
||||
|
||||
// auto switch to next page if not reaching the end
|
||||
if (state.isLogAutoScroll && state.taskLogTotal > (state.taskLogPage * state.taskLogPageSize)) {
|
||||
commit('SET_TASK_LOG_PAGE', Math.ceil(state.taskLogTotal / state.taskLogPageSize))
|
||||
if (state.isLogAutoScroll && state.taskLogTotal >
|
||||
(state.taskLogPage * state.taskLogPageSize)) {
|
||||
commit('SET_TASK_LOG_PAGE',
|
||||
Math.ceil(state.taskLogTotal / state.taskLogPageSize))
|
||||
}
|
||||
})
|
||||
},
|
||||
getTaskErrorLog ({ state, commit }, id) {
|
||||
getTaskErrorLog({ state, commit }, id) {
|
||||
return request.get(`/tasks/${id}/error-log`, {})
|
||||
.then(response => {
|
||||
commit('SET_ERROR_LOG_DATA', response.data.data || [])
|
||||
})
|
||||
},
|
||||
getTaskResults ({ state, commit }, id) {
|
||||
getTaskResults({ state, commit }, id) {
|
||||
return request.get(`/tasks/${id}/results`, {
|
||||
page_num: state.resultsPageNum,
|
||||
page_size: state.resultsPageSize
|
||||
@@ -221,10 +223,11 @@ const actions = {
|
||||
commit('SET_TASK_RESULTS_TOTAL_COUNT', response.data.total)
|
||||
})
|
||||
},
|
||||
async getTaskResultExcel ({ state, commit }, id) {
|
||||
const { data } = await request.request('GET', '/tasks/' + id + '/results/download', {}, {
|
||||
responseType: 'blob' // important
|
||||
})
|
||||
async getTaskResultExcel({ state, commit }, id) {
|
||||
const { data } = await request.request('GET',
|
||||
'/tasks/' + id + '/results/download', {}, {
|
||||
responseType: 'blob' // important
|
||||
})
|
||||
const downloadUrl = window.URL.createObjectURL(new Blob([data]))
|
||||
|
||||
const link = document.createElement('a')
|
||||
@@ -237,7 +240,7 @@ const actions = {
|
||||
link.click()
|
||||
link.remove()
|
||||
},
|
||||
cancelTask ({ state, dispatch }, id) {
|
||||
cancelTask({ state, dispatch }, id) {
|
||||
return new Promise(resolve => {
|
||||
request.post(`/tasks/${id}/cancel`)
|
||||
.then(res => {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import request from '../../api/request'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
|
||||
const user = {
|
||||
namespaced: true,
|
||||
|
||||
state: {
|
||||
// token: getToken(),
|
||||
token: getToken(),
|
||||
name: '',
|
||||
avatar: '',
|
||||
roles: [],
|
||||
@@ -22,13 +23,13 @@ const user = {
|
||||
},
|
||||
|
||||
getters: {
|
||||
userInfo (state) {
|
||||
userInfo(state) {
|
||||
if (state.userInfo) return state.userInfo
|
||||
const userInfoStr = window.localStorage.getItem('user_info')
|
||||
if (!userInfoStr) return {}
|
||||
return JSON.parse(userInfoStr)
|
||||
},
|
||||
token () {
|
||||
token() {
|
||||
return window.localStorage.getItem('token')
|
||||
}
|
||||
},
|
||||
@@ -71,43 +72,41 @@ const user = {
|
||||
|
||||
actions: {
|
||||
// 登录
|
||||
async login ({ commit }, userInfo) {
|
||||
async login({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
let res
|
||||
res = await request.post('/login', { username, password: userInfo.password })
|
||||
const res = await request.post('/login',
|
||||
{ username, password: userInfo.password })
|
||||
if (res.status === 200) {
|
||||
const token = res.data.data
|
||||
commit('SET_TOKEN', token)
|
||||
window.localStorage.setItem('token', token)
|
||||
setToken(token)
|
||||
}
|
||||
return res
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
getInfo ({ commit, state }) {
|
||||
return request.get('/me')
|
||||
.then(response => {
|
||||
// ensure compatibility
|
||||
if (!response.data.data.setting.max_error_log) {
|
||||
response.data.data.setting.max_error_log = 1000
|
||||
}
|
||||
if (!response.data.data.setting.log_expire_duration) {
|
||||
response.data.data.setting.log_expire_duration = 3600 * 24
|
||||
}
|
||||
commit('SET_USER_INFO', response.data.data)
|
||||
window.localStorage.setItem('user_info', JSON.stringify(response.data.data))
|
||||
})
|
||||
async getInfo({ commit, state }) {
|
||||
const response = await request.get('/me')
|
||||
|
||||
// ensure compatibility
|
||||
if (!response.data.data.setting.max_error_log) {
|
||||
response.data.data.setting.max_error_log = 1000
|
||||
}
|
||||
commit('SET_USER_INFO', response.data.data)
|
||||
window.localStorage.setItem('user_info',
|
||||
JSON.stringify(response.data.data))
|
||||
},
|
||||
|
||||
// 修改用户信息
|
||||
postInfo ({ commit }, form) {
|
||||
postInfo({ commit }, form) {
|
||||
return request.post('/me', form)
|
||||
},
|
||||
|
||||
// 注册
|
||||
register ({ dispatch, commit, state }, userInfo) {
|
||||
register({ dispatch, commit, state }, userInfo) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.put('/users', { username: userInfo.username, password: userInfo.password })
|
||||
request.put('/users',
|
||||
{ username: userInfo.username, password: userInfo.password })
|
||||
.then(() => {
|
||||
resolve()
|
||||
})
|
||||
@@ -118,7 +117,7 @@ const user = {
|
||||
},
|
||||
|
||||
// 登出
|
||||
logout ({ commit, state }) {
|
||||
logout({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.localStorage.removeItem('token')
|
||||
window.localStorage.removeItem('user_info')
|
||||
@@ -128,9 +127,13 @@ const user = {
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
|
||||
async resetToken({ commit }) {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
removeToken()
|
||||
},
|
||||
// 获取用户列表
|
||||
getUserList ({ commit, state }) {
|
||||
getUserList({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
request.get('/users', {
|
||||
page_num: state.pageNum,
|
||||
@@ -144,34 +147,34 @@ const user = {
|
||||
},
|
||||
|
||||
// 删除用户
|
||||
deleteUser ({ state }, id) {
|
||||
deleteUser({ state }, id) {
|
||||
return request.delete(`/users/${id}`)
|
||||
},
|
||||
|
||||
// 编辑用户
|
||||
editUser ({ state }) {
|
||||
editUser({ state }) {
|
||||
return request.post(`/users/${state.userForm._id}`, state.userForm)
|
||||
},
|
||||
|
||||
// 添加用户
|
||||
addUser ({ dispatch, commit, state }) {
|
||||
addUser({ dispatch, commit, state }) {
|
||||
return request.put('/users-add', state.userForm)
|
||||
},
|
||||
// 新增全局变量
|
||||
addGlobalVariable ({ commit, state }) {
|
||||
addGlobalVariable({ commit, state }) {
|
||||
return request.put(`/variable`, state.globalVariableForm)
|
||||
.then(() => {
|
||||
state.globalVariableForm = {}
|
||||
})
|
||||
},
|
||||
// 获取全局变量列表
|
||||
getGlobalVariable ({ commit, state }) {
|
||||
getGlobalVariable({ commit, state }) {
|
||||
request.get('/variables').then((response) => {
|
||||
commit('SET_GLOBAL_VARIABLE_LIST', response.data.data)
|
||||
})
|
||||
},
|
||||
// 删除全局变量
|
||||
deleteGlobalVariable ({ commit, state }, id) {
|
||||
deleteGlobalVariable({ commit, state }, id) {
|
||||
return request.delete(`/variable/${id}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const mutations = {
|
||||
}
|
||||
|
||||
const actions = {
|
||||
async getLatestRelease ({ commit }) {
|
||||
async getLatestRelease({ commit }) {
|
||||
const res = await request.get('/releases/latest')
|
||||
if (!res.data.error) {
|
||||
commit('SET_LATEST_RELEASE', res.data.data)
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import Cookies from 'js-cookie'
|
||||
const TokenKey = 'token'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
|
||||
export function getToken () {
|
||||
return Cookies.get(TokenKey)
|
||||
export function getToken() {
|
||||
return window.localStorage.getItem(TokenKey)
|
||||
}
|
||||
|
||||
export function setToken (token) {
|
||||
return Cookies.set(TokenKey, token)
|
||||
export function setToken(token) {
|
||||
return window.localStorage.setItem(TokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken () {
|
||||
return Cookies.remove(TokenKey)
|
||||
export function removeToken() {
|
||||
return window.localStorage.removeItem(TokenKey)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
UUID: () => {
|
||||
let s = []
|
||||
let hexDigits = '0123456789abcdef'
|
||||
const s = []
|
||||
const hexDigits = '0123456789abcdef'
|
||||
for (let i = 0; i < 36; i++) {
|
||||
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export default {
|
||||
htmlEscape: text => {
|
||||
return text.replace(/[<>"&]/g, function (match, pos, originalText) {
|
||||
return text.replace(/[<>"&]/g, function(match, pos, originalText) {
|
||||
switch (match) {
|
||||
case '<':
|
||||
return '<'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// translate router.meta.title, be used in breadcrumb sidebar tagsview
|
||||
export function generateTitle (title) {
|
||||
export function generateTitle(title) {
|
||||
const hasKey = this.$te('route.' + title)
|
||||
|
||||
if (hasKey) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import tour from './tour'
|
||||
import log from './log'
|
||||
import scrapy from './scrapy'
|
||||
import doc from './doc'
|
||||
import html from './html'
|
||||
|
||||
export default {
|
||||
stats,
|
||||
@@ -12,6 +11,5 @@ export default {
|
||||
tour,
|
||||
log,
|
||||
scrapy,
|
||||
doc,
|
||||
html
|
||||
doc
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ const regexToken = ' :,.'
|
||||
|
||||
export default {
|
||||
// errorRegex: new RegExp(`(?:[${regexToken}]|^)((?:error|exception|traceback)s?)(?:[${regexToken}]|$)`, 'gi')
|
||||
errorRegex: new RegExp(`(?:[${regexToken}]|^)((?:error|exception|traceback)s?)(?:[${regexToken}]|$)`, 'gi'),
|
||||
errorRegex: new RegExp(
|
||||
`(?:[${regexToken}]|^)((?:error|exception|traceback)s?)(?:[${regexToken}]|$)`,
|
||||
'gi'),
|
||||
errorWhitelist: [
|
||||
'log_count/ERROR'
|
||||
]
|
||||
|
||||
123
frontend/src/utils/request.js
Normal file
123
frontend/src/utils/request.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import axios from 'axios'
|
||||
import { MessageBox, Message } from 'element-ui'
|
||||
import store from '@/store'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import i18n from '@/i18n'
|
||||
import router from '@/router'
|
||||
|
||||
const codeMessage = {
|
||||
200: '服务器成功返回请求的数据。',
|
||||
201: '新建或修改数据成功。',
|
||||
202: '一个请求已经进入后台排队(异步任务)。',
|
||||
204: '删除数据成功。',
|
||||
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
|
||||
401: '用户没有权限(令牌、用户名、密码错误)。',
|
||||
403: '用户得到授权,但是访问是被禁止的。',
|
||||
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
|
||||
406: '请求的格式不可得。',
|
||||
410: '请求的资源被永久删除,且不会再得到的。',
|
||||
422: '当创建一个对象时,发生一个验证错误。',
|
||||
500: '服务器发生错误,请检查服务器。',
|
||||
502: '网关错误。',
|
||||
503: '服务不可用,服务器暂时过载或维护。',
|
||||
504: '网关超时。'
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理程序
|
||||
*/
|
||||
const errorHandler = (error) => {
|
||||
const { response } = error
|
||||
const routePath = router.currentRoute.path
|
||||
if (response && response.status) {
|
||||
const errorText = codeMessage[response.status] || response.statusText
|
||||
const { status } = response
|
||||
Message({
|
||||
message: `请求错误 ${status}: ${response.request.responseURL},${errorText}`,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
switch (status) {
|
||||
case 401:
|
||||
if (routePath !== '/login' && routePath !== '/') {
|
||||
MessageBox.confirm(
|
||||
i18n.t('auth.login_expired_message'),
|
||||
i18n.t('auth.login_expired_title'), {
|
||||
confirmButtonText: i18n.t('auth.login_expired_confirm'),
|
||||
cancelButtonText: i18n.t('auth.login_expired_cancel'),
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
store.dispatch('user/resetToken').then(() => {
|
||||
location.reload()
|
||||
})
|
||||
})
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
} else if (!response) {
|
||||
Message({
|
||||
message: `您的网络发生异常,无法连接服务器`,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
// 根据 VUE_APP_BASE_URL 生成 baseUrl
|
||||
let baseUrl = process.env.VUE_APP_BASE_URL
|
||||
? process.env.VUE_APP_BASE_URL
|
||||
: 'http://localhost:8000'
|
||||
if (!baseUrl.match(/^https?/i)) {
|
||||
baseUrl = `${window.location.protocol}//${window.location.host}${process.env.VUE_APP_BASE_URL}`
|
||||
}
|
||||
|
||||
// 如果 Docker 中设置了 CRAWLAB_API_ADDRESS 这个环境变量,则会将 baseUrl 覆盖
|
||||
const CRAWLAB_API_ADDRESS = '###CRAWLAB_API_ADDRESS###'
|
||||
if (!CRAWLAB_API_ADDRESS.match('CRAWLAB_API_ADDRESS')) {
|
||||
baseUrl = CRAWLAB_API_ADDRESS
|
||||
}
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
baseURL: baseUrl, // url = base url + request url
|
||||
// withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 5000 // request timeout
|
||||
})
|
||||
// request interceptor
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// do something before request is sent
|
||||
|
||||
if (store.getters.token) {
|
||||
// let each request carry token
|
||||
// ['X-Token'] is a custom headers key
|
||||
// please modify it according to the actual situation
|
||||
config.headers['Authorization'] = getToken()
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
// do something with request error
|
||||
console.log(error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// response interceptor
|
||||
service.interceptors.response.use(
|
||||
/**
|
||||
* If you want to get http information such as headers or status
|
||||
* Please return response => response
|
||||
*/
|
||||
/**
|
||||
* Determine the request status by custom code
|
||||
* Here is just an example
|
||||
* You can also judge the status by HTTP Status Code
|
||||
*/
|
||||
response => {
|
||||
return response
|
||||
},
|
||||
errorHandler
|
||||
)
|
||||
export default service
|
||||
@@ -1,6 +1,6 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const sendEvCrawlab = async (eventCategory, eventAction, eventLabel) => {
|
||||
const sendEvCrawlab = async(eventCategory, eventAction, eventLabel) => {
|
||||
await axios.get(process.env.VUE_APP_CRAWLAB_BASE_URL + '/track', {
|
||||
params: {
|
||||
uid: localStorage.getItem('uid'),
|
||||
@@ -14,13 +14,13 @@ const sendEvCrawlab = async (eventCategory, eventAction, eventLabel) => {
|
||||
}
|
||||
|
||||
export default {
|
||||
sendPv (page) {
|
||||
sendPv(page) {
|
||||
if (localStorage.getItem('useStats') !== '0') {
|
||||
window._hmt.push(['_trackPageview', page])
|
||||
sendEvCrawlab('访问页面', page, '')
|
||||
}
|
||||
},
|
||||
sendEv (category, eventName, optLabel, optValue) {
|
||||
sendEv(category, eventName, optLabel, optValue) {
|
||||
if (localStorage.getItem('useStats') !== '0') {
|
||||
window._hmt.push(['_trackEvent', category, eventName, optLabel, optValue])
|
||||
sendEvCrawlab(category, eventName, optLabel)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Created by jiachenpan on 16/11/18.
|
||||
*/
|
||||
|
||||
export function isValidUsername (str) {
|
||||
export function isValidUsername(str) {
|
||||
if (!str) return false
|
||||
if (str.length > 100) return false
|
||||
return true
|
||||
@@ -10,6 +10,6 @@ export function isValidUsername (str) {
|
||||
// return validMap.indexOf(str.trim()) >= 0
|
||||
}
|
||||
|
||||
export function isExternal (path) {
|
||||
export function isExternal(path) {
|
||||
return /^(https?:|mailto:|tel:)/.test(path)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops">OOPS!</div>
|
||||
<div class="bullshit__info">版权所有
|
||||
<a class="link-type" href="https://crawlab.cn/" target="_blank">Crawlab Team</a>
|
||||
<a class="link-type" href="https://wallstreetcn.com" target="_blank">华尔街见闻</a>
|
||||
</div>
|
||||
<div class="bullshit__headline">{{ message }}</div>
|
||||
<div class="bullshit__info">请检查您输入的网址是否正确,请点击以下按钮返回主页或者发送错误报告</div>
|
||||
@@ -22,19 +22,14 @@
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'Page404',
|
||||
computed: {
|
||||
message () {
|
||||
return '这个页面似乎不存在......'
|
||||
export default {
|
||||
name: 'Page404',
|
||||
computed: {
|
||||
message() {
|
||||
return '网管说这个页面你不能进......'
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// remove loading-placeholder
|
||||
const elLoading = document.querySelector('#loading-placeholder')
|
||||
elLoading.remove()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style rel="stylesheet/scss" lang="scss" scoped>
|
||||
|
||||
@@ -8,31 +8,30 @@
|
||||
>
|
||||
<el-card>
|
||||
<div class="title" :title="lang === 'zh' ? c.title_cn : c.title_en">
|
||||
{{lang === 'zh' ? c.title_cn : c.title_en}}
|
||||
{{ lang === 'zh' ? c.title_cn : c.title_en }}
|
||||
</div>
|
||||
<div class="rating block">
|
||||
<span class="label">{{$t('Difficulty')}}: </span>
|
||||
<span class="label">{{ $t('Difficulty') }}: </span>
|
||||
<el-rate
|
||||
v-model="c.difficulty"
|
||||
disabled
|
||||
>
|
||||
</el-rate>
|
||||
/>
|
||||
</div>
|
||||
<div class="achieved block">
|
||||
<span class="label">{{$t('Status')}}: </span>
|
||||
<span class="label">{{ $t('Status') }}: </span>
|
||||
<div class="content">
|
||||
<div v-if="c.achieved" class="status is-achieved">
|
||||
<i class="fa fa-check-square-o"></i>
|
||||
<span>{{$t('Achieved')}}</span>
|
||||
<i class="fa fa-check-square-o" />
|
||||
<span>{{ $t('Achieved') }}</span>
|
||||
</div>
|
||||
<div v-else class="status is-not-achieved">
|
||||
<i class="fa fa-square-o"></i>
|
||||
<span>{{$t('Not Achieved')}}</span>
|
||||
<i class="fa fa-square-o" />
|
||||
<span>{{ $t('Not Achieved') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{lang === 'zh' ? c.description_cn : c.description_en}}
|
||||
{{ lang === 'zh' ? c.description_cn : c.description_en }}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<el-button
|
||||
@@ -42,7 +41,7 @@
|
||||
icon="el-icon-check"
|
||||
disabled
|
||||
>
|
||||
{{$t('Achieved')}}
|
||||
{{ $t('Achieved') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
@@ -51,7 +50,7 @@
|
||||
icon="el-icon-s-flag"
|
||||
@click="onStartChallenge(c)"
|
||||
>
|
||||
{{$t('Start Challenge')}}
|
||||
{{ $t('Start Challenge') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
@@ -61,40 +60,40 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
export default {
|
||||
name: 'ChallengeList',
|
||||
data () {
|
||||
return {
|
||||
challenges: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('lang', [
|
||||
'lang'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
async getData () {
|
||||
await this.$request.post('/challenges-check')
|
||||
const res = await this.$request.get('/challenges')
|
||||
this.challenges = res.data.data || []
|
||||
},
|
||||
onStartChallenge (c) {
|
||||
if (c.path) {
|
||||
this.$router.push(c.path)
|
||||
} else {
|
||||
this.$message.success(this.$t('You have started the challenge.'))
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
export default {
|
||||
name: 'ChallengeList',
|
||||
data() {
|
||||
return {
|
||||
challenges: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('lang', [
|
||||
'lang'
|
||||
])
|
||||
},
|
||||
async created() {
|
||||
await this.getData()
|
||||
},
|
||||
methods: {
|
||||
async getData() {
|
||||
await this.$request.post('/challenges-check')
|
||||
const res = await this.$request.get('/challenges')
|
||||
this.challenges = res.data.data || []
|
||||
},
|
||||
onStartChallenge(c) {
|
||||
if (c.path) {
|
||||
this.$router.push(c.path)
|
||||
} else {
|
||||
this.$message.success(this.$t('You have started the challenge.'))
|
||||
}
|
||||
this.$st.sendEv('挑战', '开始挑战')
|
||||
}
|
||||
this.$st.sendEv('挑战', '开始挑战')
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
await this.getData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NodeDetail'
|
||||
}
|
||||
export default {
|
||||
name: 'NodeDetail'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -2,157 +2,167 @@
|
||||
<div class="app-container">
|
||||
<!--filter-->
|
||||
<div class="filter">
|
||||
<el-input prefix-icon="el-icon-search"
|
||||
:placeholder="$t('Search')"
|
||||
class="filter-search"
|
||||
v-model="filter.keyword"
|
||||
@change="onSearch">
|
||||
</el-input>
|
||||
<el-input
|
||||
v-model="filter.keyword"
|
||||
prefix-icon="el-icon-search"
|
||||
:placeholder="$t('Search')"
|
||||
class="filter-search"
|
||||
@change="onSearch"
|
||||
/>
|
||||
<div class="right">
|
||||
<el-button type="success"
|
||||
icon="el-icon-refresh"
|
||||
class="refresh"
|
||||
@click="onRefresh">
|
||||
{{$t('Refresh')}}
|
||||
<el-button
|
||||
type="success"
|
||||
icon="el-icon-refresh"
|
||||
class="refresh"
|
||||
@click="onRefresh"
|
||||
>
|
||||
{{ $t('Refresh') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--table list-->
|
||||
<el-table :data="filteredTableData"
|
||||
class="table"
|
||||
:header-cell-style="{background:'rgb(48, 65, 86)',color:'white'}"
|
||||
border>
|
||||
<el-table
|
||||
:data="filteredTableData"
|
||||
class="table"
|
||||
:header-cell-style="{background:'rgb(48, 65, 86)',color:'white'}"
|
||||
border
|
||||
>
|
||||
<template v-for="col in columns">
|
||||
<el-table-column v-if="col.name === 'spider_name'"
|
||||
:key="col.name"
|
||||
:label="$t(col.label)"
|
||||
:sortable="col.sortable"
|
||||
align="center"
|
||||
:width="col.width">
|
||||
<el-table-column
|
||||
v-if="col.name === 'spider_name'"
|
||||
:key="col.name"
|
||||
:label="$t(col.label)"
|
||||
:sortable="col.sortable"
|
||||
align="center"
|
||||
:width="col.width"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" href="javascript:" @click="onClickSpider(scope.row)">{{scope.row[col.name]}}</a>
|
||||
<a class="a-tag" href="javascript:" @click="onClickSpider(scope.row)">{{ scope.row[col.name] }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-else-if="col.name === 'node_id'"
|
||||
:key="col.name"
|
||||
:label="$t(col.label)"
|
||||
:sortable="col.sortable"
|
||||
align="center"
|
||||
:width="col.width">
|
||||
<el-table-column
|
||||
v-else-if="col.name === 'node_id'"
|
||||
:key="col.name"
|
||||
:label="$t(col.label)"
|
||||
:sortable="col.sortable"
|
||||
align="center"
|
||||
:width="col.width"
|
||||
>
|
||||
<template slot-scope="scope">
|
||||
<a class="a-tag" href="javascript:" @click="onClickNode(scope.row)">{{scope.row[col.name]}}</a>
|
||||
<a class="a-tag" href="javascript:" @click="onClickNode(scope.row)">{{ scope.row[col.name] }}</a>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-else
|
||||
:key="col.name"
|
||||
:property="col.name"
|
||||
:label="$t(col.label)"
|
||||
:sortable="col.sortable"
|
||||
align="center"
|
||||
:width="col.width">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-else
|
||||
:key="col.name"
|
||||
:property="col.name"
|
||||
:label="$t(col.label)"
|
||||
:sortable="col.sortable"
|
||||
align="center"
|
||||
:width="col.width"
|
||||
/>
|
||||
</template>
|
||||
<el-table-column :label="$t('Action')" align="center" width="160">
|
||||
<template slot-scope="scope">
|
||||
<el-tooltip :content="$t('View')" placement="top">
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="onView(scope.row)"></el-button>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="onView(scope.row)" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination">
|
||||
<el-pagination
|
||||
@current-change="onPageChange"
|
||||
@size-change="onPageChange"
|
||||
:current-page.sync="pagination.pageNum"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:page-size.sync="pagination.pageSize"
|
||||
layout="sizes, prev, pager, next"
|
||||
:total="deployList.length">
|
||||
</el-pagination>
|
||||
:total="deployList.length"
|
||||
@current-change="onPageChange"
|
||||
@size-change="onPageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'DeployList',
|
||||
data () {
|
||||
return {
|
||||
pagination: {
|
||||
pageNum: 0,
|
||||
pageSize: 10
|
||||
export default {
|
||||
name: 'DeployList',
|
||||
data() {
|
||||
return {
|
||||
pagination: {
|
||||
pageNum: 0,
|
||||
pageSize: 10
|
||||
},
|
||||
filter: {
|
||||
keyword: ''
|
||||
},
|
||||
// tableData,
|
||||
columns: [
|
||||
// { name: 'version', label: 'Version', width: '180' },
|
||||
// { name: 'ip', label: 'IP', width: '160' },
|
||||
// { name: 'port', label: 'Port', width: '80' },
|
||||
{ name: 'finish_ts', label: 'Time', width: '180' },
|
||||
{ name: 'spider_name', label: 'Spider', width: '180', sortable: true },
|
||||
{ name: 'node_id', label: 'Node', width: 'auto' }
|
||||
],
|
||||
nodeFormRules: {
|
||||
name: [{ required: true, message: 'Required Field', trigger: 'change' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('deploy', [
|
||||
'deployList',
|
||||
'deployForm'
|
||||
]),
|
||||
filteredTableData() {
|
||||
return this.deployList.filter(d => {
|
||||
if (!this.filter.keyword) return true
|
||||
for (let i = 0; i < this.columns.length; i++) {
|
||||
const colName = this.columns[i].name
|
||||
if (d[colName] && d[colName].toLowerCase().indexOf(this.filter.keyword.toLowerCase()) > -1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
.filter((d, index) => {
|
||||
// pagination
|
||||
const { pageNum, pageSize } = this.pagination
|
||||
return (pageSize * (pageNum - 1) <= index) && (index < pageSize * pageNum)
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.dispatch('deploy/getDeployList')
|
||||
},
|
||||
methods: {
|
||||
onSearch(value) {
|
||||
console.log(value)
|
||||
},
|
||||
filter: {
|
||||
keyword: ''
|
||||
onRefresh() {
|
||||
this.$store.dispatch('deploy/getDeployList')
|
||||
this.$st.sendEv('部署', '刷新')
|
||||
},
|
||||
// tableData,
|
||||
columns: [
|
||||
// { name: 'version', label: 'Version', width: '180' },
|
||||
// { name: 'ip', label: 'IP', width: '160' },
|
||||
// { name: 'port', label: 'Port', width: '80' },
|
||||
{ name: 'finish_ts', label: 'Time', width: '180' },
|
||||
{ name: 'spider_name', label: 'Spider', width: '180', sortable: true },
|
||||
{ name: 'node_id', label: 'Node', width: 'auto' }
|
||||
],
|
||||
nodeFormRules: {
|
||||
name: [{ required: true, message: 'Required Field', trigger: 'change' }]
|
||||
onView(row) {
|
||||
this.$router.push(`/deploys/${row._id}`)
|
||||
},
|
||||
onClickSpider(row) {
|
||||
this.$router.push(`/spiders/${row.spider_id}`)
|
||||
},
|
||||
onClickNode(row) {
|
||||
this.$router.push(`/nodes/${row.node_id}`)
|
||||
},
|
||||
onPageChange() {
|
||||
this.$store.dispatch('deploy/getDeployList')
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('deploy', [
|
||||
'deployList',
|
||||
'deployForm'
|
||||
]),
|
||||
filteredTableData () {
|
||||
return this.deployList.filter(d => {
|
||||
if (!this.filter.keyword) return true
|
||||
for (let i = 0; i < this.columns.length; i++) {
|
||||
const colName = this.columns[i].name
|
||||
if (d[colName] && d[colName].toLowerCase().indexOf(this.filter.keyword.toLowerCase()) > -1) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
.filter((d, index) => {
|
||||
// pagination
|
||||
const { pageNum, pageSize } = this.pagination
|
||||
return (pageSize * (pageNum - 1) <= index) && (index < pageSize * pageNum)
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSearch (value) {
|
||||
console.log(value)
|
||||
},
|
||||
onRefresh () {
|
||||
this.$store.dispatch('deploy/getDeployList')
|
||||
this.$st.sendEv('部署', '刷新')
|
||||
},
|
||||
onView (row) {
|
||||
this.$router.push(`/deploys/${row._id}`)
|
||||
},
|
||||
onClickSpider (row) {
|
||||
this.$router.push(`/spiders/${row.spider_id}`)
|
||||
},
|
||||
onClickNode (row) {
|
||||
this.$router.push(`/nodes/${row.node_id}`)
|
||||
},
|
||||
onPageChange () {
|
||||
this.$store.dispatch('deploy/getDeployList')
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.dispatch('deploy/getDeployList')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,37 +1,24 @@
|
||||
<template>
|
||||
<div class="app-container disclaimer">
|
||||
<el-card>
|
||||
<div v-html="text"></div>
|
||||
<div v-html="text" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import showdown from 'showdown'
|
||||
import {
|
||||
mapState
|
||||
} from 'vuex'
|
||||
import showdown from 'showdown'
|
||||
|
||||
export default {
|
||||
name: 'Disclaimer',
|
||||
computed: {
|
||||
...mapState('lang', [
|
||||
'lang'
|
||||
]),
|
||||
text () {
|
||||
if (!this.converter) return ''
|
||||
if (this.lang === 'zh') {
|
||||
return this.converter.makeHtml(this.textZh)
|
||||
} else {
|
||||
return this.converter.makeHtml(this.textEn)
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
const converter = new showdown.Converter()
|
||||
return {
|
||||
converter,
|
||||
textEn: `
|
||||
export default {
|
||||
name: 'Disclaimer',
|
||||
data() {
|
||||
const converter = new showdown.Converter()
|
||||
return {
|
||||
converter,
|
||||
textEn: `
|
||||
# Disclaimer
|
||||
|
||||
This Disclaimer and privacy protection statement (hereinafter referred to as "disclaimer statement" or "this statement") is applicable to the series of software (hereinafter referred to as "crawlab") developed by crawlab development group (hereinafter referred to as "development group") after you read this statement, if you do not agree with any terms in this statement or have doubts about this statement, please stop using our software immediately. If you have started or are using crawlab, you have read and agree to all terms of this statement.
|
||||
@@ -45,7 +32,7 @@ This Disclaimer and privacy protection statement (hereinafter referred to as "di
|
||||
7. Copyright of the system: the crawleb development team owns the intellectual property rights, copyrights, copyrights and use rights for all developed or jointly developed products, which are protected by applicable intellectual property rights, copyrights, trademarks, service trademarks, patents or other laws.
|
||||
8. Communication: any company or individual who publishes or disseminates our software on the Internet is allowed, but the crawlab development team shall not be responsible for any legal and criminal events that may be caused by the company or individual disseminating the software.
|
||||
`,
|
||||
textZh: `
|
||||
textZh: `
|
||||
# 免责声明
|
||||
|
||||
本免责及隐私保护声明(下简称“免责声明”或“本声明”)适用于 Crawlab 开发组 (以下简称“开发组”)研发的系列软件(以下简称"Crawlab") 在您阅读本声明后若不同意此声明中的任何条款,或对本声明存在质疑,请立刻停止使用我们的软件。若您已经开始或正在使用 Crawlab,则表示您已阅读并同意本声明的所有条款之约定。
|
||||
@@ -59,14 +46,28 @@ This Disclaimer and privacy protection statement (hereinafter referred to as "di
|
||||
7. 系统的版权:Crawlab 开发组对所有开发的或合作开发的产品拥有知识产权,著作权,版权和使用权,这些产品受到适用的知识产权、版权、商标、服务商标、专利或其他法律的保护。
|
||||
8. 传播:任何公司或个人在网络上发布,传播我们软件的行为都是允许的,但因公司或个人传播软件可能造成的任何法律和刑事事件 Crawlab 开发组不负任何责任。
|
||||
`
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('lang', [
|
||||
'lang'
|
||||
]),
|
||||
text() {
|
||||
if (!this.converter) return ''
|
||||
if (this.lang === 'zh') {
|
||||
return this.converter.makeHtml(this.textZh)
|
||||
} else {
|
||||
return this.converter.makeHtml(this.textEn)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$request.put('/actions', {
|
||||
type: 'view_disclaimer'
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.$request.put('/actions', {
|
||||
type: 'view_disclaimer'
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user