diff --git a/.env.development.example b/.env.development.example index 409d8e846c..070cbfdf9d 100644 --- a/.env.development.example +++ b/.env.development.example @@ -22,5 +22,6 @@ VUE_APP_LOGOUT_PATH = '/core/auth/logout/' # Dev server for core proxy VUE_APP_CORE_HOST = 'http://localhost:8080' VUE_APP_CORE_WS = 'ws://localhost:8080' -VUE_APP_KAEL_HOST = 'http://localhost:8083' +VUE_APP_KOKO_HOST = 'http://localhost:5000' +VUE_APP_KOKO_WS = 'ws://localhost:5000' VUE_APP_ENV = 'development' diff --git a/.github/workflows/jms-build-test.yml b/.github/workflows/jms-build-test.yml index b5fdf4e604..562b687fed 100644 --- a/.github/workflows/jms-build-test.yml +++ b/.github/workflows/jms-build-test.yml @@ -1,32 +1,46 @@ name: "Run Build Test" on: push: - branches: - - pr@* - - repr@* + paths: + - 'Dockerfile' + - 'Dockerfile*' + - 'package.json' + - 'yarn.lock' jobs: build: runs-on: ubuntu-latest + strategy: + matrix: + component: [lina] + version: [v4] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 - - uses: docker/setup-qemu-action@v2 + - name: Prepare Build + run: | + sed -i 's@registry.npmmirror.com@registry.yarnpkg.com@g' yarn.lock - - uses: docker/setup-buildx-action@v2 + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - - uses: docker/build-push-action@v3 + - name: Build Image + uses: docker/build-push-action@v5 with: context: . - push: false - tags: jumpserver/lina:test + push: true file: Dockerfile + tags: ghcr.io/jumpserver/${{ matrix.component }}:${{ matrix.version }} + platforms: linux/amd64 + build-args: | + VERSION=${{ matrix.version }} + APT_MIRROR=http://deb.debian.org + NPM_REGISTRY=https://registry.yarnpkg.com + outputs: type=image,oci-mediatypes=true,compression=zstd,compression-level=3,force-compression=true cache-from: type=gha cache-to: type=gha,mode=max - - - uses: LouisBrunner/checks-action@v1.5.0 - if: always() - with: - token: ${{ secrets.GITHUB_TOKEN }} - name: Check Build - conclusion: ${{ job.status }} diff --git a/.github/workflows/jms-generic-action-handler.yml b/.github/workflows/jms-generic-action-handler.yml index 3f499cfb9e..450891696e 100644 --- a/.github/workflows/jms-generic-action-handler.yml +++ b/.github/workflows/jms-generic-action-handler.yml @@ -10,3 +10,4 @@ jobs: - uses: jumpserver/action-generic-handler@master env: GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }} + I18N_TOKEN: ${{ secrets.I18N_TOKEN }} diff --git a/Dockerfile b/Dockerfile index 5aa15b003a..b19edd6be7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,30 +7,35 @@ ARG DEPENDENCIES=" \ python3" ARG APT_MIRROR=http://mirrors.ustc.edu.cn -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=lina \ - sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + set -ex \ && rm -f /etc/apt/apt.conf.d/docker-clean \ - && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ + && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \ + && sed -i "s@http://.*.debian.org@${APT_MIRROR}@g" /etc/apt/sources.list \ && apt-get update \ - && apt-get install -y --no-install-recommends ${DEPENDENCIES} \ - && echo "no" | dpkg-reconfigure dash \ - && rm -rf /var/lib/apt/lists/* + && apt-get -y install --no-install-recommends ${DEPENDENCIES} \ + && echo "no" | dpkg-reconfigure dash ARG NPM_REGISTRY="https://registry.npmmirror.com" + RUN set -ex \ && npm config set registry ${NPM_REGISTRY} \ && yarn config set registry ${NPM_REGISTRY} WORKDIR /data -ADD package.json yarn.lock /data -RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=lina \ +RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked \ + --mount=type=bind,source=package.json,target=package.json \ + --mount=type=bind,source=yarn.lock,target=yarn.lock \ yarn install ARG VERSION ENV VERSION=$VERSION + ADD . /data -RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=lina \ + +RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked \ sed -i "s@version-dev@${VERSION}@g" src/layout/components/NavHeader/About.vue \ && yarn build diff --git a/package.json b/package.json index adbd196bfa..0b846687e9 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { - "name": "vue-admin-template", - "version": "4.2.1", - "description": "A vue admin template with Element UI & axios & iconfont & permission control & lint", - "author": "Pan ", - "license": "MIT", + "name": "Lina", + "version": "v4.0.0", + "description": "JumpServer Web UI", + "author": "JumpServer Team ", + "license": "GPL-3.0-or-later", "scripts": { "dev": "vue-cli-service serve", "serve": "vue-cli-service serve", @@ -20,21 +20,23 @@ "vue-i18n-report": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json'", "vue-i18n-report-json": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json' -o /tmp/abc.json", "vue-i18n-report-add-miss": "vue-i18n-extract report -v './src/**/*.?(js|vue)' -l './src/i18n/langs/**/*.json' -a", - "diff-i18n": "python ./src/i18n/langs/i18n-util.py diff en ja", - "apply-i18n": "python ./src/i18n/langs/i18n-util.py apply en ja" + "diff-i18n": "python ./src/i18n/langs/i18n-util.py diff en ja zh_Hant", + "apply-i18n": "python ./src/i18n/langs/i18n-util.py apply en ja zh_Hant" }, "dependencies": { "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@fontsource/open-sans": "^5.0.24", "@traptitech/markdown-it-katex": "^3.6.0", "@ztree/ztree_v3": "3.5.44", - "axios": "0.21.1", + "axios": "0.28.0", "axios-retry": "^3.1.9", "cron-parser": "^4.0.0", "crypto-js": "^4.1.1", "css-color-function": "^1.3.3", + "decimal.js": "^10.4.3", "deepmerge": "^4.2.2", - "echarts": "^4.7.0", - "element-ui": "2.13.2", + "echarts": "4.7.0", + "element-ui": "2.15.14", "eslint-plugin-html": "^6.0.0", "highlight.js": "^11.9.0", "install": "^0.13.0", @@ -63,6 +65,7 @@ "npm": "^7.8.0", "nprogress": "0.2.0", "path-to-regexp": "2.4.0", + "v-sanitize": "^0.0.13", "vue": "2.6.10", "vue-codemirror": "4.0.6", "vue-cookie": "^1.1.4", diff --git a/public/index.html b/public/index.html index 296fc3d9f9..5e4975076e 100644 --- a/public/index.html +++ b/public/index.html @@ -27,6 +27,9 @@ if(pathname.indexOf('/ui') === -1) { window.location.href = window.location.origin + '/ui/#' + pathname } + if (pathname.startsWith('/ui/#/chat')) { + window.location.href = window.location.origin + pathname + } }
diff --git a/public/theme/element-extra.css b/public/theme/element-extra.css index 472cc52e52..61897f1ac3 100644 --- a/public/theme/element-extra.css +++ b/public/theme/element-extra.css @@ -21,32 +21,39 @@ } .el-alert--info.is-light { - background-color: light-9; - color: light-2; + background-color: rgba(255, 255, 255, 0.5); + color: info; border: 1px solid; } .el-alert--info .el-alert__description { - color: light-2; + color: info; +} + +.el-pagination.is-background { + .el-pagination__total, + .el-pagination__sizes, + .el-pager { + color: var(--color-icon-primary); + } } .el-pagination.is-background .el-pager li:not(.disabled):hover { - color: white; + color: #fff; background-color: primary; } .el-pagination.is-background .btn-next, .el-pagination.is-background .btn-prev, .el-pagination.is-background .el-pager li { - margin: 0 5px; - background-color: white; - color: #606266; - min-width: 28px; - border-radius: 2px; - border: 1px solid #DCDFE6; - font-size: 12px; - line-height: 26px; - font-weight: 400; + margin: 0 5px; + background-color: #fff; + color: var(--color-icon-primary); + min-width: 28px; + border-radius: 2px; + border: 1px solid #DCDFE6; + font-size: 12px; + font-weight: 400; } .el-breadcrumb__inner, @@ -85,8 +92,12 @@ td .el-button.el-button--mini { - padding: 1px 5px; + padding: 1px 6px; line-height: 1.5; + + .el-icon--right { + margin-bottom: 2px; + } } .el-tabs__item.is-active, .el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active { @@ -191,7 +202,7 @@ td .el-button.el-button--mini { } .el-input--small .el-input__icon { - line-height: 34px; + line-height: 30px; } .option-group .el-select-dropdown__item.hover, .option-group .el-select-dropdown__item.selected { @@ -224,8 +235,10 @@ td .el-button.el-button--mini { } .el-tag.el-tag--info .el-tag__close { - color: #333333!important; - background-color: inherit; + display: inline-block; + margin-top: 3px; + color: var(--color-text-primary); + background-color: inherit; } .el-tag.el-tag--info.is-hit { @@ -280,7 +293,7 @@ td .el-button.el-button--mini { } .el-textarea__inner { - border-radius: 0; + color: var(--color-text-primary); } .el-pagination.is-background .number { @@ -313,11 +326,11 @@ td .el-button.el-button--mini { .el-tooltip__popper.is-light { background: #FFF; + max-width: 500px; border: 1px solid #e7eaec; -} - -.el-tooltip__popper.is-light .popper__arrow { - border-bottom-color: #e7eaec !important; + box-shadow: 0 1.6px 3.6px 0 rgba(0, 0, 0, .132), 0 .3px .9px 0 rgba(0, 0, 0, .108); + line-height: 1.5; + padding: 10px; } .el-dialog__headerbtn .el-dialog__close { @@ -326,7 +339,7 @@ td .el-button.el-button--mini { } .el-table__header thead tr th { - border-bottom: 1px solid #e7e7e7 !important; + /*border-bottom: 1px solid #e7e7e7 !important;*/ } .el-table .cell, @@ -423,14 +436,34 @@ td .el-button.el-button--mini { flex-direction: column; } +.el-dialog .el-dialog__header .el-dialog__title { + color: var(--color-text-primary); +} + .el-dialog .el-dialog__body { max-height: 80vh; overflow: auto; padding: 30px; } +.el-dialog .el-dialog__body .el-transfer-panel .el-transfer-panel__body .el-input__inner, +.el-dialog .el-dialog__body .el-transfer-panel .el-transfer-panel__header .el-checkbox__label, +.el-dialog .el-dialog__body .el-transfer-panel .el-transfer-panel__body .el-checkbox-group .el-checkbox.el-transfer-panel__item { + color: var(--color-text-primary); +} + +.el-dialog .el-dialog__body .opera .el-button.is-disabled, +.el-dialog .el-dialog__body .el-transfer-panel .vip-footer .el-button.is-disabled { + color: var(--color-input-border); +} + +.el-dialog .el-dialog__body .opera .el-button.is-disabled.el-button--primary { + color: #fff; +} + .el-dialog .el-dialog__body form { padding-right: 20px; + margin-right: 20px; } .el-dialog .el-dialog__footer { diff --git a/src/App.vue b/src/App.vue index 8de84169c5..bd7a05e4a0 100644 --- a/src/App.vue +++ b/src/App.vue @@ -16,6 +16,3 @@ export default { } } - - diff --git a/src/api/component.js b/src/api/component.js new file mode 100644 index 0000000000..84e03a447e --- /dev/null +++ b/src/api/component.js @@ -0,0 +1,9 @@ +import request from '@/utils/request' + +export function getLokiLog(data) { + return request({ + url: `/api/v1/terminal/loki/logs/`, + method: 'get', + params: data + }) +} diff --git a/src/api/ops.js b/src/api/ops.js index d0adf0cd05..7481465cf6 100644 --- a/src/api/ops.js +++ b/src/api/ops.js @@ -53,6 +53,14 @@ export function createJob(form) { }) } +export function StopJob(form) { + return request({ + url: '/api/v1/ops/job-executions/stop/', + method: 'post', + data: form + }) +} + export function JobUploadFile(form) { return request({ url: '/api/v1/ops/jobs/upload/', @@ -62,3 +70,4 @@ export function JobUploadFile(form) { data: form }) } + diff --git a/src/api/users.js b/src/api/users.js index 3482c9e891..095fe7036f 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -12,7 +12,6 @@ export function getProfile(token) { return request({ url: '/api/v1/users/profile/', method: 'get' - // params: { token } }) } diff --git a/src/assets/img/cloud/ali.svg b/src/assets/img/cloud/ali.svg new file mode 100644 index 0000000000..e360533b30 --- /dev/null +++ b/src/assets/img/cloud/ali.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/aws_china.svg b/src/assets/img/cloud/aws_china.svg new file mode 100644 index 0000000000..e843ece9fc --- /dev/null +++ b/src/assets/img/cloud/aws_china.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/aws_international.svg b/src/assets/img/cloud/aws_international.svg new file mode 100644 index 0000000000..e843ece9fc --- /dev/null +++ b/src/assets/img/cloud/aws_international.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/azure_china.svg b/src/assets/img/cloud/azure_china.svg new file mode 100644 index 0000000000..3a01122555 --- /dev/null +++ b/src/assets/img/cloud/azure_china.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/azure_international.svg b/src/assets/img/cloud/azure_international.svg new file mode 100644 index 0000000000..3a01122555 --- /dev/null +++ b/src/assets/img/cloud/azure_international.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/baidu.svg b/src/assets/img/cloud/baidu.svg new file mode 100644 index 0000000000..abe67b875c --- /dev/null +++ b/src/assets/img/cloud/baidu.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/gcp.svg b/src/assets/img/cloud/gcp.svg new file mode 100644 index 0000000000..db9707a0e6 --- /dev/null +++ b/src/assets/img/cloud/gcp.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/huawei.svg b/src/assets/img/cloud/huawei.svg new file mode 100644 index 0000000000..c550ef9794 --- /dev/null +++ b/src/assets/img/cloud/huawei.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/jd.svg b/src/assets/img/cloud/jd.svg new file mode 100644 index 0000000000..3ccb2ba9a6 --- /dev/null +++ b/src/assets/img/cloud/jd.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/kingsoft.svg b/src/assets/img/cloud/kingsoft.svg new file mode 100644 index 0000000000..69617b44fa --- /dev/null +++ b/src/assets/img/cloud/kingsoft.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/lan.svg b/src/assets/img/cloud/lan.svg new file mode 100644 index 0000000000..e60791d0bc --- /dev/null +++ b/src/assets/img/cloud/lan.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/nutanix.svg b/src/assets/img/cloud/nutanix.svg new file mode 100644 index 0000000000..3e3eaf8a20 --- /dev/null +++ b/src/assets/img/cloud/nutanix.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/openstack.svg b/src/assets/img/cloud/openstack.svg new file mode 100644 index 0000000000..76da013ef5 --- /dev/null +++ b/src/assets/img/cloud/openstack.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/qcloud.svg b/src/assets/img/cloud/qcloud.svg new file mode 100644 index 0000000000..559e4cdef0 --- /dev/null +++ b/src/assets/img/cloud/qcloud.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/qcloud_lighthouse.svg b/src/assets/img/cloud/qcloud_lighthouse.svg new file mode 100644 index 0000000000..559e4cdef0 --- /dev/null +++ b/src/assets/img/cloud/qcloud_lighthouse.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/qing.svg b/src/assets/img/cloud/qing.svg new file mode 100644 index 0000000000..b5e2da4af9 --- /dev/null +++ b/src/assets/img/cloud/qing.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/scp.svg b/src/assets/img/cloud/scp.svg new file mode 100644 index 0000000000..1141bcb3cd --- /dev/null +++ b/src/assets/img/cloud/scp.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/state.svg b/src/assets/img/cloud/state.svg new file mode 100644 index 0000000000..2d9343fd07 --- /dev/null +++ b/src/assets/img/cloud/state.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/ucloud.svg b/src/assets/img/cloud/ucloud.svg new file mode 100644 index 0000000000..8ca934a89d --- /dev/null +++ b/src/assets/img/cloud/ucloud.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/vmware.svg b/src/assets/img/cloud/vmware.svg new file mode 100644 index 0000000000..76928e52c7 --- /dev/null +++ b/src/assets/img/cloud/vmware.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/volcengine.svg b/src/assets/img/cloud/volcengine.svg new file mode 100644 index 0000000000..ffb5a70b9c --- /dev/null +++ b/src/assets/img/cloud/volcengine.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/cloud/zstack.svg b/src/assets/img/cloud/zstack.svg new file mode 100644 index 0000000000..cb6502f8b5 --- /dev/null +++ b/src/assets/img/cloud/zstack.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/icons/bsd.png b/src/assets/img/icons/bsd.png new file mode 100644 index 0000000000..846a01fa3e Binary files /dev/null and b/src/assets/img/icons/bsd.png differ diff --git a/src/assets/img/icons/cisco.png b/src/assets/img/icons/cisco.png new file mode 100644 index 0000000000..9ce6827e99 Binary files /dev/null and b/src/assets/img/icons/cisco.png differ diff --git a/src/assets/img/icons/clickhouse.png b/src/assets/img/icons/clickhouse.png new file mode 100644 index 0000000000..cf4a0a5b67 Binary files /dev/null and b/src/assets/img/icons/clickhouse.png differ diff --git a/src/assets/img/icons/db2.png b/src/assets/img/icons/db2.png new file mode 100644 index 0000000000..c7c8bcda75 Binary files /dev/null and b/src/assets/img/icons/db2.png differ diff --git a/src/assets/img/icons/gateway.png b/src/assets/img/icons/gateway.png new file mode 100644 index 0000000000..2ca2415270 Binary files /dev/null and b/src/assets/img/icons/gateway.png differ diff --git a/src/assets/img/icons/general.png b/src/assets/img/icons/general.png new file mode 100644 index 0000000000..f6dc155684 Binary files /dev/null and b/src/assets/img/icons/general.png differ diff --git a/src/assets/img/icons/gpt.png b/src/assets/img/icons/gpt.png new file mode 100644 index 0000000000..23057514e6 Binary files /dev/null and b/src/assets/img/icons/gpt.png differ diff --git a/src/assets/img/icons/huawei.png b/src/assets/img/icons/huawei.png new file mode 100644 index 0000000000..7e47247c5d Binary files /dev/null and b/src/assets/img/icons/huawei.png differ diff --git a/src/assets/img/icons/k8s.png b/src/assets/img/icons/k8s.png new file mode 100644 index 0000000000..27d10679ee Binary files /dev/null and b/src/assets/img/icons/k8s.png differ diff --git a/src/assets/img/icons/linux.png b/src/assets/img/icons/linux.png new file mode 100644 index 0000000000..0967cbe1ba Binary files /dev/null and b/src/assets/img/icons/linux.png differ diff --git a/src/assets/img/icons/macos.png b/src/assets/img/icons/macos.png new file mode 100644 index 0000000000..46c1bbb9fb Binary files /dev/null and b/src/assets/img/icons/macos.png differ diff --git a/src/assets/img/icons/mariadb.png b/src/assets/img/icons/mariadb.png new file mode 100644 index 0000000000..cca0b5941a Binary files /dev/null and b/src/assets/img/icons/mariadb.png differ diff --git a/src/assets/img/icons/mongodb.png b/src/assets/img/icons/mongodb.png new file mode 100644 index 0000000000..a19b347602 Binary files /dev/null and b/src/assets/img/icons/mongodb.png differ diff --git a/src/assets/img/icons/mysql.png b/src/assets/img/icons/mysql.png new file mode 100644 index 0000000000..982c7edfc6 Binary files /dev/null and b/src/assets/img/icons/mysql.png differ diff --git a/src/assets/img/icons/oracle.png b/src/assets/img/icons/oracle.png new file mode 100644 index 0000000000..003041b33f Binary files /dev/null and b/src/assets/img/icons/oracle.png differ diff --git a/src/assets/img/icons/other.png b/src/assets/img/icons/other.png new file mode 100644 index 0000000000..312678ad2c Binary files /dev/null and b/src/assets/img/icons/other.png differ diff --git a/src/assets/img/icons/postgresql.png b/src/assets/img/icons/postgresql.png new file mode 100644 index 0000000000..f9198bb403 Binary files /dev/null and b/src/assets/img/icons/postgresql.png differ diff --git a/src/assets/img/icons/private.png b/src/assets/img/icons/private.png new file mode 100644 index 0000000000..ecb0cc1e7b Binary files /dev/null and b/src/assets/img/icons/private.png differ diff --git a/src/assets/img/icons/redis.png b/src/assets/img/icons/redis.png new file mode 100644 index 0000000000..ec869e4525 Binary files /dev/null and b/src/assets/img/icons/redis.png differ diff --git a/src/assets/img/icons/sqlserver.png b/src/assets/img/icons/sqlserver.png new file mode 100644 index 0000000000..421955984a Binary files /dev/null and b/src/assets/img/icons/sqlserver.png differ diff --git a/src/assets/img/icons/unix.png b/src/assets/img/icons/unix.png new file mode 100644 index 0000000000..30fbcb4c1a Binary files /dev/null and b/src/assets/img/icons/unix.png differ diff --git a/src/assets/img/icons/vmware.png b/src/assets/img/icons/vmware.png new file mode 100644 index 0000000000..61da1658cd Binary files /dev/null and b/src/assets/img/icons/vmware.png differ diff --git a/src/assets/img/icons/website.png b/src/assets/img/icons/website.png new file mode 100644 index 0000000000..eaed85be5b Binary files /dev/null and b/src/assets/img/icons/website.png differ diff --git a/src/assets/img/icons/windows.png b/src/assets/img/icons/windows.png new file mode 100644 index 0000000000..d34b295eaf Binary files /dev/null and b/src/assets/img/icons/windows.png differ diff --git a/src/components/ActionsGroup/index.vue b/src/components/ActionsGroup/index.vue index 9c430e4e10..f7ba615dcb 100644 --- a/src/components/ActionsGroup/index.vue +++ b/src/components/ActionsGroup/index.vue @@ -4,6 +4,7 @@ - - diff --git a/src/components/Apps/AccountCreateUpdateForm/const.js b/src/components/Apps/AccountCreateUpdateForm/const.js new file mode 100644 index 0000000000..bbfcc4d9c7 --- /dev/null +++ b/src/components/Apps/AccountCreateUpdateForm/const.js @@ -0,0 +1,196 @@ +import { UpdateToken, UploadSecret } from '@/components/Form/FormFields' +import Select2 from '@/components/Form/FormFields/Select2.vue' +import { Required, RequiredChange } from '@/components/Form/DataForm/rules' +import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue' + +export const accountFieldsMeta = (vm) => { + const defaultPrivilegedAccounts = ['root', 'administrator'] + return { + assets: { + component: Select2, + label: vm.$t('Assets'), + rules: [Required], + el: { + multiple: true, + ajax: { + url: '/api/v1/assets/assets/', + transformOption: (item) => { + return { label: item.name + '(' + item.address + ')', value: item.id } + } + } + }, + hidden: () => { + return vm.platform || vm.asset + } + }, + template: { + component: Select2, + rules: [Required], + el: { + multiple: false, + ajax: { + url: '/api/v1/accounts/account-templates/', + transformOption: (item) => { + return { label: item.name, value: item.id } + } + } + }, + hidden: () => { + return vm.platform || vm.asset || !vm.addTemplate + } + }, + on_invalid: { + rules: [Required], + label: vm.$t('AccountPolicy'), + helpTip: vm.$t('AccountPolicyHelpText'), + hidden: () => { + return vm.platform || vm.asset + } + }, + name: { + label: vm.$t('Name'), + rules: [RequiredChange], + on: { + input: ([value], updateForm) => { + if (!vm.usernameChanged) { + if (!vm.account?.name) { + updateForm({ username: value }) + } + const maybePrivileged = defaultPrivilegedAccounts.includes(value) + if (maybePrivileged) { + updateForm({ privileged: true }) + } + } + } + }, + hidden: () => { + return vm.addTemplate + } + }, + username: { + el: { + disabled: !!vm.account?.name + }, + on: { + input: ([value], updateForm) => { + vm.usernameChanged = true + }, + change: ([value], updateForm) => { + const maybePrivileged = defaultPrivilegedAccounts.includes(value) + if (maybePrivileged) { + updateForm({ privileged: true }) + } + } + }, + hidden: () => { + return vm.addTemplate + } + }, + privileged: { + label: vm.$t('Privileged'), + hidden: () => { + return vm.addTemplate + } + }, + su_from: { + component: Select2, + hidden: (formValue) => { + return !vm.asset?.id || !vm.iPlatform.su_enabled + }, + el: { + multiple: false, + clearable: true, + ajax: { + url: `/api/v1/accounts/accounts/su-from-accounts/?account=${vm.account?.id || ''}&asset=${vm.asset?.id || ''}`, + transformOption: (item) => { + return { label: `${item.name}(${item.username})`, value: item.id } + } + } + } + }, + su_from_username: { + label: vm.$t('UserSwitchFrom'), + hidden: (formValue) => { + return vm.platform || vm.asset || vm.addTemplate + } + }, + password: { + label: vm.$t('Password'), + component: UpdateToken, + hidden: (formValue) => { + return formValue.secret_type !== 'password' || vm.addTemplate + } + }, + ssh_key: { + label: vm.$t('PrivateKey'), + component: UploadSecret, + hidden: (formValue) => formValue.secret_type !== 'ssh_key' || vm.addTemplate + }, + passphrase: { + label: vm.$t('Passphrase'), + component: UpdateToken, + hidden: (formValue) => formValue.secret_type !== 'ssh_key' || vm.addTemplate + }, + token: { + label: vm.$t('Token'), + component: UploadSecret, + hidden: (formValue) => formValue.secret_type !== 'token' || vm.addTemplate + }, + access_key: { + id: 'access_key', + label: vm.$t('AccessKey'), + component: UploadSecret, + hidden: (formValue) => formValue.secret_type !== 'access_key' || vm.addTemplate + }, + api_key: { + id: 'api_key', + label: vm.$t('ApiKey'), + component: UploadSecret, + hidden: (formValue) => formValue.secret_type !== 'api_key' || vm.addTemplate + }, + secret_type: { + type: 'radio-group', + options: [], + hidden: () => { + return vm.addTemplate + } + }, + push_now: { + helpTip: vm.$t('WindowsPushHelpText'), + hidden: (formValue) => { + const automation = vm.iPlatform.automation || {} + return !automation.push_account_enabled || + !automation.ansible_enabled || + !vm.$hasPerm('accounts.push_account') || + (formValue.secret_type === 'ssh_key' && vm.iPlatform.type.value === 'windows') || + vm.addTemplate + } + }, + params: { + label: vm.$t('PushParams'), + component: AutomationParamsForm, + el: {}, + hidden: (formValue) => { + const automation = vm.iPlatform.automation || {} + vm.fieldsMeta.params.el.method = vm.iPlatform.automation.push_account_method + vm.fieldsMeta.params.el.pushAccountParams = vm.iPlatform.automation.push_account_params + return !formValue.push_now || + !automation.push_account_enabled || + !automation.ansible_enabled || + (formValue.secret_type === 'ssh_key' && + vm.iPlatform.type.value === 'windows') || + !vm.$hasPerm('accounts.push_account') || + vm.addTemplate + } + }, + is_active: { + label: vm.$t('IsActive') + }, + comment: { + label: vm.$t('Comment'), + hidden: () => { + return vm.addTemplate + } + } + } +} diff --git a/src/components/Apps/AccountCreateUpdateForm/index.vue b/src/components/Apps/AccountCreateUpdateForm/index.vue index d0393e0c06..e3c138fc67 100644 --- a/src/components/Apps/AccountCreateUpdateForm/index.vue +++ b/src/components/Apps/AccountCreateUpdateForm/index.vue @@ -2,6 +2,8 @@ @@ -9,12 +11,8 @@ - diff --git a/src/components/Apps/AccountListTable/AccountBulkUpdateDialog.vue b/src/components/Apps/AccountListTable/AccountBulkUpdateDialog.vue new file mode 100644 index 0000000000..72afcbe819 --- /dev/null +++ b/src/components/Apps/AccountListTable/AccountBulkUpdateDialog.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/components/Apps/AccountListTable/AccountCreateUpdate.vue b/src/components/Apps/AccountListTable/AccountCreateUpdate.vue index f708a82624..3210fcb875 100644 --- a/src/components/Apps/AccountListTable/AccountCreateUpdate.vue +++ b/src/components/Apps/AccountListTable/AccountCreateUpdate.vue @@ -8,6 +8,7 @@ :title="title" :visible.sync="iVisible" v-bind="$attrs" + width="900px" v-on="$listeners" > { this.iVisible = false this.$emit('add', true) - this.$message.success(this.$tc('common.updateSuccessMsg')) + this.$message.success(this.$tc('UpdateSuccessMsg')) }).catch(error => this.setFieldError(error)) }, handleResult(resp, error) { @@ -126,7 +127,7 @@ export default { } if (!bulkCreate) { if (!error) { - this.$message.success(this.$tc('common.createSuccessMsg')) + this.$message.success(this.$tc('CreateSuccessMsg')) } else { this.setFieldError(error) } @@ -171,7 +172,3 @@ export default { } } - - diff --git a/src/components/Apps/AccountListTable/AccountList.vue b/src/components/Apps/AccountListTable/AccountList.vue index fe50f58a85..bf1184a3bd 100644 --- a/src/components/Apps/AccountListTable/AccountList.vue +++ b/src/components/Apps/AccountListTable/AccountList.vue @@ -27,7 +27,7 @@ :account="account" :add-template="true" :asset="iAsset" - :title="accountCreateUpdateTitle" + :title="accountCreateByTemplateTitle" :visible.sync="showAddTemplateDialog" @add="addAccountSuccess" @bulk-create-done="showBulkCreateResult($event)" @@ -36,6 +36,13 @@ v-if="showResultDialog" :result="createAccountResults" :visible.sync="showResultDialog" + @close-all="closeAll" + /> + @@ -49,10 +56,12 @@ import AccountCreateUpdate from './AccountCreateUpdate.vue' import { connectivityMeta } from './const' import { openTaskPage } from '@/utils/jms' import ResultDialog from './BulkCreateResultDialog.vue' +import AccountBulkUpdateDialog from '@/components/Apps/AccountListTable/AccountBulkUpdateDialog.vue' export default { name: 'AccountListTable', components: { + AccountBulkUpdateDialog, ResultDialog, ListTable, UpdateSecretInfo, @@ -110,13 +119,16 @@ export default { columnsDefault: { type: Array, default: () => ([ - 'name', 'username', 'asset', 'privileged', - 'secret_type', 'is_active', 'date_updated' + 'name', 'username', 'asset', 'date_updated' ]) }, headerExtraActions: { type: Array, default: () => [] + }, + extraQuery: { + type: Object, + default: () => ({}) } }, data() { @@ -128,7 +140,8 @@ export default { showAddDialog: false, showAddTemplateDialog: false, createAccountResults: [], - accountCreateUpdateTitle: this.$t('assets.AddAccount'), + accountCreateUpdateTitle: this.$t('AddAccount'), + accountCreateByTemplateTitle: this.$t('AddAccountByTemplate'), iAsset: this.asset, account: {}, secretUrl: '', @@ -138,9 +151,7 @@ export default { app: 'assets', resource: 'account' }, - extraQuery: { - order: '-date_updated' - }, + extraQuery: this.extraQuery, columnsExclude: ['spec_info'], columnsShow: { min: ['name', 'username', 'actions'], @@ -148,6 +159,7 @@ export default { }, columnsMeta: { name: { + width: '120px', formatter: function(row) { const to = { name: 'AssetAccountDetail', @@ -161,7 +173,6 @@ export default { } }, asset: { - label: this.$t('assets.Asset'), formatter: function(row) { const to = { name: 'AssetDetail', @@ -174,8 +185,10 @@ export default { } } }, + username: { + width: '120px' + }, secret_type: { - width: '100px', formatter: function(row) { return row.secret_type.label } @@ -186,17 +199,16 @@ export default { } }, has_secret: { - width: '100px', + width: '120px', formatterArgs: { showFalse: false } }, privileged: { - label: this.$t('assets.Privileged'), width: '120px', formatterArgs: { showText: false, - showFalse: false + showFalse: true } }, connectivity: connectivityMeta, @@ -206,11 +218,11 @@ export default { hasUpdate: false, // can set function(row, value) hasDelete: false, // can set function(row, value) hasClone: this.hasClone, - moreActionsTitle: this.$t('common.More'), + moreActionsTitle: this.$t('More'), extraActions: [ { name: 'View', - title: this.$t('common.View'), + title: this.$t('View'), can: this.$hasPerm('accounts.view_accountsecret'), type: 'primary', callback: ({ row }) => { @@ -224,25 +236,29 @@ export default { } }, { - name: 'ClearSecret', - title: this.$t('common.ClearSecret'), - can: this.$hasPerm('accounts.change_account'), - type: 'primary', + name: 'Update', + title: this.$t('Edit'), + can: this.$hasPerm('accounts.change_account') && !this.$store.getters.currentOrgIsRoot, callback: ({ row }) => { - this.$axios.patch( - `/api/v1/accounts/accounts/clear-secret/`, - { account_ids: [row.id] } - ).then(() => { - this.$message.success(this.$tc('common.ClearSuccessMsg')) + const data = { + ...this.asset, + ...row.asset + } + vm.account = row + vm.iAsset = data + vm.showAddDialog = false + vm.accountCreateUpdateTitle = this.$t('UpdateAccount') + setTimeout(() => { + vm.showAddDialog = true }) } }, { name: 'Test', - title: this.$t('common.Test'), + title: this.$t('Test'), can: ({ row }) => !this.$store.getters.currentOrgIsRoot && - this.$hasPerm('accounts.change_account') && + this.$hasPerm('accounts.verify_account') && row.asset['auto_config'].ansible_enabled && row.asset['auto_config'].ping_enabled, callback: ({ row }) => { @@ -255,20 +271,16 @@ export default { } }, { - name: 'Update', - title: this.$t('common.Update'), - can: this.$hasPerm('accounts.change_account') && !this.$store.getters.currentOrgIsRoot, + name: 'ClearSecret', + title: this.$t('ClearSecret'), + can: this.$hasPerm('accounts.change_account'), + type: 'primary', callback: ({ row }) => { - const data = { - ...this.asset, - ...row.asset - } - vm.account = row - vm.iAsset = data - vm.showAddDialog = false - vm.accountCreateUpdateTitle = this.$t('assets.UpdateAccount') - setTimeout(() => { - vm.showAddDialog = true + this.$axios.patch( + `/api/v1/accounts/accounts/clear-secret/`, + { account_ids: [row.id] } + ).then(() => { + this.$message.success(this.$tc('ClearSuccessMsg')) }) } } @@ -295,7 +307,7 @@ export default { exportOptions: { url: this.exportUrl, mfaVerifyRequired: true, - tips: this.$t('accounts.AccountExportTips') + tips: this.$t('AccountExportTips') }, importOptions: { canImportCreate: this.$hasPerm('accounts.add_account'), @@ -304,8 +316,9 @@ export default { extraActions: [ { name: 'add', - title: this.$t('common.Add'), + title: this.$t('Create'), type: 'primary', + icon: 'plus', can: () => { return vm.$hasPerm('accounts.add_account') && !this.$store.getters.currentOrgIsRoot }, @@ -314,14 +327,13 @@ export default { setTimeout(() => { vm.iAsset = this.asset vm.account = {} - vm.accountCreateUpdateTitle = this.$t('assets.AddAccount') vm.showAddDialog = true }) } }, { name: 'add-template', - title: this.$t('common.TemplateAdd'), + title: this.$t('TemplateAdd'), has: !(this.platform || this.asset), can: () => { return vm.$hasPerm('accounts.add_account') && !this.$store.getters.currentOrgIsRoot @@ -331,7 +343,6 @@ export default { setTimeout(() => { vm.iAsset = this.asset vm.account = {} - vm.accountCreateUpdateTitle = this.$t('assets.AddAccount') vm.showAddTemplateDialog = true }) } @@ -340,23 +351,63 @@ export default { ], extraMoreActions: [ { - name: 'ClearSecrets', - title: this.$t('common.ClearSecret'), + name: 'TestSelected', + title: this.$t('TestSelected'), type: 'primary', - fa: 'clean', + icon: 'fa-link', + can: ({ selectedRows }) => { + return selectedRows.length > 0 && + ['clickhouse', 'redis', 'website', 'chatgpt'].indexOf(selectedRows[0].asset.type.value) === -1 && + !this.$store.getters.currentOrgIsRoot + }, + callback: function({ selectedRows }) { + const ids = selectedRows.map(v => { + return v.id + }) + this.$axios.post( + '/api/v1/accounts/accounts/tasks/', + { action: 'verify', accounts: ids }).then(res => { + openTaskPage(res['task']) + }).catch(err => { + this.$message.error(this.$tc('BulkVerifyErrorMsg' + ' ' + err)) + }) + }.bind(this) + }, + { + name: 'BatchClearSecret', + title: this.$t('ClearSecret'), + type: 'primary', + icon: 'clean', can: ({ selectedRows }) => { return selectedRows.length > 0 && vm.$hasPerm('accounts.change_account') }, callback: function({ selectedRows }) { - const ids = selectedRows.map(v => { return v.id }) + const ids = selectedRows.map(v => { + return v.id + }) this.$axios.patch( '/api/v1/accounts/accounts/clear-secret/', { account_ids: ids }).then(() => { - this.$message.success(this.$tc('common.ClearSuccessMsg')) + this.$message.success(this.$tc('ClearSuccessMsg')) }).catch(err => { - this.$message.error(this.$tc('common.bulkClearErrorMsg' + ' ' + err)) + this.$message.error(this.$tc('ClearErrorMsg' + ' ' + err)) }) }.bind(this) + }, + { + name: 'UpdateSelected', + title: this.$t('UpdateSelected'), + icon: 'batch-update', + can: ({ selectedRows }) => { + return selectedRows.length > 0 && + !this.$store.getters.currentOrgIsRoot && + vm.$hasPerm('accounts.change_account') && + selectedRows.every(i => i.secret_type.value === selectedRows[0].secret_type.value) + }, + callback: ({ selectedRows }) => { + vm.updateSelectedDialogSetting.selectedRows = selectedRows + vm.updateSelectedDialogSetting.visible = true + } } ], canBulkDelete: vm.$hasPerm('accounts.delete_account'), @@ -365,6 +416,10 @@ export default { exclude: ['asset'] }, hasSearch: true + }, + updateSelectedDialogSetting: { + visible: false, + selectedRows: [] } } }, @@ -388,12 +443,12 @@ export default { this.tableConfig.columnsMeta.actions.formatterArgs.extraActions.push( { name: 'Delete', - title: this.$t('common.Delete'), + title: this.$t('Delete'), can: this.$hasPerm('accounts.delete_account'), type: 'primary', callback: ({ row }) => { - const msg = this.$t('accounts.AccountDeleteConfirmMsg') - this.$confirm(msg, this.$tc('common.Info'), { + const msg = this.$t('AccountDeleteConfirmMsg') + this.$confirm(msg, this.$tc('Info'), { type: 'warning', confirmButtonClass: 'el-button--danger', beforeClose: async(action, instance, done) => { @@ -401,7 +456,7 @@ export default { this.$axios.delete(`/api/v1/accounts/accounts/${row.id}/`).then(() => { done() this.$refs.ListTable.reloadTable() - this.$message.success(this.$tc('common.deleteSuccessMsg')) + this.$message.success(this.$tc('DeleteSuccessMsg')) }) } }) @@ -410,6 +465,12 @@ export default { ) } }, + activated() { + // 由于组件嵌套较深,有可能导致 Error in activated hook: "TypeError: Cannot read properties of undefined (reading 'getList')" 的问题 + setTimeout(() => { + this.refresh() + }, 300) + }, methods: { onUpdateAuthDone(account) { Object.assign(this.account, account) @@ -427,11 +488,27 @@ export default { this.$refs.ListTable.reloadTable() }, showBulkCreateResult(results) { - this.showResultDialog = false - this.createAccountResults = results setTimeout(() => { this.showResultDialog = true - }, 100) + this.createAccountResults = results + }, 350) + }, + handleAccountBulkUpdate() { + this.updateSelectedDialogSetting.visible = false + this.$refs.ListTable.reloadTable() + }, + closeAll() { + setTimeout(() => { + this.showResultDialog = false + }, 350) + + setTimeout(() => { + this.showAddDialog = false + }, 800) + + setTimeout(() => { + this.refresh() + }, 1000) } } } diff --git a/src/components/Apps/AccountListTable/BulkCreateResultDialog.vue b/src/components/Apps/AccountListTable/BulkCreateResultDialog.vue index ed676e542f..3c9770c5e0 100644 --- a/src/components/Apps/AccountListTable/BulkCreateResultDialog.vue +++ b/src/components/Apps/AccountListTable/BulkCreateResultDialog.vue @@ -30,11 +30,11 @@ export default { } }, data() { - const errorProp = this.$t('common.Error') + const errorProp = this.$t('Error') const stateMap = { - 'created': this.$tc('common.Created'), - 'updated': this.$tc('common.Updated'), - 'skipped': this.$tc('common.Skipped') + 'created': this.$tc('Created'), + 'updated': this.$tc('Updated'), + 'skipped': this.$tc('Skipped') } const stateClsMap = { 'created': 'color-primary', @@ -42,16 +42,16 @@ export default { 'skipped': 'color-default' } return { - title: this.$t('accounts.AddAccountResult'), + title: this.$t('AddAccountResult'), config: { columns: [ { prop: 'asset', - label: this.$t('assets.Asset') + label: this.$t('Asset') }, { prop: 'state', - label: this.$t('common.Status'), + label: this.$t('Status'), width: '200px', formatter: (row) => { if (row.error) { @@ -71,11 +71,11 @@ export default { computed: { summary() { const labels = { - total: this.$tc('common.Total'), - created: this.$tc('common.Created'), - updated: this.$tc('common.Updated'), - skipped: this.$tc('common.Skipped'), - error: this.$tc('common.Error') + total: this.$tc('Total'), + created: this.$tc('Created'), + updated: this.$tc('Updated'), + skipped: this.$tc('Skipped'), + error: this.$tc('Error') } const grouped = _.groupBy(this.result, 'state') const groupedLength = _.mapValues(grouped, 'length') @@ -91,7 +91,7 @@ export default { }, methods: { closeDialog() { - this.$emit('update:visible', false) + this.$emit('close-all') } } } diff --git a/src/components/Apps/AccountListTable/PasswordHistoryDialog.vue b/src/components/Apps/AccountListTable/PasswordHistoryDialog.vue index 74d6163255..2aa7b6f2a3 100644 --- a/src/components/Apps/AccountListTable/PasswordHistoryDialog.vue +++ b/src/components/Apps/AccountListTable/PasswordHistoryDialog.vue @@ -23,7 +23,7 @@ export default { data() { return { config: { - title: this.$t('accounts.HistoryPassword'), + title: this.$t('HistoryPassword'), visible: false, width: '60%', tableConfig: { @@ -32,7 +32,7 @@ export default { columns: ['secret', 'version', 'history_date'], columnsMeta: { secret: { - label: this.$t('assets.Password'), + label: this.$t('Password'), formatter: ShowKeyCopyFormatter, formatterArgs: { hasDownload: false, @@ -40,7 +40,7 @@ export default { } }, history_date: { - label: this.$t('accounts.HistoryDate') + label: this.$t('HistoryDate') }, secret_type: { width: '200px' diff --git a/src/components/Apps/AccountListTable/RemoveAccount.vue b/src/components/Apps/AccountListTable/RemoveAccount.vue index 1f54c31d78..37cd0532c8 100644 --- a/src/components/Apps/AccountListTable/RemoveAccount.vue +++ b/src/components/Apps/AccountListTable/RemoveAccount.vue @@ -65,7 +65,7 @@ export default { diff --git a/src/components/Apps/AutomationParams/index.vue b/src/components/Apps/AutomationParams/index.vue index ef358a58dd..c35b6dd96b 100644 --- a/src/components/Apps/AutomationParams/index.vue +++ b/src/components/Apps/AutomationParams/index.vue @@ -7,7 +7,7 @@ type="primary" @click="onOpenDialog" > - {{ $tc('common.Setting') }} + {{ $tc('Setting') }} - + + + diff --git a/src/components/Apps/ChatAi/components/ChitChat/ChatInput.vue b/src/components/Apps/ChatAi/components/ChitChat/ChatInput.vue index a39d290f45..a3ed109552 100644 --- a/src/components/Apps/ChatAi/components/ChitChat/ChatInput.vue +++ b/src/components/Apps/ChatAi/components/ChitChat/ChatInput.vue @@ -12,31 +12,31 @@ -
- - - -
diff --git a/src/components/Apps/ManyJsonTabs/AssetJsonTab.vue b/src/components/Apps/ManyJsonTabs/AssetJsonTab.vue index fa096958a3..951b0c26e2 100644 --- a/src/components/Apps/ManyJsonTabs/AssetJsonTab.vue +++ b/src/components/Apps/ManyJsonTabs/AssetJsonTab.vue @@ -37,7 +37,7 @@ export default { ], columnsMeta: { name: { - label: this.$t('assets.Asset'), + label: this.$t('Asset'), formatter: (row) => { const to = { name: 'AssetDetail', diff --git a/src/components/Apps/ManyJsonTabs/UserJsonTab.vue b/src/components/Apps/ManyJsonTabs/UserJsonTab.vue index 39ede24899..c6daf47b98 100644 --- a/src/components/Apps/ManyJsonTabs/UserJsonTab.vue +++ b/src/components/Apps/ManyJsonTabs/UserJsonTab.vue @@ -18,7 +18,8 @@ export default { props: { object: { type: Object, - default: () => {} + default: () => { + } } }, data() { @@ -38,7 +39,8 @@ export default { ], columnsMeta: { name: { - label: this.$t('common.Name'), + label: this.$t('Name'), + width: 85, formatter: (row) => { const to = { name: 'UserDetail', @@ -52,7 +54,7 @@ export default { } }, system_roles: { - label: this.$t('users.SystemRoles'), + label: this.$t('SystemRoles'), formatter: (row) => { return row['system_roles'].map(item => item['display_name']).join(', ') || '-' }, @@ -60,7 +62,7 @@ export default { columnKey: 'system_roles' }, org_roles: { - label: this.$t('users.OrgRoles'), + label: this.$t('OrgRoles'), formatter: (row) => { return row['org_roles'].map(item => item['display_name']).join(', ') || '-' }, diff --git a/src/components/Apps/ResourceActivity/index.vue b/src/components/Apps/ResourceActivity/index.vue index 5947aa3d28..f4db72f5f2 100644 --- a/src/components/Apps/ResourceActivity/index.vue +++ b/src/components/Apps/ResourceActivity/index.vue @@ -1,7 +1,7 @@ @@ -33,6 +33,7 @@ import IBox from '@/components/IBox/index.vue' import DiffDetail from '@/components/Dialog/DiffDetail.vue' import { openTaskPage } from '@/utils/jms' +import { toSafeLocalDateStr } from '@/utils/time' export default { name: 'ResourceActivity', @@ -49,11 +50,11 @@ export default { data() { return { activityUrl: `/api/v1/audits/activities/?resource_id=${this.object.id}`, - title: `${this.$t('common.Activity')} - ${this.$t('common.Last30')}`, + title: `${this.$t('Last30')}`, activities: [ { - content: this.$t('common.Now'), - timestamp: this.$moment().format('YYYY-MM-DD HH:mm:ss'), + content: this.$t('Now'), + timestamp: toSafeLocalDateStr(this.$moment()), type: 'primary' } ] @@ -65,9 +66,11 @@ export default { methods: { getActivities() { this.$axios.get(this.activityUrl).then(res => { - for (const i in res) { - this.activities.push(res[i]) - } + const activities = res || [] + activities.forEach(activity => { + activity.timestamp = toSafeLocalDateStr(activity.timestamp) + this.activities.push(activity) + }) }) }, onClick(activity) { diff --git a/src/components/Apps/UserConfirmDialog/index.vue b/src/components/Apps/UserConfirmDialog/index.vue index f9f7d31f74..928efa3d14 100644 --- a/src/components/Apps/UserConfirmDialog/index.vue +++ b/src/components/Apps/UserConfirmDialog/index.vue @@ -16,7 +16,7 @@ - {{ this.$t('auth.ReLogin') }} + {{ this.$t('ReLogin') }} @@ -50,7 +50,7 @@ - + @@ -73,7 +73,7 @@ - {{ this.$t('common.Confirm') }} + {{ this.$t('Confirm') }} @@ -82,6 +82,7 @@ diff --git a/src/components/Cards/DetailCard/ItemValue.vue b/src/components/Cards/DetailCard/ItemValue.vue index 0a6345d883..f8b76c4a72 100644 --- a/src/components/Cards/DetailCard/ItemValue.vue +++ b/src/components/Cards/DetailCard/ItemValue.vue @@ -1,5 +1,5 @@ diff --git a/src/components/Cards/Formatters/LabelsDetailFormatter.vue b/src/components/Cards/Formatters/LabelsDetailFormatter.vue new file mode 100644 index 0000000000..44aee28f09 --- /dev/null +++ b/src/components/Cards/Formatters/LabelsDetailFormatter.vue @@ -0,0 +1,32 @@ + + + + + diff --git a/src/components/Cards/RelationCard/index.vue b/src/components/Cards/RelationCard/index.vue index 230eee6b4b..f1143e4dbe 100644 --- a/src/components/Cards/RelationCard/index.vue +++ b/src/components/Cards/RelationCard/index.vue @@ -1,23 +1,23 @@ diff --git a/src/components/Dialog/DiffDetail.vue b/src/components/Dialog/DiffDetail.vue index 2debb89e19..f15d29607b 100644 --- a/src/components/Dialog/DiffDetail.vue +++ b/src/components/Dialog/DiffDetail.vue @@ -8,7 +8,7 @@ >
- {{ this.$tc('common.NoContent') }} + {{ this.$tc('NoContent') }}
@@ -98,7 +98,7 @@ export default { width: 100%; max-height: 80vh; - & >>> td { + & ::v-deep td { padding: 5px 0 !important; } } diff --git a/src/components/Dialog/index.vue b/src/components/Dialog/index.vue index 0bf11a6593..7363a37b09 100644 --- a/src/components/Dialog/index.vue +++ b/src/components/Dialog/index.vue @@ -1,24 +1,32 @@ diff --git a/src/components/Form/AutoDataForm/components/NestedField.vue b/src/components/Form/AutoDataForm/components/NestedField.vue index f7ce8dedbb..0466548fee 100644 --- a/src/components/Form/AutoDataForm/components/NestedField.vue +++ b/src/components/Form/AutoDataForm/components/NestedField.vue @@ -4,7 +4,7 @@ :disabled="disabled" :fields="iFields" :form="value" - style="margin-left: -26%;margin-right: -6%" + class="sub-form" v-bind="kwargs" @change="updateValue($event)" @input="updateValue($event)" @@ -105,6 +105,16 @@ export default { } - diff --git a/src/components/Form/AutoDataForm/index.vue b/src/components/Form/AutoDataForm/index.vue index b18b2ba488..2168d3841e 100644 --- a/src/components/Form/AutoDataForm/index.vue +++ b/src/components/Form/AutoDataForm/index.vue @@ -103,7 +103,7 @@ export default { this.loading = false }, generateColumns() { - const generator = new FormFieldGenerator(this.$emit) + const generator = new FormFieldGenerator() this.totalFields = generator.generateFields(this.fields, this.fieldsMeta, this.remoteMeta) this.groups = generator.groups this.$log.debug('Total fields: ', this.totalFields) @@ -144,9 +144,9 @@ export default { if (field.attrs.error === error) { error += '.' } - if (field.type === 'nestedField') { + + if (typeof error === 'string') { field.el.errors = error - } else { field.attrs.error = error } }, @@ -165,7 +165,3 @@ export default { } } - - diff --git a/src/components/Form/AutoDataForm/utils.js b/src/components/Form/AutoDataForm/utils.js index 346e32eee9..5976e87515 100644 --- a/src/components/Form/AutoDataForm/utils.js +++ b/src/components/Form/AutoDataForm/utils.js @@ -5,13 +5,13 @@ import Switcher from '@/components/Form/FormFields/Switcher.vue' import rules from '@/components/Form/DataForm/rules' import BasicTree from '@/components/Form/FormFields/BasicTree.vue' import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue' -import { assignIfNot } from '@/utils/common' +import { assignIfNot, toSentenceCase } from '@/utils/common' import TagInput from '@/components/Form/FormFields/TagInput.vue' import TransferSelect from '@/components/Form/FormFields/TransferSelect.vue' +import i18n from '@/i18n/i18n' export class FormFieldGenerator { - constructor(emit) { - this.$emite = emit + constructor() { this.groups = [] } @@ -103,6 +103,7 @@ export class FormFieldGenerator { field.el.filterable = true } } + field.type = type return field } @@ -158,12 +159,68 @@ export class FormFieldGenerator { return field } + setHelpText(field, remoteFieldMeta) { + let helpText = toSentenceCase(remoteFieldMeta['help_text']) + + if (!helpText) { + return field + } + + let helpTextAsTip = field.helpTextAsTip + if (helpTextAsTip === undefined && !helpText.startsWith('*')) { + helpTextAsTip = true + } else { + helpText = helpText.replace(/^\*/, '') + } + + let helpTextAsPlaceholder = field.helpTextAsPlaceholder + const helpTextWordLength = helpText.split(' ').length + const placeholderType = ['input', 'select', 'm2m_related_field'] + const placeholderComponent = [ObjectSelect2] + + const systemLang = document.cookie.django_language + if (helpTextAsPlaceholder !== undefined) { + helpTextAsPlaceholder = !!helpTextAsPlaceholder + } else if (placeholderType.indexOf(field.type) === -1 && placeholderComponent.indexOf(field.component) === -1) { + helpTextAsPlaceholder = false + } else if ((helpTextWordLength <= 5 || helpText.length <= 10) && systemLang === 'en') { + helpTextAsPlaceholder = true + } + + if (helpTextAsPlaceholder) { + field.placeholder = field.placeholder || helpText + } else if (helpTextAsTip) { + field.helpTip = field.helpTip || helpText + } else { + field.helpText = field.helpText || helpText + } + return field + } + + afterGenerateField(field) { + field.label = toSentenceCase(field.label) + + if (field.placeholder) { + field.el.placeholder = field.placeholder + } + + // 设置 checkbox 的 tips + if (field.tips && ['checkbox-group', 'radio-group'].indexOf(field.type) !== -1) { + field.options.map(option => { + if (!option.tip && field.tips[option.value]) { + option.tip = field.tips[option.value] + } + }) + } + + return field + } + generateField(name, fieldsMeta, remoteFieldsMeta) { let field = { id: name, prop: name, el: {}, attrs: {}, rules: [] } const remoteFieldMeta = remoteFieldsMeta[name] || {} const fieldMeta = fieldsMeta[name] || {} field.label = remoteFieldMeta.label - field.helpText = remoteFieldMeta['help_text'] field = this.generateFieldByType(remoteFieldMeta.type, field, fieldMeta, remoteFieldMeta) field = this.generateFieldByName(name, field) field = this.generateFieldByOther(field, fieldMeta, remoteFieldMeta) @@ -172,8 +229,27 @@ export class FormFieldGenerator { field = Object.assign(field, fieldMeta) field.el = el field.rules = rules + field = this.setHelpText(field, remoteFieldMeta) + field = this.setPlaceholder(field, remoteFieldMeta) + field = this.afterGenerateField(field) _.set(field, 'attrs.error', '') - // Vue.$log.debug('Generate field: ', name, field) + Vue.$log.debug('Generate field: ', name, field) + return field + } + + setPlaceholder(field, remoteFieldMeta) { + const label = field.label + if (!label) { + return field + } + if (field.placeholder || field.el.placeholder) { + return field + } + if (field.type === 'select' || [ObjectSelect2].indexOf(field.component) > -1) { + field.el.placeholder = i18n.t('PleaseSelect') + label.toLowerCase() + } else if (field.type === 'input') { + field.el.placeholder = field.label + } return field } diff --git a/src/components/Form/CronTab/Crontab.vue b/src/components/Form/CronTab/Crontab.vue index dbd2eef2e1..d3950f9248 100644 --- a/src/components/Form/CronTab/Crontab.vue +++ b/src/components/Form/CronTab/Crontab.vue @@ -1,8 +1,8 @@ /* eslint-disable */ - diff --git a/src/components/Form/DataForm/components/el-form-renderer/components/render-form-item.vue b/src/components/Form/DataForm/components/el-form-renderer/components/render-form-item.vue index 7f397c5c4e..e73ea0578d 100755 --- a/src/components/Form/DataForm/components/el-form-renderer/components/render-form-item.vue +++ b/src/components/Form/DataForm/components/el-form-renderer/components/render-form-item.vue @@ -1,19 +1,25 @@