Compare commits

..

143 Commits

Author SHA1 Message Date
ibuler
a69398ea3f perf: package json 2024-09-12 10:35:56 +08:00
ibuler
9b7c4ed353 chore: lock the pull request when workflow start 2024-09-12 10:22:01 +08:00
wangruidong
41f841532f feat: LDAP HA 2024-09-11 18:26:49 +08:00
feng
0b9f47dd84 feat: Postgresql support ssl 2024-09-11 18:12:33 +08:00
wangruidong
d32a376e8c feat: Support playbook, adhoc share 2024-09-11 18:02:03 +08:00
ibuler
0f8a8845df perf: change gateway gateway tip 2024-09-11 15:26:57 +08:00
wangruidong
8bb2c66b99 perf: Add task description 2024-09-09 18:59:25 +08:00
ibuler
b3d0be2f60 perf: update platform icon 2024-09-06 11:15:07 +08:00
ibuler
5a94ddd976 perf: api can set radio and checkbox tip 2024-09-06 11:14:03 +08:00
ibuler
7b9627a80b perf: support change gateway platform 2024-09-06 11:10:46 +08:00
feng
5fcfecc060 perf: Acl action add notify and warn 2024-09-06 11:08:21 +08:00
wangruidong
f4d7a2283c feat: Add announcement start and end dates 2024-09-06 10:56:03 +08:00
feng
40bb8410d3 perf: System settings task cleanup add change secret and push record retention days 2024-09-02 10:18:13 +08:00
fit2bot
45344ac620 perf: ldap import user error msg (#4333)
* perf: ldap import user error msg

* fix: Duplication on new code

---------

Co-authored-by: wangruidong <940853815@qq.com>
2024-08-30 17:01:18 +08:00
feng
5b894c9667 perf: View the internal message and convert the content into markdown 2024-08-29 17:28:18 +08:00
ZhaoJiSen
d89bd15b6d Merge pull request #4337 from jumpserver/pr@dev@fix_table_reload
fixed: Fixed an issue where tables could not be refreshed
2024-08-29 13:06:40 +08:00
zhaojisen
5e640dd45c fixed: Fixed an issue where tables could not be refreshed 2024-08-29 11:50:23 +08:00
fit2bot
09aa750794 chore: update checkout action 2024-08-28 11:34:09 +08:00
吴小白
744b215800 feat: bump node from 16.20 to 20.15 2024-08-27 11:07:37 +08:00
ibuler
f4e11da053 perf: revert pre org if logout 2024-08-23 15:40:16 +08:00
ibuler
2e6b5706d5 perf: remove tab in query 2024-08-21 18:04:19 +08:00
feng
440a5b27ef perf: Asset authorization request asset optimization mini limit 300 2024-08-21 11:34:17 +08:00
feng
932e16844e perf: Role detail title translate 2024-08-20 11:29:02 +08:00
feng
aff2e439dd perf: Ztree cannot be dragged under global organization 2024-08-20 10:45:21 +08:00
ZhaoJiSen
a618794c14 Merge pull request #4319 from jumpserver/pr@dev@fix_assets_tree
fixed: asset tree height
2024-08-15 17:51:17 +08:00
zhaojisen
8dd66c400a fixed: asset tree height 2024-08-15 17:49:14 +08:00
ZhaoJiSen
ac3ce464b7 Merge pull request #4318 from jumpserver/pr@dev@fix_aoumnt_formatter
fixed: Fixed an issue where the details list data would not refresh after the data was updated
2024-08-15 17:43:07 +08:00
zhaojisen
4d01c6dabe fixed: Fixed an issue where the details list data would not refresh after the data was updated 2024-08-15 17:39:59 +08:00
ZhaoJiSen
47eeac23eb Merge pull request #4317 from jumpserver/pr@dev@ticket_flow
perf: Ticket flow detail error
2024-08-15 16:42:58 +08:00
feng
592f783245 perf: Ticket flow detail error 2024-08-15 16:42:01 +08:00
Ewall555
c34ac6a56f perf: Translate ticket cancel button 2024-08-15 15:50:36 +08:00
Bai
9a7162cc9e perf: i18n for ldap user import 2024-08-15 15:49:25 +08:00
ZhaoJiSen
5edf2f34dd Merge pull request #4313 from jumpserver/pr@dev@fix_cloud_button
fixed: Fixed the issue that can be edited and deleted under the default cloud synchronization policy
2024-08-15 12:07:42 +08:00
zhaojisen
859d824195 fixed: Fixed the issue that can be edited and deleted under the default cloud synchronization policy 2024-08-15 12:06:36 +08:00
ZhaoJiSen
c382cc633e Merge pull request #4312 from jumpserver/pr@dev@fix_quick_job
fixed: Fixed the issue that the shortcut command could not get the account
2024-08-15 11:06:21 +08:00
zhaojisen
653e24773f fixed: Fixed the issue that the shortcut command could not get the account 2024-08-15 10:59:13 +08:00
wangruidong
ae489610ee perf: Translate batch approval 2024-08-14 19:28:11 +08:00
ZhaoJiSen
b5e85c66e4 Merge pull request #4311 from jumpserver/pr@dev@fix_icon_show
fixed: common icon
2024-08-14 19:01:49 +08:00
zhaojisen
4060fa4c4a fixed: common icon 2024-08-14 18:59:13 +08:00
ZhaoJiSen
9630b79daa Merge pull request #4310 from jumpserver/pr@dev@fix_common_icon
fixed: common icon
2024-08-14 18:55:52 +08:00
zhaojisen
ad93917387 fixed: common icon 2024-08-14 18:52:40 +08:00
feng
691590da7b perf: Cloud sync task interval to number 2024-08-14 18:52:19 +08:00
feng
71828fd78b perf: Translate 2024-08-14 16:38:04 +08:00
ZhaoJiSen
5a45f31ac4 Merge pull request #4305 from jumpserver/pr@dev@fix_ticket_tree
fixed: Fixed missing action book nodes in ticket details
2024-08-14 16:32:37 +08:00
zhaojisen
44901eee23 fixed: Fixed missing action book nodes in ticket details 2024-08-14 16:30:49 +08:00
ZhaoJiSen
2d91fa107d Merge pull request #4304 from jumpserver/pr@dev@fix_storage_textarea
fixed:Fixed the common item in the storage Settings to be a text field
2024-08-14 15:48:20 +08:00
zhaojisen
7a6ade4bd9 fixed:Fixed the common item in the storage Settings to be a text field 2024-08-14 15:46:31 +08:00
wangruidong
951778668f perf: profile page go to some page error 2024-08-13 18:16:04 +08:00
zhaojisen
0aca23bf38 fixed: internationalization issues 2024-08-13 17:50:49 +08:00
wangruidong
9b97dc9a74 perf: add auto generate ssh-key 2024-08-13 17:43:47 +08:00
feng
32cd030479 fix: Cloud sync task regions display 2024-08-09 17:40:50 +08:00
wangruidong
607bb476db perf: asset name edit add icon 2024-08-09 16:33:58 +08:00
feng
1d676ec9b7 perf: Domain detail gateway translate 2024-08-09 10:45:52 +08:00
wangruidong
614ebb4121 fix: platform page asset not allow go to detail page 2024-08-08 17:34:42 +08:00
ZhaoJiSen
8c2719a95d Merge pull request #4291 from jumpserver/pr@dev@fix_load
perf: add button loading status
2024-08-08 15:00:45 +08:00
zhaojisen
c6b0a958a6 perf: add button loading status 2024-08-08 14:53:42 +08:00
ZhaoJiSen
3edebf3f4f Merge pull request #4290 from jumpserver/pr@dev@fix_loading
perf: add button loading status
2024-08-08 14:47:26 +08:00
zhaojisen
f63405978e perf: add button loading status 2024-08-08 14:46:39 +08:00
fit2bot
c582c8de98 chore: remove build test 2024-08-08 13:50:57 +08:00
fit2bot
c44e79ed3b perf: add button loading status (#4285)
* perf: add button loading status

* perf: add button loading status

* perf: add button loading status

---------

Co-authored-by: zhaojisen <1301338853@qq.com>
2024-08-08 11:37:38 +08:00
feng
fc7d5b9c29 perf: Asset authorization: The number of accounts displayed is incorrect ignore @SPEC 2024-08-07 17:51:15 +08:00
Eric
335febd4a5 perf: update session page i18n 2024-08-07 17:45:52 +08:00
wangruidong
5ea2918fe7 feat: Allow users to customize asset name and comment 2024-08-07 16:47:54 +08:00
ZhaoJiSen
7668d10ba5 Merge pull request #4282 from jumpserver/pr@dev@fix_continue_add
fixed: Restore continues to add the ability to clear the form
2024-08-06 14:20:53 +08:00
zhaojisen
df15d141da fixed: Restore continues to add the ability to clear the form 2024-08-06 14:14:37 +08:00
wangruidong
e2f4bbde79 feat: support configuring multiple SSH keys for users 2024-08-05 15:21:14 +08:00
feng
3ca7de42af perf: Optimization settings auth org select2 2024-08-02 17:51:25 +08:00
Bai
e6a577ba9b perf: assets internal display for false 2024-08-02 16:04:59 +08:00
feng
6290179460 perf: Third-party user login settings default organization 2024-08-02 15:52:42 +08:00
ZhaoJiSen
4f3c9e9353 Merge pull request #4276 from jumpserver/pr@dev@fix_add_continue
fixed: Fixed the issue when adding
2024-08-01 14:57:54 +08:00
zhaojisen
aa28a8f765 fixed: Fixed the issue when adding 2024-08-01 14:57:01 +08:00
ZhaoJiSen
98d0a52fa2 Merge pull request #4275 from jumpserver/pr@dev@fix_filter_param
fixed: Filtering parameters
2024-07-31 14:30:54 +08:00
zhaojisen
8d9e1ffadb fixed: Filtering parameters 2024-07-31 14:29:49 +08:00
zhaojisen
b3be312a4d fixed: Fixed page refresh when tree nodes are clicked 2024-07-31 11:23:07 +08:00
feng
7c41d148aa perf: Support WeCom DingTalk FeiShu Lark Slack attribute mapping 2024-07-30 17:51:55 +08:00
feng
6dee911642 perf: Approval process role selection supports multiple strategies 2024-07-30 16:37:32 +08:00
wangruidong
6dd35e5173 feat: add assets amount field to platform page 2024-07-26 13:44:22 +08:00
halo
fdaa33f68d perf: Email service authentication username is optional 2024-07-26 11:39:31 +08:00
feng
0b813bb719 perf: Del profile scheduled request 2024-07-25 18:22:26 +08:00
fit2bot
7ba6b8d4e4 perf: fix session i18n default (#4258)
* perf: fix session i18n default

* perf: Update Dockerfile with new base image tag

---------

Co-authored-by: Bai <baijiangjie@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bryan <jiangjie.bai@fit2cloud.com>
2024-07-24 10:03:24 +08:00
ibuler
ef99e25fde perf: workflow 2024-07-23 17:12:37 +08:00
github-actions[bot]
d1e7b907e3 perf: Update Dockerfile with new base image tag 2024-07-23 16:52:36 +08:00
ibuler
83f7dda5e7 perf: change base image 2024-07-23 16:52:36 +08:00
ibuler
80f929fdea perf: change build
perf: using cache

perf: using pnpm
2024-07-23 16:13:36 +08:00
feng
1ec3d02933 perf: You can modify sudo permissions multiple times 2024-07-22 18:17:50 +08:00
ZhaoJiSen
fdf148cc2b Merge pull request #4250 from jumpserver/pr@dev@fix_validation_refresh
fixed: Fixed an issue where the validation date does not refresh
2024-07-22 14:57:28 +08:00
ZhaoJiSen
c9e6ef89dc Merge pull request #4249 from jumpserver/pr@dev@fix_add_continue
fixed: Fixed an issue where the data would not refresh after the Save and Continue Add button was successfully added
2024-07-22 14:57:03 +08:00
zhaojisen
59f9f88f8b fixed: Fixed an issue where the validation date does not refresh 2024-07-22 14:48:51 +08:00
zhaojisen
16417ae843 fixed: Fixed an issue where the data would not refresh after the Save and Continue Add button was successfully added 2024-07-22 10:31:54 +08:00
ZhaoJiSen
58bd0a17c8 Merge pull request #4246 from jumpserver/pr@dev@fix_asset_refresh
fixed: Fixed an issue with asset refresh when clicked
2024-07-18 12:36:55 +08:00
zhaojisen
c4a1eb6938 fixed: Fixed an issue with asset refresh when clicked 2024-07-18 12:33:28 +08:00
feng
b7d9031889 perf: Translate 2024-07-17 18:05:19 +08:00
feng
96b29a9dc2 perf: Feishu lark support attributes settings 2024-07-17 17:00:02 +08:00
zhaojisen
336e176639 style: Code Editor style change 2024-07-17 16:52:03 +08:00
zhuoyang
2df4a9d66d fix:vue.config.js 2024-07-17 16:11:17 +08:00
feng
45a102cff1 perf: Translate 2024-07-17 11:40:17 +08:00
ZhaoJiSen
57ebfa0812 Merge pull request #4235 from jumpserver/pr@dev@fix_editor_style
style: Editor style adjustments
2024-07-17 11:14:53 +08:00
zhaojisen
4e9dd57efe style: Editor style adjustments 2024-07-17 11:13:05 +08:00
ZhaoJiSen
3fcc4ca160 Merge pull request #4229 from jumpserver/pr@dev@fix_params_push
fixed: Fixed the issue that parameter push parameters could not be saved
2024-07-17 11:01:02 +08:00
feng626
958760811c Merge pull request #4230 from jumpserver/pr@dev@ticket
perf: Del ticket comment mistake date
2024-07-17 10:54:50 +08:00
feng
b60e0251c2 perf: Del ticket comment mistake date 2024-07-17 10:51:40 +08:00
zhaojisen
3db0ed756e fixed: Fixed the issue that parameter push parameters could not be saved 2024-07-17 10:51:15 +08:00
feng626
453c4b1e4e Merge pull request #4222 from jumpserver/pr@dev@download
perf: Downloading files does not trigger the beforeunload event
2024-07-16 12:35:32 +08:00
feng
bf4d8ce7a6 perf: Downloading files does not trigger the beforeunload event 2024-07-16 12:34:18 +08:00
wangruidong
34effdbe15 perf: 社区版移除magnus 2024-07-15 19:27:16 +08:00
zhaojisen
274db466f2 fixed:Fixed an issue where the user was unable to enter non-MD content 2024-07-15 19:09:05 +08:00
ZhaoJiSen
cbe697f9dc Merge pull request #4218 from jumpserver/pr@dev@fix_job_tooltip
fixed: Fixed the tooltip shortcut command interface issue
2024-07-15 18:31:28 +08:00
zhaojisen
fb7acb100e fixed: Fixed the tooltip shortcut command interface issue 2024-07-15 18:28:15 +08:00
feng626
ebb36847df Merge pull request #4213 from jumpserver/pr@dev@cloud_sync_install
perf: Cloud sync instance execution add trigger
2024-07-15 18:02:46 +08:00
feng
beba4f1994 perf: Cloud sync instance execution add trigger 2024-07-11 17:15:58 +08:00
ZhaoJiSen
452796e3f5 Merge pull request #4212 from jumpserver/pr@dev@fix_params_remain
fixed: Fixed an issue where push parameters could not be saved when adding an asset account
2024-07-11 15:34:42 +08:00
zhaojisen
02a7969d90 fixed: Fixed an issue where push parameters could not be saved when adding an asset account 2024-07-11 15:32:19 +08:00
zhaojisen
798dbec151 fixed :Event bus shutdown 2024-07-11 15:20:51 +08:00
ibuler
0e11a56e37 perf: table search two times, one init one search 2024-07-11 15:20:01 +08:00
ibuler
be5344344c perf: view cache 2024-07-11 15:18:41 +08:00
ibuler
e75d711e0a perf: nest field change may be lead blink 2024-07-11 15:17:07 +08:00
wangruidong
46ee116f3e fix: role display error when update user 2024-07-11 15:07:29 +08:00
ZhaoJiSen
f4b304338f Merge pull request #4207 from jumpserver/pr@dev@fix_switch_showTooltip
fixed: Fixed tooltip issue with switch button
2024-07-11 10:48:46 +08:00
zhaojisen
e93e78307c fixed: Fixed tooltip issue with switch button 2024-07-11 10:47:33 +08:00
wangruidong
d22079446f perf: profile improvement mfa disabled 2024-07-10 14:21:06 +08:00
ibuler
d57b99b00c perf: phone code api 2024-07-09 19:24:07 +08:00
ZhaoJiSen
e7321356af Merge pull request #4197 from jumpserver/pr@dev@fix_card_style
style: Fine-tuning styles
2024-07-09 18:58:39 +08:00
zhaojisen
a0edc2c527 style: Fine-tuning styles 2024-07-09 18:54:45 +08:00
ibuler
10ebcfd64d perf: tags display inline 2024-07-09 16:08:55 +08:00
ibuler
a3fedb9697 perf: license display for community edition 2024-07-09 16:03:18 +08:00
吴小白
1eb8a18c66 fix: FromAsCasing keywords 2024-07-09 16:02:18 +08:00
wangruidong
f491c57c34 perf: display newly invited users at the top of the list 2024-07-09 15:54:31 +08:00
ZhaoJiSen
50b7f54652 Merge pull request #4193 from jumpserver/pr@dev@fix_menu_heighlight
fixed: Missing menu highlight in command group detail page
2024-07-09 11:12:00 +08:00
zhaojisen
5bf298a5bf fixed: Missing menu highlight in command group detail page 2024-07-09 11:07:01 +08:00
wangruidong
124ff9a8c2 fix: foot content allow blank 2024-07-08 19:24:34 +08:00
ZhaoJiSen
387ab4f1b3 Merge pull request #4186 from jumpserver/pr@dev@fix_item_empty
fixed: Fixed an issue where the field is empty
2024-07-08 18:55:00 +08:00
zhaojisen
d6fbdfa7ea fixed: Fixed an issue where the field is empty 2024-07-08 18:51:10 +08:00
feng626
cd4260fd8d Merge pull request #4184 from jumpserver/pr@dev@translate
perf: Translate
2024-07-05 16:37:29 +08:00
feng
baeece62d3 perf: Translate 2024-07-05 16:36:24 +08:00
wangruidong
576c8f5891 fix: update platform with update protocols 2024-07-05 15:07:10 +08:00
feng626
5e97600807 Merge pull request #4182 from jumpserver/pr@dev@translate
perf: Translate
2024-07-04 18:15:04 +08:00
feng
4e2eb3a37d perf: Translate 2024-07-04 18:13:49 +08:00
feng626
e92173f8e8 Merge pull request #4181 from jumpserver/pr@dev@applet
perf: Virtualapp and appprovider tab perm
2024-07-04 16:46:45 +08:00
feng
412d9c804e perf: Virtualapp and appprovider tab perm 2024-07-04 16:46:00 +08:00
ZhaoJiSen
b84a725d4a Merge pull request #4179 from jumpserver/pr@dev@fix_split_job
perf: split job management
2024-07-04 16:37:39 +08:00
feng626
58b39743e0 Merge pull request #4180 from jumpserver/pr@dev@apple
perf: Apple host detail perm
2024-07-04 16:33:39 +08:00
feng
7651443c25 perf: Apple host detail perm 2024-07-04 16:32:20 +08:00
zhaojisen
8d64e331d1 perf: split job management 2024-07-04 16:04:26 +08:00
150 changed files with 2368 additions and 1340 deletions

72
.github/workflows/build-base-image.yml vendored Normal file
View 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 }}"

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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",
@@ -95,7 +96,7 @@
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "^9.5.1",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.1",
"babel-eslint": "10.0.2",
"babel-jest": "23.6.0",
"chalk": "2.4.2",
"compression-webpack-plugin": "^6.1.1",

View File

@@ -65,10 +65,6 @@ export function logout() {
})
}
export function refreshSessionIdAge() {
return getProfile()
}
export default {
getProfile,
getUserList

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 B

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -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
},

View File

@@ -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

View File

@@ -176,12 +176,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()

View File

@@ -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 }) {

View File

@@ -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() {

View File

@@ -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 = ''

View File

@@ -197,6 +197,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 +222,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
}

View File

@@ -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,7 +220,7 @@ export default {
.el-form-item__label {
padding: 0 30px 0 0;
line-height: 32px;
line-height: 30px;
color: var(--color-text-primary);
i {

View File

@@ -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
@@ -379,7 +398,6 @@ $input-border-color: #C0C4CC;
}
.editor {
//margin-left: 30px;
border: 1px solid var(--color-border);
overflow: hidden;
}

View File

@@ -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>

View File

@@ -144,9 +144,6 @@ export default {
watch: {
choices: {
handler(value, oldValue) {
if (value?.length === oldValue?.length) {
return
}
this.loading = true
setTimeout(() => {
this.setDefaultItems(value)
@@ -345,6 +342,10 @@ export default {
.protocol-item {
display: flex;
margin: 5px 0;
&:first-of-type {
margin-top: 0;
}
}
.input-button {

View File

@@ -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,

View File

@@ -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>

View File

@@ -90,8 +90,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 +111,7 @@ export default {
this.checkAll = false
this.isIndeterminate = true
}
})
},
methods: {
},
handleColumnConfirm() {
this.showColumnSettingPopover = false
this.$emit('columnsUpdate', { columns: this.iCurrentColumns, url: this.url })

View File

@@ -153,6 +153,8 @@ export default {
this.toggleRowSelection(row, true)
}
}
this.$emit('loaded')
},
handleSizeChange(val) {
localStorage.setItem('paginationSize', val)

View File

@@ -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

View File

@@ -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')

View File

@@ -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) {

View File

@@ -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

View 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

View File

@@ -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() {

View File

@@ -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
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
@@ -207,18 +209,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 +228,14 @@ export default {
.label-container {
display: flex;
height: 28px;
.label-formatter-col {
overflow: hidden;
&:hover {
overflow: auto;
}
}
&:hover {
@@ -249,15 +249,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 +278,6 @@ export default {
.tag-formatter {
margin: 2px 0;
}
.tag-tip {

View File

@@ -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;

View File

@@ -234,12 +234,10 @@ export default {
delete routeFilter.search
}
const asFilterTags = _.cloneDeep(this.filterTags)
setTimeout(() => {
this.filterTags = {
...asFilterTags,
...routeFilter
}
}, 100)
this.filterTags = {
...asFilterTags,
...routeFilter
}
},
getValueLabel(key, value) {
for (const field of this.options) {

View File

@@ -39,7 +39,7 @@ export default {
showRenameBtn: false,
drag: {
isCopy: false,
isMove: true
isMove: !this.$store.getters.currentOrgIsRoot
}
},
callback: {

View File

@@ -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: {

View File

@@ -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

View File

@@ -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) {
await this.$store.dispatch('users/setCurrentOrg', this.$store.getters.preOrg)
}
window.location.href = `${process.env.VUE_APP_LOGOUT_PATH}?next=${this.$route.fullPath}`
}
}
}

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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 = ''

View File

@@ -201,7 +201,7 @@ export default [
hidden: true,
meta: {
title: i18n.t('CommandGroupDetail'),
activeMenu: ''
activeMenu: '/console/perms/acls/cmd-acls'
}
},
{

View File

@@ -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',

View File

@@ -586,3 +586,10 @@ li.rmenu i.fa {
color: var(--color-link);
}
}
.el-table__row {
::-webkit-scrollbar {
width: 6px; /* 设置垂直滚动条的宽度 */
height: 6px; /* 设置水平滚动条的高度 */
}
}

View File

@@ -112,15 +112,15 @@
background: url('./icons/gpt.png') no-repeat center left transparent;
}
&.WebCloud_ico_docu {
background: url('icons/cloud.png') no-repeat center left transparent;
background-size: contain;
}
&.clickhouse_ico_docu {
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;
}

View File

@@ -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,18 @@ export function getCurrentOrgLocal(username) {
}
export function saveCurrentOrgLocal(username, org) {
const key = CURRENT_ORG_KEY + '_' + username
const key = CURRENT_ORG_KEY + ':' + username
localStorage.setItem(key, JSON.stringify(org))
VueCookie.set('X-JMS-ORG', org.id)
}
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)

View File

@@ -26,7 +26,8 @@ Object.assign(Table.components.TableBody.methods, {
const range = document.createRange()
range.setStart(cellChild, 0)
range.setEnd(cellChild, cellChild.childNodes.length)
const rangeWidth = range.getBoundingClientRect().width
// rangeWidth 有可能是小数,因此就会导致原本 rangeWidth + padding = cellChild.offsetWidth 的大于了 cellChild.offsetWidth
const rangeWidth = Math.floor(range.getBoundingClientRect().width)
const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0) +
(parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0)
if (

View File

@@ -109,7 +109,7 @@ export function isSameView(to, from) {
export function getPropView() {
const hasPermedViews = getPermedViews()
const preView = localStorage.getItem('PreView')
const preView = localStorage.getItem('preView')
const hasPerm = hasPermedViews.indexOf(preView) > -1
if (hasPerm) {
return preView

View File

@@ -3,7 +3,6 @@ import i18n from '@/i18n/i18n'
import { eventBus } from '@/utils/const'
import { getTokenFromCookie } from '@/utils/auth'
import { getErrorResponseMsg } from '@/utils/common'
import { refreshSessionIdAge } from '@/api/users'
import { MessageBox } from 'element-ui'
import { message } from '@/utils/message'
import store from '@/store'
@@ -102,20 +101,6 @@ export function flashErrorMsg({ response, error }) {
}
}
let timer = null
function refreshSessionAgeDelay(response) {
if (response.request.responseURL.indexOf('/users/profile/') !== -1) {
return
}
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function() {
refreshSessionIdAge()
}, 30 * 1000)
}
function ifConfirmRequired({ response, error }) {
if (response.status !== 412) {
return null
@@ -142,7 +127,6 @@ service.interceptors.response.use(
*/
response => {
// NProgress.done()
refreshSessionAgeDelay(response)
const res = response.data
store.dispatch('common/digestSQLQuery', response).then()

View File

@@ -24,6 +24,9 @@ async function checkLogin({ to, from, next }) {
} catch (e) {
Vue.$log.error(e)
const status = e.response.status
if (store.getters.currentOrg.autoEnter) {
await store.dispatch('users/setCurrentOrg', store.getters.preOrg)
}
if (status === 401 || status === 403) {
setTimeout(() => {
window.location = process.env.VUE_APP_LOGIN_PATH

View File

@@ -60,7 +60,7 @@ export default {
}
},
reviewers: {
hidden: (item) => !['review', 'warning'].includes(item.action),
hidden: (item) => !['review', 'warning', 'notify_and_warn'].includes(item.action),
rules: [rules.RequiredChange],
el: {
value: [],

View File

@@ -68,11 +68,6 @@ export default {
}
}
}
},
activated() {
setTimeout(() => {
this.$refs.listTable.reloadTable()
}, 300)
}
}
</script>

View File

@@ -32,15 +32,9 @@ export default {
title: this.$t('Basic'),
name: 'Detail'
}
],
actions: {
}
]
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -53,11 +53,6 @@ export default {
}
}
}
},
computed: {}
}
}
</script>
<style>
</style>

View File

@@ -36,8 +36,6 @@ export default {
}
}
},
mounted() {
},
methods: {
handleTabClick(tab) {
const query = _.cloneDeep(this.$route.query)

View File

@@ -47,6 +47,7 @@ export default {
return {
loading: true,
platform: {},
changePlatformID: '',
defaultConfig: {
initial: {},
platform: {},
@@ -131,7 +132,7 @@ export default {
const { defaultConfig } = this
const { node, platform } = this.$route?.query || {}
const nodesInitial = node ? [node] : []
const platformId = platform || 'Linux'
const platformId = this.changePlatformID ? this.changePlatformID : (platform || 'Linux')
const url = `/api/v1/assets/platforms/${platformId}/`
this.platform = await this.$axios.get(url)
const initial = {

View File

@@ -25,17 +25,17 @@ export default {
const platform = this.$route.query.type
const baseFields = [[this.$t('Basic'), ['db_name']]]
let tlsFields = ['use_ssl', 'ca_cert']
switch (platform) {
case 'redis':
tlsFields = tlsFields.concat(['client_cert', 'client_key'])
break
case 'mysql':
tlsFields = tlsFields.concat(['client_cert', 'client_key', 'allow_invalid_cert'])
break
case 'mongodb':
tlsFields = tlsFields.concat(['client_key', 'allow_invalid_cert'])
break
const platformFieldsMap = {
redis: ['client_cert', 'client_key'],
postgresql: ['client_cert', 'client_key', 'allow_invalid_cert'],
mysql: ['client_cert', 'client_key', 'allow_invalid_cert'],
mongodb: ['client_key', 'allow_invalid_cert']
}
if (platformFieldsMap[platform]) {
tlsFields = tlsFields.concat(platformFieldsMap[platform])
}
if (tlsFields.length > 2) {
const secureField = [
this.$t('Secure'), tlsFields, 2

View File

@@ -60,7 +60,7 @@ export default {
},
data() {
return {
title: this.$t('QuickTest'),
title: this.$t('Test'),
templateDialogVisible: false,
columnsDefault: ['name', 'username', 'asset'],
headerExtraActions: [

View File

@@ -22,15 +22,17 @@
:key="platform.id"
:span="6"
>
<el-card
:style="{ borderLeftColor: randomBorderColor(index) }"
class="platform-item"
shadow="hover"
@click.native="createAsset(platform)"
>
<img :src="loadImage(platform)" alt="icon" class="asset-icon">
<span class="platform-name">{{ platform.name }}</span>
</el-card>
<el-tooltip :content="platform.name" :open-delay="1000">
<el-card
:style="{ borderLeftColor: randomBorderColor(index) }"
class="platform-item"
shadow="hover"
@click.native="createAsset(platform)"
>
<img :src="loadImage(platform)" alt="icon" class="asset-icon">
<span class="platform-name">{{ platform.name }}</span>
</el-card>
</el-tooltip>
</el-col>
</el-collapse-item>
</el-collapse>
@@ -195,7 +197,11 @@ export default {
margin: 5px 0;
& ::v-deep .el-card__body {
padding: 10px
padding: 10px;
flex-wrap: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
border-left: solid 4px;
@@ -230,14 +236,16 @@ export default {
}
.asset-icon {
width: 1.5em;
height: 1.5em;
width: 2em;
height: 2em;
vertical-align: -0.2em;
fill: currentColor;
overflow: hidden;
}
.platform-name {
margin-left: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>

View File

@@ -89,14 +89,15 @@ export default {
},
methods: {
handleTabClick(tab) {
const query = _.cloneDeep(this.$route.query)
const newQuery = {
...query,
tab: tab.name
}
this.$nextTick(() => {
this.$router.replace({ query: newQuery })
})
// 这样不行,会闪
// const query = _.cloneDeep(this.$route.query)
// const newQuery = {
// ...query,
// tab: tab.name
// }
// this.$nextTick(() => {
// this.$router.replace({ query: newQuery })
// })
}
}
}

View File

@@ -173,7 +173,7 @@ export default {
},
{
key: this.$t('Region'),
value: this.object.task?.regions,
value: this.object.task?.regions_display,
formatter(row, value) {
return (<div>{
value?.map((content) => {

View File

@@ -64,8 +64,11 @@ export default {
formatter: DateFormatter
},
{
prop: 'summary.triggerMode',
label: this.$t('TriggerMode')
prop: 'trigger',
label: this.$t('TriggerMode'),
formatter: row => {
return row.trigger.label
}
},
{
prop: 'actions',

View File

@@ -51,6 +51,7 @@ export default {
}
},
data() {
console.log('ProviderPanel', this.providers)
return {}
},
methods: {

View File

@@ -73,6 +73,7 @@ export default {
btn.loading = true
}
})
form.value.interval = parseInt(form.value.interval, 10)
this.$refs.form.$refs.form.dataForm.submitForm('form', false)
},
handleSubmitSuccess(res) {

View File

@@ -31,6 +31,12 @@ export default {
}
],
actions: {
canUpdate: () => {
return this.Account.name !== 'default'
},
canDelete: () => {
return this.Account.name !== 'default'
},
deleteSuccessRoute: 'CloudAccountList',
updateCallback: () => {
const id = this.$route.params.id

View File

@@ -26,7 +26,7 @@ export default {
detailFields: [
'name', 'assets_amount',
{
key: this.$t('Gateways amount'),
key: this.$t('Gateway'),
value: `${this.object.gateways.length}`
},
'date_created', 'comment'

View File

@@ -11,11 +11,10 @@ export default {
BaseAssetCreateUpdate
},
data() {
const platformType = this.$route.query.platform_type
return {
url: '/api/v1/assets/gateways/',
updateInitial: async(initial) => {
const url = `/api/v1/assets/platforms/?name=Gateway`
const url = `/api/v1/assets/platforms/?name__startswith=Gateway`
const platform = await this.$axios.get(url)
initial.platform = parseInt(platform[0].id)
initial.domain = this.$route.query.domain
@@ -26,11 +25,11 @@ export default {
disabled: true
},
platform: {
disabled: true,
helpText: this.$t('GatewayPlatformHelpText'),
el: {
multiple: false,
ajax: {
url: `/api/v1/assets/platforms/?type=${platformType}`,
url: `/api/v1/assets/platforms/?name__startswith=Gateway`,
transformOption: (item) => {
return { label: item.name, value: item.id }
}

View File

@@ -32,7 +32,7 @@
<script>
import Dialog from '@/components/Dialog'
import AutoDataForm from '@/components/Form/AutoDataForm'
import { DynamicInput } from '@/components/Form/FormFields'
import { DynamicInput, Switcher } from '@/components/Form/FormFields'
export default {
components: {
@@ -145,6 +145,9 @@ export default {
case 'list':
component = DynamicInput
break
case 'boolean':
component = Switcher
break
}
if (param) {

View File

@@ -1,9 +1,10 @@
<template>
<BaseList v-bind="tableConfig" />
<BaseList v-bind="config" />
</template>
<script>
import BaseList from '../../Asset/AssetList/components/BaseList'
import { DetailFormatter } from '@/components/Table/TableFormatters'
export default {
components: {
@@ -12,20 +13,34 @@ export default {
props: {
object: {
type: Object,
default: () => { }
default: () => {
}
}
},
data() {
return {
tableConfig: {
config: {
category: 'all',
url: `/api/v1/assets/assets/?platform=${this.object.id}`,
tableConfig: {
columnsMeta: {
name: {
formatter: DetailFormatter,
formatterArgs: {
can: false
}
},
actions: {
has: false
}
}
},
headerActions: {
hasCreate: false,
hasRefresh: true,
hasRefresh: false,
hasExport: false,
hasImport: false,
hasMoreActions: true
hasMoreActions: false
}
}
}

View File

@@ -15,6 +15,7 @@
<script>
import { GenericListTable, TabPage } from '@/layout/components'
import { ChoicesFormatter, ProtocolsFormatter } from '../../../components/Table/TableFormatters'
import AmountFormatter from '@/components/Table/TableFormatters/AmountFormatter.vue'
export default {
components: {
@@ -35,9 +36,28 @@ export default {
columnsExclude: ['automation'],
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'category', 'type', 'actions']
default: ['name', 'assets_amount', 'category', 'type', 'actions']
},
columnsMeta: {
assets_amount: {
width: '160px',
formatter: AmountFormatter,
formatterArgs: {
async: true,
permissions: 'assets.view_asset',
getRoute({ row }) {
return {
name: 'PlatformDetail',
params: {
id: row.id
},
query: {
tab: 'Assets'
}
}
}
}
},
type: {
formatter: ChoicesFormatter
},
@@ -72,7 +92,10 @@ export default {
width: '140px'
},
internal: {
width: '100px'
width: '100px',
formatterArgs: {
showFalse: false
}
},
actions: {
formatterArgs: {

View File

@@ -29,10 +29,10 @@ export const filterSelectValues = (values) => {
function updatePlatformProtocols(vm, platformType, updateForm, isPlatformChanged = false) {
setTimeout(() => vm.init().then(() => {
const isCreate = vm?.$route?.meta.action === 'create' && vm?.$route?.query.clone_from === undefined
const need_modify = isCreate || isPlatformChanged
const platformProtocols = vm.platform.protocols
if (!need_modify) return
if (platformType === 'website') {
const need_modify = isCreate || isPlatformChanged
if (!need_modify) return
const platformProtocols = vm.platform.protocols
const setting = Array.isArray(platformProtocols) ? platformProtocols[0].setting : platformProtocols.setting
updateForm({
'autofill': setting.autofill ? setting.autofill : 'basic',
@@ -42,6 +42,8 @@ function updatePlatformProtocols(vm, platformType, updateForm, isPlatformChanged
'username_selector': setting.username_selector
})
}
vm.iConfig.fieldsMeta.protocols.el.choices = platformProtocols.map(item => ({ name: item.name, port: item.port }))
updateForm({ protocols: [] })
}), 100)
}
@@ -100,12 +102,10 @@ export const assetFieldsMeta = (vm) => {
change: ([event], updateForm) => {
const pk = event.pk
const url = window.location.href
const newURL = url.replace(/platform=[^&]*/, 'platform=' + pk)
vm.changePlatformID = pk
if (url.includes('clone')) {
updatePlatformProtocols(vm, platformType, updateForm, true)
} else {
window.history.replaceState(null, null, newURL)
vm.$nextTick(() => {
updatePlatformProtocols(vm, platformType, updateForm, true)
})

View File

@@ -32,7 +32,7 @@ export default {
width: '60%',
tableConfig: {
hasSelection: false,
url: `/api/v1/ops/adhocs/`,
url: `/api/v1/ops/adhocs/?only_mine=true`,
columns: ['name', 'module', 'args', 'comment', 'actions'],
columnsMeta: {
name: {

View File

@@ -120,11 +120,21 @@ export default {
autoComplete: true,
query: (query, cb) => {
const { hosts, nodes } = this.getSelectedNodesAndHosts()
if (hosts.length === 0) {
this.$message.warning(`${this.$t('RequiredAssetOrNode')}`)
return cb([])
}
this.$axios.post('/api/v1/ops/username-hints/', {
nodes: nodes,
assets: hosts,
query: query
}).then(data => {
if (Array.isArray(data) && data.length === 0) {
this.$message.info(`${this.$t('NoAccountFound')}`)
return cb([])
}
const ns = data.map(item => {
return { value: item.username }
})
@@ -470,9 +480,9 @@ $container-bg-color: #f7f7f7;
flex-direction: column;
.xterm-container {
margin-left: 30px;
height: calc(100vh - 549px);
min-height: 255px;
margin-left: 30px;
border: 1px solid var(--color-border);
border-radius: 5px;
background-color: $container-bg-color;

View File

@@ -83,7 +83,7 @@ export default {
multiple: false,
value: [],
ajax: {
url: '/api/v1/ops/playbooks/',
url: `/api/v1/ops/playbooks/?only_mine=true`,
transformOption: (item) => {
return { label: item.name, value: item.id }
}

View File

@@ -0,0 +1,129 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
</div>
</template>
<script>
import JobRunDialog from '@/views/ops/Job/JobRunDialog'
import GenericListTable from '@/layout/components/GenericListTable'
import { openTaskPage } from '@/utils/jms'
import { ActionsFormatter, DateFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
export default {
components: {
GenericListTable,
JobRunDialog
},
data() {
return {
item: {},
tableConfig: {
url: '/api/v1/ops/jobs/?type=adhoc',
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'type', 'asset_amount', 'average_time_cost',
'summary', 'date_last_run', 'actions'
]
},
columns: [
'name', 'type', 'summary', 'average_time_cost', 'asset_amount',
'date_last_run', 'comment', 'date_updated', 'date_created', 'actions'
],
columnsMeta: {
name: {
width: '140px',
formatter: DetailFormatter,
formatterArgs: {
can: true,
getRoute: ({ row }) => ({
name: 'JobDetail',
params: { id: row.id }
})
}
},
type: {
width: '96px',
formatter: (row) => {
return row.type.label
}
},
comment: {
width: '240px'
},
summary: {
label: this.$t('Summary'),
formatter: (row) => {
return row.summary['success'] + '/' + row.summary['total']
}
},
average_time_cost: {
formatter: (row) => {
return row.average_time_cost.toFixed(2) + 's'
}
},
asset_amount: {
label: this.$t('AssetsOfNumber'),
formatter: (row) => {
return row.assets.length
}
},
date_last_run: {
width: '140px',
formatter: DateFormatter
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_job') && !this.$store.getters.currentOrgIsRoot,
updateRoute: 'JobUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_job'),
hasClone: false,
extraActions: [
{
title: this.$t('Run'),
name: 'run',
can: this.$hasPerm('ops.add_jobexecution') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
if (row?.use_parameter_define && row?.parameters_define) {
const params = JSON.parse(row.parameters_define)
if (Object.keys(params).length > 0) {
this.item = row
this.showJobRunDialog = true
}
} else {
this.runJob(row)
}
}
}
]
}
}
}
},
headerActions: {
createRoute: 'JobCreate',
hasRefresh: true,
hasExport: false,
hasImport: false
},
showJobRunDialog: false
}
},
methods: {
runJob(row, parameters) {
this.$axios.post('/api/v1/ops/job-executions/', {
job: row.id,
parameters: parameters
}).then((resp) => {
openTaskPage(resp.task_id)
})
}
}
}
</script>

View File

@@ -0,0 +1,136 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
</div>
</template>
<script>
import JobRunDialog from '@/views/ops/Job/JobRunDialog'
import GenericListTable from '@/layout/components/GenericListTable'
import { openTaskPage } from '@/utils/jms'
import { ActionsFormatter, DateFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
export default {
components: {
GenericListTable,
JobRunDialog
},
data() {
return {
item: {},
tableConfig: {
url: '/api/v1/ops/jobs/?type=playbook',
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'type', 'asset_amount', 'average_time_cost',
'summary', 'date_last_run', 'actions'
]
},
columns: [
'name', 'type', 'summary', 'average_time_cost', 'asset_amount',
'date_last_run', 'comment', 'date_updated', 'date_created', 'actions'
],
columnsMeta: {
name: {
width: '140px',
formatter: DetailFormatter,
formatterArgs: {
can: true,
getRoute: ({ row }) => ({
name: 'JobDetail',
params: { id: row.id }
})
}
},
type: {
width: '96px',
formatter: (row) => {
return row.type.label
}
},
comment: {
width: '240px'
},
summary: {
label: this.$t('Summary'),
formatter: (row) => {
return row.summary['success'] + '/' + row.summary['total']
}
},
average_time_cost: {
formatter: (row) => {
return row.average_time_cost.toFixed(2) + 's'
}
},
asset_amount: {
label: this.$t('AssetsOfNumber'),
formatter: (row) => {
return row.assets.length
}
},
date_last_run: {
width: '140px',
formatter: DateFormatter
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_job') && !this.$store.getters.currentOrgIsRoot,
updateRoute: 'JobUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_job'),
hasClone: false,
extraActions: [
{
title: this.$t('Run'),
name: 'run',
can: this.$hasPerm('ops.add_jobexecution') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
if (row?.use_parameter_define && row?.parameters_define) {
const params = JSON.parse(row.parameters_define)
if (Object.keys(params).length > 0) {
this.item = row
this.showJobRunDialog = true
}
} else {
this.runJob(row)
}
}
}
]
}
}
}
},
headerActions: {
hasRefresh: true,
hasExport: false,
hasImport: false,
createRoute: () => {
return {
name: 'JobCreate',
query: {
type: 'playbook'
}
}
}
},
showJobRunDialog: false
}
},
methods: {
runJob(row, parameters) {
this.$axios.post('/api/v1/ops/job-executions/', {
job: row.id,
parameters: parameters
}).then((resp) => {
openTaskPage(resp.task_id)
})
}
}
}
</script>

View File

@@ -1,151 +1,39 @@
<template>
<div>
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
<GenericListPage :header-actions="headerActions" :table-config="tableConfig" />
</div>
<TabPage
:active-menu.sync="activeMenu"
:submenu="submenu"
>
<component :is="activeMenu" />
</TabPage>
</template>
<script>
import GenericListPage from '@/layout/components/GenericListPage'
import { ActionsFormatter, DateFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import JobRunDialog from '@/views/ops/Job/JobRunDialog'
import { openTaskPage } from '@/utils/jms'
import Adhoc from './components/Adhoc.vue'
import Playbook from './components/PlayBook.vue'
import TabPage from '@/layout/components/TabPage/index.vue'
export default {
components: {
JobRunDialog,
GenericListPage
Adhoc,
TabPage,
Playbook
},
data() {
return {
item: {},
runtime_parameters: {},
showJobRunDialog: false,
tableConfig: {
url: '/api/v1/ops/jobs/',
columnsShow: {
min: ['name', 'actions'],
default: [
'name', 'type', 'asset_amount', 'average_time_cost',
'summary', 'date_last_run', 'actions'
]
activeMenu: 'Adhoc',
submenu: [
{
title: this.$t('AdhocManage'),
name: 'Adhoc',
hidden: () => !this.$hasPerm('ops.view_adhoc')
},
columns: [
'name', 'type', 'summary', 'average_time_cost', 'asset_amount',
'date_last_run', 'comment', 'date_updated', 'date_created', 'actions'
],
columnsMeta: {
name: {
width: '140px',
formatter: DetailFormatter,
formatterArgs: {
can: true,
getRoute: ({ row }) => ({
name: 'JobDetail',
params: { id: row.id }
})
}
},
type: {
width: '96px',
formatter: (row) => {
return row.type.label
}
},
comment: {
width: '240px'
},
summary: {
label: this.$t('Summary'),
formatter: (row) => {
return row.summary['success'] + '/' + row.summary['total']
}
},
average_time_cost: {
formatter: (row) => {
return row.average_time_cost.toFixed(2) + 's'
}
},
asset_amount: {
label: this.$t('AssetsOfNumber'),
formatter: (row) => {
return row.assets.length
}
},
date_last_run: {
width: '140px',
formatter: DateFormatter
},
actions: {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_job') && !this.$store.getters.currentOrgIsRoot,
updateRoute: 'JobUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_job'),
hasClone: false,
extraActions: [
{
title: this.$t('Run'),
name: 'run',
can: this.$hasPerm('ops.add_jobexecution') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
if (row?.use_parameter_define && row?.parameters_define) {
const params = JSON.parse(row.parameters_define)
if (Object.keys(params).length > 0) {
this.item = row
this.showJobRunDialog = true
}
} else {
this.runJob(row)
}
}
}
]
}
}
{
title: this.$t('PlaybookManage'),
name: 'Playbook',
hidden: () => !this.$hasPerm('ops.view_playbook')
}
},
headerActions: {
createRoute: 'JobCreate',
hasRefresh: true,
hasExport: false,
hasImport: false,
moreCreates: {
callback: (item) => {
this.$router.push({
name: 'JobCreate',
query: { type: item.name }
})
},
dropdown: [
{
name: 'adhoc',
title: this.$t('Command')
},
{
name: 'playbook',
title: 'Playbook'
}
]
}
}
}
},
methods: {
runJob(row, parameters) {
this.$axios.post('/api/v1/ops/job-executions/', {
job: row.id,
parameters: parameters
}).then((resp) => {
openTaskPage(resp.task_id)
})
]
}
}
}
</script>
<style>
</style>

View File

@@ -11,12 +11,13 @@ export default {
GenericListTable
},
data() {
const currentUserID = this.$store.state.users.profile.id
return {
tableConfig: {
url: '/api/v1/ops/adhocs/',
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'module', 'args', 'comment', 'date_created', 'actions']
default: ['name', 'module', 'args', 'comment', 'scope', 'date_created', 'actions']
},
columnsMeta: {
name: {
@@ -29,10 +30,14 @@ export default {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_adhoc'),
canUpdate: ({ row }) => {
return this.$hasPerm('ops.change_adhoc') && row.creator === currentUserID
},
updateRoute: 'AdhocUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_adhoc'),
canDelete: ({ row }) => {
return this.$hasPerm('ops.delete_adhoc') && row.creator === currentUserID
},
hasClone: false
}
}
@@ -49,7 +54,3 @@ export default {
}
}
</script>
<style>
</style>

View File

@@ -14,7 +14,7 @@ export default {
return {
url: '/api/v1/ops/adhocs/',
fields: [
[this.$t('Basic'), ['name', 'module', 'args', 'comment']]
[this.$t('Basic'), ['name', 'module', 'args', 'comment', 'scope']]
],
initial: {
module: 'shell',

View File

@@ -16,6 +16,7 @@ export default {
GenericListTable
},
data() {
const currentUserID = this.$store.state.users.profile.id
return {
createDialogVisible: false,
uploadDialogVisible: false,
@@ -23,7 +24,7 @@ export default {
url: '/api/v1/ops/playbooks/',
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'comment', 'date_created', 'actions']
default: ['name', 'comment', 'scope', 'date_created', 'actions']
},
columnsMeta: {
name: {
@@ -36,11 +37,15 @@ export default {
formatter: ActionsFormatter,
formatterArgs: {
hasUpdate: true,
canUpdate: this.$hasPerm('ops.change_playbook'),
canUpdate: ({ row }) => {
return this.$hasPerm('ops.change_playbook') && row.creator === currentUserID
},
updateRoute: 'PlaybookUpdate',
hasDelete: true,
canDelete: this.$hasPerm('ops.delete_playbook'),
hasClone: false
canDelete: ({ row }) => {
return this.$hasPerm('ops.delete_playbook') && row.creator === currentUserID
},
hasClone: true
}
}
}

View File

@@ -13,7 +13,7 @@ export default {
return {
url: '/api/v1/ops/playbooks/',
fields: [
[this.$t('Basic'), ['name', 'comment']]
[this.$t('Basic'), ['name', 'comment', 'scope']]
],
createSuccessNextRoute: {
name: 'Template'

View File

@@ -2,7 +2,7 @@
<div>
<NewNodeDialog v-if="createDialogVisible" :visible.sync="createDialogVisible" @confirm="doCreate" />
<TreeTable ref="TreeTable" :tree-setting="treeSetting">
<template slot="rMenu">
<template v-if="!disableEdit" slot="rMenu">
<li id="m_create_file" class="rmenu" tabindex="-1" @click="onCreate('file')">
{{ $tc('NewFile') }}
</li>
@@ -62,7 +62,9 @@ export default {
}
},
data() {
const disableEdit = this.object.creator !== this.$store.state.users.profile.id
return {
disableEdit: disableEdit,
newNode: {},
createDialogVisible: false,
createType: 'directory',
@@ -70,7 +72,8 @@ export default {
closing: false,
DataZTree: 0,
cmOptions: {
mode: 'yaml'
mode: 'yaml',
readOnly: disableEdit
},
toolbar: {
left: {
@@ -82,6 +85,7 @@ export default {
el: {
type: 'primary'
},
isVisible: disableEdit,
callback: () => {
this.onSave()
}
@@ -94,6 +98,7 @@ export default {
el: {
type: 'primary'
},
isVisible: disableEdit,
callback: () => {
this.onReset()
}
@@ -298,7 +303,7 @@ export default {
border-radius: 2px;
}
.workspace-tab{
.workspace-tab {
::v-deep .el-tabs__header {
margin: 0 0 15px 30px !important;
}

View File

@@ -36,12 +36,6 @@ export default {
}
]
}
},
mounted() {
}
}
</script>
<style scoped>
</style>

View File

@@ -76,6 +76,8 @@ export default {
}],
el: {
value: [],
defaultPageSize: 300,
baseUrl: '/api/v1/assets/assets/?fields_size=mini',
treeSetting: {
showSearch: false,
showRefresh: false

Some files were not shown because too many files have changed in this diff Show More