Compare commits

...

171 Commits

Author SHA1 Message Date
wangruidong
a2e964d953 fix: adhoc and playbook detail page display many '-' 2024-11-22 15:23:47 +08:00
zhaojisen
0ad4945b10 fixed: Fixed an issue where the Form button was hidden 2024-11-22 15:21:59 +08:00
wangruidong
fcdd090e2d fix: run playbook job with no variable 2024-11-21 22:05:18 +08:00
Bryan
e47ddb5355 Merge pull request #4451 from jumpserver/dev
v4.4.0
2024-11-21 19:01:11 +08:00
wangruidong
475a77497f fix: disable job has no perms 2024-11-21 18:57:14 +08:00
Bai
5d1301c871 fix: public playbook variables are not displayed by other users 2024-11-21 17:50:59 +08:00
zhaojisen
772f40e13d fixed: Fixed an issue where the Form button was hidden 2024-11-21 14:44:29 +08:00
wangruidong
1b2f7ad28c fix: default value not display 2024-11-21 14:26:39 +08:00
wangruidong
84778881f6 fix: playbook job clone variable error 2024-11-20 23:06:24 +08:00
wangruidong
d6d0338666 perf: Add default value col in Variable & refactor JobUpdateCreate 2024-11-20 21:51:15 +08:00
wangruidong
de6f477d05 fix: playbook clone with variable 2024-11-20 15:35:34 +08:00
wangruidong
03e07ee280 fix: Add error handling in VariableCreateUpdateForm and wrap text in ItemValue 2024-11-20 10:55:51 +08:00
wangruidong
35e2273603 perf: Enhance MFA settings and display 2024-11-20 10:12:56 +08:00
zhaojisen
7a138388b2 fixed: Fixes ESLint issues 2024-11-19 18:05:13 +08:00
Bai
b1f42dd6bf perf: License info remove subscription id 2024-11-19 17:48:49 +08:00
wangruidong
2eeba4ee4f fix: playbook set variable perms 2024-11-19 10:12:52 +08:00
fit2bot
d91579e848 fixed: Fixed an issue where the number of commands overlaps with whether there are command fields (#4437)
* fixed: Fixed an issue where the number of commands overlaps with whether there are command fields

* fixed: Fixed an issue where the number of commands overlaps with whether there are command fields

---------

Co-authored-by: zhaojisen <1301338853@qq.com>
2024-11-18 16:13:06 +08:00
wangruidong
13751f14f3 perf: Variable table overflow show tooltip and CodeEditor cursor position 2024-11-18 15:09:51 +08:00
wangruidong
7ea00e17bd perf: Cancel template selection if the command is empty 2024-11-15 17:58:32 +08:00
wangruidong
ec78370ecc fix: duplicate variable names and job audit perms issue 2024-11-15 16:01:41 +08:00
Aaron3S
c9f62ae5d3 feat: 添加人脸识别 MFA 2024-11-13 17:42:29 +08:00
halo
b80b69dd26 perf: VAULT_BACKEND cannot be modified from the frontend 2024-11-13 17:30:48 +08:00
wangruidong
ac49415061 fix: Solve audit job and variable bugs 2024-11-13 17:30:20 +08:00
wangruidong
9edb64837d fix: Job create cannot be saved without setting interval 2024-11-12 15:37:18 +08:00
wangruidong
671fa65930 perf: Admin and auditor can view and stop task 2024-11-12 11:20:06 +08:00
Eric
0da81ea60d perf: add vnc port for endpoint 2024-11-11 21:58:21 +08:00
Halo
c610d396d6 feat: azure key vault (#4418) 2024-11-11 19:42:16 +08:00
feng
0ed02ca7f7 fix: Modify the platform protocol cursor disappears 2024-11-08 16:48:50 +08:00
feng
c22981e2c1 perf: Translate 2024-11-08 15:34:21 +08:00
fit2bot
b0af35a4b9 perf: Create a job that supports adding node parameters (#4412)
* perf: Create a job that supports adding node parameters

* feat: add variables component

* perf: Update file tip position

* feat: Add variable support to job from template

* feat: Parameters can be set when running job

* feat: Supports setting  variable type

* feat: Supports running adhoc with parameters

* feat: Supports running playbook with parameters

* feat: Support setting variables for scheduled tasks

* perf: Translate

---------

Co-authored-by: wangruidong <940853815@qq.com>
2024-11-07 10:41:07 +08:00
jiangweidong
1d16a165a5 perf: optimization 2024-11-06 15:06:27 +08:00
jiangweidong
f1f4242ad6 perf: Supports automatic release of assets and prevents accidental release of network errors 2024-11-06 15:06:27 +08:00
老广
bff8f10cd5 Create llm-code-review.yml 2024-11-05 18:23:09 +08:00
Bai
305e44ea1c feat: modify asset permission default expired from settings 2024-11-01 18:23:38 +08:00
feng
a636bb2037 perf: List preference translate 2024-10-18 14:32:45 +08:00
Bryan
56aa3caa83 Merge pull request #4410 from jumpserver/dev
v4.3.0
2024-10-17 14:56:18 +08:00
fit2bot
9b6f54c1ed perf: update placeholder (#4409)
* perf: update passkey create

* perf: update placeholder

---------

Co-authored-by: ibuler <ibuler@qq.com>
2024-10-17 11:10:18 +08:00
feng
611341307b fix: ticket duplicate submission 2024-10-16 19:09:18 +08:00
ibuler
f8479c53ff perf: update passkey create 2024-10-16 18:51:56 +08:00
wangruidong
e25bf46659 perf: url is null set remote false 2024-10-16 18:36:12 +08:00
wangruidong
6d07307e56 perf: users use Select2 2024-10-16 16:54:34 +08:00
wangruidong
6fb7fe8fa1 fix: interval field check 2024-10-16 11:01:44 +08:00
zhaojisen
19cd497095 fixed: Restores remote application styles 2024-10-15 17:06:06 +08:00
wangruidong
d858489367 fix: modify getPreference url 2024-10-15 14:13:10 +08:00
feng
341a30ba06 perf: The platform gets activeMenu and adds a default value 2024-10-15 11:32:51 +08:00
ZhaoJiSen
8390fb7429 Merge pull request #4398 from jumpserver/pr@dev@help_tip
perf: Help text automatic line wrapping
2024-10-12 14:40:43 +08:00
feng
5389f1d011 perf: Help text automatic line wrapping 2024-10-12 14:39:15 +08:00
ZhaoJiSen
bde642570f Merge pull request #4397 from jumpserver/pr@dev@perf_reveiver
style: Set special receiver to optimize the scroll wheel display
2024-10-12 11:35:16 +08:00
zhaojisen
3e7b970fe7 style: Set special receiver to optimize the scroll wheel display 2024-10-12 11:32:05 +08:00
ZhaoJiSen
2421e822b4 Merge pull request #4396 from jumpserver/pr@dev@perf_number_style
style: Optimize the number field style
2024-10-11 18:19:35 +08:00
zhaojisen
b2e474e3f6 style: Optimize the number field style 2024-10-11 18:15:46 +08:00
ZhaoJiSen
541836390a Merge pull request #4394 from jumpserver/pr@dev@fix_tab_change
fixed: Fixed command filtering and command group tab switching issues
2024-10-10 18:55:00 +08:00
zhaojisen
bb92e3f22b fixed: Fixed command filtering and command group tab switching issues 2024-10-10 18:51:50 +08:00
ZhaoJiSen
c9ad797b40 Merge pull request #4393 from jumpserver/pr@dev@fix_quick_job_node
fixed: Fixed the shortcut command selection node issue
2024-10-10 18:39:34 +08:00
zhaojisen
2a08310efc fixed: Fixed the shortcut command selection node issue 2024-10-10 18:37:47 +08:00
zhaojisen
3b2803b9a1 fixed: Fixed the shortcut command selection node issue 2024-10-10 18:36:33 +08:00
wangruidong
1b8ac9112e fix: user lack permission to view the type tree 2024-10-10 17:11:13 +08:00
ZhaoJiSen
9cc3dd4de9 Merge pull request #4390 from jumpserver/pr@dev@fix_add_lineHeight
style: Add Line Height
2024-10-10 14:40:30 +08:00
zhaojisen
120ef70eb1 style: Add Line Height 2024-10-10 14:36:01 +08:00
ZhaoJiSen
541f4ebc62 Merge pull request #4389 from jumpserver/pr@dev@fix_remote_app
style: Adjusting the styles of the remote application
2024-10-10 14:17:52 +08:00
zhaojisen
a47636abc7 style: Adjusting the styles of the remote application 2024-10-10 14:11:53 +08:00
ZhaoJiSen
78f6f4b36a Merge pull request #4388 from jumpserver/pr@dev@fix_comments_style
fixed: Fixed the presentation format of asset notes
2024-10-10 11:23:16 +08:00
zhaojisen
5abc0b77cf fixed: Fixed the presentation format of asset notes 2024-10-10 11:18:49 +08:00
ZhaoJiSen
464638e782 Merge pull request #4387 from jumpserver/pr@dev@fix_input_border
style: Fixed the display of the account number input box
2024-10-10 10:46:23 +08:00
zhaojisen
37cfeb2077 style: Fixed the display of the account number input box 2024-10-10 10:43:09 +08:00
ZhaoJiSen
44e297f01a Merge pull request #4386 from jumpserver/pr@dev@fix_tooltip_position
fixed: Fixed issue with sidebar toolTip placement
2024-10-10 09:50:33 +08:00
zhaojisen
febc283e36 fixed: Fixed issue with sidebar toolTip placement 2024-10-10 09:47:05 +08:00
ZhaoJiSen
dae33f55e8 Merge pull request #4385 from jumpserver/pr@dev@fix_special_chara
fixed: Fixed + and - not being special characters
2024-10-09 18:38:23 +08:00
zhaojisen
c62cd27690 fixed: Fixed + and - not being special characters 2024-10-09 18:34:31 +08:00
ZhaoJiSen
83443f8187 Merge pull request #4383 from jumpserver/pr@dev@fix_tab_refresh
fixed: Fixed An issue where adding a tag to asset does not work
2024-10-09 17:26:08 +08:00
zhaojisen
cb46f393e0 fixed: Fixed An issue where adding a tag to asset does not work 2024-10-09 17:17:12 +08:00
wangruidong
ddf5ac2151 fix: Determine the asset connection opening method based on the settings 2024-10-09 16:44:25 +08:00
wangruidong
88173f852a perf: Default endpoint cannot be disabled 2024-10-09 16:43:16 +08:00
wangruidong
d5f16e90e2 perf: add created_by field 2024-10-09 16:15:36 +08:00
ZhaoJiSen
d1c0aca4ff Merge pull request #4380 from jumpserver/pr@dev@fix_font_size
fixed: Fixed an issue where the font size of the resource is too large due to the parent element's font size being overridden
2024-10-09 14:47:42 +08:00
zhaojisen
a42edf17ec fixed: Fixed an issue where the font size of the resource is too large due to the parent element's font size being overridden 2024-10-09 14:32:06 +08:00
feng
dac5dfcd1c perf: Optimize file audit download prompt 2024-09-29 16:12:41 +08:00
ZhaoJiSen
8e6ca146e1 Merge pull request #4353 from jumpserver/dependabot/npm_and_yarn/serve-static-1.16.0
chore(deps-dev): bump serve-static from 1.15.0 to 1.16.0
2024-09-29 10:16:07 +08:00
ZhaoJiSen
9c06d36eab Merge pull request #4351 from jumpserver/dependabot/npm_and_yarn/path-to-regexp-3.3.0
chore(deps): bump path-to-regexp from 2.4.0 to 3.3.0
2024-09-29 10:14:32 +08:00
wisonic
ab7bd574f8 fix: assets proportion tooltip cause page bounced at first time 2024-09-27 17:34:25 +08:00
wangruidong
6007dc8621 perf: Endpoint add is_active field 2024-09-27 16:00:40 +08:00
wangruidong
a0e7c48dc9 perf: adhoc can clone 2024-09-27 14:12:13 +08:00
wangruidong
71dea791bf fix: xterm output truncate 2024-09-26 17:17:23 +08:00
ZhaoJiSen
e1b9184f41 Merge pull request #4372 from jumpserver/pr@dev@fix_perm_list_tree
fixed: Fixed an issue with draggable Tree nodes under asset authorization
2024-09-26 17:02:08 +08:00
zhaojisen
730d47f02a fixed: Fixed an issue with draggable Tree nodes under asset authorization 2024-09-26 16:59:18 +08:00
feng
e82ec68935 perf: The maximum length of the randomly generated password is changed to 36 2024-09-25 10:54:05 +08:00
feng
e8e5975d7f fix: fix after successfully creating an asset, the asset list cannot be partially refreshed 2024-09-24 15:44:55 +08:00
Bryan
19b1dc0dbc Merge pull request #4367 from jumpserver/dev
merge: from dev to master
2024-09-19 19:36:42 +08:00
ZhaoJiSen
3cb9dec978 Merge pull request #4366 from jumpserver/pr@dev@fix_style_fix
style: Restoring styles
2024-09-19 18:21:50 +08:00
zhaojisen
c025441075 style: Restoring styles 2024-09-19 18:20:41 +08:00
wangruidong
050b50fa74 fix: playbook template clone error 2024-09-19 15:07:31 +08:00
ibuler
e26befabc3 perf: filter gateway using new params 2024-09-19 11:24:51 +08:00
wangruidong
8545fc6136 fix: session is_locked show value 2024-09-19 11:20:54 +08:00
ZhaoJiSen
e343e0df9d Merge pull request #4363 from jumpserver/pr@dev@perf_remote_app
style: Adjust the remote application page layout
2024-09-18 21:16:47 +08:00
zhaojisen
74519e6f3c style: Adjust the remote application page layout 2024-09-18 21:15:49 +08:00
feng
1305f90372 perf: TimerExecution translate 2024-09-18 15:06:06 +08:00
feng
bbc3f53c0a perf: User details community version removes user acl 2024-09-18 11:11:53 +08:00
ZhaoJiSen
aa605adbc7 Merge pull request #4359 from jumpserver/pr@dev@fix_show_NaN
fixed: Fixed NaN issue when setting timing interval
2024-09-17 13:42:51 +08:00
zhaojisen
49ea7d0969 fixed: Fixed NaN issue when setting timing interval 2024-09-17 13:34:51 +08:00
feng
4221bdb2ab perf: Postgresql add ssl mode 2024-09-13 17:49:05 +08:00
dependabot[bot]
af4010e299 chore(deps-dev): bump serve-static from 1.15.0 to 1.16.0
Bumps [serve-static](https://github.com/expressjs/serve-static) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...1.16.0)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-12 02:22:33 +00:00
dependabot[bot]
879df90503 chore(deps): bump path-to-regexp from 2.4.0 to 3.3.0
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 2.4.0 to 3.3.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v2.4.0...v3.3.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-11 18:05:18 +00:00
Bryan
77ef172a23 Merge pull request #4320 from jumpserver/dev
v4.1.0
2024-08-15 21:42:42 +08:00
Bryan
4596887bf1 Merge pull request #4178 from jumpserver/dev
v4.0.0
2024-07-03 19:06:07 +08:00
Bryan
0a3dc30c85 Merge pull request #4072 from jumpserver/dev
v3.10.11-lts
2024-06-19 16:04:12 +08:00
Bryan
51d24bc8e5 Merge pull request #3941 from jumpserver/dev
v3.10.10-lts
2024-05-16 16:05:04 +08:00
Bryan
1b15a4d043 Merge pull request #3871 from jumpserver/dev
v3.10.9 (dev to master)
2024-04-22 19:44:33 +08:00
Bryan
7d3f818242 Merge pull request #3864 from jumpserver/v3.10
v3.10.8
2024-04-18 17:58:05 +08:00
Bryan
4e26f18d77 Merge pull request #3862 from jumpserver/dev
v3.10.8
2024-04-18 17:17:36 +08:00
Bryan
b22613617a Revert "build(deps): bump follow-redirects from 1.15.3 to 1.15.4"
This reverts commit e971cbf4a8.
2024-03-27 16:16:07 +08:00
dependabot[bot]
e971cbf4a8 build(deps): bump follow-redirects from 1.15.3 to 1.15.4
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 16:11:26 +08:00
wangruidong
4672abae35 fix: 刷新页面根据搜索条件过滤出对应的资源 2024-03-04 19:17:05 +08:00
Bryan
ba36d72602 Merge pull request #3761 from jumpserver/master
v3.10.4 (branch-v3.10)
2024-02-29 16:26:13 +08:00
Bryan
4bfbbba4c5 Merge pull request #3760 from jumpserver/dev
v3.10.4
2024-02-29 16:15:33 +08:00
Bryan
ea038ce43a Merge pull request #3697 from jumpserver/master
v3.10.2
2024-01-17 13:34:12 +00:00
Bryan
e16b19666c Merge pull request #3696 from jumpserver/dev
v3.10.2
2024-01-17 13:33:22 +00:00
Bryan
c7f5409eb6 Merge pull request #3694 from jumpserver/master
v3.10.2
2024-01-17 07:35:33 -04:00
Bryan
fdbd7d2222 Merge pull request #3693 from jumpserver/dev
v3.10.2
2024-01-17 07:24:50 -04:00
Bryan
ddbaeeafea Merge pull request #3668 from jumpserver/master
v3.10.1
2023-12-29 11:34:04 +05:00
Bryan
efb0e9dacb Merge pull request #3665 from jumpserver/dev
v3.10.1
2023-12-29 11:14:54 +05:00
huailei
f6f8301ad5 Revert "perf: 账号收集翻译"
This reverts commit 9a63ae63d4.
2023-12-22 15:25:31 +08:00
“huailei000”
9a63ae63d4 perf: 账号收集翻译 2023-12-22 11:31:45 +08:00
Bryan
1e007ccda3 Merge pull request #3642 from jumpserver/dev
v3.10
2023-12-21 15:15:52 +05:00
老广
d1d0b06b53 Merge pull request #3546 from jumpserver/dev
v3.9.0
2023-11-16 18:25:10 +08:00
Bryan
5fb70d2f24 Merge pull request #3450 from jumpserver/dev
v3.8.0
2023-10-19 03:33:53 -05:00
Bryan
b54a95430f Merge pull request #3404 from jumpserver/dev
v3.7.0
2023-09-21 17:04:42 +08:00
Bryan
4d8b4c45af Merge pull request #3355 from jumpserver/dev
v3.6.0
2023-08-17 14:00:33 +05:00
Bryan
a6d642df60 Merge pull request #3283 from jumpserver/dev
v3.5.0
2023-07-20 19:04:29 +08:00
Jiangjie.Bai
2e74f1522f Merge pull request #3222 from jumpserver/dev
v3.4.0
2023-06-15 14:51:36 +08:00
Jiangjie.Bai
fe615e0314 Merge pull request #3219 from jumpserver/dev
v3.4.0
2023-06-15 14:17:46 +08:00
Jiangjie.Bai
09f734e6fc Merge pull request #3135 from jumpserver/dev
v3.3.0
2023-05-18 19:18:11 +08:00
Jiangjie.Bai
3117046342 Merge pull request #3061 from jumpserver/dev
v3.2.0
2023-04-20 18:40:08 +08:00
Bai
b68aecb5cc fix: 批量更新资产平台help-text 2023-04-20 18:39:22 +08:00
Jiangjie.Bai
1c9b155d97 Merge pull request #3057 from jumpserver/dev
v3.2.0
2023-04-20 18:22:46 +08:00
Jiangjie.Bai
75b1be9864 Merge pull request #3019 from jumpserver/dev
v3.2.0 rc2
2023-04-14 19:01:37 +08:00
Jiangjie.Bai
615c3c1cf4 Merge pull request #3014 from jumpserver/dev
v3.2.0 rc1
2023-04-13 20:02:38 +08:00
Jiangjie.Bai
4d82231af4 Merge pull request #3012 from jumpserver/dev
v3.2.0 rc1
2023-04-13 19:22:38 +08:00
“huailei000”
c6cf6571b6 perf: ldap导入用户列表-组织下拉框设置最大宽度 2023-03-16 16:44:36 +08:00
Bai
8ea990d070 fix: 修复创建资产添加账号模版报错问题 2023-03-16 16:44:36 +08:00
“huailei000”
f4a32170d5 perf: message 2023-03-16 16:44:36 +08:00
ibuler
073508675e perf: 添加默认的信息 2023-03-16 16:44:36 +08:00
Jiangjie.Bai
1d6ca0a93a Merge pull request #2924 from jumpserver/dev
v3.1.0 rc4
2023-03-15 19:46:31 +08:00
Jiangjie.Bai
36aea652d6 Merge pull request #2788 from jumpserver/dev
v3.0.0
2023-02-23 20:16:41 +08:00
Jiangjie.Bai
1a42ce90ab Merge pull request #2760 from jumpserver/dev
v3.0.0-rc-latest
2023-02-22 22:21:54 +08:00
Jiangjie.Bai
31a401b55d Merge pull request #2463 from jumpserver/dev
v3.0.0-rc4
2023-01-31 18:55:34 +08:00
Jiangjie.Bai
582a84178d Merge pull request #2187 from jumpserver/dev
v2.28.0
2022-11-17 17:44:19 +08:00
Jiangjie.Bai
9b9f7c936c Merge pull request #2184 from jumpserver/dev
v2.28.0-rc5
2022-11-17 14:18:15 +08:00
Jiangjie.Bai
2a6100957f Merge pull request #2182 from jumpserver/dev
v2.28.0-rc4
2022-11-16 21:08:55 +08:00
Jiangjie.Bai
16606d6a27 Merge pull request #2176 from jumpserver/dev
v2.28.0-rc2
2022-11-14 10:01:05 +08:00
Jiangjie.Bai
0a612f50e6 Merge pull request #2164 from jumpserver/dev
v2.28.0-rc1
2022-11-10 17:45:47 +08:00
Jiangjie.Bai
fe36fa9390 Merge pull request #2117 from jumpserver/dev
v2.27.0-rc4
2022-10-18 21:02:10 +08:00
Jiangjie.Bai
ba109900ec Merge pull request #2113 from jumpserver/dev
v2.27.0-rc3
2022-10-18 11:20:57 +08:00
Jiangjie.Bai
ec7768267f Merge pull request #2105 from jumpserver/dev
v2.27.0-rc2
2022-10-14 11:01:32 +08:00
Jiangjie.Bai
cc58b374ab Merge pull request #2101 from jumpserver/dev
v2.27.0-rc1
2022-10-13 17:44:53 +08:00
Jiangjie.Bai
04ffbb8fd6 Merge pull request #2097 from jumpserver/dev
v2.27.0-rc1
2022-10-13 15:14:40 +08:00
Jiangjie.Bai
49880f6739 Merge pull request #2059 from jumpserver/dev
v2.26.0
2022-09-15 17:49:44 +08:00
Jiangjie.Bai
e6f98d58c4 Merge pull request #2057 from jumpserver/dev
v2.26.0-rc4
2022-09-15 16:18:03 +08:00
Jiangjie.Bai
fd1f16d43c Merge pull request #2050 from jumpserver/dev
v2.26.0-rc2
2022-09-13 17:41:39 +08:00
Jiangjie.Bai
968b2415b1 Merge pull request #2043 from jumpserver/dev
v2.26.0-rc1
2022-09-08 15:46:44 +08:00
Jiangjie.Bai
776090d6ba Merge pull request #2001 from jumpserver/dev
v2.25.0
2022-08-18 16:12:45 +08:00
Jiangjie.Bai
3a37952288 Merge pull request #1996 from jumpserver/dev
v2.25.0-rc4
2022-08-17 16:53:23 +08:00
Jiangjie.Bai
62b8fc0e3b Merge pull request #1994 from jumpserver/dev
v2.25.0-rc3
2022-08-16 19:08:23 +08:00
Jiangjie.Bai
b2028869cb Merge pull request #1986 from jumpserver/dev
v2.25.0-rc2
2022-08-12 18:06:56 +08:00
Jiangjie.Bai
5277a725f8 Merge pull request #1973 from jumpserver/dev
v2.25.0-rc1
2022-08-11 14:11:59 +08:00
Jiangjie.Bai
f137788c1a Merge pull request #1957 from jumpserver/dev
v2.24.0-rc5
2022-07-20 19:06:03 +08:00
Jiangjie.Bai
f7d17c8de7 Merge pull request #1954 from jumpserver/dev
v2.24.0-rc4
2022-07-19 16:18:13 +08:00
Jiangjie.Bai
feea70b0be Merge pull request #1944 from jumpserver/dev
v2.24.0-rc3
2022-07-18 12:05:42 +08:00
Jiangjie.Bai
04696ef3d6 Merge pull request #1940 from jumpserver/dev
v2.24.0-rc2
2022-07-15 18:07:37 +08:00
Jiangjie.Bai
1731f4f788 Merge pull request #1934 from jumpserver/dev
v2.24.0-rc1
2022-07-14 18:27:51 +08:00
Jiangjie.Bai
6f25d93909 Merge pull request #1931 from jumpserver/dev
v2.24.0-rc1
2022-07-14 17:51:58 +08:00
Jiangjie.Bai
46461ec324 Merge pull request #1925 from jumpserver/dev
v2.24.0-rc1
2022-07-14 15:12:15 +08:00
100 changed files with 1888 additions and 508 deletions

28
.github/workflows/llm-code-review.yml vendored Normal file
View 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"

View File

@@ -66,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",
@@ -117,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",

View File

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

View File

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

View File

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

View File

@@ -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"
>
开始人脸识别
</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/mfa/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>

View File

@@ -0,0 +1,111 @@
<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() {
return {
submitBtnText: this.$t('Confirm'),
url: '/api/v1/ops/variable/',
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'
},
el: {
type: 'input'
}
},
select_default_value: {
label: this.$t('DefaultValue'),
hidden: (formValue) => {
return formValue.type !== 'select'
},
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()
}
}
]
}
},
hasSaveContinue: false,
method: 'get'
}
},
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>

View 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/variable/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>

View File

@@ -78,7 +78,7 @@ export default {
formatterData = data
}
return (
<span>{formatterData}</span>
<span style={{ whiteSpace: 'pre-wrap', 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>
)
}
}

View File

@@ -123,6 +123,7 @@ export default {
&__body {
padding: 20px 30px;
font-size: 13px;
&:has(.el-table) {
background: #f3f3f4;

View File

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

View File

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

View File

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

View File

@@ -245,7 +245,7 @@ export default {
},
set(val) {
this.$emit('update:value', val)
this.$emit('change', val)
this.$emit('input', val)
}
},
iOptions() {

View File

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

View File

@@ -48,7 +48,8 @@ export default {
type: 'input-number',
el: {
min: 8,
max: 30
max: 36,
size: 'mini'
}
},
{

View File

@@ -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,10 +143,8 @@ export default {
watch: {
choices: {
handler(value, oldValue) {
this.loading = true
setTimeout(() => {
this.setDefaultItems(value)
this.loading = false
},)
},
deep: true,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -116,7 +116,6 @@ export default {
<style lang="scss" scoped>
.page {
height: calc(100vh - 50px);
overflow-y: hidden;
overflow-x: hidden;

View File

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

View File

@@ -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
View 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']
}
}
]
}
]

View File

@@ -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;
}
@@ -593,3 +593,9 @@ li.rmenu i.fa {
height: 6px; /* 设置水平滚动条的高度 */
}
}
.black-theme-popover {
width: 300px;
max-height: 700px;
overflow-y: scroll;
}

View File

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

View File

@@ -128,6 +128,8 @@ export function getErrorResponseMsg(error) {
}).filter(i => i).join('; ')
} else if (typeof data === 'string') {
return data
} else {
msg = error.toString()
}
return msg
}
@@ -310,4 +312,26 @@ export function toSentenceCase(string) {
}).join(' ')
return s[0].toUpperCase() + s.slice(1)
}
export { BASE_URL }
export function openNewWindow(url) {
let count
let top = 50
count = parseInt(window.sessionStorage.getItem('newWindowCount'), 10)
if (isNaN(count)) {
count = 0
}
let left = 100 + count * 100
top = 50 + count * 50
if (left + screen.width / 3 > screen.width) {
// 支持两排足以
top = screen.height / 3
count = 1
left = 100
}
let params = 'toolbar=yes,scrollbars=yes,resizable=yes'
params = params + `,top=${top},left=${left},width=${screen.width / 3},height=${screen.height / 3}`
window.sessionStorage.setItem('newWindowCount', `${count + 1}`)
window.open(url, '_blank', params)
}

View File

@@ -1,5 +1,6 @@
const moment = require('moment')
import { getLangCode } from '@/i18n/utils'
import store from '@/store'
function getTimeUnits(u) {
const units = {
@@ -124,3 +125,8 @@ export function formatDate(inputTime) {
// return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second
return y + '-' + m + '-' + d + 'T' + h + ':' + minute + ':' + second
}
export function getDefaultExpiredDays() {
const years = store.getters.publicSettings.DEFAULT_EXPIRED_YEARS
return getDayFuture(years * 365, new Date()).toISOString()
}

View File

@@ -19,7 +19,7 @@ export default {
initial: {
is_periodic: false,
password_rules: {
length: 16
length: 36
},
interval: 24,
accounts: [],

View File

@@ -66,7 +66,8 @@ export const getChangeSecretFields = () => {
is_periodic,
accounts: {
label: i18n.t('Accounts'),
component: TagInput
component: TagInput,
helpText: i18n.t('ChangeSecretAccountHelpText')
}
}
}

View File

@@ -21,7 +21,7 @@ export default {
initial: {
is_periodic: false,
password_rules: {
length: 30
length: 36
},
interval: 24,
secret_type: 'password',

View File

@@ -4,10 +4,12 @@ import InputWithUnit from '@/components/Form/FormFields/InputWithUnit.vue'
import store from '@/store'
const validatorInterval = (rule, value, callback) => {
if (parseInt(value) < 1) {
return callback(new Error(i18n.t('EnsureThisValueIsGreaterThanOrEqualTo1')))
// value只能是 null 或者 >=1
if (value === null || (!isNaN(value) && Number(value) >= 1)) {
callback()
} else {
callback(new Error(i18n.t('EnsureThisValueIsGreaterThanOrEqualTo1')))
}
callback()
}
export const crontab = {

View File

@@ -2,7 +2,6 @@
<TabPage
:active-menu.sync="config.activeMenu"
:submenu="config.submenu"
@tab-click="handleTabClick"
/>
</template>
@@ -35,18 +34,6 @@ export default {
]
}
}
},
methods: {
handleTabClick(tab) {
const query = _.cloneDeep(this.$route.query)
const newQuery = {
...query,
tab: tab.name
}
this.$nextTick(() => {
this.$router.replace({ query: newQuery })
})
}
}
}
</script>

View File

@@ -27,7 +27,7 @@ export default {
let tlsFields = ['use_ssl', 'ca_cert']
const platformFieldsMap = {
redis: ['client_cert', 'client_key'],
postgresql: ['client_cert', 'client_key', 'allow_invalid_cert'],
postgresql: ['client_cert', 'client_key', 'pg_ssl_mode'],
mysql: ['client_cert', 'client_key', 'allow_invalid_cert'],
mongodb: ['client_key', 'allow_invalid_cert']
}
@@ -52,7 +52,14 @@ export default {
},
use_ssl: {
label: this.$t('UseSSL'),
component: 'el-switch'
component: 'el-switch',
on: {
change: ([event], updateForm) => {
updateForm({
pg_ssl_mode: event ? 'require' : 'prefer'
})
}
}
},
allow_invalid_cert: {
label: this.$t('AllowInvalidCert'),

View File

@@ -48,7 +48,8 @@ export default {
},
tableConfig: {
url: tableUrl,
category: 'all'
category: 'all',
extraQuery: { 'order': '-date_updated' }
},
headerActions: {
handleImportClick: ({ selectedRows }) => {

View File

@@ -61,7 +61,7 @@ export default {
row['auto_config'].ansible_enabled &&
row['auto_config'].ping_enabled,
callback: ({ row }) => {
if (row.platform.name === 'Gateway') {
if (row.platform.name.startsWith('Gateway')) {
this.GatewayVisible = true
const port = row.protocols.find(item => item.name === 'ssh').port
if (!port) {

View File

@@ -191,7 +191,7 @@ export default {
row?.auto_config?.ansible_enabled &&
row?.auto_config?.ping_enabled,
callback: ({ row }) => {
if (row.platform.name === 'Gateway') {
if (row.platform.name.startsWith('Gateway')) {
this.GatewayVisible = true
const port = row.protocols.find(item => item.name === 'ssh').port
if (!port) {

View File

@@ -76,14 +76,20 @@ export default {
},
callbacks: {
change: function(val) {
this.$axios.patch(
`/api/v1/xpack/cloud/sync-instance-tasks/${this.object.task.id}/`,
{ 'sync_ip_type': val }
).then(res => {
this.$message.success(this.$t('UpdateSuccessMsg'))
}).catch(err => {
this.$message.error(this.$t('UpdateErrorMsg' + ' ' + err))
})
this.updateTaskData({ 'sync_ip_type': val })
}.bind(this)
}
},
{
title: this.$t('ReleaseAssets'),
type: 'switch',
attrs: {
model: this.object.task.release_assets,
disabled: !this.hasEditPerm()
},
callbacks: {
change: function(val) {
this.updateTaskData({ 'release_assets': val })
}.bind(this)
}
}
@@ -109,7 +115,7 @@ export default {
}
},
{
title: this.$t('Timer'),
title: this.$t('TimerExecution'),
attrs: {
model: this.object.task,
type: 'primary',
@@ -197,6 +203,16 @@ export default {
methods: {
hasEditPerm() {
return this.$hasPerm('xpack.change_account') && this.$hasPerm('xpack.change_syncinstancetask')
},
updateTaskData(data) {
this.$axios.patch(
`/api/v1/xpack/cloud/sync-instance-tasks/${this.object.task.id}/`,
data
).then(res => {
this.$message.success(this.$tc('UpdateSuccessMsg'))
}).catch(err => {
this.$message.error(this.$tc('UpdateErrorMsg' + ' ' + err))
})
}
}
}

View File

@@ -39,7 +39,8 @@ export default {
submitBtnSize: 'mini',
submitBtnText: this.$t('Add'),
hasReset: false,
onSubmit: () => {},
onSubmit: () => {
},
submitMethod: () => 'post',
getUrl: () => '',
cleanFormValue(data) {
@@ -86,7 +87,11 @@ export default {
this.formConfig.fieldsMeta.protocols.el.hidden = true
}
this.resourceType = val
this.formConfig.fieldsMeta.value.el.ajax.url = url
if (url) {
this.formConfig.fieldsMeta.value.el.ajax.url = url
} else {
this.formConfig.fieldsMeta.attr.el.remote = false
}
this.formConfig.fieldsMeta.value.el.options = options
}
}
@@ -151,21 +156,31 @@ export default {
tableConfig: {
columns: [
{ prop: 'attr', label: this.$t('ResourceType'), formatter: tableFormatter('resource_type') },
{ prop: 'value', label: this.$t('Resource'), formatter: tableFormatter('resource', () => { return this.globalResource }) },
{
prop: 'value', label: this.$t('Resource'), formatter: tableFormatter('resource', () => {
return this.globalResource
})
},
{ prop: 'protocols', label: this.$t('Other'), formatter: tableFormatter('protocols') },
{ prop: 'action', label: this.$t('Action'), align: 'center', width: '100px', formatter: (row, col, cellValue, index) => {
return (
<div className='input-button'>
<el-button
icon='el-icon-minus'
size='mini'
style={{ 'flexShrink': 0 }}
type='danger'
onClick={ this.handleDelete(index) }
/>
</div>
)
} }
{
prop: 'action',
label: this.$t('Action'),
align: 'center',
width: '100px',
formatter: (row, col, cellValue, index) => {
return (
<div className='input-button'>
<el-button
icon='el-icon-minus'
size='mini'
style={{ 'flexShrink': 0 }}
type='danger'
onClick={this.handleDelete(index)}
/>
</div>
)
}
}
],
totalData: this.value || [],
hasPagination: false
@@ -177,7 +192,9 @@ export default {
},
methods: {
init() {
this.nameOptions.map((o) => { this.globalResource[o.value] = o.label })
this.nameOptions.map((o) => {
this.globalResource[o.value] = o.label
})
},
onSubmit() {
this.$emit('input', this.tableConfig.totalData)
@@ -218,9 +235,11 @@ export default {
::v-deep .el-form-item:nth-child(-n+3) {
width: 43.5%;
}
::v-deep .el-form-item:last-child {
width: 6%;
}
.action-input {
margin-top: -10px;
}

View File

@@ -30,7 +30,7 @@ export default {
domain_enabled: true
},
canSelect: (row) => {
return row.platform?.name !== 'Gateway'
return !row.platform?.name.startsWith('Gateway')
}
}
},

View File

@@ -25,7 +25,7 @@ export default {
return {
tableConfig: {
category: 'all',
url: `/api/v1/assets/assets/?domain=${this.$route.params.id}&exclude_platform=Gateway`,
url: `/api/v1/assets/assets/?domain=${this.$route.params.id}&is_gateway=0`,
tableConfig: {
columnsMeta: {
actions: {

View File

@@ -39,7 +39,7 @@ export default {
data() {
return {
tableConfig: {
url: `/api/v1/assets/gateways/?domain=${this.$route.params.id}&platform=Gateway`,
url: `/api/v1/assets/gateways/?domain=${this.$route.params.id}`,
columnsExclude: [
'info', 'spec_info', 'auto_config'
],
@@ -174,7 +174,7 @@ export default {
name: 'GatewayAdd',
title: this.$t('Add'),
callback: function() {
this.AddGatewaySetting.AddGatewayDialogVisible = true
this.AddGatewaySetting.addGatewayDialogVisible = true
}.bind(this)
}
]
@@ -183,7 +183,7 @@ export default {
cell: '',
visible: false,
AddGatewaySetting: {
AddGatewayDialogVisible: false
addGatewayDialogVisible: false
}
}
},

View File

@@ -68,7 +68,7 @@ export default {
domain_enabled: true
},
canSelect: (row) => {
return row.platform?.name !== 'Gateway' && this.object.assets.map(item => item.id).indexOf(row.id) === -1
return !row.platform?.name.startsWith('Gateway') && this.object.assets.map(item => item.id).indexOf(row.id) === -1
}
}
}

View File

@@ -1,11 +1,11 @@
<template>
<Dialog
v-if="setting.AddGatewayDialogVisible"
v-if="setting.addGatewayDialogVisible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="$tc('AddGatewayInDomain')"
:visible.sync="setting.AddGatewayDialogVisible"
:visible.sync="setting.addGatewayDialogVisible"
after
custom-class="asset-select-dialog"
top="15vh"
@@ -31,7 +31,7 @@ export default {
setting: {
type: Object,
default: () => {
return { AddGatewayDialogVisible: false }
return { addGatewayDialogVisible: false }
}
},
object: {
@@ -64,7 +64,7 @@ export default {
multiple: true,
clearable: true,
ajax: {
url: '/api/v1/assets/assets/?domain_enabled=true&platform=Gateway'
url: '/api/v1/assets/assets/?domain_enabled=true&is_gateway=1'
},
disabledValues: this.object.gateways.map(item => item.id)
}
@@ -83,7 +83,7 @@ export default {
},
methods: {
onSubmitSuccess(res) {
this.setting.AddGatewayDialogVisible = false
this.setting.addGatewayDialogVisible = false
this.$emit('close', res)
}
}

View File

@@ -162,7 +162,7 @@ export default {
},
activated() {
setTimeout(() => {
this.tab.activeMenu = window.localStorage.getItem('lastTab')
this.tab.activeMenu = window.localStorage.getItem('lastTab') || 'host'
this.$refs.genericListTable.reloadTable()
}, 300)
},

View File

@@ -1,6 +1,6 @@
<template>
<div>
<GenericListPage :header-actions="headerActions" :table-config="tableConfig" />
<GenericListPage ref="ListPage" :header-actions="headerActions" :table-config="tableConfig" />
</div>
</template>
@@ -8,6 +8,7 @@
import GenericListPage from '@/layout/components/GenericListPage'
import { ActionsFormatter } from '@/components/Table/TableFormatters'
import { openTaskPage } from '@/utils/jms'
import { stopJob } from '@/api/ops'
export default {
components: {
@@ -45,6 +46,20 @@ export default {
callback: ({ row }) => {
openTaskPage(row.task_id)
}
},
{
title: this.$t('Stop'),
name: 'stop',
can: ({ row }) => {
return !row.is_finished
},
type: 'danger',
callback: ({ row }) => {
stopJob({ task_id: row.task_id }).then(() => {
this.$refs.ListPage.reloadTable()
this.$message.success(this.$t('StopJobMsg'))
})
}
}
]
}

View File

@@ -0,0 +1,88 @@
<template>
<div>
<GenericListPage ref="ListPage" :header-actions="headerActions" :table-config="tableConfig" />
</div>
</template>
<script type="text/jsx">
import GenericListPage from '@/layout/components/GenericListPage'
import { SwitchFormatter } from '@/components/Table/TableFormatters'
export default {
components: {
GenericListPage
},
data() {
const vm = this
return {
tableConfig: {
url: '/api/v1/audits/jobs/',
columnsShow: {
min: ['name', 'material'],
default: [
'name', 'material', 'type', 'crontab', 'interval', 'created_by', 'is_periodic_display', 'is_periodic'
]
},
columns: [
'name', 'args', 'material', 'type', 'crontab', 'interval', 'date_last_run', 'summary',
'created_by', 'is_periodic_display', 'is_periodic'
],
columnsMeta: {
actions: {
has: false
},
name: {
formatter: (row) => row.name
},
material: {
width: '200px',
label: this.$t('Command')
},
summary: {
width: '130px',
label: `${this.$t('Success')}/${this.$t('Total')}`,
formatter: (row) => {
return <div>
<span Class='text-primary'>{row.summary.success}</span>/
<span>{row.summary.total}</span>
</div>
}
},
is_periodic_display: {
width: '100px',
label: this.$t('Periodic')
},
is_periodic: {
width: '140px',
label: `${this.$t('Enable')}/${this.$t('Disable')}`,
formatter: SwitchFormatter,
formatterArgs: {
isDisplay(row) {
return row.is_periodic
},
getPatchUrl(row) {
return `/api/v1/audits/jobs/${row.id}/`
},
getPatchData(row) {
return {
is_periodic: !row.is_periodic
}
},
callback() {
vm.$refs.ListPage.reloadTable()
}
}
}
}
},
headerActions: {
hasLeftActions: false,
hasImport: false
}
}
}
}
</script>
<style>
</style>

View File

@@ -128,7 +128,8 @@ export default {
tip += current.label + '' + current.total + '<br/>'
}
return tip
}
},
appendToBody: true
},
grid: {
top: '60%',

View File

@@ -52,6 +52,7 @@ export default {
title: this.$tc('Select'),
name: 'select',
can: true,
type: 'primary',
callback: ({ row }) => {
this.$emit('select', row)
this.iVisible = false

View File

@@ -8,6 +8,12 @@
:visible.sync="showOpenAdhocSaveDialog"
/>
<VariableHelpDialog :visible.sync="showHelpDialog" />
<setVariableDialog
:form-data="variableFormData"
:query-param="variableQueryParam"
:visible.sync="showSetVariableDialog"
@submit="onSubmitVariable"
/>
<AssetTreeTable ref="TreeTable" :tree-setting="treeSetting">
<template slot="table">
<div class="transition-box" style="width: calc(100% - 17px);">
@@ -35,6 +41,7 @@
</template>
<script>
import $ from '@/utils/jquery-vendor.js'
import AssetTreeTable from '@/components/Apps/AssetTreeTable'
import QuickJobTerm from '@/views/ops/Adhoc/components/QuickJobTerm.vue'
import CodeEditor from '@/components/Form/FormFields/CodeEditor'
@@ -42,7 +49,8 @@ import Page from '@/layout/components/Page'
import AdhocOpenDialog from './AdhocOpenDialog.vue'
import AdhocSaveDialog from './AdhocSaveDialog.vue'
import VariableHelpDialog from './VariableHelpDialog.vue'
import { createJob, getJob, getTaskDetail, StopJob } from '@/api/ops'
import setVariableDialog from '@/views/ops/Template/components/setVariableDialog'
import { createJob, getJob, getTaskDetail, stopJob } from '@/api/ops'
export default {
name: 'CommandExecution',
@@ -50,6 +58,7 @@ export default {
VariableHelpDialog,
AdhocSaveDialog,
AdhocOpenDialog,
setVariableDialog,
AssetTreeTable,
Page,
QuickJobTerm,
@@ -69,6 +78,7 @@ export default {
showHelpDialog: false,
showOpenAdhocDialog: false,
showOpenAdhocSaveDialog: false,
showSetVariableDialog: false,
DataZTree: 0,
runas: '',
runasPolicy: 'skip',
@@ -92,6 +102,10 @@ export default {
type: 'primary'
},
callback: () => {
if (this.variableFormData.length !== 0) {
this.showSetVariableDialog = true
return
}
this.execute()
}
},
@@ -286,9 +300,19 @@ export default {
view: {
dblClickExpand: false,
showLine: true
},
callback: {
onCheck: function(_event, treeId, treeNode) {
const treeObj = $.fn.zTree.getZTreeObj(treeId)
if (treeNode.checked) {
treeObj.expandNode(treeNode, true, false, true)
}
}
}
},
iShowTree: true
iShowTree: true,
variableFormData: [],
variableQueryParam: ''
}
},
computed: {
@@ -299,6 +323,13 @@ export default {
return this.$refs.TreeTable.$refs.TreeList.$refs.AutoDataZTree.$refs.AutoDataZTree.$refs.dataztree.$refs.ztree
}
},
watch: {
command(iNew, iOld) {
if (iNew === '') {
this.variableFormData = []
}
}
},
mounted() {
this.enableWS()
this.initData()
@@ -330,6 +361,10 @@ export default {
}
},
onSelectAdhoc(adhoc) {
this.variableFormData = adhoc?.variable.map((data) => {
return data.form_data
})
this.variableQueryParam = 'adhoc=' + adhoc.id
this.command = adhoc.args
},
enableWS() {
@@ -423,7 +458,6 @@ export default {
this.$message.error(this.$tc('RequiredRunas'))
return
}
const data = {
assets: hosts,
nodes: nodes,
@@ -438,6 +472,9 @@ export default {
if (this.chdir) {
data.chdir = this.chdir
}
if (this.parameters) {
data.parameters = this.parameters
}
createJob(data).then(res => {
this.executionInfo.timeCost = 0
this.executionInfo.status = { value: 'running', label: this.$t('Running') }
@@ -449,9 +486,9 @@ export default {
})
},
stop() {
StopJob({ task_id: this.currentTaskId }).then(() => {
stopJob({ task_id: this.currentTaskId }).then(() => {
this.xterm.write('\x1b[31m' +
this.$tc('StopLogOutput').replace('currentTaskId', this.currentTaskId) + '\x1b[0m')
this.$tc('StopLogOutput').replace('currentTaskId', this.currentTaskId) + '\x1b[0m')
this.xterm.write(this.wrapperError(''))
this.getTaskStatus()
}).catch((e) => {
@@ -467,6 +504,11 @@ export default {
}
this.toolbar.left.run.isVisible = this.executionInfo.status.value === 'running'
this.toolbar.left.stop.isVisible = this.executionInfo.status.value !== 'running'
},
onSubmitVariable(parameters) {
this.parameters = parameters
this.showSetVariableDialog = false
this.execute()
}
}
}

View File

@@ -75,6 +75,7 @@ export default {
fontFamily: 'monaco, Consolas, "Lucida Console", monospace',
lineHeight: 1.2,
fontSize: 13,
scrollback: 9999999,
rightClickSelectsWord: true,
theme: {
background: '#fff',

View File

@@ -28,11 +28,14 @@ export default {
columnsShow: {
min: ['material', 'actions'],
default: [
'id', 'job', 'material', 'job_type', 'is_finished', 'is_success',
'id', 'material', 'job_type', 'is_finished', 'is_success',
'time_cost', 'date_created', 'actions'
]
},
columnsMeta: {
material: {
width: '500px'
},
id: {
formatter(row) {
return row.id.slice(0, 8)
@@ -44,6 +47,7 @@ export default {
}
},
is_finished: {
width: '100px',
formatter: (row) => {
if (row.is_finished) {
return <i Class='fa fa-check text-primary'/>
@@ -52,6 +56,7 @@ export default {
}
},
is_success: {
width: '100px',
formatter: (row) => {
if (!row.is_finished) {
return <i Class='fa fa fa-spinner fa-spin'/>

View File

@@ -535,7 +535,7 @@ export default {
.empty-file-tip {
position: absolute;
right: 20%;
right: calc(50% - 230px);
top: 50%;
font-size: 18px;
color: #c5c9cc;

View File

@@ -13,6 +13,8 @@ import i18n from '@/i18n/i18n'
import VariableHelpDialog from '@/views/ops/Adhoc/VariableHelpDialog.vue'
import { Required } from '@/components/Form/DataForm/rules'
import { crontab, interval } from '@/views/accounts/const'
import LoadTemplateLink from '@/views/ops/Job/components/loadTemplateLink'
import Variable from '@/views/ops/Template/components/Variable'
export default {
components: {
@@ -27,8 +29,8 @@ export default {
url: '/api/v1/ops/jobs/',
fields: [
[this.$t('Basic'), ['name', 'type', 'instant']],
[this.$t('Task'), ['module', 'args', 'playbook', 'chdir', 'timeout']],
[this.$t('Asset'), ['assets', 'runas', 'runas_policy']],
[this.$t('Asset'), ['assets', 'nodes', 'runas', 'runas_policy']],
[this.$t('Task'), ['module', 'argsLoadFromTemplate', 'args', 'playbook', 'variable', 'chdir', 'timeout', 'parameters']],
[this.$t('Plan'), ['run_after_save', 'is_periodic', 'interval', 'crontab']],
[this.$t('Other'), ['comment']]
],
@@ -88,19 +90,66 @@ export default {
return { label: item.name, value: item.id }
}
}
},
on: {
change: ([event], updateForm) => {
this.queryParam = `playbook=${event.pk}`
this.$axios.get(`/api/v1/ops/playbooks/${event.pk}/`,
).then(data => {
data?.variable.map(item => {
delete item.job
delete item.playbook
delete item.id
return item
})
updateForm({ variable: data.variable })
})
}
}
},
assets: {
type: 'assetSelect',
component: AssetSelect,
label: this.$t('Asset'),
rules: [Required],
el: {
baseUrl: '/api/v1/perms/users/self/assets/',
baseNodeUrl: '/api/v1/perms/users/self/nodes/',
typeUrl: '/api/v1/perms/users/self/nodes/children-with-assets/category/tree',
value: []
}
},
nodes: {
el: {
value: [],
ajax: {
url: '/api/v1/perms/users/self/nodes/',
filterOption: (item) => {
if (item.value !== 'favorite') {
return item
}
},
transformOption: (item) => {
return { label: item.full_value || item.name, value: item.id }
}
}
}
},
argsLoadFromTemplate: {
label: this.$t('Templates'),
hidden: (formValue) => {
return formValue.type !== 'adhoc'
},
component: LoadTemplateLink,
on: {
change: ([event], updateForm) => {
updateForm({
args: event.args,
module: event.module.value,
variable: event.variable
})
}
}
},
args: {
rules: [Required],
hidden: (formValue) => {
@@ -124,6 +173,9 @@ export default {
]
}
},
variable: {
component: Variable
},
timeout: {
helpText: i18n.t('TimeoutHelpText')
},
@@ -141,7 +193,7 @@ export default {
run_after_save: {
type: 'checkbox',
hidden: (formValue) => {
return this.instantTask
return true
}
},
is_periodic: {
@@ -150,11 +202,34 @@ export default {
return this.instantTask
}
},
parameters: {
hidden: () => {
return true
}
},
interval,
crontab
},
createSuccessNextRoute: { name: 'JobManagement' },
updateSuccessNextRoute: { name: 'JobManagement' }
updateSuccessNextRoute: { name: 'JobManagement' },
cleanFormValue: (data) => {
Object.assign(data, { periodic_variable: this.periodicVariableValue })
return data
},
moreButtons: [
{
title: this.$t('ExecuteAfterSaving'),
callback: (value, form, btn) => {
form.value.run_after_save = true
const parameters = form.value.variable.reduce((acc, item) => {
acc[item.var_name] = item.default_value
return acc
}, {})
form.value['parameters'] = parameters
this.submitForm(form, btn)
}
}
]
}
},
mounted() {
@@ -191,7 +266,16 @@ export default {
this.ready = true
}
},
methods: {}
methods: {
submitForm(form, btn) {
form.validate((valid) => {
if (valid) {
btn.loading = true
}
})
this.$refs.form.$refs.createUpdateForm.$refs.form.$refs.dataForm.submitForm('form', false)
}
}
}
</script>

View File

@@ -1,129 +1,16 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
<BaseJob />
</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'
import BaseJob from './BaseJob'
export default {
name: 'Adhoc',
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)
})
}
BaseJob
}
}
</script>

View File

@@ -0,0 +1,167 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
<setVariableDialog
v-if="showVariableDialog"
:form-data="formData"
:query-param="'job=' + item.id"
:visible.sync="showVariableDialog"
@submit="runJobWithParams"
/>
</div>
</template>
<script>
import JobRunDialog from '@/views/ops/Job/JobRunDialog'
import GenericListTable from '@/layout/components/GenericListTable'
import setVariableDialog from '@/views/ops/Template/components/setVariableDialog'
import { openTaskPage } from '@/utils/jms'
import { ActionsFormatter, DateFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
export default {
components: {
GenericListTable,
JobRunDialog,
setVariableDialog
},
props: {
type: {
type: String,
default: 'adhoc'
}
},
data() {
return {
item: {},
tableConfig: {
url: `/api/v1/ops/jobs/?type=${this.type}`,
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',
order: 5,
type: 'primary',
can: this.$hasPerm('ops.add_jobexecution') && !this.$store.getters.currentOrgIsRoot,
callback: ({ row }) => {
this.item = row
if (row?.use_parameter_define && row?.parameters_define) {
const params = JSON.parse(row.parameters_define)
if (Object.keys(params).length > 0) {
this.showJobRunDialog = true
}
} else if (row?.variable?.length) {
this.showVariableDialog = true
} else {
this.runJob(row)
}
}
}
]
}
}
}
},
headerActions: {
createRoute: () => {
return {
name: 'JobCreate',
query: {
type: this.type
}
}
},
hasRefresh: true,
hasExport: false,
hasImport: false
},
showJobRunDialog: false,
showVariableDialog: false
}
},
computed: {
formData() {
return this.item.variable.map((data) => {
return data.form_data
})
}
},
methods: {
runJob(row, parameters) {
this.$axios.post('/api/v1/ops/job-executions/', {
job: row.id,
parameters: parameters
}).then((resp) => {
this.showVariableDialog = false
openTaskPage(resp.task_id)
})
},
runJobWithParams(parameters) {
this.runJob(this.item, parameters)
}
}
}
</script>

View File

@@ -1,136 +1,16 @@
<template>
<div>
<GenericListTable :header-actions="headerActions" :table-config="tableConfig" />
<JobRunDialog v-if="showJobRunDialog" :item="item" :visible.sync="showJobRunDialog" @submit="runJob" />
<BaseJob :type="'playbook'" />
</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'
import BaseJob from './BaseJob'
export default {
name: 'PlayBook',
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)
})
}
BaseJob
}
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div>
<AdhocOpenDialog v-if="showOpenAdhocDialog" :visible.sync="showOpenAdhocDialog" @select="onSelectAdhoc" />
<el-link :underline="false" type="default" @click="onClick()">
{{ $t('LoadTemplate') }}
</el-link>
</div>
</template>
<script>
import AdhocOpenDialog from '@/views/ops/Adhoc/AdhocOpenDialog'
export default {
name: 'LoadTemplateLink',
components: {
AdhocOpenDialog
},
data() {
return {
showOpenAdhocDialog: false
}
},
methods: {
onClick() {
this.showOpenAdhocDialog = true
},
onSelectAdhoc(adhoc) {
adhoc?.variable.map(item => {
delete item.id
delete item.job
delete item.adhoc
return item
})
this.$emit('change', adhoc)
}
}
}
</script>
<style>
</style>

View File

@@ -17,7 +17,7 @@ export default {
url: '/api/v1/ops/adhocs/',
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'module', 'args', 'comment', 'scope', 'date_created', 'actions']
default: ['name', 'module', 'args', 'comment', 'scope', 'date_created', 'actions', 'created_by']
},
columnsMeta: {
name: {
@@ -38,7 +38,8 @@ export default {
canDelete: ({ row }) => {
return this.$hasPerm('ops.delete_adhoc') && row.creator === currentUserID
},
hasClone: false
hasClone: true,
cloneRoute: 'AdhocCreate'
}
}
}

View File

@@ -1,7 +1,7 @@
<template>
<el-row :gutter="20">
<el-col :md="16" :sm="24">
<AutoDetailCard :fields="detailFields" :object="object" :url="url" />
<AutoDetailCard :excludes="excludes" :object="object" :url="url" />
</el-col>
</el-row>
</template>
@@ -21,7 +21,8 @@ export default {
},
data() {
return {
url: `/api/v1/ops/adhocs/${this.object.id}/`
url: `/api/v1/ops/adhocs/${this.object.id}/`,
excludes: ['variable']
}
},
computed: {

View File

@@ -5,6 +5,7 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import CodeEditor from '@/components/Form/FormFields/CodeEditor'
import Variable from '@/views/ops/Template/components/Variable'
export default {
components: {
@@ -14,7 +15,7 @@ export default {
return {
url: '/api/v1/ops/adhocs/',
fields: [
[this.$t('Basic'), ['name', 'module', 'args', 'comment', 'scope']]
[this.$t('Basic'), ['name', 'scope', 'module', 'args', 'variable', 'comment']]
],
initial: {
module: 'shell',
@@ -23,6 +24,9 @@ export default {
fieldsMeta: {
args: {
component: CodeEditor
},
variable: {
component: Variable
}
},
createSuccessNextRoute: {
@@ -30,6 +34,16 @@ export default {
},
updateSuccessNextRoute: {
name: 'Template'
},
cleanFormValue(value) {
const isClone = this?.$route?.query.clone_from !== undefined
if (isClone) {
value?.variable.map((item) => {
delete item.id
delete item.adhoc
})
}
return value
}
}
}

View File

@@ -24,7 +24,7 @@ export default {
url: '/api/v1/ops/playbooks/',
columnsShow: {
min: ['name', 'actions'],
default: ['name', 'comment', 'scope', 'date_created', 'actions']
default: ['name', 'comment', 'scope', 'date_created', 'actions', 'created_by']
},
columnsMeta: {
name: {
@@ -45,7 +45,8 @@ export default {
canDelete: ({ row }) => {
return this.$hasPerm('ops.delete_playbook') && row.creator === currentUserID
},
hasClone: true
hasClone: true,
cloneRoute: 'PlaybookCreate'
}
}
}

View File

@@ -13,13 +13,29 @@ export default {
return {
url: '/api/v1/ops/playbooks/',
fields: [
[this.$t('Basic'), ['name', 'comment', 'scope']]
[this.$t('Basic'), ['name', 'scope', 'comment', 'variable']]
],
fieldsMeta: {
variable: {
hidden: () => true
}
},
createSuccessNextRoute: {
name: 'Template'
},
updateSuccessNextRoute: {
name: 'Template'
},
cleanFormValue(value) {
const isClone = this?.$route?.query.clone_from !== undefined
if (isClone) {
value?.variable.map((item) => {
delete item.id
delete item.playbook
delete item.job
})
}
return value
}
}
}

View File

@@ -6,7 +6,7 @@
:title="$tc('NewFile')"
:visible.sync="iVisible"
top="1vh"
width="20%"
width="40%"
@confirm="onConfirm"
>
<el-form>
@@ -45,7 +45,8 @@ export default {
}
}
},
mounted() {},
mounted() {
},
methods: {
onConfirm() {
this.$emit('confirm', this.name)

View File

@@ -23,7 +23,7 @@ export default {
return {
url: `/api/v1/ops/playbooks/${this.object.id}/`,
excludes: [
'path', 'create_method', 'vcs_url'
'variable', 'path', 'create_method', 'vcs_url'
]
}
},

View File

@@ -34,6 +34,13 @@
</el-tab-pane>
</el-tabs>
<div style="display: flex;margin-top:10px;justify-content: space-between" />
<el-form ref="form" label-position="left" label-width="30px">
<div class="form-content">
<el-form-item label="" prop="variable">
<Variable :value.sync="variables" :disable-edit.sync="disableEdit" @input="setVariable" />
</el-form-item>
</div>
</el-form>
</div>
</template>
</TreeTable>
@@ -46,13 +53,15 @@ import CodeEditor from '@/components/Form/FormFields/CodeEditor'
import item from '@/layout/components/NavLeft/Item'
import NewNodeDialog from '@/views/ops/Template/Playbook/PlaybookDetail/Editor/NewNodeDialog.vue'
import { renameFile } from '@/api/ops'
import Variable from '@/views/ops/Template/components/Variable'
export default {
name: 'CommandExecution',
components: {
NewNodeDialog,
TreeTable,
CodeEditor
CodeEditor,
Variable
},
props: {
object: {
@@ -146,7 +155,8 @@ export default {
},
iShowTree: true,
activeEditorId: '',
openedEditor: {}
openedEditor: {},
variables: []
}
},
computed: {
@@ -167,6 +177,7 @@ export default {
}
},
mounted() {
this.variables = this.object?.variable
this.onOpenEditor({ id: 'main.yml', name: 'main.yml' })
},
methods: {
@@ -286,6 +297,15 @@ export default {
},
hasChange(editor) {
return editor.value !== editor.originValue
},
setVariable(variables) {
if (this.disableEdit) {
return
}
this.$axios.patch(`/api/v1/ops/playbooks/${this.object.id}/`,
{ variable: variables }).catch(err => {
this.$message.error(this.$tc('UpdateErrorMsg') + ' ' + err)
})
}
}
}

View File

@@ -0,0 +1,78 @@
<template>
<Dialog
v-if="iVisible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="$tc('AddVariable')"
:visible.sync="iVisible"
width="800px"
>
<VariableCreateForm
:variable="variable"
@add="addVariable"
@edit="editVariable"
/>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import VariableCreateForm from '@/components/Apps/VariableCreateUpdateForm'
export default {
name: 'AddVariableDialog',
components: {
Dialog,
VariableCreateForm
},
props: {
visible: {
type: Boolean,
default: false
},
variable: {
type: Object,
default: () => ({})
},
variables: {
type: Array,
default: () => ([])
}
},
computed: {
iVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
addVariable(variable) {
const i = this.variables.findIndex(item => item.name === variable.name || item.var_name === variable.var_name)
if (i !== -1) {
this.variables.splice(i, 1)
}
this.variables.push(variable)
this.iVisible = false
},
editVariable(form) {
const i = this.variables.findIndex(item => item.var_name === this.variable.var_name)
this.variables.splice(i, 1, form)
const count = this.variables.filter(value => value.var_name === form.var_name || value.name === form.name).length
// 不允许有相同的变量名
if (count > 1) {
this.variables.splice(i, 1)
}
this.iVisible = false
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,175 @@
<template>
<div>
<div class="variables el-data-table">
<el-table :data="variables" class="el-table--fit el-table--border">
<el-table-column show-overflow-tooltip :label="$tc('Name')" prop="name" />
<el-table-column show-overflow-tooltip :label="$tc('VariableName')" prop="var_name" />
<el-table-column show-overflow-tooltip :label="$tc('DefaultValue')" prop="default_value" />
<el-table-column v-if="!disableEdit" :label="$tc('Actions')" align="center" class-name="buttons" fixed="right" width="135">
<template v-slot="scope">
<el-button icon="el-icon-minus" size="mini" type="danger" @click="removeVariable(scope.row)" />
<el-button
:disabled="!!scope.row.template"
icon="el-icon-edit"
size="mini"
type="primary"
@click="onEditClick(scope.row)"
/>
</template>
</el-table-column>
</el-table>
<div v-if="!disableEdit" class="actions">
<el-button size="mini" type="primary" @click="onAddClick">
{{ $t('Add') }}
</el-button>
</div>
<AddVariableDialog
:variable="variable"
:variables="variables"
:visible.sync="addVariableDialogVisible"
/>
</div>
</div>
</template>
<script>
import AddVariableDialog from './AddVariableDialog'
export default {
name: 'Variable',
components: {
AddVariableDialog
},
props: {
value: {
type: [Array],
default: () => []
},
disableEdit: {
type: Boolean,
default: false
}
},
data() {
return {
variable: {},
initial: false,
addVariableDialogVisible: false
}
},
computed: {
variables: {
get() {
return this.value
},
set(val) {
this.$emit('update:value', val)
this.$emit('change', val)
}
}
},
watch: {
variables: {
handler(value) {
if (value.length > 0 || this.initial) {
value.map((item) => {
item.default_value = item.text_default_value || item.select_default_value
})
this.$emit('input', value)
}
if (value) {
this.initial = true
}
},
immediate: true,
deep: true
}
},
methods: {
removeVariable(variable) {
this.variables = this.variables.filter((item) => {
if (variable.id && item.id) {
return item.id !== variable.id
} else if (variable.var_name && item.var_name) {
return item.var_name !== variable.var_name
} else {
return variable.name !== item.name
}
})
},
onEditClick(variable) {
this.variable = variable
setTimeout(() => {
this.addVariableDialogVisible = true
})
},
onAddClick() {
this.variable = null
setTimeout(() => {
this.addVariableDialogVisible = true
})
}
}
}
</script>
<style lang="scss" scoped>
.el-data-table ::v-deep .el-table {
.table {
margin-top: 15px;
}
.el-table__row {
&.selected-row {
background-color: #f5f7fa;
}
& > td {
line-height: 1.5;
padding: 6px 0;
font-size: 13px;
border-right: none;
* {
vertical-align: middle;
}
.el-checkbox {
vertical-align: super;
}
& > div > span {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
}
.el-table__header > thead > tr > th {
padding: 6px 0;
background-color: #ffffff;
font-size: 13px;
line-height: 1.5;
border-right: none;
.cell {
white-space: nowrap !important;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
border-right: 2px solid #EBEEF5;
}
}
}
}
.el-data-table ::v-deep .el-table .el-table__header > thead > tr .is-sortable {
padding: 5px 0;
.cell {
padding-top: 3px !important;
}
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<Dialog
v-if="iVisible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="$tc('setVariable')"
:visible.sync="iVisible"
width="800px"
>
<VariableSetForm
:form-data="formData"
:query-param="queryParam"
@confirm="handleConfirm"
/>
</Dialog>
</template>
<script>
import Dialog from '@/components/Dialog'
import VariableSetForm from '@/components/Apps/VariableSetForm'
export default {
name: 'SetVariableDialog',
components: {
Dialog,
VariableSetForm
},
props: {
visible: {
type: Boolean,
default: false
},
formData: {
type: Array,
default: () => ([])
},
queryParam: {
type: String,
default: ''
}
},
data() {
return {}
},
computed: {
iVisible: {
get() {
return this.visible
},
set(val) {
this.$emit('update:visible', val)
}
}
},
methods: {
handleConfirm(variable) {
this.$emit('submit', variable)
}
}
}
</script>
<style scoped>
</style>

View File

@@ -11,7 +11,6 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import AssetSelect from '@/components/Apps/AssetSelect'
import { getDayFuture } from '@/utils/time'
import AccountFormatter from './components/AccountFormatter'
import { AllAccount } from '../const'
import ProtocolsSelect from '@/components/Form/FormFields/AllOrSpec.vue'
@@ -32,9 +31,6 @@ export default {
}
return {
initial: {
is_active: true,
date_start: new Date().toISOString(),
date_expired: getDayFuture(25550, new Date()).toISOString(),
nodes: nodesInitial,
assets: assetsInitial,
accounts: [AllAccount]

View File

@@ -37,7 +37,12 @@ export default {
notShowBuiltinTree: true,
url: '/api/v1/perms/asset-permissions/',
nodeUrl: '/api/v1/perms/asset-permissions/',
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1'
treeUrl: '/api/v1/assets/nodes/children/tree/?assets=1',
edit: {
drag: {
isMove: false
}
}
},
tableConfig: {
url: '/api/v1/perms/asset-permissions/',

View File

@@ -5,6 +5,7 @@
<script>
import GenericListPage from '@/layout/components/GenericListPage'
import { download } from '@/utils/common'
import store from '@/store'
export default {
components: {
@@ -36,7 +37,24 @@ export default {
return row.has_file
},
tip: ({ row }) => {
return row.has_file ? this.$t('Download') : this.$t('DownloadFTPFileTip')
const ftpFileMaxStore = store.getters.publicSettings['FTP_FILE_MAX_STORE']
const downloadTip = this.$t('Download')
const fileNotStoredTip = this.$t('FTPFileNotStored')
const storageNotEnabledTip = this.$t('FTPStorageNotEnabled')
const unknownStorageStateTip = this.$t('FTPUnknownStorageState')
if (row.has_file) {
return downloadTip
}
if (ftpFileMaxStore === 0) {
return storageNotEnabledTip
} else if (ftpFileMaxStore > 0) {
return fileNotStoredTip
} else {
return unknownStorageStateTip
}
},
callback: function({ row }) {
// 跳转下载页面

View File

@@ -5,7 +5,7 @@
<script type="text/jsx">
import ListTable from '@/components/Table/ListTable'
import { timeOffset } from '@/utils/time'
import { ActionsFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
import { ActionsFormatter, ChoicesFormatter, DetailFormatter } from '@/components/Table/TableFormatters'
export default {
name: 'BaseList',
@@ -32,13 +32,21 @@ export default {
]
}
}
},
columnsMeta: {
type: Object,
default: () => {}
},
columnsExclude: {
type: Array,
default: () => []
}
},
data() {
return {
tableConfig: {
url: this.url,
columnsExclude: ['terminal'],
columnsExclude: ['terminal', ...this.columnsExclude],
columnsShow: this.columnsShow,
columnsMeta: {
id: {
@@ -119,7 +127,11 @@ export default {
}
},
is_locked: {
label: this.$t('IsLocked')
label: this.$t('IsLocked'),
formatter: ChoicesFormatter,
formatterArgs: {
showFalse: true
}
},
actions: {
prop: 'actions',
@@ -133,7 +145,8 @@ export default {
hasUpdate: false,
extraActions: this.extraActions
}
}
},
...this.columnsMeta
}
},
headerActions: {

View File

@@ -1,5 +1,5 @@
<template>
<BaseList :extra-actions="extraActions" :url="url" />
<BaseList :extra-actions="extraActions" :url="url" :columns-meta="columnsMeta" :columns-exclude="columnsExclude" />
</template>
<script>
@@ -42,7 +42,13 @@ export default {
download(`/api/v1/terminal/sessions/${row.id}/replay/download/`)
}
}
]
],
columnsExclude: ['has_command'],
columnsMeta: {
command_amount: {
label: this.$t('CommandsTotal')
}
}
}
},
methods: {

View File

@@ -1,5 +1,5 @@
<template>
<BaseList :extra-actions="extraActions" :url="url" />
<BaseList :extra-actions="extraActions" :url="url" :columns-meta="columnsMeta" :columns-exclude="columnsExclude" />
</template>
<script>
@@ -100,12 +100,14 @@ export default {
window.open(monitorUrl, '_blank', 'height=600, width=850, top=400, left=400, toolbar=no, menubar=no, scrollbars=no, location=no, status=no')
}
}
]
],
columnsExclude: ['has_command'],
columnsMeta: {
command_amount: {
label: this.$t('CommandsTotal')
}
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -23,6 +23,9 @@ export default {
'AUTH_PASSKEY', 'FIDO_SERVER_ID', 'FIDO_SERVER_NAME'
],
fieldsMeta: {
'FIDO_SERVER_ID': {
placeholder: 'js.example.org'
}
},
submitMethod() {
return 'patch'
@@ -30,8 +33,7 @@ export default {
}
}
},
methods: {
}
methods: {}
}
</script>

View File

@@ -0,0 +1,61 @@
<template>
<BaseKV :config="$data" :title="$tc('AzureKeyVault')" />
</template>
<script>
import BaseKV from './Base.vue'
export default {
name: 'AzureKV',
components: {
BaseKV
},
data() {
const vm = this
return {
url: `/api/v1/settings/setting/?category=azure_kv`,
hasDetailInMsg: false,
visible: false,
moreButtons: [
{
title: this.$t('Test'),
loading: false,
callback: function(value, form, btn) {
btn.loading = true
vm.$axios.post(
'/api/v1/settings/vault/azure/testing/',
value
).then(res => {
vm.$message.success(res['msg'])
}).catch(() => {
vm.$log.error('err occur')
}).finally(() => { btn.loading = false })
}
}
],
encryptedFields: ['VAULT_AZURE_CLIENT_SECRET'],
fields: [
[this.$t('AccountStorage'),
[
'VAULT_AZURE_HOST',
'VAULT_AZURE_CLIENT_ID',
'VAULT_AZURE_CLIENT_SECRET',
'VAULT_AZURE_TENANT_ID'
]
]
],
fieldsMeta: {
},
submitMethod() {
return 'patch'
}
}
},
computed: {},
methods: {}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div>
<el-button size="mini" type="primary" icon="el-icon-setting" @click="visible=true">{{ $t('Setting') }}</el-button>
<Dialog
v-if="visible"
:destroy-on-close="true"
:show-cancel="false"
:show-confirm="false"
:title="title"
:visible.sync="visible"
width="70%"
@confirm="onConfirm()"
v-on="$listeners"
>
<GenericCreateUpdateForm ref="form" v-bind="iConfig" @submitSuccess="submitSuccess" />
</Dialog>
</div>
</template>
<script>
import Dialog from '@/components/Dialog/index.vue'
import GenericCreateUpdateForm from '@/layout/components/GenericCreateUpdateForm/index.vue'
export default {
name: 'Base',
components: {
Dialog,
GenericCreateUpdateForm
},
props: {
title: {
type: String,
default: ''
},
config: {
type: Object,
default: () => ({})
}
},
data() {
return {
visible: false
}
},
computed: {
iConfig() {
return this.config
}
},
methods: {
onConfirm() {
},
submitSuccess(res) {
this.$emit('input', !!res[this.enableField])
this.visible = false
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,64 @@
<template>
<BaseKV :config="$data" :title="$tc('HashicorpVault')" />
</template>
<script>
import BaseKV from './Base.vue'
export default {
name: 'HashiCorpKV',
components: {
BaseKV
},
data() {
const vm = this
return {
url: `/api/v1/settings/setting/?category=hcp`,
hasDetailInMsg: false,
visible: false,
moreButtons: [
{
title: this.$t('Test'),
loading: false,
callback: function(value, form, btn) {
btn.loading = true
vm.$axios.post(
'/api/v1/settings/vault/hcp/testing/',
value
).then(res => {
vm.$message.success(res['msg'])
}).catch(() => {
vm.$log.error('err occur')
}).finally(() => { btn.loading = false })
}
}
],
encryptedFields: ['VAULT_HCP_TOKEN'],
fields: [
[this.$t('AccountStorage'),
[
'VAULT_HCP_HOST',
'VAULT_HCP_TOKEN',
'VAULT_HCP_MOUNT_POINT'
]
]
],
fieldsMeta: {
VAULT_HCP_MOUNT_POINT: {
helpText: this.$t('VaultHCPMountPoint'),
helpTextAsTip: true
}
},
submitMethod() {
return 'patch'
}
}
},
computed: {},
methods: {}
}
</script>
<style scoped>
</style>

View File

@@ -12,6 +12,8 @@ import { GenericCreateUpdateForm } from '@/layout/components'
import IBox from '@/components/IBox/index.vue'
import { openTaskPage } from '@/utils/jms'
import store from '@/store'
import HashiCorpKV from './HCP.vue'
import AzureKV from './Azure.vue'
export default {
components: {
@@ -25,24 +27,6 @@ export default {
hasReset: false,
helpText: this.$t('VaultHelpText'),
moreButtons: [
{
title: this.$t('Test'),
loading: false,
disabled: !store.getters.publicSettings['VAULT_ENABLED'],
callback: function(value, form, btn) {
btn.loading = true
vm.$axios.post(
'/api/v1/settings/vault/testing/',
value
).then(res => {
vm.$message.success(res['msg'])
}).catch(() => {
vm.$log.error('err occur')
}).finally(() => {
btn.loading = false
})
}
},
{
title: this.$t('Sync'),
loading: false,
@@ -62,17 +46,9 @@ export default {
}
}
],
encryptedFields: ['VAULT_HCP_TOKEN'],
fields: [
[this.$t('Backend'),
[
'VAULT_ENABLED',
'VAULT_HCP_HOST',
'VAULT_HCP_TOKEN',
'VAULT_HCP_MOUNT_POINT'
]
],
[this.$t('History'), ['HISTORY_ACCOUNT_CLEAN_LIMIT']]
[this.$t('Basic'), ['VAULT_ENABLED', 'VAULT_BACKEND', 'HISTORY_ACCOUNT_CLEAN_LIMIT']],
[this.$t('Provider'), ['HCP', 'AZURE']]
],
fieldsMeta: {
HISTORY_ACCOUNT_CLEAN_LIMIT: {
@@ -83,21 +59,25 @@ export default {
VAULT_ENABLED: {
disabled: true
},
VAULT_HCP_HOST: {
VAULT_BACKEND: {
label: this.$t('Provider'),
hidden: (formValue) => {
return !formValue.VAULT_ENABLED
return !formValue.VAULT_ENABLED || formValue['VAULT_BACKEND'] === 'local'
},
disabled: true
},
HCP: {
label: this.$t('HashicorpVault'),
component: HashiCorpKV,
hidden: (formValue) => {
return !formValue.VAULT_ENABLED || formValue['VAULT_BACKEND'] !== 'hcp'
}
},
VAULT_HCP_TOKEN: {
AZURE: {
label: this.$t('AzureKeyVault'),
component: AzureKV,
hidden: (formValue) => {
return !formValue.VAULT_ENABLED
}
},
VAULT_HCP_MOUNT_POINT: {
helpText: this.$t('VaultHCPMountPoint'),
helpTextAsTip: true,
hidden: (formValue) => {
return !formValue.VAULT_ENABLED
return !formValue.VAULT_ENABLED || formValue['VAULT_BACKEND'] !== 'azure'
}
}
},

View File

@@ -9,7 +9,7 @@
<script>
import TabPage from '@/layout/components/TabPage/index.vue'
import Announcement from './Announcement.vue'
import Vault from './Vault.vue'
import Vault from './Vault/index.vue'
import Ticket from './Ticket.vue'
import Ops from './Ops.vue'
import Chat from './Chat.vue'

View File

@@ -114,8 +114,8 @@ export default {
}
return [
{
key: this.$t('SubscriptionID'),
value: this.licenseData.subscription_id
key: this.$t('SerialNumber'),
value: this.licenseData?.serial_no || ''
},
{
key: this.$t('Corporation'),
@@ -133,10 +133,6 @@ export default {
key: this.$t('Edition'),
value: this.licenseData.edition
},
{
key: this.$t('SerialNumber'),
value: this.licenseData?.serial_no || ''
},
{
key: this.$t('Comment'),
value: this.licenseData?.remark || ''

View File

@@ -26,11 +26,14 @@
</span>
</template>
</el-table-column>
<el-table-column :label="$tc('Receivers')" show-overflow-tooltip>
<template v-slot="scope">
<span v-if="!scope.row.children">
{{ scope.row.receivers.map(item => item.name).join(', ') }}
</span>
<el-table-column :label="$tc('Receivers')">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top" popper-class="black-theme-popover">
<p v-for="item in scope.row.receivers" :key="item.name">{{ item.name }}</p>
<span v-if="!scope.row.children" slot="reference" class="name-wrapper">
{{ scope.row.receivers.map(item => item.name).join(', ') }}
</span>
</el-popover>
</template>
</el-table-column>
<el-table-column :label="$tc('Actions')" width="200">
@@ -186,4 +189,25 @@ export default {
margin-bottom: 0;
width: 50%;
}
::v-deep .el-table .cell {
display: flex;
.name-wrapper {
display: inline-block;
max-height: 55px;
max-width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
cursor: pointer;
}
}
::v-deep .black-theme-popover .el-popover__inner {
background-color: #000 !important;
color: #fff !important;
border-color: #000 !important;
}
</style>

View File

@@ -23,7 +23,7 @@ export default {
[
this.$t('Port'),
[
'http_port', 'https_port', 'ssh_port', 'rdp_port'
'http_port', 'https_port', 'ssh_port', 'rdp_port','vnc_port'
]
],
[
@@ -33,7 +33,7 @@ export default {
'redis_port', 'sqlserver_port', 'oracle_port_range'
]
],
[this.$t('Other'), ['comment']]
[this.$t('Other'), ['is_active', 'comment']]
],
fieldsMeta: {
host: {
@@ -41,13 +41,17 @@ export default {
},
oracle_port_range: {
disabled: true
},
is_active: {
disabled: this.$route.params.id === '00000000-0000-0000-0000-000000000001'
}
},
hasDetailInMsg: false
}
},
computed: {},
created() {},
created() {
},
methods: {}
}
</script>

View File

@@ -23,9 +23,9 @@ export default {
min: ['name', 'actions'],
default: [
'name', 'host', 'actions',
'http_port', 'https_port', 'ssh_port', 'rdp_port',
'http_port', 'https_port', 'ssh_port', 'rdp_port', 'vnc_port',
'mysql_port', 'mariadb_port', 'postgresql_port',
'redis_port', 'sqlserver_port', 'oracle_port_range'
'redis_port', 'sqlserver_port', 'oracle_port_range', 'is_active'
]
},
columnsMeta: {

View File

@@ -25,7 +25,7 @@
<el-form-item style="float: right">
<template v-if="hasActionPerm">
<el-button
:disabled="object.status.value === 'closed'"
:disabled="isDisabled || object.status.value === 'closed'"
size="small"
type="primary"
@click="handleApprove"
@@ -33,7 +33,7 @@
<i class="fa fa-check" /> {{ $t('Accept') }}
</el-button>
<el-button
:disabled="object.status.value === 'closed'"
:disabled="isDisabled || object.status.value === 'closed'"
size="small"
type="warning"
@click="handleReject"
@@ -43,7 +43,7 @@
</template>
<el-button
v-if="isSelfTicket"
:disabled="object.status.value === 'closed'"
:disabled="isDisabled || object.status.value === 'closed'"
size="small"
type="danger"
@click="handleClose"
@@ -92,6 +92,7 @@ export default {
},
data() {
return {
isDisabled: false,
comments: '',
type_api: '',
imageUrl: require('@/assets/img/avatar.png'),
@@ -154,17 +155,35 @@ export default {
this.createComment(function() {
})
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/approve/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
this.$axios.put(url).then(res => {
this.reloadPage()
}).catch(err => {
this.$message.error(err)
}).finally(() => {
this.isDisabled = false
})
},
defaultReject() {
this.createComment(function() {
})
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/reject/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
this.$axios.put(url).then(res => {
this.reloadPage()
}).catch(err => {
this.$message.error(err)
}).finally(() => {
this.isDisabled = false
})
},
defaultClose() {
const url = `/api/v1/tickets/${this.type_api}/${this.object.id}/close/`
this.$axios.put(url).then(res => this.reloadPage()).catch(err => this.$message.error(err))
this.$axios.put(url).then(res => {
this.reloadPage()
}).catch(err => {
this.$message.error(err)
}).finally(() => {
this.isDisabled = false
})
},
createComment(successCallback) {
const commentText = this.form.comments
@@ -185,17 +204,42 @@ export default {
}
})
},
handleAction(actionType) {
if (this.isDisabled) {
return
}
this.isDisabled = true
let handler
switch (actionType) {
case 'approve':
handler = this.approve || this.defaultApprove
break
case 'reject':
handler = this.reject || this.defaultReject
break
case 'close':
handler = this.close || this.defaultClose
break
default:
handler = null
break
}
if (handler) {
handler()
} else {
this.$message.error('No handler for action')
}
},
handleApprove() {
const handler = this.approve || this.defaultApprove
handler()
this.handleAction('approve')
},
handleReject() {
const handler = this.reject || this.defaultReject
handler()
this.handleAction('reject')
},
handleClose() {
const handler = this.close || this.defaultClose
handler()
this.handleAction('close')
},
handleComment() {
this.createComment(

View File

@@ -4,7 +4,6 @@
<script>
import { GenericCreateUpdatePage } from '@/layout/components'
import TransSelect from '@/components/Form/FormFields/TransferSelect.vue'
export default {
components: {
@@ -23,7 +22,6 @@ export default {
],
fieldsMeta: {
users: {
component: TransSelect,
el: {
url: '/api/v1/users/users/?fields_size=mini&order=name',
ajax: {

View File

@@ -9,6 +9,7 @@ import rules from '@/components/Form/DataForm/rules'
import { mapGetters } from 'vuex'
import { Select2 } from '@/components'
import store from '@/store'
import { MFASystemSetting, MFALevel } from '../const'
export default {
components: {
@@ -44,10 +45,7 @@ export default {
}
},
mfa_level: {
disabled: false,
tips: {
2: this.$t('forceEnableMFAHelpText')
}
disabled: false
},
email: {
rules: [
@@ -176,6 +174,7 @@ export default {
if (obj?.id) {
obj.org_roles = obj.org_roles?.map(({ id }) => id)
obj.system_roles = obj.system_roles?.map(({ id }) => id)
obj.mfa_level.value = this.initial.mfa_level || obj.mfa_level.value
}
return obj
},
@@ -193,6 +192,9 @@ export default {
if (value.source !== 'local') {
delete value.need_update_password
}
if ([MFALevel.allUsers, MFALevel.onlyAdminUsers].indexOf(value.mfa_level) > -1) {
delete value.mfa_level
}
return value
}
}
@@ -231,11 +233,26 @@ export default {
this.initial.org_roles = roles.filter(role => role.name === 'OrgUser').map(role => role.id)
},
disableMFAFieldIfNeed(user) {
let options = null
let mfa_level = null
// SECURITY_MFA_AUTH 0 不开启 1 全局开启 2 管理员开启
const securityMFAAuth = store.getters.publicSettings['SECURITY_MFA_AUTH']
const adminUserIsNeed = (user?.is_superuser || user?.is_org_admin) && this.$route.meta.action === 'update' &&
store.getters.publicSettings['SECURITY_MFA_AUTH'] === 2
if (store.getters.publicSettings['SECURITY_MFA_AUTH'] === 1 || adminUserIsNeed) {
this.fieldsMeta['mfa_level'].disabled = true
securityMFAAuth === MFASystemSetting.onlyAdminUsers
if (securityMFAAuth === MFASystemSetting.allUsers) {
options = [{ 'value': MFALevel.allUsers, 'label': this.$t('MFAAllUsers') }]
mfa_level = MFALevel.allUsers
}
if (securityMFAAuth === MFASystemSetting.onlyAdminUsers && adminUserIsNeed) {
options = [{
'value': MFALevel.onlyAdminUsers,
'label': this.$t('MFAOnlyAdminUsers')
}]
mfa_level = MFALevel.onlyAdminUsers
}
if (mfa_level !== null && options !== null) {
this.fieldsMeta['mfa_level'].options = options
this.initial.mfa_level = mfa_level
}
}
}

View File

@@ -19,6 +19,7 @@ import AssetPermissionAsset from '@/views/perms/AssetPermission/AssetPermissionD
import AssetPermissionDetail from '@/views/perms/AssetPermission/AssetPermissionDetail/index.vue'
import AssetPermissionAccount from '@/views/perms/AssetPermission/AssetPermissionDetail/AssetPermissionAccount.vue'
import UserAssetPermissionRules from './UserAssetPermissionRules'
import store from '@/store'
export default {
components: {
@@ -63,7 +64,7 @@ export default {
{
title: this.$t('UserAclLists'),
name: 'UserLoginAcl',
hidden: () => !vm.$hasPerm('acls.view_loginacl')
hidden: () => !vm.$hasPerm('acls.view_loginacl') || !store.getters.publicSettings.XPACK_LICENSE_IS_VALID
},
{
title: this.$t('UserSession'),

View File

@@ -23,6 +23,8 @@ import { createSourceIdCache } from '@/api/common'
import { getDayFuture } from '@/utils/time'
import InviteUsersDialog from './components/InviteUsersDialog'
import AmountFormatter from '@/components/Table/TableFormatters/AmountFormatter.vue'
import store from '@/store'
import { MFASystemSetting } from '../const'
export default {
components: {
@@ -65,7 +67,17 @@ export default {
}
},
mfa_level: {
width: '130px'
width: '130px',
formatter: (row) => {
const securityMFAAuth = store.getters.publicSettings['SECURITY_MFA_AUTH']
if (securityMFAAuth === MFASystemSetting.allUsers) {
return this.$t('MFAAllUsers')
} else if (securityMFAAuth === MFASystemSetting.onlyAdminUsers && (row?.is_superuser || row?.is_org_admin)) {
return this.$t('MFAOnlyAdminUsers')
} else {
return row['mfa_level'].label
}
}
},
source: {
width: '120px',

View File

@@ -100,3 +100,13 @@ export const userJSONSelectMeta = (vm, withoutOrgRole = false) => {
}
}
}
export const MFALevel = {
allUsers: 3,
onlyAdminUsers: 4
}
export const MFASystemSetting = {
notEnable: 0,
allUsers: 1,
onlyAdminUsers: 2
}

View File

@@ -15,6 +15,8 @@
import GrantedAssets from '@/components/Apps/GrantedAssets/index.vue'
import Page from '@/layout/components/Page/index.vue'
import { EditableInputFormatter } from '@/components/Table/TableFormatters'
import { getPreference } from '@/api/settings'
import { openNewWindow } from '@/utils/common'
export default {
components: {
@@ -25,6 +27,7 @@ export default {
return {
treeUrl: `/api/v1/perms/users/self/nodes/children/tree/`,
tableUrl: `/api/v1/perms/users/self/assets/`,
preference: {},
actions: {
width: '88px',
align: 'center',
@@ -41,8 +44,13 @@ export default {
can: ({ row }) => row.is_active,
callback: ({ row }) => {
const oid = this.$store.getters.currentOrg ? this.$store.getters.currentOrg.id : ''
const url = `/luna/?login_to=${row.id}${oid ? `&oid=${oid}` : ''}`
window.open(url, '_blank')
const url = `/luna/connect?login_to=${row.id}`
if (this.preference?.basic?.connect_default_open_method === 'new') {
openNewWindow(url)
} else {
const url = `/luna/?login_to=${row.id}${oid ? `&oid=${oid}` : ''}`
window.open(url, '_blank')
}
}
},
{
@@ -81,6 +89,9 @@ export default {
},
mounted() {
this.refreshAllFavorites()
this.preference = getPreference().then((resp) => {
this.preference = resp
})
},
methods: {
refreshAllFavorites() {

View File

@@ -4,6 +4,8 @@
<script>
import HomeCard from './HomeCard.vue'
import { getPreference } from '@/api/settings'
import { openNewWindow } from '@/utils/common'
export default {
name: 'Announcement',
@@ -13,6 +15,7 @@ export default {
data() {
const vm = this
return {
preference: {},
cardConfig: {
title: this.$t('RecentSession')
},
@@ -71,7 +74,11 @@ export default {
type: 'primary',
can: ({ row }) => row.is_active,
callback: ({ row }) => {
window.open(`/luna/?login_to=${row.asset_id}&login_account=${row.account_id}`, '_blank')
if (this.preference?.basic?.connect_default_open_method === 'new') {
openNewWindow(`/luna/connect?login_to=${row.asset_id}&login_account=${row.account_id}`)
} else {
window.open(`/luna/?login_to=${row.asset_id}&login_account=${row.account_id}`, '_blank')
}
}
}
]
@@ -82,6 +89,11 @@ export default {
paginationSize: 10
}
}
},
mounted() {
getPreference().then(resp => {
this.preference = resp
})
}
}
</script>

View File

@@ -74,6 +74,11 @@ module.exports = {
target: 'http://127.0.0.1:4200',
changeOrigin: true
},
'/facelive/': {
target: 'http://localhost:9999',
changeOrigin: true,
ws: true
},
'^/(core|static|media)/': {
target: process.env.VUE_APP_CORE_HOST || 'http://127.0.0.1:8080',
changeOrigin: true
@@ -81,8 +86,7 @@ module.exports = {
},
after: require('./mock/mock-server.js')
},
css: {
},
css: {},
configureWebpack: {
// provide the app's title in webpack's name field, so that
// it can be accessed in index.html to inject the correct title.
@@ -90,7 +94,8 @@ module.exports = {
resolve: {
alias: {
'@': resolve('src'),
elementCss: resolve('node_modules/element-ui/lib/theme-chalk/index.css'),
elementCss: resolve(
'node_modules/element-ui/lib/theme-chalk/index.css'),
elementLocale: resolve('node_modules/element-ui/lib/locale/lang/en.js')
},
extensions: ['.vue', '.js', '.json']
@@ -148,7 +153,7 @@ module.exports = {
.end()
config
// https://webpack.js.org/configuration/devtool/#development
// https://webpack.js.org/configuration/devtool/#development
.when(process.env.NODE_ENV === 'development',
config => config.devtool('cheap-source-map')
)
@@ -159,10 +164,11 @@ module.exports = {
config
.plugin('ScriptExtHtmlWebpackPlugin')
.after('html')
.use('script-ext-html-webpack-plugin', [{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.use('script-ext-html-webpack-plugin', [
{
// `runtime` must same as runtimeChunk name. default is `runtime`
inline: /runtime\..*\.js$/
}])
.end()
config
.optimization.splitChunks({

View File

@@ -9227,7 +9227,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
resolved "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz"
integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.2, npm-package-arg@^8.1.4, npm-package-arg@^8.1.5:
npm-package-arg@^8.0.0, npm-package-arg@^8.0.1, npm-package-arg@^8.1.0, npm-package-arg@^8.1.1, npm-package-arg@^8.1.2, npm-package-arg@^8.1.5:
version "8.1.5"
resolved "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz"
integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==
@@ -9733,7 +9733,7 @@ p-try@^2.0.0:
resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
pacote@^11.1.11, pacote@^11.2.6, pacote@^11.3.1, pacote@^11.3.4, pacote@^11.3.5:
pacote@^11.1.11, pacote@^11.2.6, pacote@^11.3.0, pacote@^11.3.1, pacote@^11.3.5:
version "11.3.5"
resolved "https://registry.npmjs.org/pacote/-/pacote-11.3.5.tgz"
integrity sha512-fT375Yczn4zi+6Hkk2TBe1x1sP8FgFsEIZ2/iWaXY2r/NkhDJfxbcn5paz1+RTFCyNf+dPnaoBDJoAxXSU8Bkg==
@@ -9931,10 +9931,10 @@ path-to-regexp@0.1.7:
resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
path-to-regexp@2.4.0:
version "2.4.0"
resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz"
integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==
path-to-regexp@3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b"
integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==
path-type@^1.0.0:
version "1.1.0"
@@ -11538,7 +11538,7 @@ serve-index@^1.9.1:
mime-types "~2.1.17"
parseurl "~1.3.2"
serve-static@1.15.0, serve-static@^1.13.2:
serve-static@1.15.0:
version "1.15.0"
resolved "https://registry.npmmirror.com/serve-static/-/serve-static-1.15.0.tgz"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
@@ -11548,6 +11548,16 @@ serve-static@1.15.0, serve-static@^1.13.2:
parseurl "~1.3.3"
send "0.18.0"
serve-static@^1.16.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"
parseurl "~1.3.3"
send "0.18.0"
set-blocking@^2.0.0, set-blocking@~2.0.0:
version "2.0.0"
resolved "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz"