Compare commits
327 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a861f77609 | ||
|
|
b8b764c04c | ||
|
|
45532a3eb6 | ||
|
|
9e4afd5271 | ||
|
|
e6efd91414 | ||
|
|
19469508f7 | ||
|
|
5166287b0e | ||
|
|
494c4c5753 | ||
|
|
eddff3d75b | ||
|
|
910bef6695 | ||
|
|
59945ad798 | ||
|
|
60b8748b95 | ||
|
|
5708aa25c4 | ||
|
|
f2e6f89c08 | ||
|
|
1c222c3ce0 | ||
|
|
d6c28b8286 | ||
|
|
d94752a021 | ||
|
|
e93979ab5f | ||
|
|
5d5e87595b | ||
|
|
4bc039185c | ||
|
|
9e768f4ef4 | ||
|
|
0e53d7e657 | ||
|
|
906a1accd1 | ||
|
|
1e381b06ee | ||
|
|
e58ec6057c | ||
|
|
d1a48a2e56 | ||
|
|
5925ac448e | ||
|
|
fdb1dd886b | ||
|
|
2f9ad17b1f | ||
|
|
25c7d0a372 | ||
|
|
46898d2419 | ||
|
|
f2ac6a61ab | ||
|
|
7ab20c5885 | ||
|
|
5f904f99b6 | ||
|
|
6fbef03b33 | ||
|
|
98aa11bb64 | ||
|
|
fba83f39bd | ||
|
|
c3a2c3c23d | ||
|
|
5125038e9c | ||
|
|
42ca29bdaf | ||
|
|
1c4893bc22 | ||
|
|
5a26589052 | ||
|
|
4c2cdc1232 | ||
|
|
cb6e40bea7 | ||
|
|
4c61a84652 | ||
|
|
ffaca80b21 | ||
|
|
0f5f6b860b | ||
|
|
a9ba931bba | ||
|
|
23fae59411 | ||
|
|
320613d15d | ||
|
|
57ea32e785 | ||
|
|
3e3f80c59b | ||
|
|
c15ba5b078 | ||
|
|
651888faab | ||
|
|
d220047fe2 | ||
|
|
bbe80f0292 | ||
|
|
41e73a2c9b | ||
|
|
7d89cf9f45 | ||
|
|
8a048f692f | ||
|
|
800317346c | ||
|
|
4a1e2db7ca | ||
|
|
6702d94ba1 | ||
|
|
a87d04fb9d | ||
|
|
367db0daf1 | ||
|
|
583b28a6c4 | ||
|
|
ec5f2e69eb | ||
|
|
d058f0e777 | ||
|
|
89befaad43 | ||
|
|
548d0e0670 | ||
|
|
7b862ee0e1 | ||
|
|
d4e37e9418 | ||
|
|
b53b1d1e58 | ||
|
|
0342c73f07 | ||
|
|
727d9037d1 | ||
|
|
b24c244186 | ||
|
|
d365aea6a0 | ||
|
|
115f1171ad | ||
|
|
b2b477d4e0 | ||
|
|
1e37ecff11 | ||
|
|
9cf87404be | ||
|
|
2e51b0dea1 | ||
|
|
a1f20c6f92 | ||
|
|
6b6a60b84f | ||
|
|
4e999176ed | ||
|
|
76ff273ec7 | ||
|
|
e47ddb5355 | ||
|
|
475a77497f | ||
|
|
5d1301c871 | ||
|
|
772f40e13d | ||
|
|
1b2f7ad28c | ||
|
|
84778881f6 | ||
|
|
d6d0338666 | ||
|
|
de6f477d05 | ||
|
|
03e07ee280 | ||
|
|
35e2273603 | ||
|
|
7a138388b2 | ||
|
|
b1f42dd6bf | ||
|
|
2eeba4ee4f | ||
|
|
d91579e848 | ||
|
|
13751f14f3 | ||
|
|
7ea00e17bd | ||
|
|
ec78370ecc | ||
|
|
c9f62ae5d3 | ||
|
|
b80b69dd26 | ||
|
|
ac49415061 | ||
|
|
9edb64837d | ||
|
|
671fa65930 | ||
|
|
0da81ea60d | ||
|
|
c610d396d6 | ||
|
|
0ed02ca7f7 | ||
|
|
c22981e2c1 | ||
|
|
b0af35a4b9 | ||
|
|
1d16a165a5 | ||
|
|
f1f4242ad6 | ||
|
|
bff8f10cd5 | ||
|
|
305e44ea1c | ||
|
|
a636bb2037 | ||
|
|
56aa3caa83 | ||
|
|
9b6f54c1ed | ||
|
|
611341307b | ||
|
|
f8479c53ff | ||
|
|
e25bf46659 | ||
|
|
6d07307e56 | ||
|
|
6fb7fe8fa1 | ||
|
|
19cd497095 | ||
|
|
d858489367 | ||
|
|
341a30ba06 | ||
|
|
8390fb7429 | ||
|
|
5389f1d011 | ||
|
|
bde642570f | ||
|
|
3e7b970fe7 | ||
|
|
2421e822b4 | ||
|
|
b2e474e3f6 | ||
|
|
541836390a | ||
|
|
bb92e3f22b | ||
|
|
c9ad797b40 | ||
|
|
2a08310efc | ||
|
|
3b2803b9a1 | ||
|
|
1b8ac9112e | ||
|
|
9cc3dd4de9 | ||
|
|
120ef70eb1 | ||
|
|
541f4ebc62 | ||
|
|
a47636abc7 | ||
|
|
78f6f4b36a | ||
|
|
5abc0b77cf | ||
|
|
464638e782 | ||
|
|
37cfeb2077 | ||
|
|
44e297f01a | ||
|
|
febc283e36 | ||
|
|
dae33f55e8 | ||
|
|
c62cd27690 | ||
|
|
83443f8187 | ||
|
|
cb46f393e0 | ||
|
|
ddf5ac2151 | ||
|
|
88173f852a | ||
|
|
d5f16e90e2 | ||
|
|
d1c0aca4ff | ||
|
|
a42edf17ec | ||
|
|
dac5dfcd1c | ||
|
|
8e6ca146e1 | ||
|
|
9c06d36eab | ||
|
|
ab7bd574f8 | ||
|
|
6007dc8621 | ||
|
|
a0e7c48dc9 | ||
|
|
71dea791bf | ||
|
|
e1b9184f41 | ||
|
|
730d47f02a | ||
|
|
e82ec68935 | ||
|
|
e8e5975d7f | ||
|
|
19b1dc0dbc | ||
|
|
3cb9dec978 | ||
|
|
c025441075 | ||
|
|
050b50fa74 | ||
|
|
e26befabc3 | ||
|
|
8545fc6136 | ||
|
|
e343e0df9d | ||
|
|
74519e6f3c | ||
|
|
1305f90372 | ||
|
|
bbc3f53c0a | ||
|
|
aa605adbc7 | ||
|
|
49ea7d0969 | ||
|
|
4221bdb2ab | ||
|
|
af4010e299 | ||
|
|
9b7c4ed353 | ||
|
|
879df90503 | ||
|
|
41f841532f | ||
|
|
0b9f47dd84 | ||
|
|
d32a376e8c | ||
|
|
0f8a8845df | ||
|
|
8bb2c66b99 | ||
|
|
b3d0be2f60 | ||
|
|
5a94ddd976 | ||
|
|
7b9627a80b | ||
|
|
5fcfecc060 | ||
|
|
f4d7a2283c | ||
|
|
40bb8410d3 | ||
|
|
45344ac620 | ||
|
|
5b894c9667 | ||
|
|
d89bd15b6d | ||
|
|
5e640dd45c | ||
|
|
09aa750794 | ||
|
|
744b215800 | ||
|
|
f4e11da053 | ||
|
|
2e6b5706d5 | ||
|
|
440a5b27ef | ||
|
|
932e16844e | ||
|
|
aff2e439dd | ||
|
|
77ef172a23 | ||
|
|
a618794c14 | ||
|
|
8dd66c400a | ||
|
|
ac3ce464b7 | ||
|
|
4d01c6dabe | ||
|
|
47eeac23eb | ||
|
|
592f783245 | ||
|
|
c34ac6a56f | ||
|
|
9a7162cc9e | ||
|
|
5edf2f34dd | ||
|
|
859d824195 | ||
|
|
c382cc633e | ||
|
|
653e24773f | ||
|
|
ae489610ee | ||
|
|
b5e85c66e4 | ||
|
|
4060fa4c4a | ||
|
|
9630b79daa | ||
|
|
ad93917387 | ||
|
|
691590da7b | ||
|
|
71828fd78b | ||
|
|
5a45f31ac4 | ||
|
|
44901eee23 | ||
|
|
2d91fa107d | ||
|
|
7a6ade4bd9 | ||
|
|
951778668f | ||
|
|
0aca23bf38 | ||
|
|
9b97dc9a74 | ||
|
|
32cd030479 | ||
|
|
607bb476db | ||
|
|
1d676ec9b7 | ||
|
|
614ebb4121 | ||
|
|
8c2719a95d | ||
|
|
c6b0a958a6 | ||
|
|
3edebf3f4f | ||
|
|
f63405978e | ||
|
|
c582c8de98 | ||
|
|
c44e79ed3b | ||
|
|
fc7d5b9c29 | ||
|
|
335febd4a5 | ||
|
|
5ea2918fe7 | ||
|
|
7668d10ba5 | ||
|
|
df15d141da | ||
|
|
e2f4bbde79 | ||
|
|
3ca7de42af | ||
|
|
e6a577ba9b | ||
|
|
6290179460 | ||
|
|
4f3c9e9353 | ||
|
|
aa28a8f765 | ||
|
|
98d0a52fa2 | ||
|
|
8d9e1ffadb | ||
|
|
b3be312a4d | ||
|
|
7c41d148aa | ||
|
|
6dee911642 | ||
|
|
6dd35e5173 | ||
|
|
fdaa33f68d | ||
|
|
0b813bb719 | ||
|
|
7ba6b8d4e4 | ||
|
|
ef99e25fde | ||
|
|
d1e7b907e3 | ||
|
|
83f7dda5e7 | ||
|
|
80f929fdea | ||
|
|
1ec3d02933 | ||
|
|
fdf148cc2b | ||
|
|
c9e6ef89dc | ||
|
|
59f9f88f8b | ||
|
|
16417ae843 | ||
|
|
58bd0a17c8 | ||
|
|
c4a1eb6938 | ||
|
|
b7d9031889 | ||
|
|
96b29a9dc2 | ||
|
|
336e176639 | ||
|
|
2df4a9d66d | ||
|
|
45a102cff1 | ||
|
|
57ebfa0812 | ||
|
|
4e9dd57efe | ||
|
|
3fcc4ca160 | ||
|
|
958760811c | ||
|
|
b60e0251c2 | ||
|
|
3db0ed756e | ||
|
|
453c4b1e4e | ||
|
|
bf4d8ce7a6 | ||
|
|
34effdbe15 | ||
|
|
274db466f2 | ||
|
|
cbe697f9dc | ||
|
|
fb7acb100e | ||
|
|
ebb36847df | ||
|
|
beba4f1994 | ||
|
|
452796e3f5 | ||
|
|
02a7969d90 | ||
|
|
798dbec151 | ||
|
|
0e11a56e37 | ||
|
|
be5344344c | ||
|
|
e75d711e0a | ||
|
|
46ee116f3e | ||
|
|
f4b304338f | ||
|
|
e93e78307c | ||
|
|
d22079446f | ||
|
|
d57b99b00c | ||
|
|
e7321356af | ||
|
|
a0edc2c527 | ||
|
|
10ebcfd64d | ||
|
|
a3fedb9697 | ||
|
|
1eb8a18c66 | ||
|
|
f491c57c34 | ||
|
|
50b7f54652 | ||
|
|
5bf298a5bf | ||
|
|
124ff9a8c2 | ||
|
|
387ab4f1b3 | ||
|
|
d6fbdfa7ea | ||
|
|
cd4260fd8d | ||
|
|
baeece62d3 | ||
|
|
576c8f5891 | ||
|
|
5e97600807 | ||
|
|
4e2eb3a37d | ||
|
|
e92173f8e8 | ||
|
|
412d9c804e | ||
|
|
b84a725d4a | ||
|
|
58b39743e0 | ||
|
|
7651443c25 | ||
|
|
8d64e331d1 |
28
.github/workflows/.github/workflows/llm-code-review.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: LLM Code Review
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
llm-code-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fit2cloud/LLM-CodeReview-Action@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
|
||||
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
||||
LANGUAGE: English
|
||||
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
MODEL: qwen2-1.5b-instruct
|
||||
PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English."
|
||||
top_p: 1
|
||||
temperature: 1
|
||||
# max_tokens: 10000
|
||||
MAX_PATCH_LENGTH: 10000
|
||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||
72
.github/workflows/build-base-image.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Build and Push Base Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'pr*'
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'yarn.lock'
|
||||
- 'Dockerfile-base'
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Lock Pull Request
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-d '{"state":"pending", "description":"Action running, merge disabled", "context":"Lock PR"}' \
|
||||
"https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }}"
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract date
|
||||
id: vars
|
||||
run: echo "IMAGE_TAG=$(date +'%Y%m%d_%H%M%S')" >> $GITHUB_ENV
|
||||
|
||||
- name: Extract repository name
|
||||
id: repo
|
||||
run: echo "REPO=$(basename ${{ github.repository }})" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push multi-arch image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
file: Dockerfile-base
|
||||
tags: jumpserver/${{ env.REPO }}-base:${{ env.IMAGE_TAG }}
|
||||
|
||||
- name: Update Dockerfile
|
||||
run: |
|
||||
sed -i 's|-base:.* AS stage-build|-base:${{ env.IMAGE_TAG }} AS stage-build|' Dockerfile
|
||||
|
||||
- name: Commit changes
|
||||
run: |
|
||||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
git add Dockerfile
|
||||
git commit -m "perf: Update Dockerfile with new base image tag"
|
||||
git push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Unlock Pull Request
|
||||
run: |
|
||||
curl -X POST -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-d '{"state":"success", "description":"Action running, merge disabled", "context":"Lock PR"}' \
|
||||
"https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.sha }}"
|
||||
46
.github/workflows/jms-build-test.yml
vendored
@@ -1,46 +0,0 @@
|
||||
name: "Run Build Test"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile*'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
component: [lina]
|
||||
version: [v4]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Prepare Build
|
||||
run: |
|
||||
sed -i 's@registry.npmmirror.com@registry.yarnpkg.com@g' yarn.lock
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
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
|
||||
46
.github/workflows/jms-build-test.yml.disabled
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: "Run Build Test"
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile*'
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
component: [ lina ]
|
||||
version: [ v4 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Prepare Build
|
||||
run: |
|
||||
sed -i 's@registry.npmmirror.com@registry.yarnpkg.com@g' yarn.lock
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build Image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
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
|
||||
28
.github/workflows/llm-code-review.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: LLM Code Review
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
llm-code-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: fit2cloud/LLM-CodeReview-Action@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }}
|
||||
OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }}
|
||||
LANGUAGE: English
|
||||
OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
MODEL: qwen2-1.5b-instruct
|
||||
PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English."
|
||||
top_p: 1
|
||||
temperature: 1
|
||||
# max_tokens: 10000
|
||||
MAX_PATCH_LENGTH: 10000
|
||||
IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github"
|
||||
FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html"
|
||||
2
.github/workflows/release-drafter.yml
vendored
@@ -31,7 +31,7 @@ jobs:
|
||||
tag: ${{ steps.get_version.outputs.TAG }}
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16.20'
|
||||
node-version: '20.15'
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Build web
|
||||
|
||||
32
Dockerfile
@@ -1,34 +1,4 @@
|
||||
FROM node:16.20-bullseye-slim AS stage-build
|
||||
ARG TARGETARCH
|
||||
|
||||
ARG DEPENDENCIES=" \
|
||||
g++ \
|
||||
make \
|
||||
python3"
|
||||
|
||||
ARG APT_MIRROR=http://mirrors.ustc.edu.cn
|
||||
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 \
|
||||
&& 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 -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
|
||||
|
||||
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
|
||||
FROM jumpserver/lina-base:20240723_084702 AS stage-build
|
||||
|
||||
ARG VERSION
|
||||
ENV VERSION=$VERSION
|
||||
|
||||
22
Dockerfile-base
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM node:20.15-bullseye-slim
|
||||
|
||||
ARG DEPENDENCIES=" \
|
||||
g++ \
|
||||
make \
|
||||
python3"
|
||||
|
||||
RUN set -ex \
|
||||
&& rm -f /etc/apt/apt.conf.d/docker-clean \
|
||||
&& echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache \
|
||||
&& apt-get update \
|
||||
&& apt-get -y install --no-install-recommends ${DEPENDENCIES} \
|
||||
&& echo "no" | dpkg-reconfigure dash
|
||||
|
||||
WORKDIR /data
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
ARG NPM_MIRROR="https://registry.npmjs.org"
|
||||
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,sharing=locked,id=yarn-cache \
|
||||
sed -i "s|https://registry.npmmirror.com|${NPM_MIRROR}|g" yarn.lock \
|
||||
&& yarn install
|
||||
13
package.json
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "Lina",
|
||||
"name": "lina",
|
||||
"version": "v4.0.0",
|
||||
"description": "JumpServer Web UI",
|
||||
"author": "JumpServer Team <support@fit2cloud.com>",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"dev": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
|
||||
"serve": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service serve",
|
||||
"build": "NODE_OPTIONS=--openssl-legacy-provider vue-cli-service build",
|
||||
"build:prod": "vue-cli-service build",
|
||||
"build:stage": "vue-cli-service build --mode staging",
|
||||
"preview": "node build/index.js --preview",
|
||||
@@ -30,6 +30,7 @@
|
||||
"@ztree/ztree_v3": "3.5.44",
|
||||
"axios": "0.28.0",
|
||||
"axios-retry": "^3.1.9",
|
||||
"caniuse-lite": "^1.0.30001642",
|
||||
"cron-parser": "^4.0.0",
|
||||
"crypto-js": "^4.1.1",
|
||||
"css-color-function": "^1.3.3",
|
||||
@@ -65,7 +66,7 @@
|
||||
"normalize.css": "7.0.0",
|
||||
"npm": "^7.8.0",
|
||||
"nprogress": "0.2.0",
|
||||
"path-to-regexp": "2.4.0",
|
||||
"path-to-regexp": "3.3.0",
|
||||
"v-sanitize": "^0.0.13",
|
||||
"vue": "2.6.10",
|
||||
"vue-codemirror": "4.0.6",
|
||||
@@ -116,7 +117,7 @@
|
||||
"sass-loader": "^7.1.0",
|
||||
"script-ext-html-webpack-plugin": "2.1.3",
|
||||
"script-loader": "0.7.2",
|
||||
"serve-static": "^1.13.2",
|
||||
"serve-static": "^1.16.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"svg-sprite-loader": "4.1.3",
|
||||
"svgo": "1.2.2",
|
||||
|
||||
@@ -53,7 +53,7 @@ export function createJob(form) {
|
||||
})
|
||||
}
|
||||
|
||||
export function StopJob(form) {
|
||||
export function stopJob(form) {
|
||||
return request({
|
||||
url: '/api/v1/ops/job-executions/stop/',
|
||||
method: 'post',
|
||||
@@ -71,3 +71,10 @@ export function JobUploadFile(form) {
|
||||
})
|
||||
}
|
||||
|
||||
export function auditUpdateJob(id, form) {
|
||||
return request({
|
||||
url: `/api/v1/audits/jobs/${id}/`,
|
||||
method: 'patch',
|
||||
data: form
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export function testEmailSetting(data) {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
export function importLicense(formData) {
|
||||
return request({
|
||||
url: '/api/v1/xpack/license/import',
|
||||
@@ -25,6 +26,7 @@ export function importLicense(formData) {
|
||||
data: formData
|
||||
})
|
||||
}
|
||||
|
||||
export function testLdapSetting(data, refresh = true) {
|
||||
let url = '/api/v1/settings/ldap/testing/config/'
|
||||
if (refresh) {
|
||||
@@ -96,9 +98,17 @@ export function getPublicSettings(isOpen) {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getLogo() {
|
||||
return request({
|
||||
url: '/api/v1/xpack/interface/setting/',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getPreference() {
|
||||
return request({
|
||||
url: '/api/v1/users/preference/?category=luna',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,10 +65,6 @@ export function logout() {
|
||||
})
|
||||
}
|
||||
|
||||
export function refreshSessionIdAge() {
|
||||
return getProfile()
|
||||
}
|
||||
|
||||
export default {
|
||||
getProfile,
|
||||
getUserList
|
||||
|
||||
BIN
src/assets/img/deepSeek.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 466 B After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 961 B After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 673 B After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 916 B After Width: | Height: | Size: 2.4 KiB |
BIN
src/assets/img/icons/h3c.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 278 B After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 940 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.1 KiB |
@@ -2,6 +2,7 @@
|
||||
<Dialog
|
||||
:close-on-click-modal="false"
|
||||
:title="$tc('Assets')"
|
||||
:disabled-status="!isLoaded"
|
||||
custom-class="asset-select-dialog"
|
||||
top="2vh"
|
||||
v-bind="$attrs"
|
||||
@@ -22,6 +23,8 @@
|
||||
:url="baseUrl"
|
||||
class="tree-table"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
@loaded="handleTableLoaded"
|
||||
/>
|
||||
</Dialog>
|
||||
</template>
|
||||
@@ -64,6 +67,7 @@ export default {
|
||||
data() {
|
||||
const vm = this
|
||||
return {
|
||||
isLoaded: false,
|
||||
dialogVisible: false,
|
||||
rowSelected: _.cloneDeep(this.value) || [],
|
||||
rowsAdd: [],
|
||||
@@ -124,6 +128,9 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleTableLoaded() {
|
||||
this.isLoaded = true
|
||||
},
|
||||
handleClose() {
|
||||
this.$refs.ListPage.$refs.TreeList.componentKey += 1
|
||||
},
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
<script>
|
||||
import Select2 from '@/components/Form/FormFields/Select2.vue'
|
||||
import AssetSelectDialog from './dialog.vue'
|
||||
import { b } from 'css-color-function/lib/adjusters'
|
||||
|
||||
export default {
|
||||
componentName: 'AssetSelect',
|
||||
@@ -38,6 +37,10 @@ export default {
|
||||
type: String,
|
||||
default: '/api/v1/assets/assets/'
|
||||
},
|
||||
defaultPageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
baseNodeUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/nodes/'
|
||||
@@ -71,6 +74,7 @@ export default {
|
||||
value: iValue,
|
||||
multiple: true,
|
||||
clearable: true,
|
||||
defaultPageSize: this.defaultPageSize,
|
||||
ajax: {
|
||||
url: this.baseUrl,
|
||||
transformOption: (item) => {
|
||||
@@ -81,7 +85,6 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
b,
|
||||
handleFocus() {
|
||||
this.$refs.select2.selectRef.blur()
|
||||
this.dialogVisible = true
|
||||
|
||||
@@ -32,6 +32,10 @@ export default {
|
||||
type: String,
|
||||
default: '/api/v1/assets/assets/'
|
||||
},
|
||||
typeUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/nodes/category/tree/'
|
||||
},
|
||||
nodeUrl: {
|
||||
type: String,
|
||||
default: '/api/v1/assets/nodes/'
|
||||
@@ -105,9 +109,9 @@ export default {
|
||||
showAssets: false,
|
||||
showSearch: false,
|
||||
customTreeHeaderName: this.$t('TypeTree'),
|
||||
url: '/api/v1/assets/nodes/category/tree/',
|
||||
url: this.typeUrl,
|
||||
nodeUrl: this.treeSetting?.nodeUrl || this.nodeUrl,
|
||||
treeUrl: `/api/v1/assets/nodes/category/tree/?assets=${showAssets ? '1' : '0'}&count_resource=${this.treeSetting.countResource || 'asset'}`,
|
||||
treeUrl: `${this.typeUrl}?assets=${showAssets ? '1' : '0'}&count_resource=${this.treeSetting.countResource || 'asset'}`,
|
||||
callback: {
|
||||
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
|
||||
}
|
||||
@@ -176,12 +180,12 @@ export default {
|
||||
setParam('node_id', '')
|
||||
setParam('asset_id', assetId)
|
||||
} else if (treeNode.meta.type === 'category') {
|
||||
url = setUrlParam(url, 'category', treeNode.meta.category)
|
||||
setParam('category', treeNode.meta.category)
|
||||
} else if (treeNode.meta.type === 'type') {
|
||||
setParam('category', treeNode.meta.category)
|
||||
setParam('type', treeNode.meta._type)
|
||||
} else if (treeNode.meta.type === 'platform') {
|
||||
url = setUrlParam(url, 'platform', treeNode.id)
|
||||
setParam('platform', treeNode.id)
|
||||
}
|
||||
setTimeout(() => {
|
||||
const query = this.setTreeUrlQuery()
|
||||
|
||||
@@ -1,42 +1,74 @@
|
||||
<template>
|
||||
<div :class="{'user-role': isUserRole}" class="chat-item">
|
||||
<div class="avatar">
|
||||
<el-avatar :src="isUserRole ? userUrl : chatUrl" class="header-avatar" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="operational">
|
||||
<span class="date">
|
||||
{{ $moment(item.message.create_time).format('YYYY-MM-DD HH:mm:ss') }}
|
||||
</span>
|
||||
<div :class="{ 'user-role': isUserRole }" class="chat-item">
|
||||
<div class="chart-item-container">
|
||||
<div class="avatar">
|
||||
<el-avatar
|
||||
:src="isUserRole ? userUrl : chatUrl"
|
||||
class="header-avatar"
|
||||
/>
|
||||
</div>
|
||||
<div class="message">
|
||||
<div class="message-content">
|
||||
<span v-if="isSystemError" class="error">
|
||||
{{ item.message.content }}
|
||||
</span>
|
||||
<span v-else class="chat-text">
|
||||
<MessageText :message="item.message" />
|
||||
</span>
|
||||
<div class="content">
|
||||
<div class="operational">
|
||||
<div v-if="!item.message.is_reasoning" class="date">
|
||||
{{
|
||||
$moment(item.message.create_time).format("YYYY-MM-DD HH:mm:ss")
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div v-else class="thinking-time">{{ $i18n.t('DeeplyThoughtAbout') }}</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<el-tooltip
|
||||
v-if="isSystemError && isLoading"
|
||||
:content="$tc('Reconnect')"
|
||||
:open-delay="500"
|
||||
placement="top"
|
||||
>
|
||||
<svg-icon icon-class="refresh" @click="onRefresh" />
|
||||
</el-tooltip>
|
||||
<el-dropdown v-else size="small" @command="handleCommand">
|
||||
<span class="el-dropdown-link">
|
||||
<i class="fa fa-ellipsis-v" />
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item v-for="i in dropdownOptions" :key="i.action" :command="i.action">
|
||||
{{ i.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<div :class="item.reasoning ? 'reasoning' : 'message'">
|
||||
<div class="message-content">
|
||||
<div v-if="!item.reasoning">
|
||||
<span v-if="isSystemError" class="error">
|
||||
{{ item.message.content }}
|
||||
</span>
|
||||
<span v-else class="chat-text">
|
||||
<MessageText :message="item.message" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="thinking-wrapper">
|
||||
<div class="thinking-content">
|
||||
<!-- eslint-disable-next-line -->
|
||||
<div class="divider"></div>
|
||||
<p>
|
||||
<MessageText :message="item.reasoning" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="thinking-result">
|
||||
<span v-if="isServerError" class="error">
|
||||
{{ isServerError }}
|
||||
</span>
|
||||
<MessageText :message="item.result" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action">
|
||||
<el-tooltip
|
||||
v-if="isSystemError && isLoading"
|
||||
:content="$tc('Reconnect')"
|
||||
:open-delay="500"
|
||||
placement="top"
|
||||
>
|
||||
<svg-icon icon-class="refresh" @click="onRefresh" />
|
||||
</el-tooltip>
|
||||
<el-dropdown v-else size="small" @command="handleCommand">
|
||||
<span class="el-dropdown-link">
|
||||
<i class="fa fa-ellipsis-v" />
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="i in dropdownOptions"
|
||||
:key="i.action"
|
||||
:command="i.action"
|
||||
>
|
||||
{{ i.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,7 +77,7 @@
|
||||
|
||||
<script>
|
||||
import MessageText from './MessageText.vue'
|
||||
import { mapState } from 'vuex'
|
||||
import { mapGetters, mapState } from 'vuex'
|
||||
import { copy } from '@/utils/common'
|
||||
import { useChat } from '../../useChat.js'
|
||||
import { reconnect } from '@/utils/socket'
|
||||
@@ -65,7 +97,6 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
chatUrl: require('@/assets/img/chat.png'),
|
||||
userUrl: '/api/v1/settings/logo/',
|
||||
dropdownOptions: [
|
||||
{
|
||||
@@ -79,11 +110,26 @@ export default {
|
||||
...mapState({
|
||||
isLoading: state => state.chat.loading
|
||||
}),
|
||||
...mapGetters([
|
||||
'publicSettings'
|
||||
]),
|
||||
isUserRole() {
|
||||
return this.item.message?.role === 'user'
|
||||
},
|
||||
isSystemError() {
|
||||
return this.item.type === 'error' && this.item.message?.role === 'assistant'
|
||||
return (
|
||||
this.item.type === 'error' && this.item?.role === 'assistant'
|
||||
)
|
||||
},
|
||||
isServerError() {
|
||||
return (this.item.type === 'finish' && this.item.result.content === '')
|
||||
? this.$i18n.t('ServerBusyRetry')
|
||||
: ''
|
||||
},
|
||||
chatUrl() {
|
||||
return this.publicSettings.CHAT_AI_TYPE === 'gpt'
|
||||
? require('@/assets/img/chat.png')
|
||||
: require('@/assets/img/deepSeek.png')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -94,7 +140,7 @@ export default {
|
||||
},
|
||||
handleCommand(value) {
|
||||
if (value === 'copy') {
|
||||
copy(this.item.message.content)
|
||||
copy(this.item.result.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,101 +150,160 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.chat-item {
|
||||
display: flex;
|
||||
padding: 16px 14px 0;
|
||||
padding: 0.5rem;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
.chart-item-container {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
|
||||
.avatar {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-top: 2px;
|
||||
.avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-top: 2px;
|
||||
|
||||
.header-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.header-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
|
||||
&::v-deep img {
|
||||
background-color: #e5e5e7;
|
||||
&::v-deep img {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
.operational {
|
||||
.content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-direction: column;
|
||||
// gap: 0.5rem;
|
||||
overflow: hidden;
|
||||
|
||||
.copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.operational {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
|
||||
.message {
|
||||
display: -webkit-box;
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
border-radius: 2px 12px 12px;
|
||||
background-color: #f0f1f5;
|
||||
}
|
||||
|
||||
.action {
|
||||
.svg-icon {
|
||||
transform: translateY(50%);
|
||||
margin-left: 3px;
|
||||
cursor: pointer;
|
||||
.date {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
height: 32px;
|
||||
line-height: 37px;
|
||||
font-size: 13px;
|
||||
.thinking-time {
|
||||
width: 6rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 5px 10px;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
i {
|
||||
padding: 4px 5px;
|
||||
font-size: 15px;
|
||||
color: #8d9091;
|
||||
.copy {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #7b8085
|
||||
.reasoning {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: flex-end;
|
||||
|
||||
.message-content .thinking-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
|
||||
.thinking-content {
|
||||
position: relative;
|
||||
color: #8b8b8b;
|
||||
|
||||
.divider {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
border-left: 2px solid #e5e5e5;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: unset;
|
||||
padding-left: 0.5rem;
|
||||
|
||||
::v-deep p {
|
||||
color: #8b8b8b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
.message {
|
||||
display: -webkit-box;
|
||||
|
||||
.message-content {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
border-radius: 2px 12px 12px;
|
||||
background-color: #f0f1f5;
|
||||
}
|
||||
|
||||
.action {
|
||||
.svg-icon {
|
||||
transform: translateY(50%);
|
||||
margin-left: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
height: 32px;
|
||||
line-height: 37px;
|
||||
font-size: 13px;
|
||||
|
||||
.el-dropdown-link {
|
||||
i {
|
||||
padding: 4px 5px;
|
||||
font-size: 15px;
|
||||
color: #8d9091;
|
||||
|
||||
&:hover {
|
||||
color: #7b8085;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-role {
|
||||
flex-direction: row-reverse;
|
||||
&:last-child {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-right: 10px;
|
||||
&.user-role {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.operational {
|
||||
.chart-item-container {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message {
|
||||
flex-direction: row-reverse;
|
||||
.content {
|
||||
margin-right: 10px;
|
||||
|
||||
.message-content {
|
||||
background-color: var(--menu-hover);
|
||||
border-radius: 12px 2px 12px 12px;
|
||||
.operational {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message {
|
||||
flex-direction: row-reverse;
|
||||
|
||||
.message-content {
|
||||
background-color: var(--menu-hover);
|
||||
border-radius: 12px 2px 12px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,17 @@ export default {
|
||||
setLoading(true)
|
||||
removeLoadingMessageInChat()
|
||||
this.conversationId = data.id
|
||||
updateChaMessageContentById(data.message.id, data)
|
||||
|
||||
const newFragment = {
|
||||
message: { id: data.message.id, is_reasoning: data.message.is_reasoning },
|
||||
reasoning: { content: data.message.is_reasoning ? data.message.content : '' },
|
||||
result: { content: data.message.is_reasoning ? '' : data.message.content },
|
||||
role: data.message.role,
|
||||
type: data.message.type,
|
||||
create_time: data.message.create_time
|
||||
}
|
||||
|
||||
updateChaMessageContentById(data.message.id, newFragment)
|
||||
}
|
||||
if (data.message?.type === 'finish') {
|
||||
setLoading(false)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<AssetTreeTable
|
||||
ref="AssetTreeTable"
|
||||
:header-actions="headerActions"
|
||||
:table-config="tableConfig"
|
||||
:tree-setting="treeSetting"
|
||||
@@ -47,9 +48,19 @@ export default {
|
||||
return this.tableUrl.replace('/assets/', `/assets/${row.id}/accounts/`)
|
||||
}
|
||||
},
|
||||
nameDisabled: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
name: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail',
|
||||
can: true
|
||||
}
|
||||
})
|
||||
},
|
||||
comment: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -80,11 +91,7 @@ export default {
|
||||
},
|
||||
columnsMeta: {
|
||||
name: {
|
||||
formatter: DetailFormatter,
|
||||
formatterArgs: {
|
||||
route: 'AssetDetail',
|
||||
can: !this.nameDisabled
|
||||
}
|
||||
...this.name
|
||||
},
|
||||
labels: {
|
||||
formatterArgs: {
|
||||
@@ -99,7 +106,8 @@ export default {
|
||||
formatter: AccountInfoFormatter,
|
||||
width: '100px'
|
||||
},
|
||||
connectivity: connectivityMeta
|
||||
connectivity: connectivityMeta,
|
||||
comment: { ...this.comment }
|
||||
},
|
||||
tableAttrs: {
|
||||
rowClassName({ row }) {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:visible.sync="visible"
|
||||
class="dialog-content"
|
||||
v-bind="$attrs"
|
||||
width="600px"
|
||||
width="740px"
|
||||
@confirm="visible = false"
|
||||
v-on="$listeners"
|
||||
>
|
||||
@@ -52,11 +52,21 @@
|
||||
<el-row :gutter="24" style="margin: 0 auto;">
|
||||
<el-col :md="24" :sm="24" style="display: flex; align-items: center; margin-bottom: 20px;">
|
||||
<el-input
|
||||
v-if="subTypeSelected !== 'face'"
|
||||
v-model="secretValue"
|
||||
:placeholder="inputPlaceholder"
|
||||
:show-password="showPassword"
|
||||
@keyup.enter.native="handleConfirm"
|
||||
/>
|
||||
|
||||
<iframe
|
||||
v-if="isFaceCaptureVisible && subTypeSelected ==='face' && faceCaptureUrl"
|
||||
:src="faceCaptureUrl"
|
||||
allow="camera"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
style="width: 100%; height: 800px;border: none;"
|
||||
/>
|
||||
|
||||
<span v-if="subTypeSelected === 'sms'" style="margin: -1px 0 0 20px;">
|
||||
<el-button
|
||||
:disabled="smsBtnDisabled"
|
||||
@@ -72,9 +82,24 @@
|
||||
</el-row>
|
||||
<el-row :gutter="24" style="margin: 10px auto;">
|
||||
<el-col :md="24" :sm="24">
|
||||
<el-button class="confirm-btn" size="mini" type="primary" @click="handleConfirm">
|
||||
<el-button
|
||||
v-if="subTypeSelected!=='face'"
|
||||
class="confirm-btn"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ this.$t('Confirm') }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="subTypeSelected==='face'&&!isFaceCaptureVisible"
|
||||
class="confirm-btn"
|
||||
size="mini"
|
||||
type="primary"
|
||||
@click="handleFaceCapture"
|
||||
>
|
||||
{{ this.$tc('VerifyFace') }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
@@ -113,7 +138,10 @@ export default {
|
||||
visible: false,
|
||||
callback: null,
|
||||
cancel: null,
|
||||
processing: false
|
||||
processing: false,
|
||||
isFaceCaptureVisible: false,
|
||||
faceToken: null,
|
||||
faceCaptureUrl: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -129,6 +157,10 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
handleSubTypeChange(val) {
|
||||
if (val !== 'face') {
|
||||
this.isFaceCaptureVisible = false
|
||||
}
|
||||
|
||||
this.inputPlaceholder = this.subTypeChoices.filter(item => item.name === val)[0]?.placeholder
|
||||
this.smsWidth = val === 'sms' ? 6 : 0
|
||||
},
|
||||
@@ -192,6 +224,29 @@ export default {
|
||||
this.$message.error(this.$tc('FailedToSendVerificationCode'))
|
||||
})
|
||||
},
|
||||
startFaceCapture() {
|
||||
const url = '/api/v1/authentication/face/context/'
|
||||
this.$axios.post(url).then(data => {
|
||||
const token = data['token']
|
||||
this.faceCaptureUrl = '/facelive/capture?token=' + token
|
||||
this.isFaceCaptureVisible = true
|
||||
|
||||
const timer = setInterval(() => {
|
||||
this.$axios.get(url + `?token=${token}`).then(data => {
|
||||
if (data['is_finished']) {
|
||||
clearInterval(timer)
|
||||
this.isFaceCaptureVisible = false
|
||||
this.handleConfirm()
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
}).catch(() => {
|
||||
this.$message.error(this.$tc('FailedToStartFaceCapture'))
|
||||
})
|
||||
},
|
||||
handleFaceCapture() {
|
||||
this.startFaceCapture()
|
||||
},
|
||||
handleConfirm() {
|
||||
if (this.confirmTypeRequired === 'relogin') {
|
||||
return this.logout()
|
||||
@@ -214,6 +269,8 @@ export default {
|
||||
})
|
||||
}).catch((err) => {
|
||||
this.$message.error(err.message || this.$tc('ConfirmFailed'))
|
||||
this.faceCaptureUrl = null
|
||||
this.isFaceCaptureVisible = false
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -221,21 +278,21 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-content ::v-deep .el-dialog__footer {
|
||||
padding: 0;
|
||||
}
|
||||
.dialog-content ::v-deep .el-dialog__footer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dialog-content ::v-deep .el-dialog {
|
||||
padding: 8px;
|
||||
.dialog-content ::v-deep .el-dialog {
|
||||
padding: 8px;
|
||||
|
||||
.el-dialog__body {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
.el-dialog__body {
|
||||
padding-top: 30px;
|
||||
padding-bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
}
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
133
src/components/Apps/VariableCreateUpdateForm/index.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<AutoDataForm
|
||||
ref="AutoDataForm"
|
||||
class="variable-add"
|
||||
:submit-btn-text="submitBtnText"
|
||||
v-bind="$data"
|
||||
@submit="confirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutoDataForm from '@/components/Form/AutoDataForm/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'VariableCreateForm',
|
||||
components: {
|
||||
AutoDataForm
|
||||
},
|
||||
props: {
|
||||
variable: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const defaultValidator = (rule, value, callback) => {
|
||||
if (this.defaultValueRequired && !value) {
|
||||
callback(new Error(this.$t('FieldRequiredError')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
defaultValueRequired: false,
|
||||
submitBtnText: this.$t('Confirm'),
|
||||
url: '/api/v1/ops/variables/',
|
||||
form: Object.assign({ 'on_invalid': 'error' }, this.variable || {}),
|
||||
fields: [
|
||||
['', ['name', 'var_name', 'type', 'text_default_value', 'select_default_value', 'extra_args', 'tips', 'required']]
|
||||
],
|
||||
fieldsMeta: {
|
||||
text_default_value: {
|
||||
label: this.$t('DefaultValue'),
|
||||
hidden: (formValue) => {
|
||||
return formValue.type !== 'text'
|
||||
},
|
||||
rules: [{ validator: defaultValidator }],
|
||||
helpTip: this.$t('DefaultValueTip'),
|
||||
el: {
|
||||
type: 'input'
|
||||
}
|
||||
},
|
||||
select_default_value: {
|
||||
label: this.$t('DefaultValue'),
|
||||
helpTip: this.$t('DefaultValueTip'),
|
||||
hidden: (formValue) => {
|
||||
return formValue.type !== 'select'
|
||||
},
|
||||
rules: [{ validator: defaultValidator }],
|
||||
el: { type: 'input' }
|
||||
},
|
||||
extra_args: {
|
||||
hidden: (formValue) => {
|
||||
return formValue.type !== 'select'
|
||||
},
|
||||
el: { type: 'textarea', rows: 4, placeholder: this.$t('ExtraArgsPlaceholder') },
|
||||
rules: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
try {
|
||||
const lines = value.split('\n')
|
||||
const regex = /^[^:]+:[^:]+$/
|
||||
for (const line of lines) {
|
||||
if (!regex.test(line.trim())) {
|
||||
callback(new Error(this.$t('ExtraArgsFormatError')))
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
callback(new Error(this.$t('ExtraArgsFormatError')))
|
||||
}
|
||||
callback()
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
required: {
|
||||
on: {
|
||||
change: ([event], updateForm) => {
|
||||
this.defaultValueRequired = event
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
hasSaveContinue: false,
|
||||
method: 'get'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.defaultValueRequired = this.variable?.required || false
|
||||
},
|
||||
methods: {
|
||||
confirm(form) {
|
||||
if (this.variable?.name) {
|
||||
this.$emit('edit', form)
|
||||
} else {
|
||||
this.$emit('add', form)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.variable-add {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 5px;
|
||||
|
||||
.help-block {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .form-group-header {
|
||||
.hr-line-dashed {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
74
src/components/Apps/VariableSetForm/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<AutoDataForm
|
||||
ref="AutoDataForm"
|
||||
class="variable-set"
|
||||
:submit-btn-text="submitBtnText"
|
||||
v-bind="$data"
|
||||
:fields="fields"
|
||||
@submit="confirm"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AutoDataForm from '@/components/Form/AutoDataForm/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'VariableSetForm',
|
||||
components: {
|
||||
AutoDataForm
|
||||
},
|
||||
props: {
|
||||
formData: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
queryParam: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
submitBtnText: this.$t('Confirm'),
|
||||
// 防止缓存form remoteMeta
|
||||
url: `/api/v1/ops/variables/form-data/?t=${new Date().getTime()}&` + this.queryParam,
|
||||
form: {},
|
||||
hasSaveContinue: false,
|
||||
method: 'get',
|
||||
hasReset: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fields() {
|
||||
return [['', this.formData.map(item => item.var_name)]]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirm(form) {
|
||||
this.$emit('confirm', form)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.variable-set {
|
||||
::v-deep .el-form-item {
|
||||
margin-bottom: 5px;
|
||||
|
||||
.help-block {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .form-group-header {
|
||||
.hr-line-dashed {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
formatterData = data
|
||||
}
|
||||
return (
|
||||
<span>{formatterData}</span>
|
||||
<span style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', lineHeight: '1.2' }}>{formatterData}</span>
|
||||
)
|
||||
}
|
||||
if (this.value instanceof Array) {
|
||||
@@ -92,7 +92,7 @@ export default {
|
||||
)
|
||||
}
|
||||
return (
|
||||
<span title={this.displayValue}>{this.displayValue}</span>
|
||||
<span style='white-space: pre-wrap;' title={this.displayValue}>{this.displayValue}</span>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
@click="handleClick(action)"
|
||||
>
|
||||
<el-tooltip :content="action.tip" :disabled="!action.tip" :open-delay="500" placement="top">
|
||||
<span>
|
||||
<span :title="action.tip">
|
||||
<span v-if="action.icon && !action.icon.startsWith('el-')" style="vertical-align: initial">
|
||||
<i v-if="action.icon.startsWith('fa')" :class="'fa ' + action.icon" />
|
||||
<svg-icon v-else :icon-class="action.icon" />
|
||||
@@ -249,10 +249,11 @@ $color-drop-menu-border: #e4e7ed;
|
||||
align-items: flex-end;
|
||||
|
||||
.el-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2px 6px;
|
||||
padding: 2px 5px;
|
||||
color: $btn-text-color;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
* {
|
||||
vertical-align: baseline !important;
|
||||
|
||||
@@ -10,13 +10,16 @@
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<slot />
|
||||
<div v-loading="disabledStatus">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div v-if="showButtons" slot="footer" class="dialog-footer">
|
||||
<slot name="footer">
|
||||
<el-button v-if="showCancel && showButtons" size="small" @click="onCancel">{{ cancelTitle }}</el-button>
|
||||
<el-button
|
||||
v-if="showConfirm && showButtons"
|
||||
:loading="loadingStatus"
|
||||
:disabled="disabledStatus"
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="onConfirm"
|
||||
@@ -69,7 +72,7 @@ export default {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loadingStatus: {
|
||||
disabledStatus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
@@ -79,7 +82,8 @@ export default {
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iWidth() {
|
||||
@@ -119,6 +123,7 @@ export default {
|
||||
|
||||
&__body {
|
||||
padding: 20px 30px;
|
||||
font-size: 13px;
|
||||
|
||||
&:has(.el-table) {
|
||||
background: #f3f3f4;
|
||||
|
||||
@@ -85,6 +85,7 @@ export default {
|
||||
// 如果不想等,证明是 value 自己变化导致的, 需要重新渲染
|
||||
if (valJson !== this.formJson) {
|
||||
this.iValue = val
|
||||
this.$log.debug('Sub form value changed, rerender form: ', this.formJson, valJson)
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
@@ -95,11 +96,12 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
outputValue: _.debounce(function(val) {
|
||||
this.$emit('input', val)
|
||||
}),
|
||||
updateValue(val) {
|
||||
this.iValue = val
|
||||
setTimeout(() => {
|
||||
this.$emit('input', val)
|
||||
}, 100)
|
||||
this.outputValue(val)
|
||||
},
|
||||
objectToString(obj) {
|
||||
let data = ''
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<span
|
||||
<div
|
||||
v-for="(group, i) in groups"
|
||||
:key="'group-'+group.name"
|
||||
:slot="'id:'+group.name"
|
||||
@@ -18,7 +18,7 @@
|
||||
:index="i"
|
||||
:line="i !== 0 && !groupHidden(groups[i - 1], i - 1)"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</DataForm>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import BasicTree from '@/components/Form/FormFields/BasicTree.vue'
|
||||
import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue'
|
||||
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 {
|
||||
@@ -135,9 +134,6 @@ export class FormFieldGenerator {
|
||||
case 'comment':
|
||||
field.el.type = 'textarea'
|
||||
break
|
||||
case 'users':
|
||||
field.component = TransferSelect
|
||||
field.el.label = field.label
|
||||
}
|
||||
return field
|
||||
}
|
||||
@@ -197,6 +193,24 @@ export class FormFieldGenerator {
|
||||
return field
|
||||
}
|
||||
|
||||
setChoicesTips(field, fieldMeta, fieldRemoteMeta) {
|
||||
// 设置 checkbox 的 tips
|
||||
if (['checkbox-group', 'radio-group'].indexOf(field.type) !== -1) {
|
||||
field.options.map(option => {
|
||||
if (!option.tip && field.tips) {
|
||||
option.tip = field.tips[option.value]
|
||||
}
|
||||
if (!option.tip) {
|
||||
const match = option.label.match(/^(.+?)\s*\((.*?)\)$/)
|
||||
if (match) {
|
||||
option.label = match[1]
|
||||
option.tip = match[2]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
afterGenerateField(field) {
|
||||
field.label = toSentenceCase(field.label)
|
||||
|
||||
@@ -204,15 +218,7 @@ export class FormFieldGenerator {
|
||||
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]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
this.setChoicesTips(field)
|
||||
return field
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
placement="right"
|
||||
popper-class="help-tips"
|
||||
>
|
||||
<div slot="content" v-sanitize="data.helpTip" /> <!-- Noncompliant -->
|
||||
<div slot="content" v-sanitize="data.helpTip" class="help-tip-content" /> <!-- Noncompliant -->
|
||||
<i class="fa fa-question-circle-o help-tip-icon" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
@@ -322,4 +322,9 @@ export default {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.help-tip-content {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
>
|
||||
{{ iSubmitBtnText }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="defaultButton && hasSaveContinue"
|
||||
size="small"
|
||||
@@ -40,6 +41,7 @@
|
||||
>
|
||||
{{ $t("SaveAndAddAnother") }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="defaultButton && hasReset"
|
||||
size="small"
|
||||
@@ -47,6 +49,7 @@
|
||||
>
|
||||
{{ $t("Reset") }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-for="button in moreButtons"
|
||||
v-show="!button.hidden"
|
||||
@@ -217,9 +220,13 @@ export default {
|
||||
|
||||
.el-form-item__label {
|
||||
padding: 0 30px 0 0;
|
||||
line-height: 32px;
|
||||
line-height: 30px;
|
||||
color: var(--color-text-primary);
|
||||
|
||||
span {
|
||||
display: unset;
|
||||
}
|
||||
|
||||
i {
|
||||
color: var(--color-icon-primary);
|
||||
}
|
||||
|
||||
@@ -8,99 +8,118 @@
|
||||
:label="item.name"
|
||||
:prop="item.name"
|
||||
>
|
||||
|
||||
<template v-if="item.type === 'button' && !item.isVisible">
|
||||
<el-button
|
||||
:type="item.el && item.el.type"
|
||||
class="start-stop-btn"
|
||||
size="mini"
|
||||
@click="item.callback()"
|
||||
>
|
||||
<i :class="item.icon" />{{ item.name }}
|
||||
</el-button>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-button
|
||||
:type="item.el && item.el.type"
|
||||
class="start-stop-btn"
|
||||
size="mini"
|
||||
@click="item.callback()"
|
||||
>
|
||||
<i :class="item.icon" />
|
||||
|
||||
{{ item.name }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'input' && item.el && item.el.autoComplete">
|
||||
<el-autocomplete
|
||||
v-model="formModel[item.name]"
|
||||
:fetch-suggestions="item.el.query"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@change="handleInputChange(item)"
|
||||
@select="handleInputChange(item)"
|
||||
/>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-autocomplete
|
||||
v-model="formModel[item.name]"
|
||||
:fetch-suggestions="item.el.query"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
clearable
|
||||
@change="handleInputChange(item)"
|
||||
@select="handleInputChange(item)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-else-if="item.type === 'input'">
|
||||
<el-input
|
||||
v-model="formModel[item.name]"
|
||||
:class="!isFold ? 'special-style' : ''"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-input
|
||||
v-model="formModel[item.name]"
|
||||
:class="!isFold ? 'special-style' : ''"
|
||||
:placeholder="item.placeholder"
|
||||
class="inline-input"
|
||||
size="mini"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'select' && item.el && item.el.create">
|
||||
<span class="filter-label">{{ item.name }}:</span>
|
||||
<el-select
|
||||
v-if="item.type === 'select' && item.el && item.el.create"
|
||||
:key="index"
|
||||
v-model="formModel[item.name]"
|
||||
:allow-create="item.el.create || false"
|
||||
:filterable="item.el.create || false"
|
||||
:multiple="item.el.multiple"
|
||||
:placeholder="item.name"
|
||||
class="autoWidth-select"
|
||||
default-first-option
|
||||
size="mini"
|
||||
@change="item.callback(item.value)"
|
||||
>
|
||||
<template slot="prefix">{{ item.label + ':' + item.value }}</template>
|
||||
<el-option
|
||||
v-for="(option, id) in item.options"
|
||||
:key="id"
|
||||
:label="option.label"
|
||||
:title="option.value"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<span class="filter-label">{{ item.name }}:</span>
|
||||
<el-select
|
||||
v-if="item.type === 'select' && item.el && item.el.create"
|
||||
:key="index"
|
||||
v-model="formModel[item.name]"
|
||||
:allow-create="item.el.create || false"
|
||||
:filterable="item.el.create || false"
|
||||
:multiple="item.el.multiple"
|
||||
:placeholder="item.name"
|
||||
class="autoWidth-select"
|
||||
default-first-option
|
||||
size="mini"
|
||||
@change="item.callback(item.value)"
|
||||
>
|
||||
<template slot="prefix">{{ item.label + ':' + item.value }}</template>
|
||||
<el-option
|
||||
v-for="(option, id) in item.options"
|
||||
:key="id"
|
||||
:label="option.label"
|
||||
:title="option.value"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'select' && (!item.el || !item.el.create)">
|
||||
<el-dropdown
|
||||
class="select-dropdown"
|
||||
trigger="click"
|
||||
@command="(command) => {
|
||||
item.value = command
|
||||
item.callback(command)
|
||||
}"
|
||||
>
|
||||
<el-button size="mini" type="primary">
|
||||
<div class="text-content">
|
||||
<span class="content">
|
||||
{{ getLabel(item.value, item.options) }}
|
||||
<i class="el-icon-arrow-down el-icon--right" />
|
||||
</span>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-dropdown-menu v-slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="(option, i) in item.options"
|
||||
:key="i"
|
||||
:command="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-dropdown
|
||||
class="select-dropdown"
|
||||
trigger="click"
|
||||
@command="(command) => {
|
||||
item.value = command
|
||||
item.callback(command)
|
||||
}"
|
||||
>
|
||||
<el-button size="mini" type="primary">
|
||||
<div class="text-content">
|
||||
<span class="content">
|
||||
{{ getLabel(item.value, item.options) }}
|
||||
<i class="el-icon-arrow-down el-icon--right" />
|
||||
</span>
|
||||
</div>
|
||||
</el-button>
|
||||
<el-dropdown-menu v-slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="(option, i) in item.options"
|
||||
:key="i"
|
||||
:command="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template v-if="item.type === 'switch'">
|
||||
<el-switch
|
||||
v-model="formModel[item.name]"
|
||||
:active-text="item.name"
|
||||
:disabled="item.disabled"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
<el-tooltip :disabled="!item.tip" :content="item.tip">
|
||||
<el-switch
|
||||
v-model="formModel[item.name]"
|
||||
:active-text="item.name"
|
||||
:disabled="item.disabled"
|
||||
@change="item.callback(formModel[item.name])"
|
||||
/>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<div
|
||||
@@ -226,7 +245,7 @@ export default {
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('update:value', val)
|
||||
this.$emit('change', val)
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
iOptions() {
|
||||
@@ -379,7 +398,6 @@ $input-border-color: #C0C4CC;
|
||||
}
|
||||
|
||||
.editor {
|
||||
//margin-left: 30px;
|
||||
border: 1px solid var(--color-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
html:lang(pt-br) {
|
||||
.datepicker ::v-deep .el-range-separator {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.datepicker {
|
||||
margin-left: 10px;
|
||||
width: 233px;
|
||||
|
||||
@@ -15,6 +15,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultValue: 24,
|
||||
displayMapper: {
|
||||
'second': this.$t('Second'), // 'sec' is the default value of 'unit
|
||||
'min': this.$t('Minute'), // 'min' is the default value of 'unit
|
||||
|
||||
@@ -48,7 +48,8 @@ export default {
|
||||
type: 'input-number',
|
||||
el: {
|
||||
min: 8,
|
||||
max: 30
|
||||
max: 36,
|
||||
size: 'mini'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-input v-model="rawValue.phone" required :placeholder="$tc('InputPhone')" @input="OnInputChange">
|
||||
<el-input v-model="rawValue.phone" :placeholder="$tc('InputPhone')" required @input="onInputChange">
|
||||
<el-select
|
||||
slot="prepend"
|
||||
:placeholder="$tc('Select')"
|
||||
:value="rawValue.code"
|
||||
style="width: 75px;"
|
||||
@change="OnChange"
|
||||
style="width: 105px;"
|
||||
@change="onChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="country in countries"
|
||||
:key="country.value"
|
||||
:key="country.name"
|
||||
:label="country.value"
|
||||
:value="country.value"
|
||||
style="width: 200px;"
|
||||
>
|
||||
<span style="float: left">{{ country.name }}</span>
|
||||
<span class="country-name">{{ country.name }}</span>
|
||||
<span style="float: right; font-size: 13px">{{ country.value }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
@@ -24,19 +23,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'PhoneInput',
|
||||
props: {
|
||||
value: {
|
||||
type: [Object, String],
|
||||
default: () => ({ 'code': '+86', 'phone': '' })
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rawValue: {}
|
||||
rawValue: {},
|
||||
countries: [{ 'name': 'China', 'value': '+86' }]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -45,26 +44,38 @@ export default {
|
||||
return ''
|
||||
}
|
||||
return `${this.rawValue.code}${this.rawValue.phone}`
|
||||
},
|
||||
countries: {
|
||||
get() {
|
||||
return this.publicSettings.COUNTRY_CALLING_CODES
|
||||
}
|
||||
},
|
||||
...mapGetters(['publicSettings'])
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.rawValue = this.value || { code: '+86', phone: '' }
|
||||
const defaults = { code: localStorage.getItem('prePhoneCode') || '+86', phone: '' }
|
||||
this.rawValue = this.value || defaults
|
||||
this.$axios.get('/api/v1/common/countries/').then(res => {
|
||||
this.countries = res.map(item => {
|
||||
return { name: `${item.flag} ${item.name}`, value: item.phone_code }
|
||||
})
|
||||
})
|
||||
this.$emit('input', this.fullPhone)
|
||||
},
|
||||
methods: {
|
||||
OnChange(countryCode) {
|
||||
onChange(countryCode) {
|
||||
this.rawValue.code = countryCode
|
||||
this.OnInputChange()
|
||||
this.onInputChange()
|
||||
localStorage.setItem('prePhoneCode', countryCode)
|
||||
},
|
||||
OnInputChange() {
|
||||
onInputChange() {
|
||||
this.$emit('input', this.fullPhone)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.country-name {
|
||||
display: inline-block;
|
||||
width: 150px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="!loading" :class="showSetting ? 'show-setting' : 'hide-setting'">
|
||||
<div :class="showSetting ? 'show-setting' : 'hide-setting'">
|
||||
<div v-for="(item, index) in items" :key="item.name" class="protocol-item">
|
||||
<el-input
|
||||
v-model="item.port"
|
||||
@@ -114,8 +114,7 @@ export default {
|
||||
name: '',
|
||||
items: [],
|
||||
currentProtocol: {},
|
||||
showDialog: false,
|
||||
loading: false
|
||||
showDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -144,13 +143,8 @@ export default {
|
||||
watch: {
|
||||
choices: {
|
||||
handler(value, oldValue) {
|
||||
if (value?.length === oldValue?.length) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
setTimeout(() => {
|
||||
this.setDefaultItems(value)
|
||||
this.loading = false
|
||||
},)
|
||||
},
|
||||
deep: true,
|
||||
@@ -345,6 +339,10 @@ export default {
|
||||
.protocol-item {
|
||||
display: flex;
|
||||
margin: 5px 0;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.input-button {
|
||||
|
||||
@@ -125,16 +125,19 @@ export default {
|
||||
allowCreate: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
defaultPageSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const vm = this
|
||||
const defaultPageSize = 10
|
||||
const defaultParams = {
|
||||
search: '',
|
||||
page: 1,
|
||||
hasMore: true,
|
||||
pageSize: defaultPageSize
|
||||
pageSize: vm.defaultPageSize
|
||||
}
|
||||
// 设置axios全局报错提示不显示
|
||||
const validateStatus = (status) => {
|
||||
@@ -194,7 +197,6 @@ export default {
|
||||
}
|
||||
},
|
||||
iAjax() {
|
||||
const defaultPageSize = 10
|
||||
const defaultMakeParams = (params) => {
|
||||
const page = params.page || 1
|
||||
const offset = (page - 1) * params.pageSize
|
||||
@@ -237,7 +239,7 @@ export default {
|
||||
}
|
||||
const defaultAjax = {
|
||||
url: '',
|
||||
pageSize: defaultPageSize,
|
||||
pageSize: this.defaultPageSize,
|
||||
makeParams: defaultMakeParams,
|
||||
transformOption: defaultTransformOption,
|
||||
processResults: defaultProcessResults,
|
||||
|
||||
@@ -14,10 +14,12 @@
|
||||
:close-on-click-modal="false"
|
||||
:title="label"
|
||||
:visible.sync="showTransfer"
|
||||
:disabled-status="!isLoaded"
|
||||
class="the-dialog"
|
||||
width="730px"
|
||||
@cancel="handleTransCancel"
|
||||
@confirm="handleTransConfirm"
|
||||
v-on="$listeners"
|
||||
>
|
||||
<krryPaging v-if="selectInitialized" ref="pageTransfer" class="transfer" v-bind="pagingTransfer" />
|
||||
</Dialog>
|
||||
@@ -77,13 +79,16 @@ export default {
|
||||
if (keyword) {
|
||||
params['search'] = keyword
|
||||
}
|
||||
this.isLoaded = false
|
||||
const data = await this.$axios.get(url, { params })
|
||||
this.isLoaded = true
|
||||
return data['results'].map(item => {
|
||||
const n = transformOption(item)
|
||||
return { id: n.value, label: n.label }
|
||||
})
|
||||
}
|
||||
return {
|
||||
isLoaded: false,
|
||||
showTransfer: false,
|
||||
selectInitialized: false,
|
||||
select2: {
|
||||
@@ -167,7 +172,3 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -42,7 +42,7 @@ export default {
|
||||
patterns.push([/\d/, i18n.t('NUMBER_REQUIRED')])
|
||||
}
|
||||
if (passwordRule['SECURITY_PASSWORD_SPECIAL_CHAR']) {
|
||||
const pattern = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?]")
|
||||
const pattern = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、?_+-]")
|
||||
patterns.push([pattern, i18n.t('SPECIAL_CHAR_REQUIRED')])
|
||||
}
|
||||
for (const [pattern, msg] of patterns) {
|
||||
|
||||
@@ -39,6 +39,22 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
html:lang(en) .quick-actions ::v-deep button {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
html:lang(ja) .quick-actions ::v-deep button {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
html:lang(zh-tw) .quick-actions ::v-deep button {
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
html:lang(zh-cn) .quick-actions ::v-deep button {
|
||||
width: 65px;
|
||||
}
|
||||
|
||||
.quick-actions ::v-deep table {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -58,11 +74,9 @@ export default {
|
||||
.quick-actions ::v-deep button {
|
||||
padding: 4px 5px;
|
||||
font-size: 13px;
|
||||
width: 65px;
|
||||
|
||||
span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap; /* 控制文本不换行 */
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="showColumnSettingPopover"
|
||||
:cancel-title="$tc('RestoreDefault')"
|
||||
:destroy-on-close="true"
|
||||
:title="$tc('TableSetting')"
|
||||
:title="$tc('ListPreference')"
|
||||
:visible.sync="showColumnSettingPopover"
|
||||
top="10%"
|
||||
width="50%"
|
||||
@@ -36,6 +36,7 @@
|
||||
<el-checkbox
|
||||
:disabled="item.prop==='actions' || minColumns.indexOf(item.prop)!==-1"
|
||||
:label="item.prop"
|
||||
:title="item.label"
|
||||
>
|
||||
{{ item.label }}
|
||||
</el-checkbox>
|
||||
@@ -90,8 +91,14 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('showColumnSettingPopover', this.showColumnSettingPopoverHandler)
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showColumnSettingPopover', ({ url }) => {
|
||||
this.$eventBus.$on('showColumnSettingPopover', this.showColumnSettingPopoverHandler)
|
||||
},
|
||||
methods: {
|
||||
showColumnSettingPopoverHandler({ url }) {
|
||||
if (url === this.url) {
|
||||
this.checkAll = false
|
||||
this.showColumnSettingPopover = true
|
||||
@@ -105,9 +112,7 @@ export default {
|
||||
this.checkAll = false
|
||||
this.isIndeterminate = true
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
handleColumnConfirm() {
|
||||
this.showColumnSettingPopover = false
|
||||
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })
|
||||
|
||||
@@ -65,7 +65,17 @@ export default {
|
||||
isDeactivated: false
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
dynamicActionWidth() {
|
||||
if (this.$i18n.locale === 'en') {
|
||||
return '120px'
|
||||
}
|
||||
if (this.$i18n.locale === 'pt-br') {
|
||||
return '160px'
|
||||
}
|
||||
return '100px'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config: {
|
||||
handler: _.debounce(function(iNew, iOld) {
|
||||
@@ -131,7 +141,7 @@ export default {
|
||||
prop: 'actions',
|
||||
label: i18n.t('Actions'),
|
||||
align: 'center',
|
||||
width: '100px',
|
||||
width: this.dynamicActionWidth,
|
||||
formatter: ActionsFormatter,
|
||||
fixed: 'right',
|
||||
formatterArgs: {}
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
<template v-else>
|
||||
<el-table
|
||||
ref="table"
|
||||
v-loading="loading"
|
||||
v-loading="tableLoading"
|
||||
:data="data"
|
||||
:row-class-name="rowClassName"
|
||||
v-bind="tableAttrs"
|
||||
@select="selectStrategy.onSelect"
|
||||
v-on="$listeners"
|
||||
@selection-change="selectStrategy.onSelectionChange"
|
||||
@select-all="selectStrategy.onSelectAll($event, canSelect)"
|
||||
@select-all="handleSelectAll($event, canSelect)"
|
||||
@sort-change="onSortChange"
|
||||
>
|
||||
<!--TODO 不用jsx写, 感觉template逻辑有点不清晰了-->
|
||||
@@ -749,7 +749,7 @@ export default {
|
||||
page: defaultFirstPage,
|
||||
// https://github.com/ElemeFE/element/issues/1153
|
||||
total: null,
|
||||
loading: false,
|
||||
tableLoading: false,
|
||||
// 多选项的数组
|
||||
selected: [],
|
||||
|
||||
@@ -940,11 +940,11 @@ export default {
|
||||
},
|
||||
getListFromStaticData({ loading = true } = {}) {
|
||||
if (loading) {
|
||||
this.loading = true
|
||||
this.tableLoading = true
|
||||
}
|
||||
if (!this.hasPagination) {
|
||||
this.data = this.totalData
|
||||
this.loading = false
|
||||
this.tableLoading = false
|
||||
if (this.isTree) {
|
||||
this.data = this.tree2Array(this.data, this.expandAll)
|
||||
}
|
||||
@@ -957,7 +957,7 @@ export default {
|
||||
const end = (page + pageOffset) * this.size
|
||||
this.$log.debug(`page: ${page}, size: ${this.size}, start: ${start}, end: ${end}`)
|
||||
this.data = this.totalData.slice(start, end)
|
||||
this.loading = false
|
||||
this.tableLoading = false
|
||||
this.data = this.tree2Array(this.data, this.expandAll)
|
||||
return this.data
|
||||
},
|
||||
@@ -983,7 +983,7 @@ export default {
|
||||
queryUtil.stringify(query, '=', '&')
|
||||
|
||||
// 请求开始
|
||||
this.loading = loading
|
||||
this.tableLoading = loading
|
||||
|
||||
// 存储query记录, 便于后面恢复
|
||||
if (this.saveQuery) {
|
||||
@@ -1023,7 +1023,7 @@ export default {
|
||||
this.total === 0 &&
|
||||
(_isEmpty(formValue) || _values(formValue).every(isFalsey))
|
||||
|
||||
this.loading = false
|
||||
this.tableLoading = false
|
||||
/**
|
||||
* 请求返回, 数据更新后触发
|
||||
* @property {object} data - table的数据
|
||||
@@ -1043,7 +1043,7 @@ export default {
|
||||
*/
|
||||
this.$emit('error', err)
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
this.tableLoading = false
|
||||
})
|
||||
},
|
||||
search(attrs, reset) {
|
||||
@@ -1106,6 +1106,14 @@ export default {
|
||||
this.page = val
|
||||
this.getList()
|
||||
},
|
||||
handleSelectAll(selection, selectable = () => true) {
|
||||
this.tableLoading = true
|
||||
try {
|
||||
this.selectStrategy.onSelectAll(selection, selectable)
|
||||
} finally {
|
||||
this.tableLoading = false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否
|
||||
*
|
||||
|
||||
@@ -69,12 +69,48 @@ class StrategyPersistSelection extends StrategyAbstract {
|
||||
* 用户切换当前页的多选
|
||||
*/
|
||||
onSelectAll(selection, selectable = () => true) {
|
||||
const isSelected = !!selection.length
|
||||
this.elDataTable.data.forEach(r => {
|
||||
if (selectable(r)) {
|
||||
this.toggleRowSelection(r, isSelected)
|
||||
}
|
||||
})
|
||||
const { id, selected, data } = this.elDataTable
|
||||
const selectableRows = data.filter(selectable)
|
||||
// const isSelected = !!selection.length
|
||||
|
||||
// 创建已选择项的 id 集合,用于快速查找
|
||||
const selectedIds = new Set(selected.map(r => r[id]))
|
||||
const currentPageIds = new Set(selectableRows.map(row => row[id]))
|
||||
|
||||
// 前页面的选中状态
|
||||
const currentPageSelectedCount = selectableRows.filter(row =>
|
||||
selectedIds.has(row[id])
|
||||
).length
|
||||
|
||||
// 判断是全选还是取消全选
|
||||
const shouldSelectAll = currentPageSelectedCount < selectableRows.length
|
||||
|
||||
this.elTable.clearSelection()
|
||||
|
||||
if (shouldSelectAll) {
|
||||
selectableRows.forEach(row => {
|
||||
if (!selectedIds.has(row[id])) selected.push(row)
|
||||
|
||||
this.elTable.toggleRowSelection(row, true)
|
||||
|
||||
// ! 这里需要触发事件,否则在 el-table 中无法触发 selection-change 事件
|
||||
this.elDataTable.$emit('toggle-row-selection', true, row)
|
||||
})
|
||||
} else {
|
||||
const newSelected = []
|
||||
|
||||
selected.forEach(row => {
|
||||
if (!currentPageIds.has(row[id])) {
|
||||
newSelected.push(row)
|
||||
} else {
|
||||
this.elDataTable.$emit('toggle-row-selection', false, row)
|
||||
}
|
||||
})
|
||||
|
||||
this.elDataTable.selected = newSelected
|
||||
}
|
||||
|
||||
this.elDataTable.$emit('selection-change', this.elDataTable.selected)
|
||||
}
|
||||
/**
|
||||
* toggleRowSelection和clearSelection管理elDataTable的selected数组
|
||||
@@ -83,14 +119,17 @@ class StrategyPersistSelection extends StrategyAbstract {
|
||||
toggleRowSelection(row, isSelected) {
|
||||
const { id, selected } = this.elDataTable
|
||||
const foundIndex = selected.findIndex(r => r[id] === row[id])
|
||||
|
||||
if (typeof isSelected === 'undefined') {
|
||||
isSelected = foundIndex <= -1
|
||||
}
|
||||
|
||||
if (isSelected && foundIndex === -1) {
|
||||
selected.push(row)
|
||||
} else if (!isSelected && foundIndex > -1) {
|
||||
selected.splice(foundIndex, 1)
|
||||
}
|
||||
|
||||
this.elDataTable.$emit('toggle-row-selection', isSelected, row)
|
||||
this.updateElTableSelection()
|
||||
}
|
||||
@@ -103,14 +142,17 @@ class StrategyPersistSelection extends StrategyAbstract {
|
||||
*/
|
||||
updateElTableSelection() {
|
||||
const { data, id, selected } = this.elDataTable
|
||||
// 历史勾选的行已经不在当前页了,所以要将当前页的行数据和selected合并
|
||||
const mergeData = _.uniqWith([...data, ...selected], _.isEqual)
|
||||
mergeData.forEach(r => {
|
||||
const isSelected = !!selected.find(r2 => r[id] === r2[id])
|
||||
if (!this.elTable) {
|
||||
return
|
||||
const selectedIds = new Set(selected.map(r => r[id]))
|
||||
|
||||
this.elTable.clearSelection()
|
||||
|
||||
data.forEach(row => {
|
||||
const shouldBeSelected = selectedIds.has(row[id])
|
||||
if (!this.elTable) return
|
||||
|
||||
if (shouldBeSelected) {
|
||||
this.elTable.toggleRowSelection(row, true)
|
||||
}
|
||||
this.elTable.toggleRowSelection(r, isSelected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,8 @@ export default {
|
||||
this.toggleRowSelection(row, true)
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit('loaded')
|
||||
},
|
||||
handleSizeChange(val) {
|
||||
localStorage.setItem('paginationSize', val)
|
||||
|
||||
@@ -171,14 +171,18 @@ export default {
|
||||
]
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('showExportDialog', this.showExportDialogHandler)
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showExportDialog', ({ selectedRows, url, name }) => {
|
||||
this.$eventBus.$on('showExportDialog', this.showExportDialogHandler)
|
||||
},
|
||||
methods: {
|
||||
showExportDialogHandler({ selectedRows, url, name }) {
|
||||
if (url === this.url || url.indexOf(this.url) > -1) {
|
||||
this.showExportDialog()
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
showExportDialog() {
|
||||
if (!this.mfaVerifyRequired) {
|
||||
this.exportDialogShow = true
|
||||
|
||||
@@ -142,14 +142,18 @@ export default {
|
||||
this.showTable = false
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$eventBus.$off('showImportDialog', this.showImportEventHandler)
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('showImportDialog', ({ url }) => {
|
||||
this.$eventBus.$on('showImportDialog', this.showImportEventHandler)
|
||||
},
|
||||
methods: {
|
||||
showImportEventHandler({ url }) {
|
||||
if (url === this.url) {
|
||||
this.showImportDialog = true
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
closeDialog() {
|
||||
this.showImportDialog = false
|
||||
this.$emit('importDialogClose')
|
||||
|
||||
@@ -44,8 +44,9 @@
|
||||
import DataTable from '@/components/Table/DataTable/index.vue'
|
||||
import { getUpdateObjURL } from '@/utils/common'
|
||||
import { sleep } from '@/utils/time'
|
||||
import { EditableInputFormatter, StatusFormatter } from '@/components/Table/TableFormatters'
|
||||
import { EditableInputFormatter } from '@/components/Table/TableFormatters'
|
||||
import { encryptPassword } from '@/utils/crypto'
|
||||
import getStatusColumnMeta from '@/components/Table/ListTable/TableAction/const'
|
||||
|
||||
export default {
|
||||
name: 'ImportTable',
|
||||
@@ -223,38 +224,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
generateTableColumns(tableTitles, tableData) {
|
||||
const vm = this
|
||||
const columns = [{
|
||||
prop: '@status',
|
||||
label: vm.$t('Status'),
|
||||
width: '80px',
|
||||
align: 'center',
|
||||
formatter: StatusFormatter,
|
||||
formatterArgs: {
|
||||
faChoices: {
|
||||
ok: 'fa-check text-primary',
|
||||
error: 'fa-times text-danger',
|
||||
pending: 'fa-clock-o'
|
||||
},
|
||||
getChoicesKey(val) {
|
||||
if (val === 'ok' || val === 'pending') {
|
||||
return val
|
||||
}
|
||||
return 'error'
|
||||
},
|
||||
getTip(val) {
|
||||
if (val === 'ok') {
|
||||
return vm.$t('Success')
|
||||
} else if (val === 'pending') {
|
||||
return vm.$t('Pending')
|
||||
} else if (val && val.name === 'error') {
|
||||
return val.error
|
||||
}
|
||||
return ''
|
||||
},
|
||||
hasTips: true
|
||||
}
|
||||
}]
|
||||
const columns = [{ ...getStatusColumnMeta.bind(this)().status }]
|
||||
for (const item of tableTitles) {
|
||||
const dataItemLens = tableData.map(d => {
|
||||
if (!d) {
|
||||
|
||||
@@ -71,7 +71,16 @@ export default {
|
||||
this.listenViewPort()
|
||||
},
|
||||
mounted() {
|
||||
this.$eventBus.$on('labelSearch', label => {
|
||||
this.$eventBus.$on('labelSearch', this.labelSearchHandler)
|
||||
},
|
||||
beforeDestroy(label) {
|
||||
this.$eventBus.$off('labelSearch', this.labelSearchHandler)
|
||||
},
|
||||
methods: {
|
||||
handleCascaderFocus() {
|
||||
this.setSearchFocus()
|
||||
},
|
||||
labelSearchHandler(label) {
|
||||
if (!label) {
|
||||
this.labelValue = []
|
||||
this.showLabelSearch = true
|
||||
@@ -82,14 +91,6 @@ export default {
|
||||
setTimeout(() => {
|
||||
this.showLabelSearch = true
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
this.$eventBus.$off('labelSearch')
|
||||
},
|
||||
methods: {
|
||||
handleCascaderFocus() {
|
||||
this.setSearchFocus()
|
||||
},
|
||||
handleCascaderVisibleChange(visible) {
|
||||
const input = this.$refs.labelCascader.$el
|
||||
|
||||
@@ -40,8 +40,8 @@ export default {
|
||||
handleExportClick: {
|
||||
type: Function,
|
||||
default: function({ selectedRows }) {
|
||||
const { exportOptions, tableUrl } = this
|
||||
const url = exportOptions?.url ? exportOptions.url : tableUrl
|
||||
// const { exportOptions, tableUrl } = this
|
||||
const url = this.iExportOptions.url
|
||||
this.dialogExportVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$eventBus.$emit('showExportDialog', { selectedRows, url, name: this.name })
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
{
|
||||
name: 'actionSetting',
|
||||
icon: 'system-setting',
|
||||
tip: this.$t('TableSetting'),
|
||||
tip: this.$t('ListPreference'),
|
||||
has: this.hasColumnSetting,
|
||||
callback: this.handleTableSettingClick.bind(this)
|
||||
},
|
||||
@@ -155,7 +155,18 @@ export default {
|
||||
})
|
||||
},
|
||||
iExportOptions() {
|
||||
return assignIfNot(this.exportOptions, { url: this.tableUrl })
|
||||
/**
|
||||
* 原本是使用 assignIfNot 此函数内部使用 partialRight, 该函数
|
||||
* 只在目标对象的属性未定义时才从源对象复制属性,如果目标对象已经有值,则保留原值
|
||||
* 那如果首次点击的树节点,那么此时 url 就会被确定,后续点击的树节点,那么 url 就将不会携带节点信息
|
||||
*
|
||||
*/
|
||||
// return assignIfNot(this.exportOptions, { url: this.tableUrl })
|
||||
|
||||
return {
|
||||
...this.exportOptions,
|
||||
url: this.tableUrl
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
40
src/components/Table/ListTable/TableAction/const.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { StatusFormatter } from '@/components/Table/TableFormatters'
|
||||
import i18n from '@/i18n/i18n'
|
||||
|
||||
export const getStatusColumnMeta = (prop = '@status') => {
|
||||
return {
|
||||
status: {
|
||||
prop: prop,
|
||||
label: i18n.t('Status'),
|
||||
width: '80px',
|
||||
align: 'center',
|
||||
formatter: StatusFormatter,
|
||||
formatterArgs: {
|
||||
faChoices: {
|
||||
ok: 'fa-check text-primary',
|
||||
error: 'fa-times text-danger',
|
||||
pending: 'fa-clock-o'
|
||||
},
|
||||
getChoicesKey: (val) => {
|
||||
if (val === 'ok' || val === 'pending') {
|
||||
return val
|
||||
}
|
||||
return 'error'
|
||||
},
|
||||
getTip: (val) => {
|
||||
if (val === 'ok') {
|
||||
return i18n.t('Success')
|
||||
} else if (val === 'pending') {
|
||||
return i18n.t('Pending')
|
||||
} else if ((val && val.name === 'error') || val.error !== undefined) {
|
||||
return val.error
|
||||
}
|
||||
return ''
|
||||
},
|
||||
hasTips: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default getStatusColumnMeta
|
||||
@@ -189,22 +189,23 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.urlUpdated[this.tableUrl] = location.href
|
||||
this.$set(this.urlUpdated, this.tableUrl, location.href)
|
||||
},
|
||||
deactivated() {
|
||||
this.isDeactivated = true
|
||||
},
|
||||
activated() {
|
||||
this.isDeactivated = false
|
||||
const preURL = this.urlUpdated[this.tableUrl]
|
||||
if (!preURL || preURL === location.href) {
|
||||
return
|
||||
}
|
||||
this.urlUpdated[this.tableUrl] = location.href
|
||||
this.$log.debug('Reload the table get latest data: pre ', preURL, ' current: ', location.href)
|
||||
setTimeout(() => {
|
||||
this.$nextTick(() => {
|
||||
this.isDeactivated = false
|
||||
const cleanUrl = this.tableUrl.split('?')[0]
|
||||
const preURL = this.urlUpdated[cleanUrl]
|
||||
|
||||
if (!preURL || preURL === location.href) return
|
||||
|
||||
this.$set(this.urlUpdated, this.tableUrl, location.href)
|
||||
this.$log.debug('Reload the table get latest data: pre ', preURL, ' current: ', location.href)
|
||||
this.reloadTable()
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
handleActionInitialDone() {
|
||||
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs || {})
|
||||
return {
|
||||
formatterArgs: formatterArgs,
|
||||
data: formatterArgs.async ? [] : (this.cellValue || []),
|
||||
listData: formatterArgs.async ? [] : (this.cellValue || []),
|
||||
amount: '',
|
||||
asyncGetDone: false
|
||||
}
|
||||
@@ -68,17 +68,18 @@ export default {
|
||||
return [this.$t('Loading') + '...']
|
||||
}
|
||||
const getItem = this.formatterArgs.getItem || (item => item.name)
|
||||
|
||||
let data = []
|
||||
if (Array.isArray(this.data)) {
|
||||
data = this.data.map(item => getItem(item)) || []
|
||||
} else {
|
||||
// object {key: [value]}
|
||||
data = Object.entries(this.data).map(([key, value]) => {
|
||||
|
||||
if (Array.isArray(this.listData)) {
|
||||
data = this.listData.map(item => getItem(item)).filter(Boolean)
|
||||
} else if (this.listData && typeof this.listData === 'object') {
|
||||
data = Object.entries(this.listData).map(([key, value]) => {
|
||||
const item = { key: key, value: value }
|
||||
return getItem(item)
|
||||
}) || []
|
||||
}).filter(Boolean)
|
||||
}
|
||||
data = data.filter(Boolean)
|
||||
|
||||
return data
|
||||
},
|
||||
showItems() {
|
||||
@@ -86,8 +87,12 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cellValue() {
|
||||
this.computeAmount()
|
||||
cellValue: {
|
||||
handler(newValue) {
|
||||
// listData 需要重新赋值一遍 items 重新计算
|
||||
this.listData = newValue
|
||||
this.computeAmount()
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -105,6 +110,7 @@ export default {
|
||||
// object {key: [value]}
|
||||
cellValue = Object.keys(this.cellValue)
|
||||
}
|
||||
|
||||
this.amount = (cellValue?.filter(value => !this.cellValueToRemove.includes(value)) || []).length
|
||||
}
|
||||
},
|
||||
@@ -127,7 +133,7 @@ export default {
|
||||
const params = this.formatterArgs.ajax.params || {}
|
||||
const transform = this.formatterArgs.ajax.transform || (resp => resp[this.col.prop.replace('_amount', '')])
|
||||
const response = await this.$axios.get(url, { params: params })
|
||||
this.data = transform(response)
|
||||
this.listData = transform(response)
|
||||
this.asyncGetDone = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<span>{{ value }}</span>
|
||||
<span class="date">{{ dateValue }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -10,24 +10,31 @@ export default {
|
||||
name: 'DateFormatter',
|
||||
extends: BaseFormatter,
|
||||
data() {
|
||||
let value
|
||||
if (this.cellValue) {
|
||||
value = toSafeLocalDateStr(this.cellValue)
|
||||
} else {
|
||||
value = '-'
|
||||
}
|
||||
// let value
|
||||
// if (this.cellValue) {
|
||||
// value = toSafeLocalDateStr(this.cellValue)
|
||||
// } else {
|
||||
// value = '-'
|
||||
// }
|
||||
// const locale = this.$i18n.locale
|
||||
// const value = dt.toLocaleString(locale, { hourCycle: 'h23' })
|
||||
// debug(this.$i18n.locale)
|
||||
return {
|
||||
value: value
|
||||
}
|
||||
// return {
|
||||
// value: value
|
||||
// }
|
||||
// return {
|
||||
// value: `${year}-${month}-${date} ${hour}:${minutes}:${seconds}`
|
||||
// }
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
dateValue() {
|
||||
if (this.cellValue) {
|
||||
return toSafeLocalDateStr(this.cellValue)
|
||||
} else {
|
||||
return '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div style="width: 100%;min-height: 20px" @click.stop="editCell">
|
||||
<div class="edit-container" style="min-height: 20px" @click.stop="editCell">
|
||||
<el-input
|
||||
v-if="inEditMode"
|
||||
ref="inputRef"
|
||||
v-model="value"
|
||||
class="editInput"
|
||||
size="mini"
|
||||
@@ -9,8 +10,17 @@
|
||||
@keyup.enter.native="onInputEnter"
|
||||
/>
|
||||
<template v-else>
|
||||
<span>{{ iCellValue }}</span>
|
||||
<span class="cellValue">{{ iCellValue }}</span>
|
||||
<a
|
||||
v-if="formatterArgs.showEditBtn"
|
||||
:class="[{ 'disabled-link': this.$store.getters.currentOrgIsRoot },'edit-btn']"
|
||||
style="padding-left: 5px"
|
||||
@click="editCell"
|
||||
>
|
||||
<i class="fa fa-edit" />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,6 +71,9 @@ export default {
|
||||
editCell() {
|
||||
if (this.formatterArgs.canEdit) {
|
||||
this.inEditMode = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.inputRef.focus()
|
||||
})
|
||||
}
|
||||
},
|
||||
getCellValue(val) {
|
||||
@@ -88,7 +101,7 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
.editInput ::v-deep .el-input__inner {
|
||||
padding: 2px;
|
||||
line-height: 12px;
|
||||
@@ -97,4 +110,35 @@ export default {
|
||||
.editInput {
|
||||
padding: -6px;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
transition: all 1s;
|
||||
|
||||
& > i {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.edit-container {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&:hover {
|
||||
.edit-btn {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.cellValue {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<span v-if="!iLabels || iLabels.length === 0" style="vertical-align: top;">
|
||||
-
|
||||
</span>
|
||||
<div v-else>
|
||||
<div
|
||||
<span v-else class="label-wrapper">
|
||||
<span
|
||||
v-for="label of iLabels"
|
||||
:key="label.id"
|
||||
>
|
||||
@@ -15,8 +15,9 @@
|
||||
class="tag-formatter"
|
||||
@click="handleLabelSearch(label)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span />
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
v-if="formatterArgs.showEditBtn"
|
||||
@@ -30,15 +31,16 @@
|
||||
v-if="showDialog"
|
||||
:title="$tc('BindLabel')"
|
||||
:visible.sync="showDialog"
|
||||
class="tag-dialog"
|
||||
width="600px"
|
||||
@cancel="handleCancel"
|
||||
@confirm="handleConfirm"
|
||||
>
|
||||
<el-row :gutter="1" class="tag-select">
|
||||
<el-row class="tag-select">
|
||||
<el-col :span="12">
|
||||
<Select2 v-model="keySelect2.value" v-bind="keySelect2" @change="handleKeyChanged" />
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-col :span="12" style="padding-left: 5px">
|
||||
<Select2
|
||||
v-model="valueSelect2.value"
|
||||
:disabled="!keySelect2.value"
|
||||
@@ -129,6 +131,16 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
watch: {
|
||||
cellValue: {
|
||||
handler(newValue) {
|
||||
if (newValue) {
|
||||
this.initial = this.formatterArgs.getLabels(this.cellValue)
|
||||
this.iLabels = [...this.initial]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initial = this.formatterArgs.getLabels(this.cellValue)
|
||||
this.iLabels = [...this.initial]
|
||||
@@ -207,18 +219,11 @@ export default {
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-select {
|
||||
::v-deep .el-input__inner::placeholder {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
@@ -233,9 +238,14 @@ export default {
|
||||
|
||||
.label-container {
|
||||
display: flex;
|
||||
height: 28px;
|
||||
|
||||
.label-formatter-col {
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -249,15 +259,23 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.tag-zone {
|
||||
margin: 20px 0 0 0;
|
||||
border: solid 1px #ebeef5;
|
||||
padding: 10px;
|
||||
background: #f2f2f5;
|
||||
.tag-dialog {
|
||||
.tag-zone {
|
||||
margin: 20px 0 0 0;
|
||||
border: solid 1px #ebeef5;
|
||||
padding: 10px;
|
||||
background: #f2f2f5;
|
||||
|
||||
.tag-formatter {
|
||||
margin: 1px 3px;
|
||||
display: inline-block;
|
||||
.tag-formatter {
|
||||
margin: 1px 3px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-select {
|
||||
::v-deep .el-input__inner::placeholder {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +288,6 @@ export default {
|
||||
|
||||
.tag-formatter {
|
||||
margin: 2px 0;
|
||||
|
||||
}
|
||||
|
||||
.tag-tip {
|
||||
|
||||
@@ -24,6 +24,9 @@ export default {
|
||||
},
|
||||
isDisplay(row) {
|
||||
return true
|
||||
},
|
||||
callback({ row }) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,10 +52,11 @@ export default {
|
||||
methods: {
|
||||
onChange(val) {
|
||||
this.$axios.patch(this.patchUrl, this.patchData).then(res => {
|
||||
this.$message.success(this.$t('updateSuccessMsg'))
|
||||
this.formatterArgs.callback(this.row)
|
||||
this.$message.success(this.$t('UpdateSuccessMsg'))
|
||||
}).catch(err => {
|
||||
this.value = !val
|
||||
this.$message.error(this.$t('updateErrorMsg' + ' ' + err))
|
||||
this.$message.error(this.$t('UpdateErrorMsg' + ' ' + err))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,9 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag {
|
||||
display: flex;
|
||||
display: inline-block;
|
||||
flex-wrap: wrap;
|
||||
|
||||
& > span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
size="small"
|
||||
type="info"
|
||||
@click="handleTagClick(v,k)"
|
||||
@close="handleTagClose(k)"
|
||||
@close.stop="handleTagClose(k)"
|
||||
>
|
||||
<strong v-if="v.label">{{ v.label + ':' }}</strong>
|
||||
<span v-if="v.valueLabel">{{ v.valueLabel }}</span>
|
||||
@@ -128,7 +128,7 @@ export default {
|
||||
deep: true
|
||||
},
|
||||
filterTags: {
|
||||
handler() {
|
||||
handler(newValue) {
|
||||
this.$emit('tag-search', this.filterMaps)
|
||||
},
|
||||
deep: true
|
||||
@@ -137,6 +137,15 @@ export default {
|
||||
if (newValue === '' && oldValue !== '') {
|
||||
this.emptyCount = 1
|
||||
}
|
||||
},
|
||||
'$route'(to, from) {
|
||||
if (from.query !== to.query) {
|
||||
this.filterTags = {}
|
||||
if (to.query && Object.keys(to.query).length) {
|
||||
const routeFilter = this.checkInTableColumns(this.options)
|
||||
this.filterTagSearch(routeFilter)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -202,9 +211,10 @@ export default {
|
||||
label: current.label,
|
||||
value: valueDecode
|
||||
}
|
||||
|
||||
if (curChildren.length > 0) {
|
||||
for (const item of curChildren) {
|
||||
if (valueDecode === item.value) {
|
||||
if (valueDecode === String(item.value)) {
|
||||
searchFieldOption.valueLabel = item.label
|
||||
break
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export default {
|
||||
showRenameBtn: false,
|
||||
drag: {
|
||||
isCopy: false,
|
||||
isMove: true
|
||||
isMove: !this.$store.getters.currentOrgIsRoot
|
||||
}
|
||||
},
|
||||
callback: {
|
||||
|
||||
@@ -35,13 +35,29 @@ export default {
|
||||
]),
|
||||
announcement() {
|
||||
const ann = this.publicSettings.ANNOUNCEMENT
|
||||
return { id: ann['ID'], subject: ann['SUBJECT'], content: ann['CONTENT'], link: ann['LINK'] }
|
||||
return {
|
||||
id: ann['ID'],
|
||||
subject: ann['SUBJECT'],
|
||||
content: ann['CONTENT'],
|
||||
link: ann['LINK'],
|
||||
date_start: ann['DATE_START'],
|
||||
date_end: ann['DATE_END']
|
||||
}
|
||||
},
|
||||
enabled() {
|
||||
return this.publicSettings.ANNOUNCEMENT_ENABLED && (this.announcement.content || this.announcement.subject)
|
||||
return this.publicSettings.ANNOUNCEMENT_ENABLED && (this.announcement.content || this.announcement.subject) && this.isDateValid
|
||||
},
|
||||
title() {
|
||||
return this.$t('Announcement') + ': ' + this.announcement.subject
|
||||
},
|
||||
isDateValid() {
|
||||
if (this.announcement.date_start === undefined || this.announcement.date_end === undefined) {
|
||||
return true
|
||||
}
|
||||
const now = new Date()
|
||||
const start = new Date(this.announcement.date_start)
|
||||
const end = new Date(this.announcement.date_end)
|
||||
return now >= start && now <= end
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import VueCookie from 'vue-cookie'
|
||||
import store from '@/store'
|
||||
|
||||
export function getLangCode() {
|
||||
export function getLangCode(withInternalCode = false) {
|
||||
const cookieLang = VueCookie.get('django_language')
|
||||
const browserLang = navigator.systemLanguage || navigator.language || navigator.userLanguage
|
||||
let lang = cookieLang || browserLang || 'en'
|
||||
lang = lang.replace('-', '_')
|
||||
if (lang !== 'zh_hant') {
|
||||
lang = lang.slice(0, 2)
|
||||
let lang = cookieLang || navigator.language.toLowerCase()
|
||||
if (withInternalCode) {
|
||||
const languages = store.getters.publicSettings['LANGUAGES']
|
||||
for (const langObj of languages) {
|
||||
if (langObj['other_codes'].indexOf(lang) > -1) {
|
||||
lang = langObj['code']
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return lang
|
||||
}
|
||||
|
||||
@@ -33,11 +33,13 @@ export default {
|
||||
query[k] = v
|
||||
}
|
||||
|
||||
let key
|
||||
if (this.$route.name.toLowerCase().includes('list')) {
|
||||
return _.trimEnd(this.$route.path, '/') + '?' + new URLSearchParams(query).toString()
|
||||
key = _.trimEnd(this.$route.path, '/') + '?' + new URLSearchParams(query).toString()
|
||||
} else {
|
||||
return new Date().getTime()
|
||||
key = new Date().getTime()
|
||||
}
|
||||
return key
|
||||
},
|
||||
chatAiEnabled() {
|
||||
return this.publicSettings?.CHAT_AI_ENABLED
|
||||
@@ -50,7 +52,7 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
.app-main {
|
||||
background-color: #f3f3f4;
|
||||
height: 100% !important;
|
||||
height: 100vh !important;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
|
||||
@@ -59,18 +59,22 @@ export default {
|
||||
this.$router.push({ name: 'Profile' })
|
||||
break
|
||||
case 'PasswordAndSSHKey':
|
||||
this.$router.push({ name: 'PasswordAndSSHKey' })
|
||||
this.$router.push({ name: 'SSHKeyList' })
|
||||
break
|
||||
case 'Preferences':
|
||||
this.$router.push({ name: 'Preferences' })
|
||||
break
|
||||
case 'logout':
|
||||
this.logout()
|
||||
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
|
||||
break
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
async logout() {
|
||||
const currentOrg = this.$store.getters.currentOrg
|
||||
if (currentOrg.autoEnter || currentOrg.is_system) {
|
||||
await this.$store.dispatch('users/setCurrentOrg', this.$store.getters.preOrg)
|
||||
}
|
||||
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ export default {
|
||||
return {
|
||||
langCookeName: 'django_language', // 后端Django需要的COOKIE KEY
|
||||
supportLanguages: [],
|
||||
currentLangCode: '',
|
||||
defaultLang: {
|
||||
title: 'English',
|
||||
code: 'en',
|
||||
@@ -41,41 +40,25 @@ export default {
|
||||
}, {})
|
||||
},
|
||||
currentLang() {
|
||||
const lang = this.supportedLangMapper[this.currentLangCode] || this.defaultLang
|
||||
return lang
|
||||
const lang = getLangCode(true)
|
||||
return this.supportedLangMapper[lang] || this.defaultLang
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.currentLangCode = getLangCode()
|
||||
this.supportLanguages = store.getters.publicSettings['LANGUAGES'].map(item => {
|
||||
let code = item.code.replace('-', '_')
|
||||
if (code !== 'zh_hant') {
|
||||
code = code.slice(0, 2)
|
||||
}
|
||||
return {
|
||||
title: item.name,
|
||||
code: code,
|
||||
code: item.code,
|
||||
cookieCode: item.code
|
||||
}
|
||||
})
|
||||
this.changeMomentLang()
|
||||
},
|
||||
methods: {
|
||||
changeLang() {
|
||||
if (this.currentLang.code !== this.$i18n.locale) {
|
||||
this.changeLangTo(this.currentLang)
|
||||
}
|
||||
},
|
||||
changeMomentLang() {
|
||||
if (this.currentLang.code.indexOf('en') > -1) {
|
||||
this.$moment.locale('en')
|
||||
} else if (this.currentLang.code.indexOf('ja') > -1) {
|
||||
this.$moment.locale('ja')
|
||||
} else if (this.currentLang.code.indexOf('zh_hant') > -1) {
|
||||
this.$moment.locale('zh-tw')
|
||||
} else {
|
||||
this.$moment.locale('zh-cn')
|
||||
}
|
||||
const lang = getLangCode()
|
||||
this.$moment.locale(lang)
|
||||
document.documentElement.lang = lang
|
||||
},
|
||||
changeLangTo(item) {
|
||||
this.$axios.get(`/core/i18n/${item.cookieCode}/`).then(() => {
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<span class="msg-detail-time">{{ formatDate(currentMsg.date_created) }}</span>
|
||||
</div>
|
||||
<div class="msg-detail-txt">
|
||||
<span v-sanitize="currentMsg.content.message" />
|
||||
<MarkDown :value="currentMsg.content.message" />
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
@@ -80,10 +80,14 @@
|
||||
<script>
|
||||
import { toSafeLocalDateStr } from '@/utils/time'
|
||||
import Dialog from '@/components/Dialog'
|
||||
import MarkDown from '@/components/Widgets/MarkDown'
|
||||
|
||||
export default {
|
||||
name: 'SiteMessages',
|
||||
components: { Dialog },
|
||||
components: {
|
||||
Dialog,
|
||||
MarkDown
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
|
||||
@@ -108,7 +108,7 @@ export default {
|
||||
methods: {
|
||||
async handleSelectView(key, keyPath) {
|
||||
const routeName = this.viewsMapper[key] || '/'
|
||||
localStorage.setItem('PreView', key)
|
||||
localStorage.setItem('preView', key)
|
||||
// Next 之前要重置 init 状态,否则这些路由守卫就不走了
|
||||
await store.dispatch('app/reset')
|
||||
if (!this.tipHasRead) {
|
||||
|
||||
@@ -176,7 +176,7 @@ export default {
|
||||
|
||||
// 未找到与之对应的
|
||||
& ::v-deep .el-submenu__title {
|
||||
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
padding: 0 8px;
|
||||
line-height: $headerHeight;
|
||||
height: $headerHeight;
|
||||
|
||||
@@ -17,6 +17,14 @@ export default {
|
||||
const { icon, title } = context.props
|
||||
const vNodes = []
|
||||
|
||||
const ellipsisStyle = {
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
width: '100%',
|
||||
display: 'inline-block'
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
if (icon.startsWith('fa-')) {
|
||||
vNodes.push(<i class={`fa ${icon}`} />)
|
||||
@@ -26,7 +34,7 @@ export default {
|
||||
}
|
||||
|
||||
if (title) {
|
||||
vNodes.push(<span slot='title'>{title}</span>)
|
||||
vNodes.push(<span slot='title' style={ellipsisStyle} title={ title }>{title}</span>)
|
||||
}
|
||||
return vNodes
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
trigger="hover"
|
||||
>
|
||||
<span slot="reference" style="width: 100%">
|
||||
<span v-show="!isCollapse" class="view-title">
|
||||
{{ isRouteMeta.title || '' }}
|
||||
</span>
|
||||
<el-tooltip v-show="!isCollapse" :content="isRouteMeta.title" :open-delay="1000" placement="bottom" effect="dark" class="view-title">
|
||||
<span class="text-overflow">{{ isRouteMeta.title || '' }}</span>
|
||||
</el-tooltip>
|
||||
<span class="icon-zone">
|
||||
<svg-icon class="icon" icon-class="switch" />
|
||||
</span>
|
||||
|
||||
@@ -116,7 +116,6 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
height: calc(100vh - 50px);
|
||||
overflow-y: hidden;
|
||||
overflow-x: hidden;
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<el-alert v-if="helpMessage" type="success">
|
||||
<span v-sanitize="helpMessage" class="announcement-main" />
|
||||
</el-alert>
|
||||
<transition v-if="!loading" appear mode="out-in" name="fade-transform">
|
||||
<transition appear mode="out-in" name="fade-transform">
|
||||
<slot>
|
||||
<keep-alive>
|
||||
<component :is="computeActiveComponent" />
|
||||
@@ -83,16 +83,18 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
toSentenceCase: toSentenceCase
|
||||
loading: false,
|
||||
toSentenceCase: toSentenceCase,
|
||||
activeTab: this.activeMenu
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iActiveMenu: {
|
||||
get() {
|
||||
return this.activeMenu
|
||||
return this.activeTab
|
||||
},
|
||||
set(item) {
|
||||
this.activeTab = item
|
||||
this.$emit('update:activeMenu', item)
|
||||
}
|
||||
},
|
||||
@@ -119,16 +121,13 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
const activeTab = to.query?.tab
|
||||
if (activeTab && this.iActiveMenu !== activeTab) {
|
||||
this.iActiveMenu = activeTab
|
||||
}
|
||||
// 好像没必要
|
||||
// const activeTab = to.query?.tab
|
||||
// if (activeTab && this.iActiveMenu !== activeTab) {
|
||||
// this.iActiveMenu = activeTab
|
||||
// }
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.iActiveMenu = this.getPropActiveTab()
|
||||
this.loading = false
|
||||
},
|
||||
created() {
|
||||
this.iActiveMenu = this.getPropActiveTab()
|
||||
this.loading = false
|
||||
@@ -136,15 +135,8 @@ export default {
|
||||
methods: {
|
||||
handleTabClick(tab) {
|
||||
this.$emit('tab-click', tab)
|
||||
this.$emit('update:activeMenu', tab.name)
|
||||
|
||||
this.iActiveMenu = tab.name
|
||||
this.$cookie.set(this.$route.path, tab.name, 1)
|
||||
|
||||
if (this.$router.currentRoute.query[this.$route.path]) {
|
||||
this.$router.push({
|
||||
query: { ...this.$route.query, [this.$route.path]: '' }
|
||||
})
|
||||
}
|
||||
},
|
||||
getPropActiveTab() {
|
||||
let activeTab = ''
|
||||
|
||||
@@ -80,15 +80,5 @@ export default [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'job-execution-log',
|
||||
name: 'JobExecutionLog',
|
||||
component: () => import('@/views/audits/JobExecutionLogList'),
|
||||
meta: {
|
||||
title: i18n.t('JobExecutionLog'),
|
||||
icon: 'task',
|
||||
permissions: ['audits.view_joblog']
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@ import i18n from '@/i18n/i18n'
|
||||
|
||||
import SessionRoutes from './sessions'
|
||||
import LogRoutes from './audits'
|
||||
import JobRoutes from './jobs'
|
||||
import empty from '@/layout/empty'
|
||||
import store from '@/store'
|
||||
|
||||
@@ -54,6 +55,18 @@ export default {
|
||||
permissions: []
|
||||
},
|
||||
children: LogRoutes
|
||||
},
|
||||
{
|
||||
path: '/audit/jobs',
|
||||
component: empty,
|
||||
redirect: '',
|
||||
name: 'AuditsJobs',
|
||||
meta: {
|
||||
title: i18n.t('JobsAudit'),
|
||||
icon: 'job',
|
||||
permissions: ['audits.view_joblog']
|
||||
},
|
||||
children: JobRoutes
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
53
src/router/audit/jobs.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import i18n from '@/i18n/i18n'
|
||||
import empty from '@/layout/empty'
|
||||
|
||||
export default [
|
||||
{
|
||||
path: 'job-list',
|
||||
name: 'JobList',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'JobList'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('JobList'),
|
||||
icon: 'task',
|
||||
permissions: ['audits.view_joblog']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AuditJobList',
|
||||
component: () => import('@/views/audits/JobPeriodTaskList'),
|
||||
meta: {
|
||||
title: i18n.t('JobList'),
|
||||
permissions: []
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'job-execution-log',
|
||||
name: 'JobExecutionLog',
|
||||
component: empty,
|
||||
redirect: {
|
||||
name: 'AuditJobExecutionLog'
|
||||
},
|
||||
meta: {
|
||||
title: i18n.t('JobExecutionLog'),
|
||||
icon: 'history',
|
||||
permissions: ['audits.view_joblog']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'AuditJobExecutionLog',
|
||||
component: () => import('@/views/audits/JobExecutionLogList'),
|
||||
meta: {
|
||||
title: i18n.t('JobExecutionLog'),
|
||||
permissions: ['audits.view_joblog']
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -24,7 +24,6 @@ export default [
|
||||
meta: {
|
||||
title: i18n.t('BaseUserLoginAclList'),
|
||||
app: 'acls',
|
||||
licenseRequired: true,
|
||||
resource: 'loginacl',
|
||||
disableOrgsChange: true
|
||||
},
|
||||
@@ -201,7 +200,7 @@ export default [
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('CommandGroupDetail'),
|
||||
activeMenu: ''
|
||||
activeMenu: '/console/perms/acls/cmd-acls'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@ import i18n from '@/i18n/i18n'
|
||||
import empty from '@/layout/empty'
|
||||
import XPackRoutes from './xpack'
|
||||
|
||||
const globalSubmenu = () => import('@/layout/globalOrg.vue')
|
||||
export default [
|
||||
{
|
||||
path: 'assets',
|
||||
@@ -294,12 +293,11 @@ export default [
|
||||
},
|
||||
{
|
||||
path: 'platforms',
|
||||
component: globalSubmenu,
|
||||
component: empty,
|
||||
meta: {
|
||||
permissions: ['assets.view_platform'],
|
||||
resource: 'platform',
|
||||
icon: 'platform',
|
||||
disableOrgsChange: true
|
||||
icon: 'platform'
|
||||
},
|
||||
redirect: '',
|
||||
children: [
|
||||
|
||||
@@ -28,13 +28,39 @@ export default {
|
||||
},
|
||||
{
|
||||
path: '/profile/password-and-ssh-key',
|
||||
name: 'PasswordAndSSHKey',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/index'),
|
||||
component: empty,
|
||||
meta: {
|
||||
title: i18n.t('PasswordAndSSHKey'),
|
||||
icon: 'personal',
|
||||
permissions: []
|
||||
}
|
||||
icon: 'personal'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/index'),
|
||||
name: 'SSHKeyList',
|
||||
icon: 'key',
|
||||
meta: { title: i18n.t('PasswordAndSSHKey'), permissions: ['authentication.view_sshkey'] }
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/SSHKey/SSHKeyCreateUpdate.vue'),
|
||||
name: 'SSHKeyCreate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('SSHKey'),
|
||||
permissions: ['authentication.add_sshkey']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: ':id/update',
|
||||
component: () => import('@/views/profile/PasswordAndSSHKey/SSHKey/SSHKeyCreateUpdate.vue'),
|
||||
name: 'SSHKeyUpdate',
|
||||
hidden: true,
|
||||
meta: {
|
||||
title: i18n.t('SSHKey'),
|
||||
permissions: ['authentication.change_sshkey']
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/profile/passkeys',
|
||||
|
||||
@@ -40,11 +40,26 @@ const mutations = {
|
||||
|
||||
updateChaMessageContentById(state, { id, data }) {
|
||||
const chats = state.activeChat.chats || []
|
||||
const filterChat = chats.filter((chat) => chat.message.id === id)?.[0] || {}
|
||||
if (Object.keys(filterChat).length > 0) {
|
||||
filterChat.message.content = data.message.content
|
||||
const index = chats.findIndex((chat) => chat.message.id === data.message.id)
|
||||
|
||||
if (index === -1) {
|
||||
// 如果没有记录,直接添加新消息
|
||||
chats.push({
|
||||
message: { id: data.message.id },
|
||||
reasoning: { content: data.reasoning.content },
|
||||
result: { content: data.result.content },
|
||||
role: data.role,
|
||||
type: data.type,
|
||||
create_time: data.create_time
|
||||
})
|
||||
} else {
|
||||
chats?.push(data)
|
||||
if (data.reasoning.content !== '') {
|
||||
chats[index].reasoning.content = data.reasoning.content
|
||||
}
|
||||
|
||||
if (data.result.content !== '') {
|
||||
chats[index].result.content = data.result.content
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,10 +71,11 @@ const mutations = {
|
||||
state.consoleOrgs = state.consoleOrgs.filter(i => i.id !== org.id)
|
||||
},
|
||||
SET_CURRENT_ORG(state, org) {
|
||||
// 系统组织和全局组织不设置成 Pre org
|
||||
if (!state.currentOrg?.autoEnter && !state.currentOrg?.is_root) {
|
||||
state.preOrg = state.currentOrg
|
||||
setPreOrgLocal(state.username, state.currentOrg)
|
||||
// 系统组织不设置成 Pre org
|
||||
const currentOrg = state.currentOrg
|
||||
if (currentOrg && !currentOrg.autoEnter && !currentOrg.is_system) {
|
||||
state.preOrg = currentOrg
|
||||
setPreOrgLocal(state.username, currentOrg)
|
||||
}
|
||||
state.currentOrg = org
|
||||
saveCurrentOrgLocal(state.username, org)
|
||||
@@ -144,6 +145,7 @@ const actions = {
|
||||
const systemOrg = {
|
||||
id: orgUtil.SYSTEM_ORG_ID,
|
||||
name: 'SystemSetting',
|
||||
is_system: true,
|
||||
autoEnter: new Date().getTime()
|
||||
}
|
||||
commit('SET_CURRENT_ORG', systemOrg)
|
||||
|
||||
BIN
src/styles/icons/cloud.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
@@ -38,7 +38,7 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: auto;
|
||||
color: var(--color-text-primary);
|
||||
font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.428;
|
||||
}
|
||||
@@ -586,3 +586,24 @@ li.rmenu i.fa {
|
||||
color: var(--color-link);
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__row {
|
||||
::-webkit-scrollbar {
|
||||
width: 6px; /* 设置垂直滚动条的宽度 */
|
||||
height: 6px; /* 设置水平滚动条的高度 */
|
||||
}
|
||||
}
|
||||
|
||||
.black-theme-popover {
|
||||
width: 300px;
|
||||
max-height: 700px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.text-overflow {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@ $single-menu-height: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--vertical {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
border-right: none !important;
|
||||
background-color: inherit !important;
|
||||
@@ -139,6 +143,10 @@ $single-menu-height: 38px;
|
||||
.nest-menu .level2-menu {
|
||||
line-height: $single-menu-height;
|
||||
}
|
||||
|
||||
.el-tooltip {
|
||||
width: 55px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,11 @@
|
||||
background: url('./icons/clickhouse.png') no-repeat center left transparent;
|
||||
}
|
||||
|
||||
&.WebCloud_ico_docu {
|
||||
background: url('./icons/cloud.png') no-repeat center left transparent;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
&.ico_loading {
|
||||
background: url(./icons/loading.gif) no-repeat scroll 0 0 transparent;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import VueCookie from 'vue-cookie'
|
||||
|
||||
const CURRENT_ORG_KEY = 'jms_current_org'
|
||||
const CURRENT_ROLE_KEY = 'jms_current_role'
|
||||
const CURRENT_ORG_KEY = 'currentOrg'
|
||||
const CURRENT_ROLE_KEY = 'currentRole'
|
||||
let cookieNamePrefix = VueCookie.get('SESSION_COOKIE_NAME_PREFIX')
|
||||
if (!cookieNamePrefix || ['""', "''"].indexOf(cookieNamePrefix) > -1) {
|
||||
cookieNamePrefix = ''
|
||||
@@ -17,7 +17,7 @@ export function setTokenToCookie(value, expires) {
|
||||
}
|
||||
|
||||
export function getCurrentRoleLocal(username) {
|
||||
const key = CURRENT_ROLE_KEY + '_' + username
|
||||
const key = CURRENT_ROLE_KEY + ':' + username
|
||||
const role = localStorage.getItem(key)
|
||||
if (role) {
|
||||
return parseInt(role) || null
|
||||
@@ -26,12 +26,12 @@ export function getCurrentRoleLocal(username) {
|
||||
}
|
||||
|
||||
export function saveCurrentRoleLocal(username, role) {
|
||||
const key = CURRENT_ROLE_KEY + '_' + username
|
||||
const key = CURRENT_ROLE_KEY + ':' + username
|
||||
return localStorage.setItem(key, role)
|
||||
}
|
||||
|
||||
export function getCurrentOrgLocal(username) {
|
||||
const key = CURRENT_ORG_KEY + '_' + username
|
||||
const key = CURRENT_ORG_KEY + ':' + username
|
||||
const value = localStorage.getItem(key)
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
@@ -41,18 +41,23 @@ export function getCurrentOrgLocal(username) {
|
||||
}
|
||||
|
||||
export function saveCurrentOrgLocal(username, org) {
|
||||
const key = CURRENT_ORG_KEY + '_' + username
|
||||
localStorage.setItem(key, JSON.stringify(org))
|
||||
VueCookie.set('X-JMS-ORG', org.id)
|
||||
const key = CURRENT_ORG_KEY + ':' + username
|
||||
if (org) {
|
||||
localStorage.setItem(key, JSON.stringify(org))
|
||||
VueCookie.set('X-JMS-ORG', org.id)
|
||||
} else {
|
||||
localStorage.removeItem(key)
|
||||
VueCookie.delete('X-JMS-ORG')
|
||||
}
|
||||
}
|
||||
|
||||
export function setPreOrgLocal(username, org) {
|
||||
const key = 'PRE_ORG_' + username
|
||||
const key = 'preOrg' + ':' + username
|
||||
localStorage.setItem(key, JSON.stringify(org))
|
||||
}
|
||||
|
||||
export function getPreOrgLocal(username) {
|
||||
const key = 'PRE_ORG_' + username
|
||||
const key = 'preOrg' + ':' + username
|
||||
const value = localStorage.getItem(key)
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
|
||||