Compare commits

...

247 Commits

Author SHA1 Message Date
Yumao
d59aa5392c
Merge pull request #1567 from MCSManager/dependabot/npm_and_yarn/frontend/vite-4.5.13
chore(deps-dev): bump vite from 4.5.10 to 4.5.13 in /frontend
2025-04-15 11:17:28 +08:00
Yumao
fe7992c698
Merge pull request #1561 from MCSManager/dependabot/npm_and_yarn/frontend/axios-1.8.2
chore(deps): bump axios from 1.7.4 to 1.8.2 in /frontend
2025-04-15 11:17:11 +08:00
Yumao
c614b59445
Merge pull request #1559 from MCSManager/dependabot/npm_and_yarn/daemon/koa-2.16.1
chore(deps): bump koa from 2.15.4 to 2.16.1 in /daemon
2025-04-15 11:16:57 +08:00
dependabot[bot]
079f046d03
chore(deps-dev): bump vite from 4.5.10 to 4.5.13 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.10 to 4.5.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 4.5.13
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-14 09:19:17 +00:00
YuMao
6c039af16f chore: i18n text 2025-04-14 17:13:18 +08:00
YuMao
cb767de61f chore: sort language keys 2025-04-14 14:26:11 +08:00
YuMao
1bd34c70e7 chore: sort lang script 2025-04-14 14:24:18 +08:00
YuMao
4d44129424 chore: i18n text 2025-04-14 11:47:17 +08:00
Yumao
660c42e4d7
Merge pull request #1558 from MCSManager/dependabot/npm_and_yarn/daemon/multi-b29f0fd473
chore(deps): bump tar-fs and dockerode in /daemon
2025-04-11 10:43:09 +08:00
dependabot[bot]
23e5eded55
chore(deps): bump axios from 1.7.4 to 1.8.2 in /frontend
Bumps [axios](https://github.com/axios/axios) from 1.7.4 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.4...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.8.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 14:47:43 +00:00
dependabot[bot]
39ac65a425
chore(deps): bump koa from 2.15.4 to 2.16.1 in /daemon
Bumps [koa](https://github.com/koajs/koa) from 2.15.4 to 2.16.1.
- [Release notes](https://github.com/koajs/koa/releases)
- [Changelog](https://github.com/koajs/koa/blob/master/History.md)
- [Commits](https://github.com/koajs/koa/compare/2.15.4...v2.16.1)

---
updated-dependencies:
- dependency-name: koa
  dependency-version: 2.16.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 13:48:13 +00:00
dependabot[bot]
bace05284b
chore(deps): bump tar-fs and dockerode in /daemon
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) to 2.1.2 and updates ancestor dependency [dockerode](https://github.com/apocas/dockerode). These dependencies need to be updated together.


Updates `tar-fs` from 2.0.1 to 2.1.2
- [Commits](https://github.com/mafintosh/tar-fs/compare/v2.0.1...v2.1.2)

Updates `dockerode` from 3.1.0 to 4.0.5
- [Release notes](https://github.com/apocas/dockerode/releases)
- [Commits](https://github.com/apocas/dockerode/compare/v3.1.0...v4.0.5)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.2
  dependency-type: indirect
- dependency-name: dockerode
  dependency-version: 4.0.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 03:29:09 +00:00
YuMao
dee299bcf8 feat: change docker default workdir = true 2025-04-09 11:15:16 +08:00
Yumao
e17fb9e2e5
Merge pull request #1551 from MCSManager/dependabot/npm_and_yarn/panel/axios-1.8.2
chore(deps): bump axios from 1.7.4 to 1.8.2 in /panel
2025-03-31 14:09:39 +08:00
Yumao
5a002fc504
Merge pull request #1552 from MCSManager/dependabot/npm_and_yarn/frontend/babel/runtime-7.27.0
chore(deps): bump @babel/runtime from 7.22.6 to 7.27.0 in /frontend
2025-03-31 14:09:29 +08:00
Yumao
52888d25ff
Merge pull request #1553 from MCSManager/dependabot/npm_and_yarn/frontend/vite-4.5.10
chore(deps-dev): bump vite from 4.5.9 to 4.5.10 in /frontend
2025-03-31 14:09:18 +08:00
Yumao
4d78bb756a
Merge pull request #1554 from MCSManager/dependabot/npm_and_yarn/daemon/babel/runtime-7.27.0
chore(deps): bump @babel/runtime from 7.18.9 to 7.27.0 in /daemon
2025-03-31 14:09:08 +08:00
Yumao
588beda158
Merge pull request #1555 from zyx006/master
Update zh_CN/TW.json for (Neo)Forge configs
2025-03-27 10:49:51 +08:00
zyx007
572a91c247 Update zh_CN/TW.json for (Neo)Forge configs 2025-03-26 14:47:02 +08:00
dependabot[bot]
bb0632fc1a
chore(deps): bump @babel/runtime from 7.18.9 to 7.27.0 in /daemon
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.18.9 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 03:10:27 +00:00
dependabot[bot]
32968947ae
chore(deps-dev): bump vite from 4.5.9 to 4.5.10 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.9 to 4.5.10.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.10/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.10/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 03:10:26 +00:00
dependabot[bot]
fcd1808b73
chore(deps): bump @babel/runtime from 7.22.6 to 7.27.0 in /frontend
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.22.6 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 03:10:20 +00:00
dependabot[bot]
245669f8b8
chore(deps): bump axios from 1.7.4 to 1.8.2 in /panel
Bumps [axios](https://github.com/axios/axios) from 1.7.4 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.4...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 03:10:18 +00:00
Yumao
2055a1dd85
Merge pull request #1542 from SkyKingPX/master
feat: Add Forge Config, NeoForge Tag and NeoForge Configs & fix German "MC Bedrock" Translation
2025-03-26 11:09:17 +08:00
Antony Blem
d0a65fe1a1 Revert ch_CN changes 2025-03-25 13:39:43 +01:00
Antony
ff17c03894
feat: Add Forge Config, NeoForge Tag and NeoForge Configs, fix German "MC Bedrock" Translation 2025-03-18 08:10:40 +00:00
Yumao
ac3c30939a
Merge pull request #1541 from MCSManager/OvQ
fix: check processType
2025-03-15 16:49:57 +08:00
Lazy
703a951ffb fix: check processType 2025-03-14 22:29:30 -07:00
Yumao
09a8885d68
Merge pull request #1538 from MCSManager/OvQ
fix: update instance config
2025-03-14 17:36:51 +08:00
Yumao
d00d97cf97
Merge pull request #1540 from MCSManager/dependabot/npm_and_yarn/panel/babel/runtime-7.26.10
chore(deps): bump @babel/runtime from 7.20.7 to 7.26.10 in /panel
2025-03-14 17:35:18 +08:00
Yumao
e875650842
Merge pull request #1539 from MCSManager/dependabot/npm_and_yarn/daemon/axios-1.8.2
chore(deps): bump axios from 1.7.4 to 1.8.2 in /daemon
2025-03-14 17:35:08 +08:00
dependabot[bot]
903c1ae415
chore(deps): bump @babel/runtime from 7.20.7 to 7.26.10 in /panel
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.20.7 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-14 09:12:37 +00:00
dependabot[bot]
05accb3a91
chore(deps): bump axios from 1.7.4 to 1.8.2 in /daemon
Bumps [axios](https://github.com/axios/axios) from 1.7.4 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.4...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-14 09:12:35 +00:00
YuMao
7d86d1129c chore: optimize vite config 2025-03-14 17:11:24 +08:00
Lazy
77fd7876cc fix: update instance config 2025-03-14 01:41:38 -07:00
Yumao
3175897a6f
Merge pull request #1537 from MCSManager/OvQ
Card iframe offside
2025-03-13 00:44:56 +08:00
Lazy
2e7bf36de8 fix: card iframe offside 2025-03-12 22:00:55 +08:00
Lazy
2ff0f7e29c fix: card iframe offside 2025-03-12 20:54:40 +08:00
Lazy
6962066605 fix: btn text color 2025-03-12 20:53:52 +08:00
YuMao
e57cd413ce Merge branch 'master' of github.com:MCSManager/MCSManager 2025-03-10 17:39:55 +08:00
YuMao
176c8ad02e fix: https://github.com/MCSManager/MCSManager/issues/1533 2025-03-10 17:39:50 +08:00
Yumao
a22621a436
Merge pull request #1531 from grinheckerdev/master
Better Russian translation.
2025-03-10 10:56:18 +08:00
grinheckerdev
186a8fe134 Fix russian translation. 2025-03-07 12:38:13 +03:00
Yumao
3579fe529f
Merge pull request #1527 from MCSManager/OvQ
feat: edit docker env
2025-03-07 12:02:01 +08:00
Yumao
311455c532
Merge pull request #1528 from zyx006/master
Refine zh_CN/TW.json and update bat
2025-03-06 11:07:02 +08:00
zyx007
38744a40ef Refine zh_CN/TW.json and update bat 2025-03-05 23:16:17 +08:00
Yumao
921c01dd2c
Merge pull request #1526 from MCSManager/dev
fix: https://github.com/MCSManager/MCSManager/issues/1523
2025-03-05 16:56:39 +08:00
Lazy
0dd75ac7a7 opt: check instance advanced params 2025-03-05 16:39:29 +08:00
Lazy
96ecc0ce77 feat: edit docker env 2025-03-04 23:09:16 +08:00
YuMao
1e3b99d5c6 fix: https://github.com/MCSManager/MCSManager/issues/1523#issuecomment-2694123018 2025-03-04 11:54:37 +08:00
Yumao
e61f5b3a6e
Merge pull request #1525 from MCSManager/dev
Chore: 第三方翻译内容
2025-03-04 11:12:28 +08:00
Yumao
d801a46e1c
Merge pull request #1521 from WT2024/master
Update version number.
2025-02-28 10:34:15 +08:00
吴桐
3fea34745a Update version number. 2025-02-27 21:28:26 +08:00
Yumao
f757a27738
Merge pull request #1516 from XiaoXinYo/master
Correct:Effective Apache 2
2025-02-26 11:13:11 +08:00
Yang KaiQi
83dbf56ace
Correct:Effective Apache 2 2025-02-26 09:39:39 +08:00
Yumao
ed9af31a97
Merge pull request #1513 from zyx006/master
Update zh_CN.json、zh_TW.json
2025-02-25 11:46:22 +08:00
zyx007
ef818a876d 调整zh_CN与zh_TW的键值对顺序使其与en_US一致,对zh_CN进行了部分精校,对zh_TW补充了大量繁中翻译后文本键值对 2025-02-24 23:26:29 +08:00
Yumao
8982aebc83
Merge pull request #1511 from KenRouKoro/master
Update zh_CN.json
2025-02-24 17:21:05 +08:00
姚凯翔
91aa370a2d
Update zh_CN.json
更新中文翻译,待精校
2025-02-24 17:15:20 +08:00
YuMao
165b337584 chore: update version 2025-02-24 16:40:52 +08:00
YuMao
aad44fa7cb chore: i18n text 2025-02-24 14:42:34 +08:00
Yumao
73ad4a79a5
Merge pull request #1501 from MCSManager/absurd
chore: i18n
2025-02-20 14:08:55 +08:00
Yumao
c96601b8a8
Merge pull request #1502 from ChunghwaMC/master
追隨更新
2025-02-19 11:17:34 +08:00
ChunghwaMC
d6fab115c9
追隨更新 2025-02-18 20:23:18 +08:00
Lazy
2e4b1a979c chore: i18n 2025-02-18 16:27:21 +08:00
YuMao
42e6019427 chore: i18n 2025-02-18 15:58:53 +08:00
Yumao
77316b25ce
Merge pull request #1499 from MCSManager/nya
feat: warning about daemon using local network ip
2025-02-18 15:56:56 +08:00
Yumao
ec69be678f
Merge pull request #1498 from MCSManager/absurd
refactor: copy btn
2025-02-18 10:46:02 +08:00
Yumao
8d68116d97
Merge pull request #1500 from ChunghwaMC/master
追隨更新
2025-02-18 10:45:24 +08:00
ChunghwaMC
483ee5e6b4
追隨更新 2025-02-17 20:12:06 +08:00
alongw
f99cab3f0a
feat: warning about daemon using local network ip 2025-02-17 19:57:00 +08:00
Lazy
c1285cdb10 opt: show details 2025-02-17 17:08:48 +08:00
Lazy
02d986d25e refactor: copy btn 2025-02-17 16:56:56 +08:00
Yumao
1fcef8866d
Merge pull request #1494 from MCSManager/absurd
feat: edit cmd settings
2025-02-14 16:30:17 +08:00
Lazy
e4abb18302 refactor: rename var 2025-02-14 16:10:48 +08:00
Lazy
57cc3ddd4f feat: edit cmd settings 2025-02-14 12:33:52 +08:00
Yumao
8fc6cec023
Merge pull request #1493 from MCSManager/absurd
feat: 普通用户编辑 启动&更新 命令
2025-02-14 11:16:11 +08:00
Lazy
b5aae32f08 delete: unused apis 2025-02-14 00:19:48 +08:00
Lazy
27c0a9b75c fix: i18n 2025-02-14 00:16:10 +08:00
Lazy
e0cdf74da3 feat: instance fundamental details 2025-02-14 00:03:45 +08:00
Lazy
0efa85a162 fix: i18n 2025-02-13 23:56:33 +08:00
Yumao
e2ebd62100
Merge pull request #1491 from MCSManager/dependabot/npm_and_yarn/panel/koa-2.15.4
chore(deps): bump koa from 2.14.1 to 2.15.4 in /panel
2025-02-13 11:51:16 +08:00
dependabot[bot]
ec36b86175
chore(deps): bump koa from 2.14.1 to 2.15.4 in /panel
Bumps [koa](https://github.com/koajs/koa) from 2.14.1 to 2.15.4.
- [Release notes](https://github.com/koajs/koa/releases)
- [Changelog](https://github.com/koajs/koa/blob/2.15.4/History.md)
- [Commits](https://github.com/koajs/koa/compare/2.14.1...2.15.4)

---
updated-dependencies:
- dependency-name: koa
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 03:50:55 +00:00
Yumao
b7b6835ff1
Merge pull request #1490 from MCSManager/dependabot/npm_and_yarn/daemon/koa-2.15.4
chore(deps): bump koa from 2.13.1 to 2.15.4 in /daemon
2025-02-13 11:49:48 +08:00
dependabot[bot]
ad45502f56
chore(deps): bump koa from 2.13.1 to 2.15.4 in /daemon
Bumps [koa](https://github.com/koajs/koa) from 2.13.1 to 2.15.4.
- [Release notes](https://github.com/koajs/koa/releases)
- [Changelog](https://github.com/koajs/koa/blob/2.15.4/History.md)
- [Commits](https://github.com/koajs/koa/compare/2.13.1...2.15.4)

---
updated-dependencies:
- dependency-name: koa
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-12 19:44:26 +00:00
Yumao
42a8e8dbfe
Merge pull request #1486 from SkyKingPX/master
Update READMEs and Copyright
2025-02-07 10:45:42 +08:00
Antony Blem
58e06b5221 Update MCSManager Copyright to 2025 2025-02-05 19:45:57 +01:00
Antony Blem
f0a44a4686 Add German README.md (README_DE.md) 2025-02-05 19:42:34 +01:00
Antony Blem
0b19bd7fc1 Update/Correct English README.md 2025-02-05 19:02:37 +01:00
Yumao
c3929d61b8
Merge pull request #1476 from MCSManager/nya
feat: image viewer
2025-01-31 16:35:44 +08:00
alongw
fb0693903e
fix: i18n 2025-01-26 13:50:00 +08:00
alongw
3f28f79368
style: change image viewer style 2025-01-23 22:57:20 +08:00
alongw
76fa65ce25
feat: image viewer
closed #1472
2025-01-23 20:53:20 +08:00
Yumao
61450c41cb
Merge pull request #1474 from MCSManager/dependabot/npm_and_yarn/frontend/vite-4.5.9
chore(deps-dev): bump vite from 4.5.3 to 4.5.9 in /frontend
2025-01-23 11:08:16 +08:00
dependabot[bot]
954f5e3241
chore(deps-dev): bump vite from 4.5.3 to 4.5.9 in /frontend
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 4.5.3 to 4.5.9.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v4.5.9/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v4.5.9/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-22 06:12:16 +00:00
YuMao
d49e800508 fix: https://github.com/MCSManager/MCSManager/issues/1470 2025-01-21 20:27:05 +08:00
YuMao
37c2ee2bcf chore: optimize log info 2025-01-21 20:17:15 +08:00
alongw
2f974e8728
feat: create image viewer 2025-01-21 20:04:29 +08:00
YuMao
1748fcd751 feat: for redeem.mcsmanager.com 2025-01-21 16:53:00 +08:00
alongw
386388822d refactor: click file logic 2025-01-21 16:46:07 +08:00
Yumao
0ba3e32abc
Merge pull request #1469 from ChunghwaMC/master
追隨更新
2025-01-20 11:18:53 +08:00
Yumao
d6cf7323de
Merge pull request #1471 from inactivood/patch-1
Update es_ES.json
2025-01-20 11:15:48 +08:00
inactivo
f0060c3c69
Update es_ES.json 2025-01-19 16:35:27 -03:00
ChunghwaMC
51f2d02eaf
追隨更新 2025-01-19 22:50:55 +08:00
YuMao233
cbe03d0074 chore: optimize quick flow animate 2025-01-19 18:04:02 +08:00
YuMao233
60d3a4a17c fix: https://github.com/MCSManager/MCSManager/issues/1425 2025-01-19 17:30:55 +08:00
YuMao233
ec5beae2a9 fix: https://github.com/MCSManager/MCSManager/issues/1468 2025-01-19 15:01:03 +08:00
YuMao233
2e79b2dcfe chore: optimize ui 2025-01-19 14:38:23 +08:00
Yumao
bd3e2bb711
Merge pull request #1467 from ZoftTy/reverse-proxy-fix
fix: incorrect client IP display in logs under reverse proxy mode
2025-01-19 14:26:26 +08:00
YuMao
47c86b2bc5 fix: toAbsolutePath err for linux 2025-01-19 14:23:34 +08:00
YuMao233
c66c880eb3 fix: toAbsolutePath err for linux 2025-01-19 14:02:19 +08:00
YuMao
f9e6702557 Merge branch 'master' of github.com:MCSManager/MCSManager 2025-01-19 13:19:01 +08:00
YuMao
19f2284c2d feat: delay mission del 2025-01-19 13:18:47 +08:00
YuMao233
89ad69b21f fix: del err path check 2025-01-18 12:56:02 +08:00
YuMao233
56eb8c1640 fix: del err path check 2025-01-18 12:53:19 +08:00
ZoftTy
763943a007
fix: incorrect client IP display in logs under reverse proxy mode (#1442) 2025-01-16 23:50:27 +08:00
YuMao
a060540995 format: lang files 2025-01-13 11:23:35 +08:00
Yumao
8be2efca4c
Merge pull request #1462 from Dominicus0830/Dominicus0830-Korean-lang-file-v1
Korean lang file v1
2025-01-11 18:44:57 +08:00
Dominicus
79f69eba4b
Korean lang file v1 2025-01-11 14:51:10 +09:00
Yumao
457e7d499f
Merge pull request #1455 from OlegFM/master
Update ru_RU.json to use correct translation from en_EN.json
2025-01-07 11:31:57 +08:00
Yumao
3b8017e24c
Merge pull request #1456 from SkyKingPX/master
Add & Correct de_DE translations
2025-01-07 11:31:13 +08:00
Antony Blem
9938a8590e fix: Correct *one* German translation... 2025-01-06 16:02:31 +01:00
Antony Blem
52c8ac4ff9 fix: Correct some German translations, as they made no sense or weren't correct 2025-01-06 15:33:44 +01:00
Oleg Fominsky
d562cb0448
Merge pull request #1 from OlegFM/fix_russian_translation
Add new translations and update existing entries in ru_RU.json
2025-01-06 17:31:07 +03:00
Oleg Fominsky
04031cf2db Add new translations and update existing entries in ru_RU.json
Extended the Russian language JSON file with updated translations, increasing the total number of entries. This update aims to improve localization accuracy and expand language coverage.
2025-01-06 17:29:08 +03:00
Antony Blem
d2be793eea feat: Add German translations 2025-01-06 13:56:13 +01:00
YuMao
853793a964
Merge pull request #1450 from SkyKingPX/master
feat: Add Purpur tag and configuration file
2024-12-24 17:28:00 +08:00
Antony Blem
3146352d85
Merge branch 'master' of https://github.com/SkyKingPX/MCSManager 2024-12-24 09:24:22 +00:00
Antony Blem
0fbc373c94
fix: revert zh_CN Purpur translations 2024-12-24 09:18:48 +00:00
Antony Blem
3bbd01a0ae
fix: revert zh_CN Purpur translations 2024-12-24 08:37:20 +00:00
Antony Blem
b9aff22c5a
fix: Purpur.yml should not be available for Pufferfish users 2024-12-23 07:51:14 +00:00
Antony Blem
ba42e1f6b3
fix: Correct translations 2024-12-23 07:45:10 +00:00
Antony Blem
7e174a3f71
Merge branch 'master' of https://github.com/SkyKingPX/MCSManager 2024-12-23 07:40:18 +00:00
Antony Blem
69459c82d5
feat: Add Purpur tag and Configuration file 2024-12-23 07:17:04 +00:00
YuMao
96f145acdc
Merge pull request #1448 from SkyKingPX/master
feat: Add Pufferfish tag and Configuration file
2024-12-23 14:23:44 +08:00
Antony Blem
0b61eb408e feat: Add Pufferfish tag and Configuration file 2024-12-22 13:28:54 +01:00
YuMao
3a22884898
Merge pull request #1446 from MCSManager/feature/i18n-script
feat: add useless key scanner
2024-12-16 20:30:35 +08:00
YuMao
412f5132fe feat: add useless key scanner 2024-12-16 20:29:45 +08:00
YuMao
fca4690f21 feat: add useless key scanner 2024-12-16 19:37:52 +08:00
YuMao
5c0d98ee74
Merge pull request #1445 from MCSManager/absurd
Scheduled tasks support edit
2024-12-16 15:41:57 +08:00
Lazy
3af2c1437e Update TerminalCore.vue 2024-12-16 15:33:16 +08:00
Lazy
d78deac2a4 fix: time render 2024-12-16 14:31:37 +08:00
Lazy
8a027fb2f1 fix: render terminal color 2024-12-16 02:08:15 +08:00
Lazy
b3c2a4d19c fix: i18n 2024-12-16 01:44:08 +08:00
Lazy
c6222a5f85 Refactor: schedule 2024-12-16 01:33:53 +08:00
YuMao
92e63ee44a
Merge pull request #1444 from MCSManager/dependabot/npm_and_yarn/panel/nanoid-3.3.8
chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /panel
2024-12-15 18:55:14 +08:00
dependabot[bot]
55a39c2b7b
chore(deps): bump nanoid from 3.3.7 to 3.3.8 in /panel
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-15 05:32:16 +00:00
YuMao
2c24401b4a
Merge pull request #1443 from MCSManager/dependabot/npm_and_yarn/frontend/nanoid-3.3.8
chore(deps): bump nanoid from 3.3.6 to 3.3.8 in /frontend
2024-12-15 13:31:20 +08:00
YuMao
681da12893
Merge pull request #1441 from SkyKingPX/master
fix: Remove visibility from 'Steam Rcon Protocol' when Instance is tagged with 'Minecraft'
2024-12-15 13:31:07 +08:00
dependabot[bot]
2dd1dc384c
chore(deps): bump nanoid from 3.3.6 to 3.3.8 in /frontend
Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.6 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.6...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-14 05:13:01 +00:00
Antony Blem
086a831cd2 fix: Remove visibility from 'Steam Rcon Protocol' when Instance is tagged with 'Minecraft' 2024-12-13 10:11:40 +01:00
YuMao
3195753e0e feat: update vscode settings 2024-12-01 21:19:37 +08:00
YuMao
cfe70b6be4 feat: update vscode settings 2024-12-01 21:17:57 +08:00
YuMao
4f61f3e5d0 chore: add vscode/settings.json & update readme 2024-12-01 12:41:33 +08:00
YuMao
e521258b9d feat: i18n code 2024-12-01 12:17:42 +08:00
YuMao
5fd3564b47 fix: exchange router bug 2024-12-01 12:16:28 +08:00
YuMao
46ff718a8f
Merge pull request #1434 from MCSManager/feature/business
Feature/business-with-redeem-code
2024-11-30 21:14:47 +08:00
YuMao
d63914eb0b
Merge pull request #1435 from MCSManager/baka
feat: instance list delete button
2024-11-30 11:32:15 +08:00
alongw
1bb3ce33f1 refactor: cancel the independent splitting of API requests 2024-11-29 22:38:30 +08:00
alongw
ed4f68f670 refactor: cancel the independent splitting of API requests 2024-11-29 22:31:56 +08:00
YuMao
629e5fc2e6 feat: hide shop 2024-11-29 18:03:24 +08:00
YuMao
3336b49695 chore: del useless code 2024-11-29 16:40:17 +08:00
YuMao
6a6bdeb220 feat: i18n code 2024-11-29 16:37:01 +08:00
YuMao
8941f34624 Merge branch 'master' into feature/business 2024-11-29 16:18:32 +08:00
YuMao
e17cddf9da feat: auto create host path 2024-11-29 15:52:57 +08:00
alongw
5d6e13f478 refactor: move file 2024-11-27 23:30:34 +08:00
alongw
c0f39d6950 feat: instance list delete button 2024-11-27 23:20:25 +08:00
YuMao
8522fc261e feat: instance list auto sort 2024-11-27 17:27:58 +08:00
YuMao
c0e0e04105 chore: standards code 2024-11-27 16:25:52 +08:00
YuMao
ec79de28df chore: useless import 2024-11-27 16:14:33 +08:00
YuMao
9ade8e9462 fix: default workspace dir 2024-11-27 16:14:09 +08:00
YuMao
a4f871b8fd fix: startTimestamp flow 2024-11-27 15:51:11 +08:00
YuMao
586bc4a9ef fix: DockerStartCommand extends error & optimize create instance UI 2024-11-27 15:31:19 +08:00
YuMao
137808048d feat: changeWorkdir option for docker 2024-11-27 14:35:48 +08:00
YuMao
0bd617d5ed fix: dependency package warnings 2024-11-26 19:58:25 +08:00
YuMao
39a662eb52 fix: https://github.com/MCSManager/MCSManager/issues/1365 & https://github.com/MCSManager/MCSManager/issues/1433 2024-11-26 19:43:22 +08:00
YuMao
d7a5da9261 feat: i18n code 2024-11-25 20:21:22 +08:00
YuMao
1572a6104f feat: i18n code 2024-11-25 20:18:56 +08:00
YuMao
cfd962c159 chore: optimize ui & code 2024-11-25 19:52:30 +08:00
YuMao
46a2192f4a feat: i18n code 2024-11-25 19:29:35 +08:00
YuMao
a7ec8a12b1 feat: error template 2024-11-25 19:22:18 +08:00
YuMao
15ac453054 feat: shopping history query 2024-11-25 17:44:07 +08:00
YuMao
6b7d42e61f feat: shop api 2024-11-25 17:14:36 +08:00
YuMao
8e1c54abef feat: shop api 2024-11-25 17:10:01 +08:00
YuMao
17d8ee3b79 feat: shop api 2024-11-25 17:08:53 +08:00
YuMao
aa33f228a0 feat: renewal instance 2024-11-25 16:09:01 +08:00
YuMao
c74811898b feat: buy instance api 2024-11-25 15:21:21 +08:00
YuMao
38322c38c8 feat: buy instance api 2024-11-25 14:27:30 +08:00
YuMao
6967bb736c feat: shop page request 2024-11-22 17:48:52 +08:00
YuMao
d71592b766 feat: shop page request 2024-11-22 17:30:35 +08:00
YuMao
470d569fec feat: shop page 2024-11-21 20:25:41 +08:00
YuMao
547d1d6c40 feat: shop page 2024-11-21 19:37:45 +08:00
YuMao
8799afb02c feat: business mode 2024-11-21 14:48:05 +08:00
YuMao
76e0bc023c fix: instance.process.write(encode(buf, instance.config.oe)); -> inputCode 2024-11-20 19:40:24 +08:00
YuMao
1dcafd9128 Merge branch 'master' of github.com:MCSManager/MCSManager 2024-11-17 14:38:11 +08:00
YuMao
a141de7cc7 fix: empty remote daemon 2024-11-17 14:37:58 +08:00
YuMao
16d88f768b
Merge pull request #1418 from MCSManager/alongw
fix: user assignment list not cleared when deleting instance
2024-10-31 16:10:58 +08:00
alongw
7c824a419d fix: fix deleteUserInstances method args type error 2024-10-30 21:06:16 +08:00
YuMao
c05038c1f1
Merge pull request #1419 from KagurazakaNyaa/master
chore: reduce web image size
2024-10-30 19:35:11 +08:00
alongw
e2a7e20a49 feat: add type check 2024-10-30 17:05:06 +08:00
alongw
1dd417f05c fix: user assignment list not cleared when deleting instance
fixed #1415
2024-10-30 16:55:05 +08:00
KagurazakaNyaa
ab7bb109e0 fix: alpine build need wget 2024-10-30 16:52:25 +08:00
KagurazakaNyaa
d6f0691828 chore: change base image to node:lts-alpine to reduce image size 2024-10-30 16:41:02 +08:00
YuMao
3822a7bfb4 chore: i18n code 2024-10-30 14:34:57 +08:00
YuMao
78dcc1de4f
Fix: https://github.com/MCSManager/MCSManager/issues/1379 2024-10-30 14:23:01 +08:00
YuMao
4d9959f666
Merge pull request #1414 from KagurazakaNyaa/master
ci: add linux/arm64 platform support for docker
2024-10-30 14:19:12 +08:00
YuMao
b3caf3aa05 Merge branch 'master' of github.com:MCSManager/MCSManager 2024-10-30 14:18:58 +08:00
YuMao
8b21e5feb7 refactor: instance "command exec()" replace to "execPreset()" 2024-10-30 14:18:52 +08:00
Lazy
019ccf04d1 fix: spell check 2024-10-30 00:16:57 +08:00
Lazy
781bd32c48 fix: do not refresh 2024-10-30 00:08:13 +08:00
Lazy
ea49d3f36d fix: operate icon 2024-10-30 00:07:53 +08:00
Lazy
6a4145ae72 fix: terminal color render 2024-10-29 23:52:53 +08:00
YuMao
264b8facaf
Update README.md 2024-10-29 14:34:02 +08:00
YuMao
c3e6fb7f0e
chore: add custom card gif 2024-10-29 14:30:49 +08:00
YuMao
abb20ad0f1 fix: https://github.com/MCSManager/MCSManager/issues/1323 2024-10-29 11:40:49 +08:00
YuMao
92438f876a fix: text typo 2024-10-29 11:05:52 +08:00
KagurazakaNyaa
95973a6c77 chore: Suppress docker build warnings 2024-10-29 10:12:54 +08:00
KagurazakaNyaa
d260e737d8 ci: alway use amd64 on build stage to speed up build 2024-10-29 10:06:23 +08:00
KagurazakaNyaa
0a97ad9d70 ci: Remove unused architectures to speed up builds 2024-10-29 09:39:23 +08:00
KagurazakaNyaa
dd6cc84461 chore: Remove part of the architecture support for daemon due to compatibility issues 2024-10-29 09:38:00 +08:00
KagurazakaNyaa
3934e26e21 ci: docker image support multiple platform 2024-10-29 09:20:41 +08:00
YuMao
a6f6f34e37
Merge pull request #1411 from JackER4565/master
added spanish readme
2024-10-28 10:41:59 +08:00
Fabrizio Rondinella
0e7da93735 added spanish readme 2024-10-26 11:41:39 -03:00
Fabrizio Rondinella
d95b952c77
Create README_ES.md 2024-10-26 11:12:32 -03:00
YuMao
bb825ec931
Merge pull request #1410 from kukinghan/master
fix issues 1409
2024-10-26 10:04:48 +08:00
kukinghan
f0ce2f3bd0 fix: search logic 2024-10-25 22:38:36 +08:00
kukinghan
573fb80f65 fix: search change not retrieving files correctly after pagination 2024-10-25 22:35:38 +08:00
YuMao
d277497861 optimize: placeholder desc 2024-10-21 17:53:27 +08:00
YuMao
3fa9c1e784 Merge branch 'master' of github.com:MCSManager/MCSManager 2024-10-21 17:44:58 +08:00
YuMao
358c7f4402 feat: parseTextParams of instance 2024-10-21 17:44:54 +08:00
YuMao
d3aaeb39f1 feat: parseTextParams of instance 2024-10-21 17:36:20 +08:00
YuMao
ef91fdb2a7
Merge pull request #1407 from KagurazakaNyaa/master
feat: daemon内置java运行时
2024-10-21 16:03:06 +08:00
KagurazakaNyaa
336dd6b19f chore: prevent web build trigger repeatedly 2024-10-21 12:18:49 +08:00
KagurazakaNyaa
b3d6c04b23 chore: dont trigger docker.io image build on test 2024-10-21 12:14:16 +08:00
KagurazakaNyaa
c11910a14e feat: daemon embedded java runtime 2024-10-21 12:05:01 +08:00
YuMao
adf7c50981 fix: MCSM_INSTANCES_BASE_PATH path err 2024-10-17 15:08:31 +08:00
YuMao
90bedcd985
Merge pull request #1403 from MCSManager/feature/docker-push
fix: MCSM_INSTANCES_BASE_PATH path err
2024-10-17 15:05:48 +08:00
YuMao
50c85cc8e3 fix: MCSM_INSTANCES_BASE_PATH path err 2024-10-17 14:50:13 +08:00
YuMao
903417f7b9
Merge pull request #1400 from MCSManager/feature/docker-push
feat: package support docker hub
2024-10-14 14:19:00 +08:00
YuMao
aa28a14ed5 feat: pack support docker hub 2024-10-14 14:16:51 +08:00
YuMao
bf2b3ed439 feat: pack support docker hub 2024-10-14 14:15:19 +08:00
YuMao
b14d594fc2 feat: support docker hub 2024-10-14 14:12:23 +08:00
YuMao
6c97db269f feat: update version 2024-10-14 10:45:31 +08:00
YuMao
fcc082b71d
Merge pull request #1395 from KagurazakaNyaa/master
feat: docker image build
2024-10-14 10:41:27 +08:00
KagurazakaNyaa
3bb4eff36b fix lib perms 2024-10-12 16:45:55 +08:00
KagurazakaNyaa
cb36d76685 lowcase image name 2024-10-12 16:39:43 +08:00
KagurazakaNyaa
4348d72e1c try build docker image 2024-10-12 16:38:48 +08:00
148 changed files with 24560 additions and 12775 deletions

1
.dockerignore Symbolic link
View File

@ -0,0 +1 @@
.gitignore

BIN
.github/panel-custom-layout.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

102
.github/workflows/docker.yml vendored Normal file
View File

@ -0,0 +1,102 @@
name: Release Docker Build
on:
release:
types: [published]
jobs:
build-web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Docker meta web
id: meta_web
uses: docker/metadata-action@v5
with:
images: |
name=ghcr.io/${{ github.repository }}-web
name=githubyumao/mcsmanager-web,enable=${{ github.repository == 'MCSManager/MCSManager' }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
if: ${{ github.repository == 'MCSManager/MCSManager' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and Push Web
uses: docker/build-push-action@v6
with:
file: dockerfile/web.dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta_web.outputs.tags }}
labels: ${{ steps.meta_web.outputs.labels }}
build-args: |
BUILDPLATFORM=linux/amd64
build-daemon:
runs-on: ubuntu-latest
strategy:
matrix:
java_version: [8, 11, 17, 21]
steps:
- uses: actions/checkout@v4
- name: Docker meta daemon
id: meta_daemon
uses: docker/metadata-action@v5
with:
images: |
name=ghcr.io/${{ github.repository }}-daemon,enable=${{ matrix.java_version == 21 }}
name=githubyumao/mcsmanager-daemon,enable=${{ github.repository == 'MCSManager/MCSManager' && matrix.java_version == 21 }}
name=ghcr.io/${{ github.repository }}-daemon-jdk${{ matrix.java_version }}
name=githubyumao/mcsmanager-daemon-jdk${{ matrix.java_version }},enable=${{ github.repository == 'MCSManager/MCSManager' }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Login to DockerHub
if: ${{ github.repository == 'MCSManager/MCSManager' }}
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Build and Push Daemon
uses: docker/build-push-action@v6
with:
file: dockerfile/daemon.dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta_daemon.outputs.tags }}
labels: ${{ steps.meta_daemon.outputs.labels }}
build-args: |
BUILDPLATFORM=linux/amd64
JAVA_RUNTIME=${{ matrix.java_version }}

2
.gitignore vendored
View File

@ -7,8 +7,6 @@ yarn-error.log*
lerna-debug.log*
panel/public/
*.code-workspace
vscode/
.vscode/
data/
dist/
out/

19
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,19 @@
// Please do not commit changes to this file.
// You need to install the "i18n ally" VSCode plugin.
{
// Change this to your language, available options: "languages/*.json"
// "i18n-ally.displayLanguage": "en_US"
"i18n-ally.sourceLanguage": "en",
"i18n-ally.extract.keyMaxLength": 10000,
"cSpell.words": ["BUKKIT", "BUNGEECORD", "MINECRAFT"],
"i18n-ally.localesPaths": [
"languages",
"frontend/src/lang",
"daemon/src/i18n",
"panel/src/app/i18n"
],
"i18n-ally.keystyle": "flat",
"i18n-ally.enabledFrameworks": ["vue"],
"editor.wordSeparators": "`~!@#$%^&*()=+[{]}\\|;:'\",.<>/?"
}

View File

@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Copyright 2025 MCSManager
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@ -16,36 +16,42 @@
[Official Website](http://mcsmanager.com/) | [Docs](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md)
[简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Deutsch](README_DE.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md) | [Spanish](README_ES.md)
</div>
<br />
## What is MCSManager?
**MCSManager Panel** (MCSM) is a **modern, secure, and distributed control panel** designed for managing Minecraft and Steam game servers.
MCSManager has already gained a certain level of popularity within the community, specifically Minecraft. MCSManager excels in offering a centralized management solution for multiple server instances and provides a secure and reliable multi-user permission system. In addition, We are committed to supporting server administrators not only for Minecraft but also for Terraria and various Steam games. Our goal is to foster a thriving and supportive community for game server management.
MCSManager has already gained a certain level of popularity within the community, specifically because of Minecraft. MCSManager excels in offering a centralized management solution for multiple server instances and provides a secure and reliable multi-user permission system. In addition, we are committed to support server administrators, not only for Minecraft but also for Terraria and various Steam games. Our goal is to foster a thriving and supportive community for game server management.
MCSManager **supports English, French, German, Italian, Japanese, Portuguese, Simplified Chinese, and Traditional Chinese**, with plans to add support for more languages in the future!
**Terminal**
![failed_to_load_screenshot.png](/.github/panel-image.png)
**Instance List**
![failed_to_load_screenshot.png](/.github/panel-instances.png)
**Custom Layout**
![failed_to_load_screenshot.png](/.github/panel-custom-layout.gif)
## Features
1. One-click deployment of `Minecraft` Java/Bedrock Server
2. Compatible with most `Steam` game servers. (e.g. `Palworld`, `Squad`, `Project Zomboid`, `Terraria`, etc.)
3. Customizable UI, create your own layout
4. Support `Docker` virtualization, multiuser, and commercial services
4. Supports all images on `Docker Hub`, supports multiple users and supports commercial services!
5. Manage multiple servers with a single web interface
6. More...
6. The technology stack is simple, and you only need to be good at Typescript to complete the entire MCSManager development.
7. And More!
<br />
@ -53,7 +59,7 @@ MCSManager **supports English, French, German, Italian, Japanese, Portuguese, Si
MCSM supports both `Windows` and `Linux`. The only requirement is `Node.js` and some libraries **for decompression**.
Require [Node.js 16.20.2](https://nodejs.org/en) or above.
Requires [Node.js 16.20.2](https://nodejs.org/en) or above.
<br />
@ -61,7 +67,7 @@ Require [Node.js 16.20.2](https://nodejs.org/en) or above.
### Windows
For Windows, We provides packaged executable files:
For Windows, we provide packaged executable files:
Go to: [https://mcsmanager.com/](https://mcsmanager.com/)
@ -71,7 +77,7 @@ Go to: [https://mcsmanager.com/](https://mcsmanager.com/)
**One-Command Deployment**
> Script needs to register system services, it requires root permissions.
> The script needs to register system services and requires root permissions because of that.
```bash
sudo su -c "wget -qO- https://script.mcsmanager.com/setup.sh | bash"
@ -91,7 +97,7 @@ systemctl stop mcsm-{web,daemon}
**Linux Manual Installation**
- If the installation script failed to execute correctly, you can try install it manually.
- If the installation script fails to execute correctly, you can try to install it manually.
```bash
# Create /opt directory if not already
@ -133,17 +139,19 @@ This installation approach does not automatically set up MCSManager as a system
<br />
## Browser Compatibility
- Supported on modern browsers including `Chrome`, `Firefox`, and `Safari`.
- Support for `IE` has been discontinued.
<br />
## Development
This section is specifically designed for developers. General users may disregard this portion without concern.
### Plugins
We use "VS Code" to develop MCSManager. You may need to install these plugins:
- i18n display support (I18n Ally)
- Code formatter (Prettier)
- Vue - Offcial
- ESLint
### MacOS
```bash
@ -160,6 +168,10 @@ git clone https://github.com/MCSManager/MCSManager.git
./npm-dev-windows.bat
```
### Dependency Files
You'll need to go to the [PTY](https://github.com/MCSManager/PTY) and [Zip-Tools](https://github.com/MCSManager/Zip-Tools) projects to download the corresponding binary files and place them in the `daemon/lib` directory to ensure the proper functioning of the `Emulation Terminal` and `File Decompression`.
### Build Production Version
```bash
@ -167,7 +179,7 @@ git clone https://github.com/MCSManager/MCSManager.git
./build.sh # MacOS
```
Next, you'll need to go to the [PTY](https://github.com/MCSManager/PTY) and [Zip-Tools](https://github.com/MCSManager/Zip-Tools) projects to download the corresponding binary files and place them in the `daemon/lib` directory to ensure the proper functioning of the `Emulation Terminal` and `File Decompression`.
Output Directory: "production-code"
<br />
@ -179,6 +191,13 @@ Please ensure that any submitted code adheres to our existing coding style. For
<br />
## Browser Compatibility
- Supported on modern browsers including `Chrome`, `Firefox`, and `Safari`.
- Support for `IE` has been discontinued.
<br />
## BUG Reporting
**Open Issue:** [Click here](https://github.com/MCSManager/MCSManager/issues/new/choose)
@ -202,4 +221,4 @@ Thanks to these contributors for providing a substantial amount of translation:
The source code of MCSManager is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) License.
Copyright ©2024 MCSManager.
Copyright ©2025 MCSManager.

224
README_DE.md Normal file
View File

@ -0,0 +1,224 @@
<div align="center">
<a href="https://mcsmanager.com/" target="_blank">
<img src="https://public-link.oss-cn-shenzhen.aliyuncs.com/mcsm_picture/logo.png" alt="MCSManagerLogo.png" width="510px" />
</a>
<br />
<h1 id="mcsmanager">
<a href="https://mcsmanager.com/" target="_blank">MCSManager Panel</a>
</h1>
[![--](https://img.shields.io/badge/Support-Windows/Linux-green.svg)](https://github.com/MCSManager)
[![Status](https://img.shields.io/badge/npm-v8.9.14-blue.svg)](https://www.npmjs.com/)
[![Status](https://img.shields.io/badge/node-v16.20.2-blue.svg)](https://nodejs.org/en/download/)
[![Status](https://img.shields.io/badge/License-Apache%202.0-red.svg)](https://github.com/MCSManager)
[Offizielle Website](http://mcsmanager.com/) | [Dokumentation](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
[Englisch](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md) | [Spanisch](README_ES.md)
</div>
<br />
## Was ist MCSManager?
**MCSManager Panel** (MCSM) ist ein **modernes, sicheres und verbreitetes Verwaltungspanel**, designt für die Verwaltung von Minecraft und Steam Spielserver.
MCSManager hat bereits eine gewisse Popularität in der Community erlangt, insbesondere durch Minecraft. MCSManager zeichnet sich durch eine zentralisierte Verwaltungslösung für mehrere Serverinstanzen aus und bietet ein sicheres und zuverlässiges Berechtigungssystem für mehrere Benutzer. Darüber hinaus engagieren wir uns für die Unterstützung von Serveradministratoren, nicht nur für Minecraft, sondern auch für Terraria und verschiedene Steam Spiele. Unser Ziel ist es, eine florierende und unterstützende Community für die Verwaltung von Spielservern zu fördern.
MCSManager **unterstützt Englisch, Französisch, Deutsch, Italienisch, Japanisch, Portugiesisch, Chinesisch (Vereinfacht) und Chinesisch (Traditionell)**, mit Plänen mehr Sprachen in der Zukunft zu unterstützen!
**Terminal**
![failed_to_load_screenshot.png](/.github/panel-image.png)
**Instanzliste**
![failed_to_load_screenshot.png](/.github/panel-instances.png)
**Benutzerdefiniertes Layout**
![failed_to_load_screenshot.png](/.github/panel-custom-layout.gif)
## Funktionen
1. Ein-Klick-Bereitstellung von `Minecraft` Java/Bedrock Servers
2. Kompatibel mit den meisten `Steam` Spielservern. (z.B. `Palworld`, `Squad`, `Project Zomboid`, `Terraria`, etc.)
3. Anpassbare Benutzeroberfläche, erstellen Sie Ihr eigenes Layout
4. Unterstützt alle Images auf `Docker Hub`, unterstützt mehrere Benutzer und unterstützt kommerzielle Dienste!
5. Verwalten Sie mehrere Server mit einer einzigen Weboberfläche
6. Der Technologie-Stack ist einfach, und Sie müssen nur gut in Typescript sein, um die gesamte MCSManager-Entwicklung abzuschließen.
7. Und mehr!
<br />
## Laufzeitumgebung
MCSM unterstützt `Windows` und `Linux`. Die einzige Anforderung ist `Node.js` und ein Paar andere Bibliotheken **für die Dekomprimierung**.
Benötigt [Node.js 16.20.2](https://nodejs.org/en) oder höher.
<br />
## Installation
### Windows
Für Windows, stellen wir gepackte ausführbare Dateien zur Verfügung:
Gehe zu: [https://mcsmanager.com/](https://mcsmanager.com/)
<br />
### Linux
**Bereitstellung mit einem Befehl**
> Das Skript muss Systemdienste registrieren und benötigt daher Root-Berechtigungen.
```bash
sudo su -c "wget -qO- https://script.mcsmanager.com/setup.sh | bash"
```
**Verwendung**
```bash
systemctl start mcsm-{web,daemon}
systemctl stop mcsm-{web,daemon}
```
- Unterstützt nur Ubuntu/Centos/Debian/Archlinux.
- Installationsort: `/opt/mcsmanager/`.
<br />
**Linux - Manuelle Installation**
- Wenn das Installationsskript nicht ordnungsgemäß ausgeführt wird, können Sie versuchen, es manuell zu installieren.
```bash
# Erstellen des /opt Verzeichnisses, wenn es nicht bereits existiert
mkdir /opt
# Wechseln zu /opt/
cd /opt/
# Node.js 20.11 herunterladen. Wenn Sie bereits Node.js 16+ installiert haben, können sie diesen Schritt ignorieren.
wget https://nodejs.org/dist/v20.11.0/node-v20.11.0-linux-x64.tar.xz
# Node.js entpacken
tar -xvf node-v20.11.0-linux-x64.tar.xz
# Node.js zum System PATH hinzufügen
ln -s /opt/node-v20.11.0-linux-x64/bin/node /usr/bin/node
ln -s /opt/node-v20.11.0-linux-x64/bin/npm /usr/bin/npm
# MCSM's Installationsort vorbereiten
mkdir /opt/mcsmanager/
cd /opt/mcsmanager/
# MCSManager herunterladen
wget https://github.com/MCSManager/MCSManager/releases/latest/download/mcsmanager_linux_release.tar.gz
tar -zxf mcsmanager_linux_release.tar.gz
# Abhängigkeiten installieren
./install.sh
# Bitte öffnen Sie zwei Terminals oder Screens.
# Starten Sie den Daemon zuerst.
./start-daemon.sh
# Starten Sie anschließend das Web-Interface im zweiten Terminal oder Screen.
./start-web.sh
# Gehen Sie für den Webzugriff zu http://localhost:23333/
# Im Allgemeinen scannt das Webinterface automatisch den lokalen Daemon und fügt ihn hinzu.
```
Bei diesem Installationsansatz wird MCSManager nicht automatisch als Systemdienst eingerichtet. Daher ist es notwendig, "Screen" für die Verwaltung zu verwenden. Wenn Sie daran interessiert sind, MCSManager über einen Systemdienst zu verwalten, lesen Sie bitte unser Wiki/unsere Dokumentation.
<br />
## Entwicklung
Dieser Abschnitt wurde speziell für Entwickler entwickelt. Allgemeine Benutzer können diesen Teil ohne Bedenken ignorieren.
### Plugins
Wir verwenden "VS Code", um MCSManager zu entwickeln. Möglicherweise müssen Sie diese Plugins installieren:
- i18n display support (I18n Ally)
- Code formatter (Prettier)
- Vue - Offcial
- ESLint
### MacOS
```bash
git clone https://github.com/MCSManager/MCSManager.git
./install-dependents.sh
./npm-dev-macos.sh
```
### Windows
```bash
git clone https://github.com/MCSManager/MCSManager.git
./install-dependents.bat
./npm-dev-windows.bat
```
### Abhängigkeiten
Sie müssen zu den Projekten [PTY](https://github.com/MCSManager/PTY) und [Zip-Tools](https://github.com/MCSManager/Zip-Tools) gehen, um die entsprechenden Binärdateien herunterzuladen und sie im Verzeichnis 'daemon/lib' abzulegen, um das ordnungsgemäße Funktionieren des `Emulation Terminals` und der `File Decompression` sicherzustellen.
### Produktionsversion erstellen
```bash
./build.bat # Windows
./build.sh # MacOS
```
Ausgabe-Verzeichnis: "production-code"
<br />
## Code beitragen
Sollten Sie Probleme bei der Nutzung von MCSManager haben, können Sie gerne [ein Issue](https://github.com/MCSManager/MCSManager/issues/new/choose) einreichen. Alternativ können Sie das Projekt forken und direkt beitragen, indem Sie einen Pull Request einreichen.
Bitte stellen Sie sicher, dass der eingereichte Code unserem bestehenden Codierungsstil entspricht. Weitere Informationen finden Sie in den Richtlinien in [diesem Issue](https://github.com/MCSManager/MCSManager/issues/544).
<br />
## Browser Kompatibilität
- Wird in modernen Browsern wie `Chrome`, `Firefox` und `Safari` unterstützt.
- Die Unterstützung für `IE` wurde eingestellt.
<br />
## BUG Reporting
**Issue erstellen:** [Hier drücken](https://github.com/MCSManager/MCSManager/issues/new/choose)
**Bericht über Sicherheitslücken:** [SECURITY.md](SECURITY.md)
<br />
## Internationalisierung
Vielen Dank an diese Mitwirkenden für die Bereitstellung einer beträchtlichen Menge an Übersetzungen:
- [KevinLu2000](https://github.com/KevinLu2000)
- [Unitwk](https://github.com/unitwk)
- [JianyueLab](https://github.com/JianyueLab)
- [IceBrick](https://github.com/IceBrick01)
<br />
## Lizens
Der Quellcode von MCSManager ist unter der [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) Lizenz lizenziert.
Copyright ©2025 MCSManager.

199
README_ES.md Normal file
View File

@ -0,0 +1,199 @@
<div align="center">
<a href="https://mcsmanager.com/" target="_blank">
<img src="https://public-link.oss-cn-shenzhen.aliyuncs.com/mcsm_picture/logo.png" alt="MCSManagerLogo.png" width="510px" />
</a>
<br />
<h1 id="mcsmanager">
<a href="https://mcsmanager.com/" target="_blank">Panel de MCSManager</a>
</h1>
[![--](https://img.shields.io/badge/Support-Windows/Linux-green.svg)](https://github.com/MCSManager)
[![Estado](https://img.shields.io/badge/npm-v8.9.14-blue.svg)](https://www.npmjs.com/)
[![Estado](https://img.shields.io/badge/node-v16.20.2-blue.svg)](https://nodejs.org/en/download/)
[![Licencia](https://img.shields.io/badge/License-Apache%202.0-red.svg)](https://github.com/MCSManager)
[Sitio Oficial](http://mcsmanager.com/) | [Documentación](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
[Inglés](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Deutsch](README_DE.md) | [Português BR](README_PTBR.md) | [日本語](README_JP.md)
</div>
<br />
## ¿Qué es MCSManager?
**Panel de MCSManager** (MCSM) es un **panel de control moderno, seguro y distribuido** diseñado para gestionar servidores de juego de Minecraft y Steam.
MCSManager ha ganado popularidad en la comunidad, especialmente en Minecraft. MCSManager ofrece una solución centralizada para gestionar múltiples instancias de servidor y proporciona un sistema de permisos multiusuario seguro y confiable. Nos comprometemos a apoyar a los administradores de servidores no solo para Minecraft, sino también para Terraria y varios juegos de Steam. Nuestro objetivo es fomentar una comunidad próspera y de apoyo en la gestión de servidores de juego.
MCSManager **admite inglés, francés, alemán, italiano, japonés, portugués, chino simplificado y chino tradicional**, ¡y planea agregar más idiomas en el futuro!
![failed_to_load_screenshot.png](/.github/panel-image.png)
![failed_to_load_screenshot.png](/.github/panel-instances.png)
## Características
1. Implementación con un clic de servidor `Minecraft` Java/Bedrock.
2. Compatible con la mayoría de servidores de juegos de `Steam` (p. ej., `Palworld`, `Squad`, `Project Zomboid`, `Terraria`, etc.).
3. Interfaz personalizable; crea tu propio diseño.
4. Soporte para virtualización con `Docker`, multiusuario y servicios comerciales.
5. Gestiona múltiples servidores desde una sola interfaz web.
6. ¡Y más!
<br />
## Entorno de Ejecución
MCSM es compatible con `Windows` y `Linux`. El único requisito es `Node.js` y algunas librerías **para descompresión**.
Requiere [Node.js 16.20.2](https://nodejs.org/en) o superior.
<br />
## Instalación
### Windows
Para Windows, ofrecemos archivos ejecutables empaquetados:
Ir a: [https://mcsmanager.com/](https://mcsmanager.com/)
<br />
### Linux
**Despliegue con un solo comando**
> El script necesita registrar servicios del sistema, requiere permisos de root.
```bash
sudo su -c "wget -qO- https://script.mcsmanager.com/setup.sh | bash"
```
**Uso**
```bash
systemctl start mcsm-{web,daemon}
systemctl stop mcsm-{web,daemon}
```
- Solo compatible con Ubuntu/Centos/Debian/Archlinux.
- Directorio de instalación: `/opt/mcsmanager/`.
<br />
**Instalación Manual en Linux**
- Si el script de instalación falla, puedes intentar instalarlo manualmente.
```bash
# Crear directorio /opt si no existe
mkdir /opt
# Cambiar a /opt
cd /opt/
# Descargar Node.js 20.11. Si ya tienes Node.js 16+ instalado, omite este paso.
wget https://nodejs.org/dist/v20.11.0/node-v20.11.0-linux-x64.tar.xz
# Descomprimir Node.js
tar -xvf node-v20.11.0-linux-x64.tar.xz
# Agregar Node.js al PATH del sistema
ln -s /opt/node-v20.11.0-linux-x64/bin/node /usr/bin/node
ln -s /opt/node-v20.11.0-linux-x64/bin/npm /usr/bin/npm
# Preparar el directorio de instalación de MCSM
mkdir /opt/mcsmanager/
cd /opt/mcsmanager/
# Descargar MCSManager
wget https://github.com/MCSManager/MCSManager/releases/latest/download/mcsmanager_linux_release.tar.gz
tar -zxf mcsmanager_linux_release.tar.gz
# Instalar dependencias
./install.sh
# Abrir dos terminales o pantallas.
# Iniciar el daemon primero.
./start-daemon.sh
# Iniciar la interfaz web en la segunda terminal o pantalla.
./start-web.sh
# Para acceder a la web, ir a http://localhost:23333/
# En general, la interfaz web escaneará y añadirá automáticamente el daemon local.
```
Este método de instalación no configura automáticamente MCSManager como un servicio del sistema. Por lo tanto, es necesario usar `screen` para la administración. Para quienes quieran administrar MCSManager a través de un servicio del sistema, por favor consulta nuestra wiki/documentación.
<br />
## Compatibilidad del Navegador
- Compatible con navegadores modernos como `Chrome`, `Firefox` y `Safari`.
- El soporte para `IE` ha sido discontinuado.
<br />
## Desarrollo
Esta sección está dirigida específicamente a desarrolladores. Los usuarios generales pueden ignorarla sin problema.
### MacOS
```bash
git clone https://github.com/MCSManager/MCSManager.git
./install-dependents.sh
./npm-dev-macos.sh
```
### Windows
```bash
git clone https://github.com/MCSManager/MCSManager.git
./install-dependents.bat
./npm-dev-windows.bat
```
### Construir Versión de Producción
```bash
./build.bat # Windows
./build.sh # MacOS
```
Luego, deberás ir a los proyectos [PTY](https://github.com/MCSManager/PTY) y [Zip-Tools](https://github.com/MCSManager/Zip-Tools) para descargar los archivos binarios correspondientes y colocarlos en el directorio `daemon/lib` para asegurar el funcionamiento adecuado del `Terminal de Emulación` y la `Descompresión de Archivos`.
<br />
## Contribución de Código
Si experimentas problemas al usar MCSManager, puedes [enviar un Issue](https://github.com/MCSManager/MCSManager/issues/new/choose). Alternativamente, puedes hacer un fork del proyecto y contribuir directamente enviando un Pull Request.
Asegúrate de que el código enviado siga nuestro estilo de codificación existente. Para más detalles, consulta las pautas en [este issue](https://github.com/MCSManager/MCSManager/issues/544).
<br />
## Reporte de Errores
**Abrir Issue:** [Haz clic aquí](https://github.com/MCSManager/MCSManager/issues/new/choose)
**Reporte de Vulnerabilidades de Seguridad:** [SECURITY.md](SECURITY.md) (en ingles)
<br />
## Internacionalización
Gracias a estos colaboradores por proporcionar una gran cantidad de traducciones:
- [KevinLu2000](https://github.com/KevinLu2000)
- [Unitwk](https://github.com/unitwk)
- [JianyueLab](https://github.com/JianyueLab)
- [IceBrick](https://github.com/IceBrick01)
<br />
## Licencia
El código fuente de MCSManager está licenciado bajo la [Licencia Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0).
Copyright ©2025 MCSManager.

View File

@ -17,7 +17,7 @@
[HP](http://mcsmanager.com/) | [ドキュメント](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md)
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Deutsch](README_DE.md) | [Português BR](README_PTBR.md) | [Spanish](README_ES.md)
</div>
@ -202,4 +202,4 @@ git clone https://github.com/MCSManager/MCSManager.git
ソースコードは、 [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0)ライセンス使っています。
Copyright ©2024 MCSManager.x
Copyright ©2025 MCSManager.

View File

@ -16,8 +16,8 @@
[Website Oficial](http://mcsmanager.com/) | [Documentação](https://docs.mcsmanager.com/) | [Discord](https://discord.gg/BNpYMVX7Cd)
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md)
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Deutsch](README_DE.md) |
[日本語](README_JP.md) | [Spanish](README_ES.md)
</div>
@ -201,4 +201,4 @@ Agradecemos aos seguintes colaboradores por fornecerem uma quantidade substancia
O código-fonte do MCSManager é licenciado sob a Licença [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0).
Copyright ©2024 MCSManager.
Copyright ©2025 MCSManager.

View File

@ -16,8 +16,8 @@
[官方網站](http://mcsmanager.com/) | [教學說明](https://docs.mcsmanager.com/#/zh-cn/) | [TG 群組](https://t.me/MCSManager_dev) | [成為贊助者](https://afdian.net/a/mcsmanager)
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md)
[English](README.md) | [简体中文](README_ZH.md) | [Deutsch](README_DE.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md) | [Spanish](README_ES.md)
</div>
@ -215,4 +215,4 @@ git clone https://github.com/MCSManager/MCSManager.git
原始碼遵循 [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) 協定。
Copyright ©2024 MCSManager.
Copyright ©2025 MCSManager.

View File

@ -16,8 +16,8 @@
[官方网站](http://mcsmanager.com/) | [使用文档](https://docs.mcsmanager.com/#/zh-cn/) | [QQ 群](https://jq.qq.com/?_wv=1027&k=Pgl9ScGw) | [TG 群](https://t.me/MCSManager_dev) | [成为赞助者](https://afdian.net/a/mcsmanager)
[English](README.md) | [简体中文](README_ZH.md) | [繁體中文](README_TW.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md)
[English](README.md) | [繁體中文](README_TW.md) | [Deutsch](README_DE.md) | [Português BR](README_PTBR.md) |
[日本語](README_JP.md) | [Spanish](README_ES.md)
</div>
@ -40,9 +40,9 @@
1. 支持一键开服!轻松部署 `Minecraft` Java 版/基岩版游戏服务器。
2. 兼容大部分 `Steam` 游戏服务器,列如 `幻兽帕鲁``战术小队``僵尸毁灭工程``泰拉瑞亚` 等。
3. 网页支持拖拽式的小卡片布局,打造自己喜欢的界面布局。
4. 支持 `Docker` 虚拟化,支持多用户,支持商业出租行为
5. 支持所有 `Docker` 镜像,轻松打造预设!
6. 支持分布式,一个网页即可同时管理数台机器。
4. 支持 `Docker Hub` 上的所有镜像,支持多用户,支持商业服务
5. 支持分布式,一个网页即可同时管理数台机器。
6. 技术栈简单,仅需擅长 Typescript 即可完成整个 MCSManager 开发!
7. 更多...
<br />
@ -127,17 +127,19 @@ tar -zxf mcsmanager_linux_release.tar.gz
<br />
## 浏览器兼容性
- 支持 `Chrome` `Firefox` `Safari` `Opera` 等现代主流浏览器。
- 已放弃支持 `IE` 浏览器。
<br />
## 搭建开发环境
此段落面向开发人员,普通用户无需关注也无需执行。
### 必备插件
我们使用 “VS Code” 开发 MCSManager你可能需要安装这些插件
- i18n 文案显示支持I18n Ally
- 代码格式化Prettier
- Vue - Offcial
- ESLint
### MacOS
```bash
@ -154,6 +156,10 @@ git clone https://github.com/MCSManager/MCSManager.git
./npm-dev-windows.bat
```
### 依赖文件
接下来你还需要前往 [PTY](https://github.com/MCSManager/PTY) 和 [Zip-Tools](https://github.com/MCSManager/Zip-Tools) 两个项目下载对应的二进制文件,将他们存放到 `daemon/lib` 目录下,以确保 `仿真终端``文件解压缩` 的正常工作。
### 构建生产环境版本
```bash
@ -161,7 +167,7 @@ git clone https://github.com/MCSManager/MCSManager.git
./build.sh # MacOS
```
接下来你还需要前往 [PTY](https://github.com/MCSManager/PTY) 和 [Zip-Tools](https://github.com/MCSManager/Zip-Tools) 两个项目下载对应的二进制文件,将他们存放到 `daemon/lib` 目录下,以确保 `仿真终端``文件解压缩` 的正常工作。
最终产物目录: "production-code"
<br />
@ -173,6 +179,13 @@ git clone https://github.com/MCSManager/MCSManager.git
<br />
## 浏览器兼容性
- 支持 `Chrome` `Firefox` `Safari` `Opera` 等现代主流浏览器。
- 已放弃支持 `IE` 浏览器。
<br />
## BUG 报告
欢迎发现的任何问题进行反馈,必当及时修复。
@ -196,4 +209,4 @@ git clone https://github.com/MCSManager/MCSManager.git
源代码遵循 [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) 协议。
Copyright ©2024 MCSManager.
Copyright ©2025 MCSManager.

View File

@ -11,13 +11,11 @@ cd daemon
call npm run build
echo "Build panel..."
cd ..
cd panel
cd ../panel
call npm run build
echo "Build frontend..."
cd ..
cd frontend
cd ../frontend
call npm run build
echo "Collecting files..."

1
common/global.d.ts vendored
View File

@ -74,6 +74,7 @@ export interface IGlobalInstanceDockerConfig {
cpuUsage?: number;
workingDir?: string;
env?: string[];
changeWorkdir?: boolean;
}
export interface IPanelResponseProtocol {

1080
daemon/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "mcsmanager-daemon",
"version": "4.5.2",
"version": "4.6.0",
"description": "Provides remote control capability for MCSManager to manage processes, scheduled tasks, I/O streams, and more",
"scripts": {
"dev": "ts-node --project tsconfig.json src/app.ts",
@ -20,15 +20,14 @@
"@iarna/toml": "^2.2.5",
"@koa/router": "^10.0.0",
"archiver": "^5.3.1",
"axios": "^1.1.3",
"axios": "^1.8.2",
"compressing": "^1.10.0",
"crypto": "^1.0.1",
"dockerode": "3.1.0",
"dockerode": "4.0.5",
"formidable": "^3.5.1",
"fs-extra": "^9.0.1",
"i18next": "^21.8.14",
"iconv-lite": "^0.6.2",
"koa": "^2.13.1",
"koa": "^2.16.1",
"koa-body-patch": "^6.0.1",
"koa-send": "^5.0.1",
"log4js": "^6.4.0",
@ -46,15 +45,12 @@
},
"devDependencies": {
"@types/archiver": "^5.3.1",
"@types/axios": "^0.14.0",
"@types/dockerode": "^3.2.7",
"@types/formidable": "^3.4.5",
"@types/fs-extra": "^9.0.11",
"@types/iconv-lite": "0.0.1",
"@types/koa": "^2.13.4",
"@types/koa__router": "^8.0.7",
"@types/koa-send": "^4.1.3",
"@types/log4js": "^2.3.5",
"@types/mocha": "^8.2.2",
"@types/node": "^18.11.18",
"@types/node-schedule": "^1.3.2",

View File

@ -1,12 +0,0 @@
import Instance from "../instance/instance";
import InstanceCommand from "./base/command";
export default class SendCommand extends InstanceCommand {
constructor(public readonly cmd: string) {
super("SendCommand");
}
async exec(instance: Instance) {
return await instance.execPreset("command", this.cmd);
}
}

View File

@ -10,8 +10,6 @@ import DockerStartCommand from "./docker/docker_start";
import TimeCheck from "./task/time";
import GeneralUpdateCommand from "./general/general_update";
import PtyStartCommand from "./pty/pty_start";
import PtyStopCommand from "./pty/pty_stop";
import OpenFrpTask from "./task/openfrp";
import RconCommand from "./steam/rcon_command";
import DockerResizeCommand from "./docker/docker_pty_resize";
import PtyResizeCommand from "./pty/pty_resize";
@ -45,7 +43,7 @@ export default class FunctionDispatcher extends InstanceCommand {
// the component that the instance must mount
instance.lifeCycleTaskManager.registerLifeCycleTask(new TimeCheck());
instance.lifeCycleTaskManager.registerLifeCycleTask(new OpenFrpTask());
// instance.lifeCycleTaskManager.registerLifeCycleTask(new OpenFrpTask());
// Instance general preset capabilities
instance.setPreset("command", new GeneralSendCommand());
@ -64,7 +62,6 @@ export default class FunctionDispatcher extends InstanceCommand {
// Enable emulated terminal mode
if (instance.config.terminalOption.pty && instance.config.processType === "general") {
instance.setPreset("start", new PtyStartCommand());
instance.setPreset("stop", new PtyStopCommand());
instance.setPreset("resize", new PtyResizeCommand());
}
// Whether to enable Docker PTY mode

View File

@ -9,17 +9,13 @@ import {
SetupDockerContainer,
StartupDockerProcessError
} from "../../../service/docker_process_service";
import AbsStartCommand from "../start";
export default class DockerStartCommand extends InstanceCommand {
constructor() {
super("DockerStartCommand");
}
async exec(instance: Instance, source = "Unknown") {
export default class DockerStartCommand extends AbsStartCommand {
protected async createProcess(instance: Instance) {
if (!instance.hasCwdPath() || !instance.config.ie || !instance.config.oe)
throw new StartupDockerProcessError($t("TXT_CODE_a6424dcc"));
if (!fs.existsSync(instance.absoluteCwdPath()))
throw new StartupDockerProcessError($t("TXT_CODE_instance.dirNoE"));
if (!fs.existsSync(instance.absoluteCwdPath())) fs.mkdirpSync(instance.absoluteCwdPath());
// Docker Image check
try {

View File

@ -4,16 +4,31 @@ import Instance from "../../instance/instance";
import { encode } from "iconv-lite";
import InstanceCommand from "../base/command";
export const CTRL_C = "\x03";
export function isExitCommand(instance: Instance, buf: any) {
if (String(buf).toLowerCase() === "^c") {
instance.process?.kill("SIGINT");
return true;
}
if (buf == CTRL_C) {
instance.process?.write(CTRL_C);
return true;
}
return false;
}
export default class GeneralSendCommand extends InstanceCommand {
constructor() {
super("SendCommand");
}
async exec(instance: Instance, buf?: any): Promise<any> {
if (isExitCommand(instance, buf)) return;
// The server shutdown command needs to send a command, but before the server shutdown command is executed, the status will be set to the shutdown state.
// So here the command can only be executed by whether the process exists or not
if (instance?.process) {
instance.process.write(encode(buf, instance.config.oe));
instance.process.write(encode(buf, instance.config.ie));
if (instance.config.crlf === 2) return instance.process.write("\r\n");
return instance.process.write("\n");
} else {

View File

@ -1,3 +1,4 @@
import { $t } from "../../../i18n";
import logger from "../../../service/log";
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
@ -8,6 +9,14 @@ export default class GeneralKillCommand extends InstanceCommand {
}
async exec(instance: Instance) {
if (instance.status() === Instance.STATUS_STOP) return;
if (instance.startTimestamp && instance.startTimestamp + 6 * 1000 > Date.now()) {
return instance.failure(new Error($t("TXT_CODE_6259357c")));
}
instance.ignoreEventTaskOnce();
const task = instance?.asynchronousTask;
if (task && task.stop) {
task
@ -17,9 +26,9 @@ export default class GeneralKillCommand extends InstanceCommand {
logger.error(`Instance ${instance.config.nickname} asynchronousTask stop error:`, err);
});
}
if (instance.process) {
await instance.process.kill("SIGKILL");
}
instance.setLock(false);
}
}

View File

@ -9,9 +9,10 @@ export default class GeneralRestartCommand extends InstanceCommand {
async exec(instance: Instance) {
try {
instance.ignoreEventTaskOnce();
instance.println("INFO", $t("TXT_CODE_restart.start"));
await instance.execPreset("stop");
instance.setLock(true);
await instance.execPreset("stop");
const startCount = instance.startCount;
// Check the instance status every second,
// if the instance status is stopped, restart the server immediately
@ -28,9 +29,9 @@ export default class GeneralRestartCommand extends InstanceCommand {
}
if (instance.status() === Instance.STATUS_STOP) {
instance.println("INFO", $t("TXT_CODE_restart.restarting"));
await instance.execPreset("start");
instance.setLock(false);
clearInterval(task);
await instance.execPreset("start");
}
} catch (error: any) {
clearInterval(task);

View File

@ -1,14 +1,13 @@
import { $t } from "../../../i18n";
import os from "os";
import Instance from "../../instance/instance";
import logger from "../../../service/log";
import fs from "fs-extra";
import InstanceCommand from "../base/command";
import EventEmitter from "events";
import { IInstanceProcess } from "../../instance/interface";
import { ChildProcess, exec, spawn } from "child_process";
import { ChildProcess, spawn } from "child_process";
import { commandStringToArray } from "../base/command_parser";
import { killProcess } from "common";
import AbsStartCommand from "../start";
// Error exception at startup
class StartupError extends Error {
@ -57,12 +56,8 @@ class ProcessAdapter extends EventEmitter implements IInstanceProcess {
}
}
export default class GeneralStartCommand extends InstanceCommand {
constructor() {
super("StartCommand");
}
async exec(instance: Instance, source = "Unknown") {
export default class GeneralStartCommand extends AbsStartCommand {
async createProcess(instance: Instance, source = "") {
if (
(!instance.config.startCommand && instance.config.processType === "general") ||
!instance.hasCwdPath() ||
@ -70,8 +65,7 @@ export default class GeneralStartCommand extends InstanceCommand {
!instance.config.oe
)
throw new StartupError($t("TXT_CODE_general_start.instanceConfigErr"));
if (!fs.existsSync(instance.absoluteCwdPath()))
throw new StartupError($t("TXT_CODE_general_start.cwdPathNotExist"));
if (!fs.existsSync(instance.absoluteCwdPath())) fs.mkdirpSync(instance.absoluteCwdPath());
// command parsing
const commandList = commandStringToArray(instance.config.startCommand);

View File

@ -1,8 +1,6 @@
import { $t } from "../../../i18n";
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import SendCommand from "../cmd";
import RconCommand from "../steam/rcon_command";
export default class GeneralStopCommand extends InstanceCommand {
constructor() {
@ -15,20 +13,16 @@ export default class GeneralStopCommand extends InstanceCommand {
return instance.failure(new Error($t("TXT_CODE_general_stop.notRunning")));
instance.status(Instance.STATUS_STOPPING);
instance.ignoreEventTaskOnce();
const stopCommandList = stopCommand.split("\n");
for (const stopCommand of stopCommandList) {
if (stopCommand.toLowerCase() == "^c") {
instance.process.kill("SIGINT");
} else if (instance.config.enableRcon) {
await instance.exec(new RconCommand(stopCommand));
} else {
await instance.exec(new SendCommand(stopCommand));
}
await instance.execPreset("command", stopCommand);
}
instance.print("\n");
instance.println("INFO", $t("TXT_CODE_general_stop.execCmd", { stopCommand }));
instance.println("INFO", $t("TXT_CODE_pty_stop.execCmd", { stopCommand: `\n${stopCommand}` }));
const cacheStartCount = instance.startCount;
// If the instance is still in the stopped state after 10 minutes, restore the state

View File

@ -1,17 +0,0 @@
import Instance from "../instance/instance";
import InstanceCommand from "./base/command";
export default class KillCommand extends InstanceCommand {
constructor() {
super("KillCommand");
}
async exec(instance: Instance) {
// If the automatic restart function is enabled, the setting is ignored once
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
instance.config.eventTask.ignore = true;
// send stop command
return await instance.execPreset("kill");
}
}

View File

@ -5,7 +5,6 @@ import logger from "../../../service/log";
import fs from "fs-extra";
import path from "path";
import readline from "readline";
import InstanceCommand from "../base/command";
import EventEmitter from "events";
import { IInstanceProcess } from "../../instance/interface";
import { ChildProcess, ChildProcessWithoutNullStreams, exec, spawn } from "child_process";
@ -15,6 +14,7 @@ import FunctionDispatcher from "../dispatcher";
import { PTY_PATH } from "../../../const";
import { Writable } from "stream";
import { v4 } from "uuid";
import AbsStartCommand from "../start";
interface IPtySubProcessCfg {
pid: number;
@ -44,22 +44,26 @@ export class GoPtyProcessAdapter extends EventEmitter implements IInstanceProces
process.stdout?.on("data", (text) => this.emit("data", text));
process.stderr?.on("data", (text) => this.emit("data", text));
process.on("exit", (code) => this.emit("exit", code));
try {
this.initNamedPipe();
} catch (error: any) {
logger.error(`Init Pipe Err: ${pipeName}, ${error}`);
}
this.initNamedPipe();
}
private initNamedPipe() {
const fd = fs.openSync(this.pipeName, "w");
const writePipe = fs.createWriteStream("", { fd });
writePipe.on("close", () => {});
writePipe.on("end", () => {});
writePipe.on("error", (err) => {
logger.error("Pipe error:", this.pipeName, err);
});
this.pipeClient = writePipe;
private async initNamedPipe() {
try {
const fd = await fs.open(this.pipeName, "w");
const writePipe = fs.createWriteStream("", { fd });
writePipe.on("close", () => {});
writePipe.on("end", () => {});
writePipe.on("error", (err) => {
logger.error("Pipe error:", this.pipeName, err);
});
this.pipeClient = writePipe;
} catch (error) {
throw new Error(
$t("TXT_CODE_9d1d244f", {
pipeName: error
})
);
}
}
public resize(w: number, h: number) {
@ -111,11 +115,7 @@ export class GoPtyProcessAdapter extends EventEmitter implements IInstanceProces
}
}
export default class PtyStartCommand extends InstanceCommand {
constructor() {
super("PtyStartCommand");
}
export default class PtyStartCommand extends AbsStartCommand {
readPtySubProcessConfig(subProcess: ChildProcessWithoutNullStreams): Promise<IPtySubProcessCfg> {
return new Promise((r, j) => {
const errConfig = {
@ -141,7 +141,7 @@ export default class PtyStartCommand extends InstanceCommand {
});
}
async exec(instance: Instance, source = "Unknown") {
async createProcess(instance: Instance) {
if (
!instance.config.startCommand ||
!instance.hasCwdPath() ||
@ -149,13 +149,12 @@ export default class PtyStartCommand extends InstanceCommand {
!instance.config.oe
)
throw new StartupError($t("TXT_CODE_pty_start.cmdErr"));
if (!fs.existsSync(instance.absoluteCwdPath()))
throw new StartupError($t("TXT_CODE_pty_start.cwdNotExist"));
if (!fs.existsSync(instance.absoluteCwdPath())) fs.mkdirpSync(instance.absoluteCwdPath());
if (!path.isAbsolute(path.normalize(instance.absoluteCwdPath())))
throw new StartupError($t("TXT_CODE_pty_start.mustAbsolutePath"));
// PTY mode correctness check
logger.info($t("TXT_CODE_pty_start.startPty", { source: source }));
logger.info($t("TXT_CODE_pty_start.startPty", { source: "" }));
let checkPtyEnv = true;
if (!fs.existsSync(PTY_PATH)) {
@ -167,12 +166,11 @@ export default class PtyStartCommand extends InstanceCommand {
// Close the PTY type, reconfigure the instance function group, and restart the instance
instance.config.terminalOption.pty = false;
await instance.forceExec(new FunctionDispatcher());
await instance.execPreset("start", source); // execute the preset command directly
await instance.execPreset("start"); // execute the preset command directly
return;
}
// Set the startup state & increase the number of startups
instance.setLock(true);
instance.status(Instance.STATUS_STARTING);
instance.startCount++;
@ -210,7 +208,7 @@ export default class PtyStartCommand extends InstanceCommand {
];
logger.info("----------------");
logger.info($t("TXT_CODE_pty_start.sourceRequest", { source: source }));
logger.info($t("TXT_CODE_pty_start.sourceRequest", { source: "" }));
logger.info($t("TXT_CODE_pty_start.instanceUuid", { instanceUuid: instance.instanceUuid }));
logger.info($t("TXT_CODE_pty_start.startCmd", { cmd: commandList.join(" ") }));
logger.info($t("TXT_CODE_pty_start.ptyPath", { path: PTY_PATH }));
@ -246,7 +244,6 @@ export default class PtyStartCommand extends InstanceCommand {
// create process adapter
const ptySubProcessCfg = await this.readPtySubProcessConfig(subProcess);
const processAdapter = new GoPtyProcessAdapter(subProcess, ptySubProcessCfg.pid, pipeName);
logger.info(`pty.exe subprocess PID: ${JSON.stringify(ptySubProcessCfg)}`);
// After reading the configuration, Need to check the process status
// The "processAdapter.pid" here represents the process created by the PTY process

View File

@ -1,43 +0,0 @@
import { $t } from "../../../i18n";
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import SendCommand from "../cmd";
export default class PtyStopCommand extends InstanceCommand {
constructor() {
super("PtyStopCommand");
}
async exec(instance: Instance) {
let stopCommand = instance.config.stopCommand;
if (instance.status() === Instance.STATUS_STOP || !instance.process)
return instance.failure(new Error($t("TXT_CODE_pty_stop.notRunning")));
instance.status(Instance.STATUS_STOPPING);
instance.println("INFO", $t("TXT_CODE_pty_stop.execCmd", { stopCommand: stopCommand }));
const stopCommandList = stopCommand.split("\n");
for (const stopCommandColumn of stopCommandList) {
if (stopCommandColumn.toLocaleLowerCase() == "^c") {
await instance.exec(new SendCommand("\x03"));
} else {
await instance.exec(new SendCommand(stopCommandColumn));
}
}
// If the instance is still in the stopped state after 10 minutes, restore the state
const cacheStartCount = instance.startCount;
setTimeout(() => {
if (
instance.status() === Instance.STATUS_STOPPING &&
instance.startCount === cacheStartCount
) {
instance.println("ERROR", $t("TXT_CODE_pty_stop.stopErr"));
instance.status(Instance.STATUS_RUNNING);
}
}, 1000 * 60 * 10);
return instance;
}
}

View File

@ -1,21 +0,0 @@
import { $t } from "../../i18n";
import Instance from "../instance/instance";
import InstanceCommand from "./base/command";
export default class RestartCommand extends InstanceCommand {
constructor() {
super("RestartCommand");
}
async exec(instance: Instance) {
// If the automatic restart function is enabled, the setting is ignored once
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
instance.config.eventTask.ignore = true;
if (instance.status() !== Instance.STATUS_RUNNING) {
throw new Error($t("TXT_CODE_d58ffa0f"));
}
return await instance.execPreset("restart");
}
}

View File

@ -2,55 +2,46 @@ import { $t } from "../../i18n";
import Instance from "../instance/instance";
import InstanceCommand from "./base/command";
class StartupError extends Error {
export class StartupError extends Error {
constructor(msg: string) {
super(msg);
}
}
export default class StartCommand extends InstanceCommand {
public source: string;
constructor(source = "Unknown") {
super("StartCommand");
this.source = source;
}
export default abstract class AbsStartCommand extends InstanceCommand {
private async sleep() {
return new Promise((ok) => {
setTimeout(ok, 1000 * 3);
setTimeout(ok, 1000 * 2);
});
}
async exec(instance: Instance) {
if (instance.status() !== Instance.STATUS_STOP)
return instance.failure(new StartupError($t("TXT_CODE_start.instanceNotDown")));
try {
instance.setLock(true);
instance.status(Instance.STATUS_STARTING);
instance.startCount++;
// expiration time check
instance.startTimestamp = Date.now();
if (instance.config.endTime) {
const endTime = instance.config.endTime;
if (endTime) {
const currentTime = Date.now();
if (endTime <= currentTime) {
if (endTime <= instance.startTimestamp) {
throw new Error($t("TXT_CODE_start.instanceMaturity"));
}
}
}
const currentTimestamp = Date.now();
instance.startTimestamp = currentTimestamp;
instance.print("\n");
instance.print("\n\n");
instance.println("INFO", $t("TXT_CODE_start.startInstance"));
// prevent the dead-loop from starting
await this.sleep();
return await instance.execPreset("start", this.source);
return await this.createProcess(instance);
} catch (error: any) {
try {
await instance.execPreset("kill");
@ -62,4 +53,6 @@ export default class StartCommand extends InstanceCommand {
instance.setLock(false);
}
}
protected abstract createProcess(instance: Instance): Promise<void>;
}

View File

@ -2,6 +2,7 @@ import { t } from "i18next";
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import Rcon from "rcon-srcds";
import { isExitCommand } from "../general/general_command";
async function sendRconCommand(instance: Instance, command: string) {
const targetIp = instance.config.rconIp || "localhost";
@ -45,6 +46,7 @@ export default class RconCommand extends InstanceCommand {
}
async exec(instance: Instance, text?: string): Promise<any> {
if (isExitCommand(instance, text)) return;
try {
if (text || this.cmd) {
await sendRconCommand(instance, String(text ?? this.cmd));

View File

@ -1,18 +0,0 @@
import Instance from "../instance/instance";
import InstanceCommand from "./base/command";
import SendCommand from "./cmd";
export default class StopCommand extends InstanceCommand {
constructor() {
super("StopCommand");
}
async exec(instance: Instance) {
// If the automatic restart function is enabled, the setting is ignored once
if (instance.config.eventTask && instance.config.eventTask.autoRestart)
instance.config.eventTask.ignore = true;
// send stop command
return await instance.execPreset("stop");
}
}

View File

@ -1,12 +1,8 @@
import { v4 } from "uuid";
import fs from "fs-extra";
import path from "path";
import { spawn, ChildProcess } from "child_process";
import os from "os";
import { killProcess } from "common";
import { ILifeCycleTask } from "../../instance/life_cycle";
import Instance from "../../instance/instance";
import KillCommand from "../kill";
import logger from "../../../service/log";
import { $t } from "../../../i18n";
import { ProcessWrapper } from "common";

View File

@ -1,6 +1,5 @@
import { ILifeCycleTask } from "../../instance/life_cycle";
import Instance from "../../instance/instance";
import KillCommand from "../kill";
// When the instance is running, continue to check the expiration time
export default class TimeCheck implements ILifeCycleTask {
@ -17,7 +16,7 @@ export default class TimeCheck implements ILifeCycleTask {
const currentTime = Date.now();
if (endTime <= currentTime) {
// Expired, execute the end process command
await instance.exec(new KillCommand());
await instance.execPreset("kill");
clearInterval(this.task);
}
}

View File

@ -1,14 +0,0 @@
import Instance from "../instance/instance";
import InstanceCommand from "./base/command";
import SendCommand from "./cmd";
export default class UpdateCommand extends InstanceCommand {
constructor() {
super("UpdateCommand");
}
async exec(instance: Instance) {
// Execute the update preset, the preset and function scheduler are set before starting
return await instance.execPreset("update");
}
}

View File

@ -66,7 +66,8 @@ export default class InstanceConfig implements IGlobalInstanceConfig {
io: 0,
network: 0,
workingDir: "/workspace/",
env: []
env: [],
changeWorkdir: true
};
public pingConfig = {

View File

@ -209,6 +209,7 @@ export default class Instance extends EventEmitter {
configureEntityParams(this.config.docker, cfg.docker, "cpuUsage", Number);
configureEntityParams(this.config.docker, cfg.docker, "env");
configureEntityParams(this.config.docker, cfg.docker, "workingDir", String);
configureEntityParams(this.config.docker, cfg.docker, "changeWorkdir", Boolean);
}
if (cfg.pingConfig) {
configureEntityParams(this.config.pingConfig, cfg.pingConfig, "ip", String);
@ -228,23 +229,12 @@ export default class Instance extends EventEmitter {
}
setLock(bool: boolean) {
if (this.lock === true && bool === true) {
throw new Error($t("TXT_CODE_ca030197"));
}
this.lock = bool;
}
// Execute the corresponding command for this instance
async execCommand(command: InstanceCommand) {
if (this.lock)
throw new Error($t("TXT_CODE_instanceConf.instanceLock", { info: command.info }));
if (this.status() == Instance.STATUS_BUSY)
throw new Error($t("TXT_CODE_instanceConf.instanceBusy"));
return await command.exec(this);
}
// Execute the corresponding command for this instance Alias
async exec(command: InstanceCommand) {
return await this.execCommand(command);
}
// force the command to execute
async forceExec(command: InstanceCommand) {
return await command.exec(this);
@ -288,6 +278,7 @@ export default class Instance extends EventEmitter {
this.releaseResources();
if (this.instanceStatus != Instance.STATUS_STOP) {
this.instanceStatus = Instance.STATUS_STOP;
this.startTimestamp = 0;
this.emit("exit", code);
StorageSubsystem.store("InstanceConfig", this.instanceUuid, this.config);
}
@ -297,30 +288,33 @@ export default class Instance extends EventEmitter {
this.lifeCycleTaskManager.execLifeCycleTask(0);
// If automatic restart is enabled, the startup operation is performed immediately
if (this.config.eventTask.autoRestart) {
if (!this.config.eventTask.ignore) {
this.forceExec(new StartCommand("Event Task: Auto Restart"))
.then(() => {
this.println($t("TXT_CODE_instanceConf.info"), $t("TXT_CODE_instanceConf.autoRestart"));
})
.catch((err) => {
this.println(
$t("TXT_CODE_instanceConf.error"),
$t("TXT_CODE_instanceConf.autoRestartErr", { err: err })
);
});
}
this.config.eventTask.ignore = false;
if (!this.config.eventTask.ignore && this.config.eventTask.autoRestart) {
this.execPreset("start")
.then(() => {
this.println($t("TXT_CODE_instanceConf.info"), $t("TXT_CODE_instanceConf.autoRestart"));
})
.catch((err) => {
this.println(
$t("TXT_CODE_instanceConf.error"),
$t("TXT_CODE_instanceConf.autoRestartErr", { err: err })
);
});
}
this.config.eventTask.ignore = false;
// Turn off the warning immediately after startup, usually the startup command is written incorrectly
const currentTimestamp = new Date().getTime();
const startThreshold = 6 * 1000;
const startThreshold = 2 * 1000;
if (currentTimestamp - this.startTimestamp < startThreshold) {
this.println("ERROR", $t("TXT_CODE_instanceConf.instantExit"));
this.println("ERROR", $t("TXT_CODE_aae2918f"));
}
}
ignoreEventTaskOnce() {
if (this.config.eventTask) this.config.eventTask.ignore = true;
}
// custom output method, formatting
println(level: string, text: string) {
const str = `[${level}] ${text}\n`;
@ -361,9 +355,6 @@ export default class Instance extends EventEmitter {
}
absoluteCwdPath() {
const envInstancesBasePath = toText(process.env.MCSM_INSTANCES_BASE_PATH);
if (envInstancesBasePath)
return path.normalize(path.join(envInstancesBasePath, this.instanceUuid));
if (!this.config || !this.config.cwd) throw new Error("Instance config error, cwd is Null!");
if (path.isAbsolute(this.config.cwd)) return path.normalize(this.config.cwd);
return path.normalize(path.join(process.cwd(), this.config.cwd));
@ -413,6 +404,13 @@ export default class Instance extends EventEmitter {
this.info.latency = 0;
}
public parseTextParams(text: string) {
text = text.replace(/\{mcsm_workspace\}/gim, this.absoluteCwdPath());
text = text.replace(/\{mcsm_instance_id\}/gim, this.instanceUuid);
text = text.replace(/\{mcsm_cwd\}/gim, this.absoluteCwdPath());
return text;
}
private pushOutput(data: string) {
if (data.length > LINE_MAX_SIZE * 100) {
this.outputStack.push(IGNORE_TEXT);

View File

@ -61,7 +61,7 @@ export class ProcessConfig {
}
}
if (this.iProcessConfig.type === "json") {
text = JSON.stringify(object);
text = JSON.stringify(object, null, 4);
}
if (this.iProcessConfig.type === "txt") {
text = object.toString();

View File

@ -7,19 +7,14 @@ import Instance from "../entity/instance/instance";
import logger from "../service/log";
import path from "path";
import StartCommand from "../entity/commands/start";
import StopCommand from "../entity/commands/stop";
import SendCommand from "../entity/commands/cmd";
import KillCommand from "../entity/commands/kill";
import { IInstanceDetail, IJson } from "../service/interfaces";
import ProcessInfoCommand from "../entity/commands/process_info";
import FileManager from "../service/system_file";
import { ProcessConfig } from "../entity/instance/process_config";
import RestartCommand from "../entity/commands/restart";
import { TaskCenter } from "../service/async_task_service";
import { createQuickInstallTask } from "../service/async_task_service/quick_install";
import { QuickInstallTask } from "../service/async_task_service/quick_install";
import { toNumber, toText } from "common";
import { toNumber } from "common";
import { arrayUnique } from "common";
// Some instances operate router authentication middleware
@ -66,9 +61,11 @@ routerApp.on("instance/select", (ctx, data) => {
return false;
if (condition.status && v.instanceStatus !== Number(condition.status)) return false;
const curInstanceTagText = v.config.tag.join(",");
if (searchTags.length > 0 && searchTags.some((v) => !curInstanceTagText.includes(v)))
return false;
if (searchTags.length > 0) {
const myTags = v.config.tag || [];
const res = myTags.filter((v) => searchTags.includes(v));
if (res.length === 0 || res.length !== searchTags.length) return false;
}
return true;
});
result = result.sort((a, b) => (a.config.nickname > b.config.nickname ? 1 : -1));
@ -86,6 +83,9 @@ routerApp.on("instance/select", (ctx, data) => {
});
overview.sort((a, b) => {
if (a.status !== b.status) {
return b.status - a.status;
}
return a.config.nickname >= b.config.nickname ? 1 : -1;
});
@ -221,7 +221,7 @@ routerApp.on("instance/open", async (ctx, data) => {
for (const instanceUuid of data.instanceUuids) {
const instance = InstanceSubsystem.getInstance(instanceUuid);
try {
await instance!.exec(new StartCommand(ctx.socket.id));
await instance!.execPreset("start");
if (!disableResponse) protocol.msg(ctx, "instance/open", { instanceUuid });
} catch (err: any) {
if (!disableResponse) {
@ -242,7 +242,7 @@ routerApp.on("instance/stop", async (ctx, data) => {
const instance = InstanceSubsystem.getInstance(instanceUuid);
try {
if (!instance) throw new Error($t("TXT_CODE_3bfb9e04"));
await instance.exec(new StopCommand());
await instance.execPreset("stop");
//Note: Removing this reply will cause the front-end response to be slow, because the front-end will wait for the panel-side message to be forwarded
if (!disableResponse) protocol.msg(ctx, "instance/stop", { instanceUuid });
} catch (err: any) {
@ -259,7 +259,7 @@ routerApp.on("instance/restart", async (ctx, data) => {
const instance = InstanceSubsystem.getInstance(instanceUuid);
try {
if (!instance) throw new Error($t("TXT_CODE_3bfb9e04"));
await instance.exec(new RestartCommand());
await instance.execPreset("restart");
if (!disableResponse) protocol.msg(ctx, "instance/restart", { instanceUuid });
} catch (err: any) {
if (!disableResponse)
@ -275,7 +275,7 @@ routerApp.on("instance/kill", async (ctx, data) => {
const instance = InstanceSubsystem.getInstance(instanceUuid);
if (!instance) continue;
try {
await instance.forceExec(new KillCommand());
await instance.execPreset("kill");
if (!disableResponse) protocol.msg(ctx, "instance/kill", { instanceUuid });
} catch (err: any) {
if (!disableResponse)
@ -292,7 +292,7 @@ routerApp.on("instance/command", async (ctx, data) => {
const instance = InstanceSubsystem.getInstance(instanceUuid);
try {
if (!instance) throw new Error($t("TXT_CODE_3bfb9e04"));
await instance.exec(new SendCommand(command));
await instance.execPreset("command", command);
if (!disableResponse) protocol.msg(ctx, "instance/command", { instanceUuid });
} catch (err: any) {
if (!disableResponse)

View File

@ -55,8 +55,8 @@ router.get("/download/:key/:fileName", async (ctx) => {
// File upload route
router.post("/upload/:key", async (ctx) => {
const key = ctx.params.key;
const unzip = ctx.query.unzip;
const key = String(ctx.params.key);
const unzip = Boolean(ctx.query.unzip);
const zipCode = String(ctx.query.code);
let tmpFiles: formidable.File | formidable.File[] | undefined;
try {
@ -120,8 +120,8 @@ router.post("/upload/:key", async (ctx) => {
});
if (unzip) {
const fileManager = new FileManager(instance.absoluteCwdPath());
fileManager.unzip(fileSaveAbsolutePath, "", zipCode);
const instanceFiles = new FileManager(instance.absoluteCwdPath());
instanceFiles.unzip(fileSaveAbsolutePath, ".", zipCode);
}
ctx.body = "OK";
return;

View File

@ -7,7 +7,6 @@ import {
streamLoginSuccessful
} from "../service/mission_passport";
import InstanceSubsystem from "../service/system_instance";
import SendCommand from "../entity/commands/cmd";
import { IGNORE } from "../const";
// Authorization authentication middleware
@ -79,7 +78,7 @@ routerApp.on("stream/input", async (ctx, data) => {
const command = data.command;
const instanceUuid = ctx.session?.stream?.instanceUuid;
const instance = InstanceSubsystem.getInstance(instanceUuid);
await instance?.exec(new SendCommand(command));
await instance?.execPreset("command", command);
} catch (error: any) {
// Ignore potential high frequency exceptions here
}

View File

@ -12,6 +12,8 @@ import { EventEmitter } from "stream";
import { IInstanceProcess } from "../entity/instance/interface";
import { AsyncTask } from "./async_task_service";
import iconv from "iconv-lite";
import { toText } from "common";
import fs from "fs-extra";
// Error exception at startup
export class StartupDockerProcessError extends Error {
@ -83,19 +85,19 @@ export class SetupDockerContainer extends AsyncTask {
}
// memory limit
let maxMemory = undefined;
let maxMemory: number | undefined = undefined;
if (instance.config.docker.memory) maxMemory = instance.config.docker.memory * 1024 * 1024;
// CPU usage calculation
let cpuQuota = undefined;
let cpuPeriod = undefined;
let cpuQuota: number | undefined = undefined;
let cpuPeriod: number | undefined = undefined;
if (instance.config.docker.cpuUsage) {
cpuQuota = instance.config.docker.cpuUsage * 10 * 1000;
cpuPeriod = 1000 * 1000;
}
// Check the number of CPU cores
let cpusetCpus = undefined;
let cpusetCpus: string | undefined = undefined;
if (instance.config.docker.cpusetCpus) {
const arr = instance.config.docker.cpusetCpus.split(",");
arr.forEach((v) => {
@ -115,16 +117,36 @@ export class SetupDockerContainer extends AsyncTask {
// Whether to use TTY mode
const isTty = instance.config.terminalOption.pty;
const workingDir = instance.config.docker.workingDir ?? "";
const cwd = instance.absoluteCwdPath();
const workingDir = instance.config.docker.workingDir || undefined;
let cwd = instance.absoluteCwdPath();
const hostRealPath = toText(process.env.MCSM_DOCKER_WORKSPACE_PATH);
if (hostRealPath) {
cwd = path.normalize(path.join(hostRealPath, instance.instanceUuid));
}
if (workingDir) {
instance.println("CONTAINER", $t("TXT_CODE_e76e49e9") + cwd + " --> " + workingDir + "\n");
} else {
instance.println("CONTAINER", $t("TXT_CODE_ffa884f9"));
}
// output startup log
const mounts: Docker.MountConfig =
extraBinds.map((v) => {
const hostPath = instance.parseTextParams(v.hostPath);
if (!fs.existsSync(hostPath)) fs.mkdirsSync(hostPath);
return {
Type: "bind",
Source: hostPath,
Target: instance.parseTextParams(v.containerPath)
};
}) || [];
if (workingDir && cwd) {
mounts.push({
Type: "bind",
Source: cwd,
Target: instance.parseTextParams(workingDir)
});
}
logger.info("----------------");
logger.info(`[SetupDockerContainer]`);
logger.info(`UUID: [${instance.instanceUuid}] [${instance.config.nickname}]`);
@ -133,33 +155,12 @@ export class SetupDockerContainer extends AsyncTask {
logger.info(`CWD: ${cwd}, WORKING_DIR: ${workingDir}`);
logger.info(`NET_MODE: ${instance.config.docker.networkMode}`);
logger.info(`OPEN_PORT: ${JSON.stringify(publicPortArray)}`);
logger.info(
`BINDS: ${JSON.stringify([
workingDir ? `${cwd} --> ${workingDir}` : "<Working directory not mounted>",
...extraBinds
])}`
);
logger.info(`Volume Mounts: ${JSON.stringify(mounts)}`);
logger.info(`NET_ALIASES: ${JSON.stringify(instance.config.docker.networkAliases)}`);
logger.info(`MEM_LIMIT: ${maxMemory || "--"} MB`);
logger.info(`TYPE: Docker Container`);
logger.info("----------------");
const mounts: Docker.MountConfig =
extraBinds.map((v) => {
return {
Type: "bind",
Source: v.hostPath,
Target: v.containerPath
};
}) || [];
if (workingDir && cwd) {
mounts.push({
Type: "bind",
Source: cwd,
Target: workingDir
});
}
// Start Docker container creation and running
const docker = new DefaultDocker();
this.container = await docker.createContainer({
@ -170,12 +171,13 @@ export class SetupDockerContainer extends AsyncTask {
AttachStdout: true,
AttachStderr: true,
Tty: isTty,
WorkingDir: workingDir,
Cmd: commandList ? commandList : undefined,
WorkingDir: instance.config.docker.changeWorkdir ? workingDir : undefined,
Cmd: commandList.length > 0 ? commandList : undefined,
OpenStdin: true,
StdinOnce: false,
ExposedPorts: exposedPorts,
Env: instance.config.docker?.env || [],
HostConfig: {
Memory: maxMemory,
AutoRemove: true,
@ -198,7 +200,7 @@ export class SetupDockerContainer extends AsyncTask {
await this.container.start();
// Listen to events
this.container.wait(async (v) => {
this.container.wait(() => {
this.stop();
});
}

View File

@ -19,8 +19,7 @@ export class InstanceUpdateAction extends AsyncTask {
}
public async onStart() {
let updateCommand = this.instance.config.updateCommand;
updateCommand = updateCommand.replace(/\{mcsm_workspace\}/gm, this.instance.absoluteCwdPath());
const updateCommand = this.instance.parseTextParams(this.instance.config.updateCommand);
logger.info(
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })
);
@ -29,6 +28,7 @@ export class InstanceUpdateAction extends AsyncTask {
);
logger.info(updateCommand);
this.instance.print("\n");
this.instance.println(
$t("TXT_CODE_general_update.update"),
$t("TXT_CODE_general_update.readyUpdate", { instanceUuid: this.instance.instanceUuid })

View File

@ -6,6 +6,7 @@ interface IMission {
start: number;
end: number;
count?: number;
isDeleted?: boolean;
}
// Task passport manager
@ -18,9 +19,9 @@ class MissionPassport {
setInterval(() => {
const t = new Date().getTime();
this.missions.forEach((m, k) => {
if (t > m.end) this.missions.delete(k);
if (t > m.end || m.isDeleted) this.missions.delete(k);
});
}, 1000);
}, 1000 * 60);
}
// register task passport
@ -39,7 +40,8 @@ class MissionPassport {
}
public deleteMission(password: string) {
this.missions.delete(password);
const m = this.missions.get(password);
if (m) m.isDeleted = true;
}
}

View File

@ -39,7 +39,10 @@ export default class FileManager {
toAbsolutePath(fileName: string = "") {
const topAbsolutePath = this.topPath;
let finalPath: string = "";
if (path.normalize(fileName).indexOf(topAbsolutePath) === 0) return fileName;
let finalPath = "";
if (os.platform() === "win32") {
const reg = new RegExp("^[A-Za-z]{1}:[\\\\/]{1}");
if (reg.test(this.cwd)) {
@ -48,6 +51,7 @@ export default class FileManager {
finalPath = path.normalize(fileName);
}
}
if (!finalPath) {
finalPath = path.normalize(path.join(this.topPath, this.cwd, fileName));
}

View File

@ -4,7 +4,6 @@ import path from "path";
import os from "os";
import Instance from "../entity/instance/instance";
import EventEmitter from "events";
import KillCommand from "../entity/commands/kill";
import logger from "./log";
import { v4 } from "uuid";
import { Socket } from "socket.io";
@ -13,7 +12,6 @@ import InstanceConfig from "../entity/instance/Instance_config";
import { QueryMapWrapper, InstanceStreamListener } from "common";
import FunctionDispatcher from "../entity/commands/dispatcher";
import InstanceControl from "./system_instance_control";
import StartCommand from "../entity/commands/start";
import { globalConfiguration } from "../entity/config";
// init instance default install path
@ -44,25 +42,27 @@ class InstanceSubsystem extends EventEmitter {
private autoStart() {
this.instances.forEach((instance) => {
if (instance.config.eventTask.autoStart) {
instance
.exec(new StartCommand())
.then(() => {
logger.info(
$t("TXT_CODE_system_instance.autoStart", {
name: instance.config.nickname,
uuid: instance.instanceUuid
})
);
})
.catch((reason) => {
logger.error(
$t("TXT_CODE_system_instance.autoStartErr", {
name: instance.config.nickname,
uuid: instance.instanceUuid,
reason: reason
})
);
});
setTimeout(() => {
instance
.execPreset("start")
.then(() => {
logger.info(
$t("TXT_CODE_system_instance.autoStart", {
name: instance.config.nickname,
uuid: instance.instanceUuid
})
);
})
.catch((reason) => {
logger.error(
$t("TXT_CODE_system_instance.autoStartErr", {
name: instance.config.nickname,
uuid: instance.instanceUuid,
reason: reason
})
);
});
}, 1000 * 10);
}
});
}
@ -108,7 +108,6 @@ class InstanceSubsystem extends EventEmitter {
this.GLOBAL_INSTANCE_UUID
);
// handle autostart
this.autoStart();
}
@ -228,7 +227,7 @@ class InstanceSubsystem extends EventEmitter {
`Instance ${instance.config.nickname} (${instance.instanceUuid}) is running or busy, and is being forced to end.`
);
promises.push(
instance.execCommand(new KillCommand()).then(() => {
instance.execPreset("kill").then(() => {
if (!this.isGlobalInstance(instance))
StorageSubsystem.store("InstanceConfig", instance.instanceUuid, instance.config);
logger.info(

View File

@ -3,11 +3,6 @@ import schedule from "node-schedule";
import InstanceSubsystem from "./system_instance";
import StorageSubsystem from "../common/system_storage";
import logger from "./log";
import StartCommand from "../entity/commands/start";
import StopCommand from "../entity/commands/stop";
import SendCommand from "../entity/commands/cmd";
import RestartCommand from "../entity/commands/restart";
import KillCommand from "../entity/commands/kill";
import FileManager from "./system_file";
// Scheduled task configuration item interface
@ -170,26 +165,26 @@ class InstanceControlSubsystem {
// logger.info(`Execute scheduled task: ${task.name} ${task.action} ${task.time} ${task.count} `);
if (task.action === "start") {
if (instanceStatus === 0) {
return await instance.exec(new StartCommand("ScheduleJob"));
return await instance.execPreset("start");
}
}
if (task.action === "stop") {
if (instanceStatus === 3) {
return await instance.exec(new StopCommand());
return await instance.execPreset("stop");
}
}
if (task.action === "restart") {
if (instanceStatus === 3) {
return await instance.exec(new RestartCommand());
return await instance.execPreset("restart");
}
}
if (task.action === "command") {
if (instanceStatus === 3) {
return await instance.exec(new SendCommand(payload));
return await instance.execPreset("command", payload);
}
}
if (task.action === "kill") {
return await instance.exec(new KillCommand());
return await instance.execPreset("kill");
}
} catch (error: any) {
logger.error(

View File

@ -1,118 +0,0 @@
import { $t } from "../i18n";
import readline from "readline";
import * as protocol from "./protocol";
import InstanceSubsystem from "./system_instance";
import { globalConfiguration } from "../entity/config";
import logger from "./log";
import StartCommand from "../entity/commands/start";
import StopCommand from "../entity/commands/stop";
import KillCommand from "../entity/commands/kill";
import SendCommand from "../entity/commands/cmd";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log($t("TXT_CODE_ui.help"));
function stdin() {
rl.question("> ", async (answer) => {
try {
const cmds = answer.split(" ");
logger.info(`[Terminal] ${answer}`);
const result = await command(cmds[0], cmds[1], cmds[2], cmds[3]);
if (result) console.log(result);
else console.log(`Command ${answer} does not exist, type help to get help.`);
} catch (err) {
logger.error("[Terminal]", err);
} finally {
// next
stdin();
}
});
}
stdin();
/**
* Pass in relevant UI commands and output command results
* @param {String} cmd
* @return {String}
*/
async function command(cmd: string, p1: string, p2: string, p3: string) {
if (cmd === "instance") {
if (p1 === "start") {
InstanceSubsystem?.getInstance(p2)?.exec(new StartCommand("Terminal"));
return "Done.";
}
if (p1 === "stop") {
InstanceSubsystem?.getInstance(p2)?.exec(new StopCommand());
return "Done.";
}
if (p1 === "kill") {
InstanceSubsystem?.getInstance(p2)?.exec(new KillCommand());
return "Done.";
}
if (p1 === "send") {
InstanceSubsystem?.getInstance(p2)?.exec(new SendCommand(p3));
return "Done.";
}
return "Parameter error";
}
if (cmd === "instances") {
const objs = InstanceSubsystem.instances;
let result = "instance name | instance UUID | status code\n";
objs.forEach((v) => {
result += `${v.config.nickname} ${v.instanceUuid} ${v.status()}\n`;
});
result += "\nStatus Explanation:\n Busy=-1;Stop=0;Stopping=1;Starting=2;Running=3;\n";
return result;
}
if (cmd === "sockets") {
const sockets = protocol.socketObjects();
let result = "IP address | identifier\n";
sockets.forEach((v) => {
result += `${v.handshake.address} ${v.id}\n`;
});
result += `Total ${sockets.size} online.\n`;
return result;
}
if (cmd == "key") {
return globalConfiguration.config.key;
}
if (cmd == "exit") {
try {
logger.info("Preparing to shut down the daemon...");
await InstanceSubsystem.exit();
// logger.info("Data saved, thanks for using, goodbye!");
logger.info("The data is saved, thanks for using, goodbye!");
logger.info("closed.");
process.exit(0);
} catch (err) {
logger.error(
"Failed to end the program. Please check the file permissions and try again. If you still can't close it, please use Ctrl+C to close.",
err
);
}
}
if (cmd == "help") {
console.log("----------- Help document -----------");
console.log(" instances view all instances");
console.log(" Sockets view all linkers");
console.log(" key view key");
console.log(" exit to close this program (recommended method)");
console.log(" instance start <UUID> to start the specified instance");
console.log(" instance stop <UUID> to start the specified instance");
console.log(" instance kill <UUID> to start the specified instance");
console.log(" instance send <UUID> <CMD> to send a command to the instance");
console.log("----------- Help document -----------");
return "\n";
}
}

View File

@ -0,0 +1,36 @@
ARG EMBEDDED_JAVA_VERSION=21
ARG BUILDPLATFORM=linux/amd64
FROM --platform=${BUILDPLATFORM} node:lts-alpine AS builder
WORKDIR /src
COPY . /src
RUN apk add --no-cache wget &&\
chmod a+x ./install-dependents.sh &&\
chmod a+x ./build.sh &&\
./install-dependents.sh &&\
./build.sh &&\
wget --input-file=lib-urls.txt --directory-prefix=production-code/daemon/lib/ &&\
chmod a+x production-code/daemon/lib/*
FROM eclipse-temurin:${EMBEDDED_JAVA_VERSION}-jdk
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y curl &&\
curl -fsSL https://deb.nodesource.com/setup_20.x | bash &&\
apt-get update && apt-get install -y nodejs && apt-get clean
WORKDIR /opt/mcsmanager/daemon
COPY --from=builder /src/production-code/daemon/ /opt/mcsmanager/daemon/
RUN npm install --production
EXPOSE 24444
ENV MCSM_INSTANCES_BASE_PATH=/opt/mcsmanager/daemon/data/InstanceData
VOLUME ["/opt/mcsmanager/daemon/data", "/opt/mcsmanager/daemon/logs"]
CMD [ "node", "app.js", "--max-old-space-size=8192" ]

24
dockerfile/web.dockerfile Normal file
View File

@ -0,0 +1,24 @@
ARG BUILDPLATFORM=linux/amd64
FROM --platform=${BUILDPLATFORM} node:lts-alpine AS builder
WORKDIR /src
COPY . /src
RUN chmod a+x ./install-dependents.sh &&\
chmod a+x ./build.sh &&\
./install-dependents.sh &&\
./build.sh
FROM node:lts-alpine
WORKDIR /opt/mcsmanager/web
COPY --from=builder /src/production-code/web/ /opt/mcsmanager/web/
RUN npm install --production
EXPOSE 23333
VOLUME ["/opt/mcsmanager/web/data", "/opt/mcsmanager/web/logs"]
CMD [ "app.js", "--max-old-space-size=8192" ]

View File

@ -0,0 +1,24 @@
# docker-compose.yml
services:
web:
image: githubyumao/mcsmanager-web:latest
ports:
- "23333:23333"
volumes:
- /etc/localtime:/etc/localtime:ro
- <CHANGE_ME_TO_INSTALL_PATH>/web/data:/opt/mcsmanager/web/data
- <CHANGE_ME_TO_INSTALL_PATH>/web/logs:/opt/mcsmanager/web/logs
daemon:
image: githubyumao/mcsmanager-daemon:latest
restart: unless-stopped
network_mode: host # if you want run instance in daemon container
# ports:
# - "24444:24444"
environment:
- MCSM_DOCKER_WORKSPACE_PATH=<CHANGE_ME_TO_INSTALL_PATH>/daemon/data/InstanceData
volumes:
- /etc/localtime:/etc/localtime:ro
- <CHANGE_ME_TO_INSTALL_PATH>/daemon/data:/opt/mcsmanager/daemon/data
- <CHANGE_ME_TO_INSTALL_PATH>/daemon/logs:/opt/mcsmanager/daemon/logs
- /var/run/docker.sock:/var/run/docker.sock

View File

@ -26,8 +26,10 @@ declare module 'vue' {
AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AImage: typeof import('ant-design-vue/es')['Image']
AInput: typeof import('ant-design-vue/es')['Input']
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']
@ -92,6 +94,7 @@ declare module 'vue' {
NoPreviewCard: typeof import('./src/components/NoPreviewCard.vue')['default']
Params: typeof import('./src/components/NewCardList/params.vue')['default']
PlaceHolderCard: typeof import('./src/components/PlaceHolderCard.vue')['default']
PurchaseQueryDialog: typeof import('./src/components/fc/PurchaseQueryDialog.vue')['default']
ResponsiveLayoutGroup: typeof import('./src/components/ResponsiveLayoutGroup.vue')['default']
RightMenu: typeof import('./src/components/fc/RightMenu.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
@ -103,6 +106,7 @@ declare module 'vue' {
TaskLoadingDialog: typeof import('./src/components/fc/TaskLoadingDialog.vue')['default']
TerminalCore: typeof import('./src/components/TerminalCore.vue')['default']
UploadFileDialog: typeof import('./src/components/fc/UploadFileDialog.vue')['default']
UseRedeemDialog: typeof import('./src/components/fc/UseRedeemDialog.vue')['default']
WarningDialog: typeof import('./src/components/fc/WarningDialog.vue')['default']
}
}

View File

@ -21,7 +21,7 @@
"@uiw/codemirror-theme-dracula": "^4.21.24",
"@vueuse/core": "^10.3.0",
"ant-design-vue": "^4.0.7",
"axios": "^1.7.4",
"axios": "^1.8.2",
"codemirror": "^6.0.1",
"crc": "^4.3.2",
"dayjs": "^1.11.9",
@ -65,11 +65,11 @@
"less-loader": "^11.1.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.0",
"rollup-plugin-visualizer": "^5.12.0",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.64.1",
"typescript": "~5.1.6",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.5.3",
"vite": "^4.5.13",
"vitest": "^0.33.0",
"vue-tsc": "^1.8.6"
}
@ -534,11 +534,12 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.13.11"
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
@ -3084,9 +3085,10 @@
}
},
"node_modules/axios": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@ -6508,9 +6510,9 @@
"dev": true
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
@ -7359,9 +7361,10 @@
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.0",
@ -7511,13 +7514,14 @@
}
},
"node_modules/rollup-plugin-visualizer": {
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz",
"integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==",
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz",
"integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==",
"dev": true,
"license": "MIT",
"dependencies": {
"open": "^8.4.0",
"picomatch": "^2.3.1",
"picomatch": "^4.0.2",
"source-map": "^0.7.4",
"yargs": "^17.5.1"
},
@ -7525,12 +7529,16 @@
"rollup-plugin-visualizer": "dist/bin/cli.js"
},
"engines": {
"node": ">=14"
"node": ">=18"
},
"peerDependencies": {
"rolldown": "1.x",
"rollup": "2.x || 3.x || 4.x"
},
"peerDependenciesMeta": {
"rolldown": {
"optional": true
},
"rollup": {
"optional": true
}
@ -7577,6 +7585,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/rollup-plugin-visualizer/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/rollup-plugin-visualizer/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
@ -8935,10 +8956,11 @@
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/vite": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"version": "4.5.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.13.tgz",
"integrity": "sha512-Hgp8IF/yZDzKsN1hQWOuQZbrKiaFsbQud+07jJ8h9m9PaHWkpvZ5u55Xw5yYjWRXwRQ4jwFlJvY7T7FUJG9MCA==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.18.10",
"postcss": "^8.4.27",
@ -10053,11 +10075,11 @@
}
},
"@babel/runtime": {
"version": "7.22.6",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz",
"integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"requires": {
"regenerator-runtime": "^0.13.11"
"regenerator-runtime": "^0.14.0"
}
},
"@babel/template": {
@ -11957,9 +11979,9 @@
"dev": true
},
"axios": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
"requires": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
@ -14416,9 +14438,9 @@
"dev": true
},
"nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="
},
"nanopop": {
"version": "2.3.0",
@ -15015,9 +15037,9 @@
}
},
"regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"regexp.prototype.flags": {
"version": "1.5.0",
@ -15122,13 +15144,13 @@
}
},
"rollup-plugin-visualizer": {
"version": "5.12.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.12.0.tgz",
"integrity": "sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==",
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz",
"integrity": "sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==",
"dev": true,
"requires": {
"open": "^8.4.0",
"picomatch": "^2.3.1",
"picomatch": "^4.0.2",
"source-map": "^0.7.4",
"yargs": "^17.5.1"
},
@ -15156,6 +15178,12 @@
"is-wsl": "^2.2.0"
}
},
"picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true
},
"source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
@ -16142,9 +16170,9 @@
}
},
"vite": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz",
"integrity": "sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg==",
"version": "4.5.13",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.13.tgz",
"integrity": "sha512-Hgp8IF/yZDzKsN1hQWOuQZbrKiaFsbQud+07jJ8h9m9PaHWkpvZ5u55Xw5yYjWRXwRQ4jwFlJvY7T7FUJG9MCA==",
"dev": true,
"requires": {
"esbuild": "^0.18.10",

View File

@ -8,7 +8,8 @@
"build-only": "vite build",
"type-check": "vue-tsc --noEmit --skipLibCheck -p tsconfig.vitest.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"format": "prettier --write src/"
"format": "prettier --write src/",
"analyze": "vite build --mode analyze"
},
"dependencies": {
"@ant-design/icons-vue": "^6.1.0",
@ -24,7 +25,7 @@
"@uiw/codemirror-theme-dracula": "^4.21.24",
"@vueuse/core": "^10.3.0",
"ant-design-vue": "^4.0.7",
"axios": "^1.7.4",
"axios": "^1.8.2",
"codemirror": "^6.0.1",
"crc": "^4.3.2",
"dayjs": "^1.11.9",
@ -68,11 +69,11 @@
"less-loader": "^11.1.3",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.0",
"rollup-plugin-visualizer": "^5.12.0",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.64.1",
"typescript": "~5.1.6",
"unplugin-vue-components": "^0.25.1",
"vite": "^4.5.3",
"vite": "^4.5.13",
"vitest": "^0.33.0",
"vue-tsc": "^1.8.6"
}

View File

@ -55,7 +55,7 @@ html {
.global-action-float-enter-active,
.global-action-float-leave-active {
transition: all 0.6s ease;
transition: all 0.4s ease;
opacity: 1 !important;
transform: scale(1);
position: absolute;

View File

@ -45,6 +45,22 @@
display: flex;
}
.flex-col {
flex-direction: column;
}
.justify-center {
justify-content: center;
}
.justify-start {
justify-content: flex-start;
}
.justify-end {
justify-content: flex-end;
}
.flex-center {
display: flex;
align-items: center;
@ -219,9 +235,9 @@
}
.ellipsis-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
@mixin generate-styles($depth) {

View File

@ -30,8 +30,14 @@
// Antdv for dark mode
.ant-btn-default {
background-color: var(--background-color-white);
background-color: var(--color-gray-4);
box-shadow: 0 2px 0 rgba(55, 55, 55, 0.15);
}
.ant-btn-primary {
border: 1px solid #424242;
}
.ant-modal-content {
border: 1px solid var(--color-gray-5);
border-radius: 8px;

View File

@ -53,16 +53,21 @@ const menus = computed(() => {
return router
.getRoutes()
.filter((v) => {
const metaInfo = v.meta as RouterMetaInfo;
if (metaInfo.condition && !metaInfo.condition()) {
return false;
}
if (containerState.isDesignMode) {
return v.meta.onlyDisplayEditMode || v.meta.mainMenu;
return metaInfo.onlyDisplayEditMode || metaInfo.mainMenu;
}
if (isAdmin.value) {
return v.meta.mainMenu === true && v.meta.onlyDisplayEditMode !== true;
return metaInfo.mainMenu === true && metaInfo.onlyDisplayEditMode !== true;
}
return (
v.meta.mainMenu === true &&
metaInfo.mainMenu === true &&
isLogged.value &&
Number(appState.userInfo?.permission) >= Number(v.meta.permission)
Number(appState.userInfo?.permission) >= Number(metaInfo.permission)
);
})
.map((r) => {

View File

@ -1,39 +1,21 @@
<script setup lang="ts">
import { t } from "@/lang/i18n";
import { CopyOutlined } from "@ant-design/icons-vue";
import { Modal, message } from "ant-design-vue";
import { reportErrorMsg } from "@/tools/validator";
import type { ButtonType } from "ant-design-vue/es/button";
import type { SizeType } from "ant-design-vue/es/config-provider";
import { h } from "vue";
import { toCopy } from "@/tools/copy";
const props = defineProps<{
size?: string;
type?: string;
value: string;
}>();
const copy = async () => {
if (!navigator.clipboard) {
Modal.warning({
title: t("TXT_CODE_ca07c84c"),
content: [h("span", t("TXT_CODE_2452016e")), h("br"), h("span", props.value)]
});
return;
}
try {
await navigator.clipboard.writeText(props.value);
message.success(t("TXT_CODE_b858d78a"));
} catch (error: any) {
reportErrorMsg(t("TXT_CODE_81b9b599") + error);
}
};
</script>
<template>
<a-tooltip>
<template #title>{{ t("TXT_CODE_13ae6a93") }}</template>
<a-button :type="<ButtonType>type" :size="<SizeType>size" @click="copy">
<a-button :type="<ButtonType>type" :size="<SizeType>size" @click="toCopy(props.value)">
<template #icon>
<CopyOutlined />
</template>

View File

@ -8,11 +8,11 @@ const beforeEnter = (el: any) => {
};
const enter = (el: any, done: () => void) => {
let delay = el.dataset.index * Number(props.delay ?? 200);
let delay = el.dataset.index * Number(props.delay ?? 100);
setTimeout(() => {
el.style.transition = "opacity 0.4s";
el.style.transition = "opacity 0.2s";
el.style.opacity = 1;
el.style.animation = "global-fade-up-animation 0.4s 1";
el.style.animation = "global-fade-up-animation 0.2s 1";
done();
}, delay);
};

View File

@ -10,6 +10,7 @@ import type { FormInstance } from "ant-design-vue";
import CopyButton from "@/components/CopyButton.vue";
import { bind2FA } from "../services/apis/user";
import { PERMISSION_MAP } from "@/config/const";
import { toCopy } from "@/tools/copy";
const { state, updateUserInfo } = useAppStateStore();
const { state: tools } = useAppToolsStore();
@ -176,7 +177,6 @@ const disable2FACode = async () => {
</a-button>
</div>
</a-form-item>
<a-form-item label="APIKEY">
<a-typography-paragraph>
{{ t("TXT_CODE_b2dbf778") }}

View File

@ -2,7 +2,7 @@
import { onMounted, ref } from "vue";
import { t } from "@/lang/i18n";
import { CodeOutlined, DeleteOutlined, LoadingOutlined } from "@ant-design/icons-vue";
import { useTerminal } from "../hooks/useTerminal";
import { encodeConsoleColor, useTerminal } from "../hooks/useTerminal";
import { getInstanceOutputLog } from "@/services/apis/instance";
import { message } from "ant-design-vue";
import connectErrorImage from "@/assets/daemon_connection_error.png";
@ -29,7 +29,7 @@ const {
clickHistoryItem
} = useCommandHistory();
const { execute, initTerminalWindow, sendCommand, events, isConnect, socketAddress } =
const { execute, initTerminalWindow, sendCommand, state, events, isConnect, socketAddress } =
useTerminal();
const instanceId = props.instanceId;
@ -60,12 +60,6 @@ const initTerminal = async () => {
const dom = document.getElementById(terminalDomId);
if (dom) {
const term = initTerminalWindow(dom);
try {
const { value } = await getInstanceOutputLog().execute({
params: { uuid: instanceId || "", daemonId: daemonId || "" }
});
if (value) term.write(value);
} catch (error: any) {}
return term;
}
throw new Error(t("TXT_CODE_42bcfe0c"));
@ -83,6 +77,22 @@ events.on("error", (error: Error) => {
socketError.value = error;
});
events.once("detail", async () => {
try {
const { value } = await getInstanceOutputLog().execute({
params: { uuid: instanceId || "", daemonId: daemonId || "" }
});
if (value) {
if (state.value?.config?.terminalOption?.haveColor) {
term?.write(encodeConsoleColor(value));
} else {
term?.write(value);
}
}
} catch (error: any) {}
});
const clearTerminal = () => {
term?.clear();
};

View File

@ -14,7 +14,9 @@ interface Props extends MountComponent {
keyTitle?: string;
valueTitle?: string;
data: any[];
subTitle?: string;
columns?: AntColumnsType[];
textarea?: boolean;
}
const props = defineProps<Props>();
@ -91,7 +93,7 @@ const operation = (type: "add" | "del", index = 0) => {
<a-modal
v-model:open="open"
centered
width="600px"
width="1300px"
:mask-closable="false"
:title="props.title"
:ok-text="t('TXT_CODE_d507abff')"
@ -100,6 +102,10 @@ const operation = (type: "add" | "del", index = 0) => {
@cancel="cancel"
>
<div class="dialog-overflow-container">
<div v-if="props.subTitle" style="font-size: 13px" class="text-gray-400 mb-4">
<!-- eslint-disable-next-line vue/no-v-html -->
<span v-html="props.subTitle"></span>
</div>
<div class="flex justify-end mb-20">
<a-button :icon="h(PlusCircleOutlined)" @click="operation('add')">
{{ t("TXT_CODE_dfc17a0c") }}
@ -130,9 +136,17 @@ const operation = (type: "add" | "del", index = 0) => {
}
]"
>
<a-input
<a-textarea
v-if="props.textarea"
v-model:value="record[String(column.dataIndex)]"
:placeholder="t('TXT_CODE_4ea93630')"
:placeholder="(column as any).placeholder"
:rows="3"
:auto-size="false"
/>
<a-input
v-else
v-model:value="record[String(column.dataIndex)]"
:placeholder="(column as any).placeholder"
/>
</a-form-item>
</template>

View File

@ -0,0 +1,89 @@
<script setup lang="ts">
import { ref } from "vue";
import type { MountComponent } from "@/types";
import { t } from "@/lang/i18n";
import { usePromiseDialog } from "@/hooks/useDialog";
import { useRedeem, type PurchaseQueryResponse } from "@/services/apis/redeem";
import { type FormInstance } from "ant-design-vue";
import { reportErrorMsg } from "@/tools/validator";
interface Props extends MountComponent {
instanceId?: string;
}
const props = defineProps<Props>();
const formRef = ref<FormInstance>();
const { isVisible, cancel } = usePromiseDialog<PurchaseQueryResponse>(props);
const { queryPurchase } = useRedeem();
const formData = ref({
code: ""
});
const queryResult = ref<PurchaseQueryResponse>();
const handleSubmit = async () => {
await formRef.value!.validate();
try {
queryResult.value = await queryPurchase(formData.value.code);
} catch (error) {
queryResult.value = undefined;
reportErrorMsg(error);
}
};
</script>
<template>
<a-modal
v-model:open="isVisible"
centered
width="500px"
:mask-closable="false"
:title="t('TXT_CODE_2093cc1a')"
:footer="null"
@cancel="cancel"
>
<div class="dialog-overflow-container">
<a-form ref="formRef" :model="formData" layout="vertical">
<a-form-item
name="code"
:label="t('TXT_CODE_a3de630')"
:rules="[{ required: true, message: t('TXT_CODE_ffda3755') }]"
>
<a-input
v-model:value="formData.code"
name="mcsm-redeem-code"
:placeholder="t('TXT_CODE_ffda3755')"
autocomplete="off"
/>
</a-form-item>
<div class="text-center mt-20 flex justify-center">
<a-button type="primary" @click="handleSubmit">{{ t("TXT_CODE_ee8ae330") }}</a-button>
</div>
</a-form>
<div v-if="queryResult" class="mt-20">
<a-descriptions bordered size="small" :column="1">
<a-descriptions-item :label="t('TXT_CODE_f3209427')">
<a-typography-text>
<a :href="queryResult.panelAddr" target="_blank">{{ queryResult.panelAddr }}</a>
</a-typography-text>
</a-descriptions-item>
<a-descriptions-item :label="t('TXT_CODE_eb9fcdad')">
<a-typography-text copyable :content="queryResult?.username"></a-typography-text>
</a-descriptions-item>
<a-descriptions-item :label="t('TXT_CODE_ec7553c6')">
<a-typography-text
v-if="queryResult?.password"
copyable
:content="queryResult?.password"
></a-typography-text>
<a-typography-text v-else type="secondary">
{{ t("TXT_CODE_cb61062d") }}
</a-typography-text>
</a-descriptions-item>
</a-descriptions>
</div>
</div>
</a-modal>
</template>

View File

@ -244,7 +244,7 @@ const handleChangeNode = async (item: NodeStatus) => {
>
<template #bodyCell="{ column, record }: AntTableCell">
<template v-if="column.key === 'safe'">
<span v-if="record?.config?.docker?.image" style="color: var(--color-green-6)">
<span v-if="record?.config?.processType === 'docker'" style="color: var(--color-green-6)">
{{ t("TXT_CODE_a3f13157") }}
</span>
<span v-else class="color-danger">{{ t("TXT_CODE_201bc643") }}</span>

View File

@ -0,0 +1,147 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import type { MountComponent } from "@/types";
import { t } from "@/lang/i18n";
import { usePromiseDialog } from "@/hooks/useDialog";
import { useRedeem, type BuyInstanceResponse } from "@/services/apis/redeem";
import { Modal, type FormInstance } from "ant-design-vue";
import { reportErrorMsg } from "@/tools/validator";
import { useAppStateStore } from "@/stores/useAppStateStore";
interface Props extends MountComponent {
instanceId?: string;
}
const props = defineProps<Props>();
const formRef = ref<FormInstance>();
const isRenewalMode = computed(() => !!props.instanceId);
const { state: appState } = useAppStateStore();
const { isVisible, cancel, submit } = usePromiseDialog<BuyInstanceResponse>(props);
const { isLoading, buyInstance, renewInstance } = useRedeem();
const formData = ref({
code: "",
username: appState.userInfo?.userName || ""
});
const operateResult = ref<BuyInstanceResponse>();
const handleSubmit = async () => {
await formRef.value!.validate();
try {
if (!isRenewalMode.value) {
const res = await buyInstance(formData.value.username, formData.value.code);
operateResult.value = res;
} else {
const res = await renewInstance(
formData.value.username,
formData.value.code,
props.instanceId!
);
operateResult.value = res;
Modal.success({
title: t("TXT_CODE_ae51f93b"),
content:
t("TXT_CODE_8074a178") + new Date(operateResult.value?.expire ?? 0).toLocaleString()
});
submit(operateResult.value);
}
} catch (error) {
reportErrorMsg(error);
}
};
</script>
<template>
<a-modal
v-model:open="isVisible"
centered
width="500px"
:mask-closable="false"
:title="t('TXT_CODE_75bf9192')"
:footer="null"
@cancel="cancel"
>
<div class="dialog-overflow-container">
<template v-if="!operateResult">
<a-form ref="formRef" :model="formData" layout="vertical">
<a-form-item
v-if="!isRenewalMode"
name="username"
:label="t('TXT_CODE_c38813a8')"
:rules="[{ required: true, message: t('TXT_CODE_2695488c') }]"
>
<a-typography-paragraph type="secondary">
{{ t("TXT_CODE_b90e9abd") }}
</a-typography-paragraph>
<a-input
v-model:value="formData.username"
name="mcsm-redeem-username"
:placeholder="t('TXT_CODE_8028e95b')"
autocomplete="off"
/>
</a-form-item>
<a-form-item
name="code"
:label="t('TXT_CODE_fb87ccd')"
:rules="[{ required: true, message: t('TXT_CODE_ffda3755') }]"
>
<a-input
v-model:value="formData.code"
name="mcsm-redeem-code"
:placeholder="t('TXT_CODE_a95c0f85')"
autocomplete="off"
/>
</a-form-item>
<div class="text-center flex justify-center">
<div>
<div class="flex justify-center">
<a-button
class="w-28"
type="primary"
:loading="isLoading"
style="width: 100px"
@click="handleSubmit"
>
{{ t("TXT_CODE_d507abff") }}
</a-button>
</div>
</div>
</div>
</a-form>
</template>
<template v-else>
<a-typography-paragraph>
<div class="text-red-600">
<InfoCircleOutlined />
<span v-if="!isRenewalMode">{{ t("TXT_CODE_3ee20639") }}</span>
<span v-else>{{ t("TXT_CODE_ae51f93b") }}</span>
</div>
</a-typography-paragraph>
<a-descriptions bordered size="small" :column="1">
<a-descriptions-item :label="t('TXT_CODE_eb9fcdad')">
<a-typography-text copyable :content="operateResult?.username"></a-typography-text>
</a-descriptions-item>
<a-descriptions-item v-if="operateResult?.password" :label="t('TXT_CODE_551b0348')">
<a-typography-text copyable :content="operateResult?.password"></a-typography-text>
</a-descriptions-item>
<a-descriptions-item v-else :label="t('TXT_CODE_551b0348')">
<a-typography-text type="secondary">{{ t("TXT_CODE_e1b0aab2") }}</a-typography-text>
</a-descriptions-item>
<a-descriptions-item v-if="operateResult?.expire" :label="t('TXT_CODE_fa920c0')">
{{ new Date(operateResult.expire).toLocaleString() }}
</a-descriptions-item>
</a-descriptions>
<div class="text-center mt-20 flex justify-center">
<a-button type="primary" @click="submit(operateResult)">
{{ t("TXT_CODE_a676f2da") }}
</a-button>
</div>
</template>
</div>
</a-modal>
</template>
<style lang="scss" scoped></style>

View File

@ -9,6 +9,8 @@ import type { AntColumnsType } from "@/types/ant";
import UploadFileDialogVue from "./UploadFileDialog.vue";
import TaskLoadingDialog from "./TaskLoadingDialog.vue";
import TagsDialog from "./TagsDialog.vue";
import DeleteInstanceDialog from "@/widgets/instance/dialogs/DeleteInstanceDialog.vue";
import ImageViewerDialog from "@/widgets/instance/dialogs/ImageViewer.vue";
interface DockerConfigItem {
host: string;
@ -65,19 +67,23 @@ export async function usePortEditDialog(data: PortConfigItem[] = []) {
{
align: "center",
dataIndex: "host",
title: t("TXT_CODE_534db0b2")
title: t("TXT_CODE_534db0b2"),
placeholder: "eg: 8080"
},
{
align: "center",
dataIndex: "container",
title: t("TXT_CODE_b729d2e")
title: t("TXT_CODE_b729d2e"),
placeholder: "eg: 25565"
},
{
align: "center",
dataIndex: "protocol",
title: t("TXT_CODE_ad1c674c")
title: t("TXT_CODE_ad1c674c"),
placeholder: "tcp/udp"
}
] as AntColumnsType[]
] as AntColumnsType[],
textarea: false
}).mount<PortConfigItem[]>(KvOptionsDialogVue)) || []
);
}
@ -86,6 +92,7 @@ export async function useVolumeEditDialog(data: DockerConfigItem[] = []) {
return (
(await useMountComponent({
data,
subTitle: t("TXT_CODE_6c232c9c"),
title: t("TXT_CODE_820ebc92"),
columns: [
{
@ -98,7 +105,8 @@ export async function useVolumeEditDialog(data: DockerConfigItem[] = []) {
dataIndex: "container",
title: t("TXT_CODE_30258325")
}
] as AntColumnsType[]
] as AntColumnsType[],
textarea: true
}).mount<DockerConfigItem[]>(KvOptionsDialogVue)) || []
);
}
@ -119,7 +127,8 @@ export async function useDockerEnvEditDialog(data: DockerEnvItem[] = []) {
dataIndex: "value",
title: t("TXT_CODE_115e8a25")
}
] as AntColumnsType[]
] as AntColumnsType[],
textarea: true
}).mount<DockerEnvItem[]>(KvOptionsDialogVue)) || []
);
}
@ -148,3 +157,18 @@ export async function openInstanceTagsEditor(
.load<InstanceType<typeof TagsDialog>>(TagsDialog)
.openDialog();
}
export async function useDeleteInstanceDialog(instanceId: string, daemonId: string) {
return await useMountComponent({ instanceId, daemonId }).mount<boolean>(DeleteInstanceDialog);
}
export async function useImageViewerDialog(
instanceId: string,
daemonId: string,
fileName: string,
frontDir: string
) {
return await useMountComponent({ instanceId, daemonId, fileName, frontDir }).mount(
ImageViewerDialog
);
}

View File

@ -41,7 +41,8 @@ import DefaultCard from "@/widgets/DefaultCard.vue";
import Carousel from "@/widgets/others/Carousel.vue";
import PluginCard from "@/widgets/others/PluginCard.vue";
import MusicCard from "@/widgets/others/MusicCard.vue";
import ShelvesCard from "@/widgets/ShelvesCard.vue";
import ShopInfoCard from "@/widgets/ShopInfoCard.vue";
import { NEW_CARD_TYPE } from "../types/index";
import { ROLE } from "./router";
@ -85,7 +86,9 @@ export const LAYOUT_CARD_TYPES: { [key: string]: any } = {
DefaultCard,
Carousel,
PluginCard,
MusicCard
MusicCard,
ShelvesCard,
ShopInfoCard
};
export interface NewCardItem extends LayoutCard {
@ -496,6 +499,28 @@ export function getLayoutCardPool() {
description: t("TXT_CODE_cb84b22"),
height: LayoutCardHeight.SMALL,
category: NEW_CARD_TYPE.COMMON
},
{
id: getRandomId(),
permission: ROLE.GUEST,
meta: {},
type: "ShelvesCard",
title: t("TXT_CODE_b99cae18"),
width: 8,
description: t("TXT_CODE_163e2d0a"),
height: LayoutCardHeight.MEDIUM,
category: NEW_CARD_TYPE.COMMON
},
{
id: getRandomId(),
permission: ROLE.GUEST,
meta: {},
type: "ShopInfoCard",
title: t("TXT_CODE_48261ab7"),
width: 8,
description: t("TXT_CODE_1648c9ea"),
height: LayoutCardHeight.SMALL,
category: NEW_CARD_TYPE.COMMON
}
];
return LAYOUT_CARD_POOL;

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ export interface RouterMetaInfo {
permission?: number;
redirect?: string;
onlyDisplayEditMode?: boolean;
condition?: () => boolean;
breadcrumbs?: Array<{
name: string;
path: string;
@ -28,8 +29,11 @@ export interface RouterConfig {
}
export enum ROLE {
// eslint-disable-next-line no-unused-vars
ADMIN = 10,
// eslint-disable-next-line no-unused-vars
USER = 1,
// eslint-disable-next-line no-unused-vars
GUEST = 0
}
@ -232,6 +236,19 @@ const originRouterConfig: RouterConfig[] = [
mainMenu: true,
onlyDisplayEditMode: true
}
},
{
path: "/shop",
name: t("TXT_CODE_5a408a5e"),
component: LayoutContainer,
meta: {
permission: ROLE.GUEST,
mainMenu: true,
condition: () => {
const { state: appConfig } = useAppStateStore();
return appConfig.settings.businessMode;
}
}
}
];
@ -277,7 +294,10 @@ router.beforeEach((to, from, next) => {
toPagePermission
);
if (toRoutePath.includes("_open_page") || ["/login", "/install", "/404"].includes(toRoutePath)) {
if (
toRoutePath.includes("_open_page") ||
["/shop", "/login", "/install", "/404"].includes(toRoutePath)
) {
return next();
}

View File

@ -0,0 +1,35 @@
import { ref } from "vue";
export function useDialog<T = any>(props: any) {
const isVisible = ref(false);
const openDialog = async () => {
isVisible.value = true;
};
const cancel = async () => {
isVisible.value = false;
if (props.destroyComponent) props.destroyComponent();
};
const submit = async (data?: T) => {
if (props.emitResult) props.emitResult(data);
await cancel();
};
return {
openDialog,
isVisible,
cancel,
submit
};
}
export function usePromiseDialog<T = any>(props: any) {
const { isVisible, ...rest } = useDialog<T>(props);
isVisible.value = true;
return {
isVisible,
...rest
};
}

View File

@ -29,6 +29,7 @@ import type {
} from "@/types/fileManager";
import { reportErrorMsg } from "@/tools/validator";
import { openLoadingDialog } from "@/components/fc";
import { useImageViewerDialog } from "@/components/fc";
export const useFileManager = (instanceId?: string, daemonId?: string) => {
const dataSource = ref<DataType[]>();
@ -464,28 +465,34 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
}
};
const downloadFile = async (fileName: string) => {
const getFileLink = async (fileName: string, frontDir?: string) => {
frontDir = frontDir || breadcrumbs[breadcrumbs.length - 1].path;
const { state: downloadCfg, execute: getDownloadCfg } = downloadAddress();
try {
await getDownloadCfg({
params: {
file_name: breadcrumbs[breadcrumbs.length - 1].path + fileName,
daemonId: daemonId!,
uuid: instanceId!
file_name: frontDir + fileName,
daemonId: daemonId || "",
uuid: instanceId || ""
}
});
if (!downloadCfg.value) throw new Error(t("TXT_CODE_6d772765"));
window.open(
`${parseForwardAddress(downloadCfg.value.addr, "http")}/download/${
downloadCfg.value.password
}/${fileName}`
);
if (!downloadCfg.value) return null;
return `${parseForwardAddress(downloadCfg.value.addr, "http")}/download/${
downloadCfg.value.password
}/${fileName}`;
} catch (err: any) {
console.error(err);
return reportErrorMsg(err.message);
}
};
const downloadFile = async (fileName: string) => {
const link = await getFileLink(fileName);
if (!link) throw new Error(t("TXT_CODE_6d772765"));
window.open(link);
};
const handleChangeDir = async (dir: string) => {
if (breadcrumbs.findIndex((e) => e.path === dir) === -1)
return reportErrorMsg(t("TXT_CODE_96281410"));
@ -500,12 +507,17 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
const handleTableChange = (e: { current: number; pageSize: number }) => {
selectedRowKeys.value = [];
selectionData.value = [];
operationForm.value.name = "";
// operationForm.value.name = "";
operationForm.value.current = e.current;
operationForm.value.pageSize = e.pageSize;
getFileList();
};
const handleSearchChange = () => {
operationForm.value.current = 1;
getFileList();
};
const getFileStatus = async () => {
const { state, execute } = getFileStatusApi();
try {
@ -596,6 +608,16 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
spinning.value = false;
};
const isImage = (extName: string) => {
if (!extName) return;
return ["JPG", "JPEG", "PNG", "GIF", "BMP", "WEBP", "ICO"].includes(extName.toUpperCase());
};
const showImage = (file: DataType) => {
const frontDir = breadcrumbs[breadcrumbs.length - 1].path;
useImageViewerDialog(instanceId || "", daemonId || "", file.name, frontDir);
};
return {
fileStatus,
dialog,
@ -625,12 +647,16 @@ export const useFileManager = (instanceId?: string, daemonId?: string) => {
selectedFile,
rowClickTable,
downloadFile,
getFileLink,
handleChangeDir,
handleTableChange,
handleSearchChange,
getFileStatus,
changePermission,
toDisk,
pushSelected,
oneSelected
oneSelected,
isImage,
showImage
};
};

View File

@ -12,13 +12,16 @@ export const TYPE_MINECRAFT_JAVA = "minecraft/java";
export const TYPE_MINECRAFT_BUKKIT = "minecraft/java/bukkit";
export const TYPE_MINECRAFT_SPIGOT = "minecraft/java/spigot";
export const TYPE_MINECRAFT_PAPER = "minecraft/java/paper";
export const TYPE_MINECRAFT_PUFFERFISH = "minecraft/java/pufferfish";
export const TYPE_MINECRAFT_FORGE = "minecraft/java/forge";
export const TYPE_MINECRAFT_NEOFORGE = "minecraft/java/neoforge";
export const TYPE_MINECRAFT_FABRIC = "minecraft/java/fabric";
export const TYPE_MINECRAFT_BUNGEECORD = "minecraft/java/bungeecord";
export const TYPE_MINECRAFT_VELOCITY = "minecraft/java/velocity";
export const TYPE_MINECRAFT_GEYSER = "minecraft/java/geyser";
export const TYPE_MINECRAFT_SPONGE = "minecraft/java/sponge";
export const TYPE_MINECRAFT_MOHIST = "minecraft/java/mohist";
export const TYPE_MINECRAFT_PURPUR = "minecraft/java/purpur";
export const TYPE_MINECRAFT_BEDROCK = "minecraft/bedrock";
export const TYPE_MINECRAFT_BDS = "minecraft/bedrock/bds";
export const TYPE_MINECRAFT_NUKKIT = "minecraft/bedrock/nukkit";
@ -30,13 +33,18 @@ export const INSTANCE_TYPE_TRANSLATION: MapData<string> = {
[TYPE_STEAM_SERVER_UNIVERSAL]: t("TXT_CODE_3d7fbe30"),
[TYPE_MINECRAFT_JAVA]: t("TXT_CODE_97f779b3"),
[TYPE_MINECRAFT_BEDROCK]: t("TXT_CODE_7f1aef9f"),
[TYPE_MINECRAFT_NUKKIT]: t("TXT_CODE_8f3e5807"),
[TYPE_MINECRAFT_SPIGOT]: t("TXT_CODE_6c08319b"),
[TYPE_MINECRAFT_PAPER]: t("TXT_CODE_ec0cda88"),
[TYPE_MINECRAFT_PUFFERFISH]: t("TXT_CODE_c6d3bd8"),
[TYPE_MINECRAFT_BUNGEECORD]: t("TXT_CODE_ba86f4a"),
[TYPE_MINECRAFT_VELOCITY]: t("TXT_CODE_a3abb092"),
[TYPE_MINECRAFT_PURPUR]: t("TXT_CODE_e543f6c0"),
[TYPE_MINECRAFT_BDS]: t("TXT_CODE_67b5f678"),
[TYPE_MINECRAFT_SPONGE]: t("TXT_CODE_e4dbff32"),
[TYPE_MINECRAFT_FORGE]: t("TXT_CODE_5112fcb2"),
[TYPE_MINECRAFT_NEOFORGE]: t("TXT_CODE_98b4ac74"),
[TYPE_MINECRAFT_MOHIST]: t("TXT_CODE_82e624d1"),
[TYPE_MINECRAFT_FABRIC]: t("TXT_CODE_7af6d85a"),
[TYPE_MINECRAFT_BUKKIT]: t("TXT_CODE_992bf9bc"),
[TYPE_MINECRAFT_GEYSER]: t("TXT_CODE_4f57868"),
@ -179,8 +187,11 @@ export const INSTANCE_CONFIGS: InstanceConfigs[] = [
TYPE_MINECRAFT_JAVA,
TYPE_MINECRAFT_BUKKIT,
TYPE_MINECRAFT_FORGE,
TYPE_MINECRAFT_NEOFORGE,
TYPE_MINECRAFT_FABRIC,
TYPE_MINECRAFT_SPONGE
TYPE_MINECRAFT_SPONGE,
TYPE_MINECRAFT_PURPUR,
TYPE_MINECRAFT_PUFFERFISH
]
},
{
@ -195,7 +206,11 @@ export const INSTANCE_CONFIGS: InstanceConfigs[] = [
TYPE_MINECRAFT_JAVA,
TYPE_MINECRAFT_BUKKIT,
TYPE_MINECRAFT_FABRIC,
TYPE_MINECRAFT_SPONGE
TYPE_MINECRAFT_FORGE,
TYPE_MINECRAFT_NEOFORGE,
TYPE_MINECRAFT_SPONGE,
TYPE_MINECRAFT_PURPUR,
TYPE_MINECRAFT_PUFFERFISH
]
},
{
@ -208,7 +223,9 @@ export const INSTANCE_CONFIGS: InstanceConfigs[] = [
TYPE_MINECRAFT_SPIGOT,
TYPE_MINECRAFT_PAPER,
TYPE_MINECRAFT_JAVA,
TYPE_MINECRAFT_BUKKIT
TYPE_MINECRAFT_BUKKIT,
TYPE_MINECRAFT_PURPUR,
TYPE_MINECRAFT_PUFFERFISH
]
},
{
@ -221,7 +238,9 @@ export const INSTANCE_CONFIGS: InstanceConfigs[] = [
TYPE_MINECRAFT_SPIGOT,
TYPE_MINECRAFT_PAPER,
TYPE_MINECRAFT_JAVA,
TYPE_MINECRAFT_BUKKIT
TYPE_MINECRAFT_BUKKIT,
TYPE_MINECRAFT_PURPUR,
TYPE_MINECRAFT_PUFFERFISH
]
},
{
@ -270,7 +289,12 @@ export const INSTANCE_CONFIGS: InstanceConfigs[] = [
info: t("TXT_CODE_2931127f"),
path: "config/paper-global.yml",
redirect: "paper/paper-global.yml",
category: [TYPE_MINECRAFT_JAVA, TYPE_MINECRAFT_PAPER]
category: [
TYPE_MINECRAFT_JAVA,
TYPE_MINECRAFT_PAPER,
TYPE_MINECRAFT_PUFFERFISH,
TYPE_MINECRAFT_PURPUR
]
},
{
fileName: "[Paper] paper-world-defaults.yml",
@ -278,7 +302,28 @@ export const INSTANCE_CONFIGS: InstanceConfigs[] = [
info: t("TXT_CODE_4880ef77"),
path: "config/paper-world-defaults.yml",
redirect: "paper/paper-world-defaults.yml",
category: [TYPE_MINECRAFT_JAVA, TYPE_MINECRAFT_PAPER]
category: [
TYPE_MINECRAFT_JAVA,
TYPE_MINECRAFT_PAPER,
TYPE_MINECRAFT_PUFFERFISH,
TYPE_MINECRAFT_PURPUR
]
},
{
fileName: "[Purpur] pupur.yml",
type: "yml",
info: t("TXT_CODE_98e50717"),
path: "purpur.yml",
redirect: "purpur/purpur.yml",
category: [TYPE_MINECRAFT_JAVA, TYPE_MINECRAFT_PAPER, TYPE_MINECRAFT_PURPUR]
},
{
fileName: "[Pufferfish] pufferfish.yml",
type: "yml",
info: t("TXT_CODE_9213f8e3"),
path: "pufferfish.yml",
redirect: "pufferfish/pufferfish.yml",
category: [TYPE_MINECRAFT_JAVA, TYPE_MINECRAFT_PUFFERFISH]
},
{
fileName: "[Geyser] config.yml",
@ -306,10 +351,34 @@ export const INSTANCE_CONFIGS: InstanceConfigs[] = [
},
{
fileName: "[Tshock] config.json",
type: "yml",
type: "json",
info: t("TXT_CODE_1cd8f9d2"),
path: "tshock/config.json",
redirect: "tshock/config.json",
category: [TYPE_TERRARIA]
},
{
fileName: "[Forge] fml.toml",
type: "toml",
info: t("TXT_CODE_7e6a82d8"),
path: "config/fml.toml",
redirect: "forge/fml.toml",
category: [TYPE_MINECRAFT_FORGE]
},
{
fileName: "[NeoForge] neoforge-server.toml",
type: "toml",
info: t("TXT_CODE_5b6f3691"),
path: "config/neoforge-server.toml",
redirect: "neoforge/neoforge-server.toml",
category: [TYPE_MINECRAFT_NEOFORGE]
},
{
fileName: "[NeoForge] neoforge-common.toml",
type: "toml",
info: t("TXT_CODE_1efc7c5f"),
path: "config/neoforge-common.toml",
redirect: "neoforge/neoforge-common.toml",
category: [TYPE_MINECRAFT_NEOFORGE]
}
];

View File

@ -8,7 +8,7 @@ export function useMountComponent(data: Record<string, any> = {}) {
const mount = <T>(component: Component) => {
if (isOpen) return;
isOpen = true;
return new Promise<T>((resolve, reject) => {
return new Promise<T>((resolve) => {
const div = document.createElement("div");
document.body.appendChild(div);
const app = createApp(component, {

View File

@ -0,0 +1,173 @@
import { t } from "@/lang/i18n";
import { scheduleCreate, scheduleDelete, scheduleList } from "@/services/apis/instance";
import { reportErrorMsg } from "@/tools/validator";
import type { ScheduleTaskForm } from "@/types";
import { message, notification } from "ant-design-vue";
import dayjs from "dayjs";
export function useSchedule(instanceId: string, daemonId: string) {
const createTaskTypeInterval = async (newTask: ScheduleTaskForm) => {
const arr = newTask.cycle;
let ps = Number(arr[0]);
let pm = Number(arr[1]);
let ph = Number(arr[2]);
const rs = ps + pm * 60 + ph * 60 * 60;
newTask.time = rs.toString();
await createTask(newTask);
};
const createTaskTypeCycle = async (newTask: ScheduleTaskForm) => {
const weekend = newTask.weekend;
if (!newTask.objTime) throw new Error(t("TXT_CODE_349edc57"));
if (weekend.length === 0) throw new Error(t("TXT_CODE_2fe0cc84"));
const time = newTask.objTime;
const h = time.hour();
const m = time.minute();
const s = time.second();
newTask.time = `${s} ${m} ${h} * * ${weekend.join(",")}`;
await createTask(newTask);
};
const createTaskTypeSpecify = async (newTask: ScheduleTaskForm) => {
if (!newTask.objTime) throw new Error(t("TXT_CODE_349edc57"));
const time = newTask.objTime;
const mm = time.month() + 1;
const dd = time.date();
const h = time.hour();
const m = time.minute();
const s = time.second();
newTask.time = `${s} ${m} ${h} ${dd} ${mm} *`;
await createTask(newTask);
};
const calculateIntervalFromTime = (time: string): string[] => {
const totalSeconds = Number(time);
const ph = Math.floor(totalSeconds / 3600);
const pm = Math.floor((totalSeconds % 3600) / 60);
const ps = totalSeconds % 60;
return [ps.toString(), pm.toString(), ph.toString()];
};
const calculateTimeFromCycle = (time: string) => {
const regex = /(\d+) (\d+) (\d+) \* \* (.+)/;
const match = time.match(regex);
let objTime = dayjs(),
weekend: number[] = [];
if (match) {
const s = match[1];
const m = match[2];
const h = match[3];
const w = match[4].split(",").map(Number);
const now = new Date();
now.setSeconds(Number(s));
now.setMinutes(Number(m));
now.setHours(Number(h));
objTime = dayjs(now);
weekend = w;
} else {
notification.error({ message: t("TXT_CODE_afabf3ca") });
}
return {
objTime,
weekend
};
};
const parseTaskTime = (time: string) => {
const regex = /(\d+) (\d+) (\d+) (\d+) (\d+) \*/; // 匹配 time 格式
const match = time.match(regex);
let objTime = dayjs();
if (match) {
const s = Number(match[1]);
const m = Number(match[2]);
const h = Number(match[3]);
const dd = Number(match[4]);
const mm = Number(match[5]) - 1;
const now = new Date();
now.setSeconds(s);
now.setMinutes(m);
now.setHours(h);
now.setDate(dd);
now.setMonth(mm);
objTime = dayjs(now);
} else {
notification.error({ message: t("TXT_CODE_afabf3ca") });
}
return objTime;
};
const { state: createState, execute: create } = scheduleCreate();
const createTask = async (newTask: ScheduleTaskForm) => {
try {
if (!newTask.count) newTask.count = -1;
await create({
params: {
daemonId: daemonId,
uuid: instanceId
},
data: newTask
});
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const { state: schedules, execute: list, isLoading: scheduleListLoading } = scheduleList();
const getScheduleList = async () => {
try {
await list({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? ""
}
});
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const deleteSchedule = async (name: string, showMsg: boolean = true) => {
const { execute, state } = scheduleDelete();
try {
await execute({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? "",
task_name: name
}
});
if (state.value) {
showMsg && message.success(t("TXT_CODE_28190dbc"));
getScheduleList();
}
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
return {
createTaskTypeInterval,
createTaskTypeCycle,
createTaskTypeSpecify,
calculateIntervalFromTime,
calculateTimeFromCycle,
parseTaskTime,
createTask,
createState,
getScheduleList,
scheduleListLoading,
schedules,
deleteSchedule
};
}

View File

@ -3,8 +3,6 @@
import { createI18n } from "vue-i18n";
import { updateSettings } from "@/services/apis";
import enUS from "@languages/en_US.json";
// DO NOT I18N
// If you want to add the language of your own country, you need to add the code here.
export const SUPPORTED_LANGS = [
@ -73,9 +71,7 @@ export async function initInstallPageFlow(language: string) {
async function initI18n(lang: string) {
lang = toStandardLang(lang);
const messages: Record<string, any> = {
en_us: enUS
};
const messages: Record<string, any> = {};
const langFiles = import.meta.glob("../../../languages/*.json");
for (const path in langFiles) {
if (
@ -137,10 +133,10 @@ const isPT = () => {
return getCurrentLang() === "pt_br";
};
const $t = (...args: string[]): string => {
const $t = (...args: any[]): string => {
return (i18n.global.t as Function)(...args);
};
const t = (...args: string[]): string => {
const t = (...args: any[]): string => {
return (i18n.global.t as Function)(...args);
};

View File

@ -0,0 +1,182 @@
import { useDefineApi } from "@/stores/useDefineApi";
import { computed, onMounted, ref, type Ref } from "vue";
import { queryUsername } from "./user";
import { Modal } from "ant-design-vue";
import { t } from "@/lang/i18n";
import { useAppStateStore } from "@/stores/useAppStateStore";
export interface ShopItem {
productId: number;
title: string;
price: number;
ispId?: number;
daemonId: number;
payload: string;
remark: string;
}
export interface ShopInfo {
uid?: number;
nickname: string;
username: string;
lastTime: number;
introduction: string;
afterSalesGroup: string;
}
export interface ShopInfoResponse {
ispInfo: ShopInfo;
products: ShopItem[];
}
export interface BuyInstanceResponse {
instanceId: string;
expire: number;
username: string;
password: string;
uuid: string;
}
export interface PurchaseQueryResponse {
id: number;
uid: number;
nodeId: number;
productId: number;
panelAddr: string;
username: string;
password: string;
activeTime?: number;
code: string;
}
export const CURRENT_PANEL_ADDR = window.location.host.includes("localhost")
? "http://localhost:23333/"
: `${window.location.protocol}//${window.location.host}/`;
export const requestRedeemPlatform = useDefineApi<
{
data: {
targetUrl: string;
method: string;
data?: object;
params?: object;
};
},
any
>({
url: "/api/exchange/request_redeem_platform",
method: "POST",
timeout: 1000 * 30
});
export function useShopInfo() {
const config = requestRedeemPlatform();
const isError = ref<Error>();
const { state: appState } = useAppStateStore();
const loadProducts = async (businessId?: string) => {
try {
isError.value = undefined;
await config.execute({
data: {
targetUrl: "/api/instances/query_products",
method: "GET",
params: {
addr: CURRENT_PANEL_ADDR,
businessId: businessId || appState.settings.businessId
}
}
});
} catch (error) {
isError.value = error as Error;
}
};
onMounted(async () => {
await loadProducts();
});
return {
...config,
isError,
loadProducts,
isLoading: config.isLoading,
state: config.state as Ref<ShopInfoResponse>,
shopInfo: computed<ShopInfo>(() => config.state.value?.ispInfo),
products: computed<ShopItem[]>(() => config.state.value?.products)
};
}
export function useRedeem() {
const { state: appState } = useAppStateStore();
const { execute, isLoading } = requestRedeemPlatform();
const preCheckUsername = (username: string) => {
isLoading.value = true;
return new Promise((resolve, reject) => {
queryUsername()
.execute({ params: { username } })
.then((res) => {
Modal.confirm({
title: t("TXT_CODE_893567ac"),
content: res.value?.uuid
? `${username} ${t("TXT_CODE_c684d8b2")}`
: `${username} ${t("TXT_CODE_4c72565d")}`,
onOk: () => {
resolve(true);
},
onCancel: () => {
reject(t("TXT_CODE_f94e428a"));
}
});
})
.catch(() => {
reject(t("TXT_CODE_1d8f3c33"));
})
.finally(() => {
isLoading.value = false;
});
});
};
const buyInstance = async (username: string, code: string) => {
await preCheckUsername(username);
const res = await execute({
data: {
targetUrl: "/api/instances/use_redeem",
method: "POST",
data: { code, username, businessId: appState.settings.businessId }
}
});
return res.value as BuyInstanceResponse;
};
const renewalInstance = async (username: string, code: string, instanceId: string) => {
const res = await execute({
data: {
targetUrl: "/api/instances/use_redeem",
method: "POST",
data: { code, instanceId, username, businessId: appState.settings.businessId }
}
});
return res.value as BuyInstanceResponse;
};
const queryPurchase = async (code: string) => {
const res = await execute({
data: {
targetUrl: "/api/instances/purchase_history",
method: "GET",
params: { code, businessId: appState.settings.businessId }
}
});
return res.value as PurchaseQueryResponse;
};
return {
isLoading,
buyInstance,
queryPurchase,
renewInstance: renewalInstance
};
}

View File

@ -40,3 +40,18 @@ export const confirm2FA = useDefineApi<
url: "/api/auth/confirm2fa",
method: "POST"
});
export const queryUsername = useDefineApi<
{
params: {
username: string;
};
},
{
uuid?: string;
userName?: string;
}
>({
url: "/api/auth/query_username",
method: "GET"
});

View File

@ -20,7 +20,10 @@ export const useAppStateStore = createGlobalState(() => {
language: "en_us",
settings: {
canFileManager: false,
allowUsePreset: false
allowUsePreset: false,
businessMode: false,
businessId: "",
allowChangeCmd: false
}
});
@ -52,15 +55,14 @@ export const useAppStateStore = createGlobalState(() => {
const updatePanelStatus = async () => {
const { state } = useAppStateStore();
const status = await panelStatus().execute();
state.isInstall = status.value?.isInstall ?? true;
state.versionChange = status.value?.versionChange ? true : false;
state.settings = status.value?.settings ?? {
canFileManager: false,
allowUsePreset: false
};
const panelStatusRes = await panelStatus().execute();
state.isInstall = panelStatusRes.value?.isInstall ?? true;
state.versionChange = panelStatusRes.value?.versionChange ? true : false;
if (panelStatusRes.value?.settings) {
state.settings = panelStatusRes.value?.settings;
}
if (state.isInstall) {
state.language = toStandardLang(status.value?.language);
state.language = toStandardLang(panelStatusRes.value?.language);
} else {
state.language = searchSupportLanguage(window.navigator.language);
await initInstallPageFlow(state.language);

View File

@ -198,3 +198,5 @@ export const isInt = (x: any) => {
}
return true;
};
export const padZero = (num: string) => (num === "0" ? num : num.padStart(2, "0"));

View File

@ -0,0 +1,29 @@
import { t } from "@/lang/i18n";
import { useClipboard } from "@vueuse/core";
import { message, Modal } from "ant-design-vue";
import { h } from "vue";
const { copy, copied, isSupported } = useClipboard();
export const toCopy = async (sth: string | number) => {
try {
if (!isSupported.value) {
const input = document.createElement("input");
input.setAttribute("value", String(sth));
document.body.appendChild(input);
input.select();
document.execCommand("copy");
document.body.removeChild(input);
return message.success(t("TXT_CODE_b858d78a"));
} else {
await copy(String(sth));
if (copied.value) return message.success(t("TXT_CODE_b858d78a"));
}
} catch (err: any) {
console.error(err);
Modal.warning({
title: t("TXT_CODE_ca07c84c"),
content: [h("span", t("TXT_CODE_2452016e")), h("br"), h("span", sth)]
});
}
};

View File

@ -35,3 +35,8 @@ export function reportErrorMsg(error: any = {}) {
}
export const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\x00-\x7F]{9,36}$/;
export function isLocalNetworkIP(ip: string): boolean {
const localNetworks = [/^10\./, /^172\.(1[6-9]|2[0-9]|3[0-1])\./, /^192\.168\./];
return localNetworks.some((pattern) => pattern.test(ip));
}

View File

@ -105,7 +105,7 @@ export const ScheduleType = {
};
export enum ScheduleCreateType {
INTERVAL = "1",
CYCLE = "2",
SPECIFY = "3"
INTERVAL = 1,
CYCLE,
SPECIFY
}

View File

@ -1,4 +1,5 @@
/* eslint-disable no-unused-vars */
import type { Dayjs } from "dayjs";
import type {
IGlobalInstanceConfig,
ILayoutCard as GlobalLayoutCard,
@ -9,7 +10,7 @@ import type {
IQuickStartTemplate,
IQuickStartPackages
} from "../../../common/global";
import type { INSTANCE_STATUS_CODE } from "./const";
import type { INSTANCE_STATUS_CODE, ScheduleCreateType } from "./const";
export type JsonData = IJsonData;
export type MapData<T> = IMapData<T>;
@ -73,6 +74,9 @@ export interface Settings {
presetPackAddr: string;
redisUrl: string;
allowUsePreset: boolean;
businessMode: boolean;
businessId: string;
allowChangeCmd: boolean;
}
export interface ImageInfo {
@ -221,10 +225,17 @@ export interface Schedule {
export interface NewScheduleTask {
name: string;
count: string;
count: number;
time: string;
action: string;
type: string;
type: ScheduleCreateType;
}
export interface ScheduleTaskForm extends NewScheduleTask {
payload: string;
weekend: number[];
cycle: string[];
objTime: Dayjs;
}
export interface PanelStatus {
@ -234,5 +245,8 @@ export interface PanelStatus {
settings: {
canFileManager: boolean;
allowUsePreset: boolean;
businessMode: boolean;
businessId: string;
allowChangeCmd: boolean;
};
}

View File

@ -115,7 +115,6 @@ const showCardOperator = (card: ILayoutCard) => {
</template>
<style lang="scss">
// Gloabl
@import "../assets/variables.scss";
// Hide the EmptyCard component when mobile device.
@ -152,12 +151,10 @@ const showCardOperator = (card: ILayoutCard) => {
@keyframes scaleAnimation {
0% {
opacity: 0.02;
// transform: scale(0.98);
}
100% {
opacity: 1;
// transform: scale(1);
}
}
</style>

View File

@ -500,7 +500,7 @@ onMounted(async () => {
:target-instance-info="item"
:target-daemon-id="currentRemoteNode?.uuid"
@click="handleSelectInstance(item)"
@refrsh-list="initInstancesData()"
@refresh-list="initInstancesData()"
/>
</a-col>
</fade-up-animation>

View File

@ -34,7 +34,7 @@ const formData = reactive({
});
const { execute: login } = loginUser();
const { updateUserInfo, isAdmin } = useAppStateStore();
const { updateUserInfo, isAdmin, state: appConfig } = useAppStateStore();
const loginStep = ref(0);
const is2Fa = ref(false);
@ -89,6 +89,10 @@ const loginSuccess = () => {
router.push({ path: "/customer" });
}
};
const openBuyInstanceDialog = async () => {
router.push({ path: "/shop" });
};
</script>
<template>
@ -157,7 +161,7 @@ const loginSuccess = () => {
</form>
<div class="mt-24 flex-between align-center">
<div class="mcsmanager-link">
<div v-if="!appConfig.settings.businessMode" class="mcsmanager-link">
<div
v-if="pageInfoResult?.loginInfo"
class="global-markdown-html"
@ -168,9 +172,21 @@ const loginSuccess = () => {
MCSManager
</a>
</div>
<a-button size="large" type="primary" style="min-width: 95px" @click="handleLogin">
{{ t("TXT_CODE_d507abff") }}
</a-button>
<div v-else></div>
<div class="justify-end" style="gap: 10px">
<a-button
v-if="appConfig.settings.businessMode"
size="large"
class="green"
style="min-width: 95px"
@click="openBuyInstanceDialog"
>
{{ t("TXT_CODE_5a408a5e") }}
</a-button>
<a-button size="large" type="primary" style="min-width: 95px" @click="handleLogin">
{{ t("TXT_CODE_d2c1a316") }}
</a-button>
</div>
</div>
</div>
</div>

View File

@ -10,6 +10,7 @@ import {
BookOutlined,
BugOutlined,
GithubOutlined,
KeyOutlined,
LockOutlined,
MessageOutlined,
MoneyCollectOutlined,
@ -40,7 +41,7 @@ interface MySettings extends Settings {
bgUrl?: string;
}
const ApacheLicense = `Copyright 2024 MCSManager Dev
const ApacheLicense = `Copyright ${new Date().getFullYear()} MCSManager Dev
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -56,7 +57,7 @@ limitations under the License.`;
const formData = ref<MySettings>();
const submit = async () => {
const submit = async (needReload: boolean = true) => {
if (formData.value) {
try {
await submitExecute({
@ -65,7 +66,7 @@ const submit = async () => {
}
});
message.success(t("TXT_CODE_a7907771"));
setTimeout(() => window.location.reload(), 600);
if (needReload) setTimeout(() => window.location.reload(), 600);
} catch (error: any) {
reportErrorMsg(error);
}
@ -88,6 +89,12 @@ const menus = arrayFilter([
key: "security",
icon: LockOutlined
},
{
title: t("TXT_CODE_8bb8e2a1"),
key: "business",
icon: KeyOutlined,
condition: () => isCN()
},
{
title: t("TXT_CODE_3b4b656d"),
key: "about",
@ -186,6 +193,10 @@ const startDesignUI = async () => {
});
};
const gotoBusinessCenter = () => {
window.open("https://redeem.mcsmanager.com/", "_blank");
};
onMounted(async () => {
const res = await execute();
const cfg = await getSettingsConfig();
@ -376,6 +387,29 @@ onMounted(async () => {
</a-typography-text>
</a-typography-paragraph>
<a-form-item>
<a-typography-title :level="5">
{{ t("TXT_CODE_a583cae4") }}
</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("TXT_CODE_bfbdf579") }}
</a-typography-text>
</a-typography-paragraph>
<a-select
v-model:value.prop="(formData as any).allowChangeCmd"
style="max-width: 320px"
>
<a-select-option
v-for="item in allYesNo"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-typography-title :level="5">
{{ t("TXT_CODE_adab942e") }}
@ -387,7 +421,10 @@ onMounted(async () => {
{{ t("TXT_CODE_e5b7522d") }}
</a-typography-text>
</a-typography-paragraph>
<a-select v-model:value.prop="formData.canFileManager" style="max-width: 320px">
<a-select
v-model:value.prop="(formData as any).canFileManager"
style="max-width: 320px"
>
<a-select-option
v-for="item in allYesNo"
:key="item.value"
@ -407,7 +444,10 @@ onMounted(async () => {
{{ t("TXT_CODE_f5f9664") }}
</a-typography-text>
</a-typography-paragraph>
<a-select v-model:value.prop="formData.allowUsePreset" style="max-width: 320px">
<a-select
v-model:value.prop="(formData as any).allowUsePreset"
style="max-width: 320px"
>
<a-select-option
v-for="item in allYesNo"
:key="item.value"
@ -428,7 +468,10 @@ onMounted(async () => {
</a-typography-text>
</a-typography-paragraph>
<a-select v-model:value.prop="formData.crossDomain" style="max-width: 320px">
<a-select
v-model:value.prop="(formData as any).crossDomain"
style="max-width: 320px"
>
<a-select-option
v-for="item in allYesNo"
:key="item.value"
@ -450,7 +493,7 @@ onMounted(async () => {
</a-typography-paragraph>
<a-select
v-model:value.prop="formData.reverseProxyMode"
v-model:value.prop="(formData as any).reverseProxyMode"
style="max-width: 320px"
>
<a-select-option
@ -473,7 +516,10 @@ onMounted(async () => {
</a-typography-text>
</a-typography-paragraph>
<a-select v-model:value.prop="formData.loginCheckIp" style="max-width: 320px">
<a-select
v-model:value.prop="(formData as any).loginCheckIp"
style="max-width: 320px"
>
<a-select-option
v-for="item in allYesNo"
:key="item.value"
@ -484,7 +530,7 @@ onMounted(async () => {
</a-select>
</a-form-item>
<div class="button">
<a-button type="primary" :loading="submitIsLoading" @click="submit()">
<a-button type="primary" :loading="submitIsLoading" @click="submit(false)">
{{ t("TXT_CODE_abfe9512") }}
</a-button>
</div>
@ -493,6 +539,67 @@ onMounted(async () => {
</div>
</template>
<template #business>
<div
:style="{
maxHeight: card.height,
overflowY: 'auto'
}"
>
<a-typography-title :level="4" class="mb-24">
{{ t("TXT_CODE_8bb8e2a1") }}
</a-typography-title>
<div class="mb-24">
<a-typography-paragraph>
<a-typography-title :level="5">
{{ t("TXT_CODE_180884da") }}
</a-typography-title>
<a-typography-text type="secondary">
{{ t("TXT_CODE_3f227bcf") }}
</a-typography-text>
</a-typography-paragraph>
<div>
<a-switch v-model:checked="formData.businessMode" @change="submit(false)" />
</div>
</div>
<div class="mb-24">
<a-typography-paragraph>
<a-typography-title :level="5">
{{ t("TXT_CODE_d31196db") }}
</a-typography-title>
<a-typography-text type="secondary">
{{ t("TXT_CODE_59c39e03") }}
</a-typography-text>
</a-typography-paragraph>
<div>
<a-button :disabled="!formData.businessMode" @click="gotoBusinessCenter()">
{{ t("TXT_CODE_2dbd3cd3") }}
</a-button>
</div>
</div>
<div v-if="formData.businessMode" class="mb-24">
<a-typography-paragraph>
<a-typography-title :level="5">{{ t("TXT_CODE_72cfab69") }}</a-typography-title>
<a-typography-text type="secondary">
{{ t("TXT_CODE_678164d7") }}
</a-typography-text>
</a-typography-paragraph>
<div>
<a-input
v-model:value="formData.businessId"
style="max-width: 200px"
placeholder="eg: 123"
/>
</div>
</div>
<div>
<a-button type="primary" :loading="submitIsLoading" @click="submit(false)">
{{ t("TXT_CODE_abfe9512") }}
</a-button>
</div>
</div>
</template>
<template #about>
<div :style="{ maxHeight: card.height, overflowY: 'auto' }">
<a-typography-title :level="4" class="mb-24">

View File

@ -0,0 +1,164 @@
<script setup lang="ts">
import PurchaseQueryDialog from "@/components/fc/PurchaseQueryDialog.vue";
import UseRedeemDialog from "@/components/fc/UseRedeemDialog.vue";
import InnerCard from "@/components/InnerCard.vue";
import { router } from "@/config/router";
import { useMountComponent } from "@/hooks/useMountComponent";
import { t } from "@/lang/i18n";
import { useShopInfo } from "@/services/apis/redeem";
import { useAppStateStore } from "@/stores/useAppStateStore";
import type { LayoutCard } from "@/types";
defineProps<{
card: LayoutCard;
}>();
const { products: shopItems, isLoading, isError } = useShopInfo();
const { isLogged } = useAppStateStore();
const reloadPage = () => {
window.location.reload();
};
const openDialog = async () => {
useMountComponent().mount(UseRedeemDialog);
};
const openPurchaseQueryDialog = async () => {
useMountComponent().mount(PurchaseQueryDialog);
};
const openLoginPage = () => {
router.push("/login");
};
</script>
<template>
<CardPanel class="card-wrapper h-100 w-100" :style="{ maxHeight: card.height }">
<template #title>
{{ card.title }}
</template>
<template #body>
<div v-if="isError" class="error-container">
<a-result
status="error"
:title="t('TXT_CODE_84cf0951')"
:icon="null"
:sub-title="isError.message"
>
<template #extra>
<a-button type="primary" @click="reloadPage">
{{ t("TXT_CODE_d080f2d7") }}
</a-button>
</template>
</a-result>
</div>
<div v-if="isLoading">
<Loading />
</div>
<div v-if="shopItems" class="shop-item-container">
<div class="flex justify-end mb-20" style="gap: 10px">
<a-button type="primary" @click="openDialog">
{{ t("TXT_CODE_75bf9192") }}
</a-button>
<a-button v-if="!isLogged" @click="openLoginPage">
{{ t("TXT_CODE_22510c5c") }}
</a-button>
<a-button @click="openPurchaseQueryDialog">
{{ t("TXT_CODE_17b3748b") }}
</a-button>
</div>
<InnerCard
v-for="item in shopItems"
:key="item.productId"
class="shelves-card-container"
:full-height="false"
>
<template #title>{{ item.title }}</template>
<template #operator>
<a-typography-text type="secondary" style="font-weight: 400">
Product ID {{ item.productId }}
</a-typography-text>
</template>
<template #body>
<div class="">
<div class="shelves-card-item">
<div class="shelves-card-item-title">
<pre>{{ item.remark }}</pre>
</div>
<div class="shelves-card-item-price">
<div>
<div class="shelves-card-item-price-label">{{ t("TXT_CODE_4bf8a52f") }}</div>
<div>
<span class="price-text"> {{ item.price }} </span>
<span>/{{ t("TXT_CODE_6cb9bb04") }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
</InnerCard>
</div>
</template>
</CardPanel>
</template>
<style lang="less" scoped>
.shop-item-container {
overflow-y: auto;
text-align: left;
color: var(--color-gray-13);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.card-wrapper {
pre {
white-space: pre-wrap;
font-family:
ui-sans-serif,
system-ui,
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
Segoe UI Symbol,
"Noto Color Emoji";
background: unset !important;
border: unset !important;
padding: 0 !important;
}
}
.shelves-card-container {
width: 100%;
margin-bottom: 20px;
}
.shelves-card-item-title {
margin-bottom: 12px;
}
.shelves-card-item-price {
display: flex;
justify-content: flex-end;
font-size: 14px;
text-align: right;
.shelves-card-item-price-label {
opacity: 0.6;
font-size: 12px;
text-align: right;
margin-bottom: 4px;
}
}
.price-text {
color: var(--color-red-6);
font-size: 20px;
}
</style>

View File

@ -0,0 +1,82 @@
<script setup lang="ts">
import { t } from "@/lang/i18n";
import type { LayoutCard } from "@/types";
import { useShopInfo } from "@/services/apis/redeem";
import { useAppStateStore } from "@/stores/useAppStateStore";
import Loading from "@/components/Loading.vue";
defineProps<{
card: LayoutCard;
}>();
const { shopInfo, isLoading, isError } = useShopInfo();
const { isAdmin } = useAppStateStore();
</script>
<template>
<CardPanel class="card-wrapper h-100 w-100" :style="{ maxHeight: card.height }">
<template #title>
{{ shopInfo?.nickname }}
</template>
<template #body>
<div v-if="isLoading">
<Loading />
</div>
<div v-if="shopInfo" class="shop-item-container">
<div class="container flex flex-between mb-20" style="gap: 20px">
<div>
<a-typography-paragraph>
<a-typography-text v-if="shopInfo.nickname" type="secondary">
{{ shopInfo.introduction }}
</a-typography-text>
</a-typography-paragraph>
<a-typography-paragraph>
<a-typography-text v-if="shopInfo.afterSalesGroup">
<span>{{ t("TXT_CODE_ef27fda1") }}</span>
<a-tag>{{ shopInfo.afterSalesGroup }}</a-tag>
</a-typography-text>
</a-typography-paragraph>
<a-typography-paragraph v-if="isAdmin">
<a-typography-text>
{{ t("TXT_CODE_ec0b25f5") }}
</a-typography-text>
</a-typography-paragraph>
</div>
</div>
</div>
<div v-if="isError" class="error-container">
<div>
<p>
{{ t("TXT_CODE_e5bf0df1") }}
</p>
<p>
{{ t("TXT_CODE_4ef3f800") }}
</p>
</div>
</div>
</template>
</CardPanel>
</template>
<style lang="less" scoped>
.shop-item-container {
overflow-y: auto;
text-align: left;
color: var(--color-gray-13);
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.error-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 24px;
text-align: center;
}
</style>

View File

@ -29,16 +29,6 @@ const columns = [
return INSTANCE_STATUS[e.text] || e.text;
}
},
{
title: t("TXT_CODE_662ad338"),
dataIndex: "ie",
customRender: (e: { text: string; record: { oe: string; ie: string } }) => {
if (!e.record.oe && !e.record.ie) {
return "--";
}
return `${t("TXT_CODE_bb888626")}:${e.record.oe} ${t("TXT_CODE_4b6e951")}:${e.record.ie}`;
}
},
{
title: t("TXT_CODE_5ab2062d"),
dataIndex: "lastDatetime",
@ -100,7 +90,7 @@ onMounted(() => {
:disabled="record.status === INSTANCE_STATUS_CODE.BUSY"
@click="operate(record.daemonId, record.instanceUuid)"
>
{{ t("TXT_CODE_5974bf24") }}
{{ t("TXT_CODE_aa43b248") }}
</a-button>
</template>
</template>

View File

@ -99,10 +99,16 @@ onMounted(async () => {
</a-typography-paragraph>
<a-typography-paragraph v-if="instanceGameServerInfo">
{{ t("TXT_CODE_855c4a1c") }}{{ instanceGameServerInfo.players }}
<span>{{ t("TXT_CODE_855c4a1c") }}</span>
<span>{{ instanceGameServerInfo.players }}</span>
</a-typography-paragraph>
<a-typography-paragraph v-if="instanceGameServerInfo">
{{ t("TXT_CODE_e260a220") }}{{ instanceGameServerInfo.version }}
<span>
{{ t("TXT_CODE_e260a220") }}
</span>
<span>
{{ instanceGameServerInfo.version }}
</span>
</a-typography-paragraph>
<a-typography-paragraph>
@ -126,13 +132,15 @@ onMounted(async () => {
<div
v-for="(item, index) in dockerPortsArray(instanceInfo?.config.docker.ports ?? [])"
:key="index"
style="margin-bottom: 2px"
class="mb-4"
>
<span>{{ t("TXT_CODE_8dfc41ef") }}: {{ item.host }}</span>
<span style="margin-left: 6px">{{ t("TXT_CODE_8f8103b7") }}: {{ item.container }}</span>
<span style="margin-left: 8px">
<span>
<a-tag color="green">{{ item.protocol.toUpperCase() }}</a-tag>
</span>
<a-tag>
<span>{{ t("TXT_CODE_8dfc41ef") }}: {{ item.host }}</span>
<span class="ml-4"> {{ t("TXT_CODE_8f8103b7") }}: {{ item.container }} </span>
</a-tag>
</div>
</div>
</a-typography-paragraph>
@ -150,9 +158,13 @@ onMounted(async () => {
</span>
</a-typography-paragraph>
<a-typography-paragraph>
<a-typography-text> {{ t("TXT_CODE_30051f9b") }} </a-typography-text>
<a-typography-text :title="instanceInfo?.instanceUuid">
{{ t("TXT_CODE_30051f9b") }}
</a-typography-text>
<a-typography-text :copyable="{ text: instanceInfo?.instanceUuid }"> </a-typography-text>
<a-typography-text class="ml-20"> {{ t("TXT_CODE_5f2d2e30") }} </a-typography-text>
<a-typography-text class="ml-20" :title="daemonId">
{{ t("TXT_CODE_5f2d2e30") }}
</a-typography-text>
<a-typography-text :copyable="{ text: daemonId }"> </a-typography-text>
</a-typography-paragraph>
</template>

View File

@ -70,13 +70,16 @@ const {
beforeUpload,
downloadFile,
handleChangeDir,
handleSearchChange,
selectedFile,
rowClickTable,
handleTableChange,
getFileStatus,
changePermission,
toDisk,
oneSelected
oneSelected,
isImage,
showImage
} = useFileManager(instanceId, daemonId);
const { openRightClickMenu } = useRightClickMenu();
@ -189,6 +192,13 @@ const editFile = (fileName: string) => {
FileEditorDialog.value?.openDialog(path, fileName);
};
const handleClickFile = async (file: DataType) => {
if (file.type === 0) return rowClickTable(file.name, file.type);
const fileExtName = getFileExtName(file.name);
if (isImage(fileExtName)) return showImage(file);
return editFile(file.name);
};
const menuList = (record: DataType) =>
arrayFilter<ItemType & { style?: CSSProperties }>([
{
@ -308,7 +318,11 @@ onUnmounted(() => {
</template>
<template #right>
<a-typography-text v-if="selectedRowKeys.length">
{{ t("TXT_CODE_7b2c5414") + ` ${String(selectedRowKeys.length)} ` + t("TXT_CODE_5cd3b4bd") }}
{{
`${t("TXT_CODE_7b2c5414")} ${String(selectedRowKeys.length)} ${t(
"TXT_CODE_5cd3b4bd"
)}`
}}
</a-typography-text>
<a-upload
@ -383,7 +397,7 @@ onUnmounted(() => {
v-model:value.trim.lazy="operationForm.name"
:placeholder="t('TXT_CODE_7cad42a5')"
allow-clear
@change="getFileList()"
@change="handleSearchChange()"
>
<template #suffix>
<search-outlined />
@ -466,12 +480,13 @@ onUnmounted(() => {
:custom-row="
(record: DataType) => {
return {
onContextmenu: (e: MouseEvent) => handleRightClickRow(e, record)
onContextmenu: (e: MouseEvent) => handleRightClickRow(e, record as DataType)
};
}
"
@change="
(e) => handleTableChange({ current: e.current || 0, pageSize: e.pageSize || 0 })
(e: any) =>
handleTableChange({ current: e.current || 0, pageSize: e.pageSize || 0 })
"
>
<template #bodyCell="{ column, record }">
@ -479,11 +494,7 @@ onUnmounted(() => {
<a-button
type="link"
class="file-name"
@click="
record.type !== 1
? rowClickTable(record.name, record.type)
: editFile(record.name)
"
@click="handleClickFile(record as DataType)"
>
<span class="mr-4">
<component

View File

@ -19,12 +19,17 @@ import { LayoutCardHeight } from "../../config/originLayoutConfig";
import { useAppStateStore } from "@/stores/useAppStateStore";
import { useAppRouters } from "@/hooks/useAppRouters";
import { useLayoutCardTools } from "../../hooks/useCardTools";
import { TYPE_MINECRAFT_JAVA, useInstanceInfo } from "@/hooks/useInstance";
import {
TYPE_MINECRAFT_JAVA,
TYPE_STEAM_SERVER_UNIVERSAL,
useInstanceInfo
} from "@/hooks/useInstance";
import TermConfig from "./dialogs/TermConfig.vue";
import EventConfig from "./dialogs/EventConfig.vue";
import PingConfig from "./dialogs/PingConfig.vue";
import RconSettings from "./dialogs/RconSettings.vue";
import InstanceDetail from "./dialogs/InstanceDetail.vue";
import InstanceFundamentalDetail from "./dialogs/InstanceFundamentalDetail.vue";
import type { RouteLocationPathRaw } from "vue-router";
import { TYPE_UNIVERSAL, TYPE_WEB_SHELL } from "../../hooks/useInstance";
import McPingSettings from "./dialogs/McPingSettings.vue";
@ -36,6 +41,7 @@ const mcSettingsDialog = ref<InstanceType<typeof McPingSettings>>();
const eventConfigDialog = ref<InstanceType<typeof EventConfig>>();
const pingConfigDialog = ref<InstanceType<typeof PingConfig>>();
const instanceDetailsDialog = ref<InstanceType<typeof InstanceDetail>>();
const instanceFundamentalDetailDialog = ref<InstanceType<typeof InstanceFundamentalDetail>>();
const { toPage: toOtherPager } = useAppRouters();
@ -118,7 +124,9 @@ const btns = computed(() => {
icon: BuildOutlined,
click: () => {
rconSettingsDialog.value?.openDialog();
}
},
condition: () =>
instanceInfo.value?.config.type.includes(TYPE_STEAM_SERVER_UNIVERSAL) ?? false
},
{
title: t("TXT_CODE_d23631cb"),
@ -148,7 +156,6 @@ const btns = computed(() => {
eventConfigDialog.value?.openDialog();
}
},
{
title: t("TXT_CODE_4f34fc28"),
icon: AppstoreAddOutlined,
@ -156,6 +163,17 @@ const btns = computed(() => {
click: () => {
instanceDetailsDialog.value?.openDialog();
}
},
{
title: t("TXT_CODE_4f34fc28"),
icon: AppstoreAddOutlined,
condition: () =>
!isAdmin.value &&
instanceInfo.value?.config.processType === "docker" &&
state.settings.allowChangeCmd,
click: () => {
instanceFundamentalDetailDialog.value?.openDialog();
}
}
]);
});
@ -221,6 +239,14 @@ const btns = computed(() => {
@update="refreshInstanceInfo"
/>
<InstanceFundamentalDetail
ref="instanceFundamentalDetailDialog"
:instance-info="instanceInfo"
:instance-id="instanceId"
:daemon-id="daemonId"
@update="refreshInstanceInfo"
/>
<RconSettings
ref="rconSettingsDialog"
:instance-info="instanceInfo"

View File

@ -1,19 +1,19 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { t } from "@/lang/i18n";
import { t, $t } from "@/lang/i18n";
import { message } from "ant-design-vue";
import { reportErrorMsg } from "@/tools/validator";
import { DeleteOutlined, FieldTimeOutlined } from "@ant-design/icons-vue";
import { DeleteOutlined, EditOutlined, FieldTimeOutlined } from "@ant-design/icons-vue";
import CardPanel from "@/components/CardPanel.vue";
import BetweenMenus from "@/components/BetweenMenus.vue";
import { useLayoutCardTools } from "@/hooks/useCardTools";
import { useAppRouters } from "@/hooks/useAppRouters";
import { scheduleList, scheduleDelete } from "@/services/apis/instance";
import type { LayoutCard, Schedule } from "@/types/index";
import { ScheduleAction, ScheduleType, ScheduleCreateType } from "@/types/const";
import NewSchedule from "@/widgets/instance/dialogs/NewSchedule.vue";
import type { AntColumnsType } from "../../types/ant";
import { useScreen } from "@/hooks/useScreen";
import { useSchedule } from "@/hooks/useSchedule";
import { padZero } from "@/tools/common";
const props = defineProps<{
card: LayoutCard;
@ -25,83 +25,34 @@ const instanceId = getMetaOrRouteValue("instanceId");
const daemonId = getMetaOrRouteValue("daemonId");
const { toPage } = useAppRouters();
const newScheduleDialog = ref<InstanceType<typeof NewSchedule>>();
const { getScheduleList, schedules, scheduleListLoading, deleteSchedule } = useSchedule(
String(instanceId),
String(daemonId)
);
const { state, execute, isLoading } = scheduleList();
const getScheduleList = async () => {
try {
await execute({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? ""
}
});
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const deleteSchedule = async (name: string) => {
const { execute, state } = scheduleDelete();
try {
await execute({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? "",
task_name: name
}
});
if (state.value) {
message.success(t("TXT_CODE_28190dbc"));
await getScheduleList();
}
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const rendTime = (text: string, schedule: Schedule) => {
switch (schedule.type.toString()) {
case ScheduleCreateType.INTERVAL: {
const time = Number(text);
let s = time;
let m = 0;
let h = 0;
while (s >= 60) {
s -= 60;
m += 1;
}
while (m >= 60) {
m -= 60;
h += 1;
}
return `${t("TXT_CODE_ec6d29f4")} ${h} ${t("TXT_CODE_e3db239d")} ${m} ${t(
const timeRender = (text: string, schedule: Schedule) => {
const formatFunctions = {
[ScheduleCreateType.INTERVAL]: (t: string) => {
const time = Number(t);
const h = padZero(Math.floor(time / 3600).toString());
const m = padZero(Math.floor((time % 3600) / 60).toString());
const s = padZero((time % 60).toString());
return `${$t("TXT_CODE_ec6d29f4")} ${h} ${$t("TXT_CODE_e3db239d")} ${m} ${$t(
"TXT_CODE_3b1bb444"
)} ${s} ${t("TXT_CODE_acabc771")}`;
)} ${s} ${$t("TXT_CODE_acabc771")}`;
},
[ScheduleCreateType.CYCLE]: (time: string) => {
const [s, m, h, , , w] = time.split(" ");
return `${t("TXT_CODE_76750199")} ${w} / ${padZero(h)}:${padZero(m)}:${padZero(s)}`;
},
[ScheduleCreateType.SPECIFY]: (time: string) => {
const [s, m, h, dd, mm] = time.split(" ");
return `${mm}/${dd} ${padZero(h)}:${padZero(m)}:${padZero(s)}`;
}
case ScheduleCreateType.CYCLE: {
const time = text;
const timeArr = time.split(" ");
const h = timeArr[2];
const m = timeArr[1];
const s = timeArr[0];
const w = timeArr[5];
return `${t("TXT_CODE_76750199")} ${w} / ${h}:${m}:${s}`;
}
case ScheduleCreateType.SPECIFY: {
const time = text;
const timeArr = time.split(" ");
const h = timeArr[2];
const m = timeArr[1];
const s = timeArr[0];
const dd = timeArr[3];
const mm = timeArr[4];
return `${mm} ${t("TXT_CODE_6cb9bb04")} ${dd} ${t("TXT_CODE_ca923eba")} ${h}:${m}:${s}`;
}
default:
return "Unknown Time";
}
};
const formatFunction = formatFunctions[schedule.type as ScheduleCreateType];
return formatFunction(text) ?? "Unknown Time";
};
const columns: AntColumnsType[] = [
@ -148,7 +99,7 @@ const columns: AntColumnsType[] = [
dataIndex: "time",
key: "time",
minWidth: 240,
customRender: (e: { text: string; record: Schedule }) => rendTime(e.text, e.record)
customRender: (e: { text: string; record: Schedule }) => timeRender(e.text, e.record)
},
{
align: "center",
@ -205,9 +156,9 @@ onMounted(async () => {
<a-col :span="24">
<CardPanel style="height: 100%">
<template #body>
<a-spin :spinning="isLoading">
<a-spin :spinning="scheduleListLoading">
<a-table
:data-source="state"
:data-source="schedules"
:columns="columns"
:scroll="{ x: 'max-content' }"
:pagination="{
@ -216,11 +167,19 @@ onMounted(async () => {
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'actions'">
<a-button
class="mr-8"
size="large"
@click="newScheduleDialog?.openDialog(record as Schedule)"
>
{{ t("TXT_CODE_ad207008") }}
<EditOutlined />
</a-button>
<a-popconfirm
:title="t('TXT_CODE_6ff0668f')"
@confirm="deleteSchedule(record.name)"
>
<a-button size="large">
<a-button danger size="large">
{{ t("TXT_CODE_ecbd7449") }}
<DeleteOutlined />
</a-button>

View File

@ -14,7 +14,8 @@ import {
CloudDownloadOutlined,
CodeOutlined,
UserOutlined,
TagsOutlined
TagsOutlined,
DeleteOutlined
} from "@ant-design/icons-vue";
import {
openInstance,
@ -31,7 +32,8 @@ import { parseTimestamp } from "@/tools/time";
import { arrayFilter } from "@/tools/array";
import { useLayoutContainerStore } from "@/stores/useLayoutContainerStore";
import { reportErrorMsg } from "@/tools/validator";
import { openInstanceTagsEditor } from "@/components/fc/index";
import { openInstanceTagsEditor, useDeleteInstanceDialog } from "@/components/fc/index";
import _ from "lodash";
const props = defineProps<{
card: LayoutCard;
@ -39,7 +41,7 @@ const props = defineProps<{
targetDaemonId?: string;
}>();
const emits = defineEmits(["refrshList"]);
const emits = defineEmits(["refreshList"]);
const { containerState } = useLayoutContainerStore();
const { getMetaOrRouteValue } = useLayoutCardTools(props.card);
@ -69,7 +71,7 @@ const { isLoading: updateLoading, execute: executeUpdate } = updateInstance();
const refreshList = () => {
setTimeout(() => {
emits("refrshList");
emits("refreshList");
}, 500);
};
@ -194,7 +196,8 @@ const instanceOperations = computed(() =>
},
loading: killLoading.value,
disabled: containerState.isDesignMode,
danger: true
danger: true,
condition: () => !isStopped.value
},
{
area: true
@ -206,8 +209,8 @@ const instanceOperations = computed(() =>
event.stopPropagation();
if (instanceId && daemonId) {
const tags = instanceInfo.value?.config.tag || [];
await openInstanceTagsEditor(instanceId, daemonId, tags);
refreshList();
const newTags = await openInstanceTagsEditor(instanceId, daemonId, tags);
if (!_.isEqual(newTags, tags)) refreshList();
}
},
disabled: containerState.isDesignMode
@ -226,6 +229,22 @@ const instanceOperations = computed(() =>
});
},
disabled: containerState.isDesignMode
},
{
title: t("TXT_CODE_a0e19f38"),
icon: DeleteOutlined,
click: async (event: MouseEvent) => {
event.stopPropagation();
const deleteInstanceResult = await useDeleteInstanceDialog(
instanceId || "",
daemonId || ""
);
if (!deleteInstanceResult) return;
message.success(t("TXT_CODE_f486dbb4"));
refreshList();
},
danger: true,
disabled: containerState.isDesignMode
}
])
);

View File

@ -12,7 +12,8 @@ import {
RedoOutlined,
LaptopOutlined,
InteractionOutlined,
LoadingOutlined
LoadingOutlined,
MoneyCollectOutlined
} from "@ant-design/icons-vue";
import { CheckCircleOutlined, InfoCircleOutlined } from "@ant-design/icons-vue";
import { arrayFilter } from "../../tools/array";
@ -35,6 +36,8 @@ import TerminalCore from "@/components/TerminalCore.vue";
import Reinstall from "./dialogs/Reinstall.vue";
import { useAppStateStore } from "@/stores/useAppStateStore";
import { INSTANCE_TYPE_TRANSLATION } from "@/hooks/useInstance";
import { useMountComponent } from "@/hooks/useMountComponent";
import UseRedeemDialog from "@/components/fc/UseRedeemDialog.vue";
const props = defineProps<{
card: LayoutCard;
@ -179,6 +182,18 @@ const instanceOperations = computed(() =>
isStopped.value &&
(state.settings.allowUsePreset || isAdmin.value) &&
!isGlobalTerminal.value
},
{
title: t("TXT_CODE_f77093c8"),
icon: MoneyCollectOutlined,
noConfirm: true,
click: () => {
useMountComponent({
instanceId: instanceId
}).mount(UseRedeemDialog);
},
props: {},
condition: () => state.settings.businessMode
}
])
);

View File

@ -0,0 +1,91 @@
<script setup lang="ts">
import { ref } from "vue";
import { t } from "@/lang/i18n";
import { Modal } from "ant-design-vue";
import { batchDelete } from "@/services/apis/instance";
import { reportErrorMsg } from "@/tools/validator";
const props = defineProps<{
// eslint-disable-next-line no-unused-vars
emitResult: (ok: boolean) => void;
destroyComponent: () => void;
instanceId: string;
daemonId: string;
}>();
const deleteFiles = ref(false);
const isOpen = ref(true);
const submitBtnLoading = ref(false);
const submit = async (deleteFile: boolean | null = deleteFiles.value) => {
const { execute } = batchDelete();
submitBtnLoading.value = true;
try {
await execute({
params: {
daemonId: props.daemonId || ""
},
data: {
uuids: [props.instanceId || ""],
deleteFile: deleteFile || false
}
});
props.emitResult(true);
} catch (error) {
reportErrorMsg(error);
props.emitResult(false);
}
isOpen.value = false;
props.destroyComponent();
};
const onCancel = () => {
isOpen.value = false;
props.emitResult(false);
props.destroyComponent();
};
const onDelete = async () => {
if (!deleteFiles.value) return await submit();
Modal.confirm({
title: t("TXT_CODE_584d786d"),
content: t("TXT_CODE_90508729"),
okText: t("TXT_CODE_10088738"),
onOk: async () => {
await submit();
Modal.destroyAll();
},
okType: "danger"
});
};
</script>
<template>
<a-modal :visible="isOpen" :title="t('TXT_CODE_a0e19f38')" @cancel="onCancel">
<a-typography-paragraph>
<a-typography-text>
{{ t("TXT_CODE_1981470a") }}
</a-typography-text>
</a-typography-paragraph>
<a-checkbox v-model:checked="deleteFiles">
{{ t("TXT_CODE_7542201a") }}
</a-checkbox>
<template #footer>
<a-button key="back" @click="onCancel">
{{ t("TXT_CODE_a0451c97") }}
</a-button>
<a-button
key="submit"
:danger="deleteFiles"
:loading="submitBtnLoading"
type="primary"
@click="onDelete"
>
{{ deleteFiles ? t("TXT_CODE_584d786d") : t("TXT_CODE_a0e19f38") }}
</a-button>
</template>
</a-modal>
</template>

View File

@ -134,7 +134,7 @@ defineExpose({
:cancel-text="t('TXT_CODE_3b1cc020')"
:ok-text="t('TXT_CODE_abfe9512')"
:mask-closable="false"
:width="fullScreen ? '100%' : '1300px'"
:width="fullScreen ? '100%' : '1600px'"
:confirm-loading="isLoading"
@ok="submit"
@cancel="cancel"

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