From 028fa0c36c3b3fe857674d435ad934be6bd4b09a Mon Sep 17 00:00:00 2001 From: Alex Lee <3076438032@qq.com> Date: Fri, 20 Sep 2024 22:14:55 +0800 Subject: [PATCH] feat(devbox): devbox basic frontend ui and logic (#4953) * chore(devbox): Initialization environment * chore(devbox): ui library * feat(devbox): for first ui * feat(devbox): empty page * feat: devboxList ui * chore: typo error * fix(devbox): devbox edit type error * chore(devbox): remove unused code * chore(devbox): add js-zip * feat(devbox): create devbox ui * fix(devbox): typo error * fix(devbox): mock error * fix(devbox): svg color error * feat(devbox): yaml create devbox * fix(devbox): typo error * fix(devbox): typo error and json2yaml error ignore * style(devbox): global style and h style * fix(devbox): url params error * fix(devbox): some ui bug * fix(devbox): message ui show typo error * feat(devbox): modified devboxlist cpu and memory to echarts * feat(devbox): quotabox and pricebox in create devbox * feat(devbox): version ui * feat(devbox): i18n support env * fix(devbox): i18n cause api error * feat(devbox): i18n transform * feat(devbox): release modal * fix(devbox): pricebox i18n * chore(devbox): remove unused ico * fix(devbox): i18n error * fix(devbox): version ui error * chore(devbox): adjust modals file position * feat(devbox): delete devbox logic * feat(devbox): restart devbox logic * feat(devbox): pause devbox logic * feat(devbox): del devbox version logic * feat(devbox): terminal logic * feat(devbox): adjust api logic * fix(devbox): next app router error * feat(devbox): update devbox * fix(devbox): boundary case * feat(devbox): release modal prompt * feat(devbox): network config ui and logic * chore(devbox): adjust code import * fix(devbox): cpu and memory transform error * fix(devbox): delete error * feat(devbox): user quota * fix(devbox): yaml show error * fix(devbox): get image name and image name related ui * fix(devbox): devbox network yaml error * fix(devbox): goto terminal command error * fix(devbox): restart logic error and menu button ui adjust * feat(devbox): online version * style(devbox): plus icon error * fix(devbox): app online error * fix(devbox): some ts error and req.headers app router error * fix(devbox): plus svg error * feat(devbox): get vscode remote password * feat(devbox): runtime adjust * chore(devbox): remove unused code * fix(devbox): network error * fix(devbox): some little fix * fix(devbox): runtime and network bug * fix(devbox): port * feat(devbox): goto vscode * chore(devbox): typo error * fix(devbox): some error * fix(devbox): headers() transform to req.headers * fix(devbox): req.nextUrl transform to req.url * Revert "fix(devbox): req.nextUrl transform to req.url" This reverts commit 2bf617354b538dbb9b450e3f8865bd1735af19a4. * chore(devbox): DynamicServerError error * fix(devbox): some error * fix(devbox): service create failed * chore(devbox): remove ApiRes and use export namespace to replace default * feat(devbox): isEdit for modified and some config * fix&per(devbox): some fix and ui adjust * fix(devbox): update devbox logic adjust * fix(devbox): build error * fix(devbox): modified runtime ui opacity * fix(devbox): update ui * feat(devbox): registerAddr * chore(devbox): deploy.yaml * feat(devbox): i18n fix(unsucess) * fix(devbox): fix error * fix(devbox): fix bug * fix(devbox): terminal bug * chore(devbox): i18n * fix(devbox): public logo error * fix(devbox): selector bug * fix(devbox): delete app selector * fix(devbox): fix delete devbox ingress bug * fix(devbox): ui bug * chore(devbox): ingress domain bug * fix(devbox): i18n error * fix(devbox): some ui bug * fix(devbox): ui and k8s bug * fix(devbox): i18n error * Revert "fix(devbox): i18n error" This reverts commit 06881f8526860614bdf0b7839375bea7e6c4888a. * fix(devbox): some ui bug * chore(devbox): i18n bug * feat(devbox): i18n * fix: namespace bug * fix: delDevbox api bug * fix: delDevbox bug * fix: update ui bug * fix: some bug * fix: root * fix: some ui bug * fix: some ui bug * chore: networkName * fix: ingress sealos_name bug * fix: price prblem * fix: version modal bug * fix: port ui bug * fix: lastRoute bug * chore: some ui and en perf * chore: i18n default * chore: some bug * feat: devbox runtime adjust * fix: ui bug * fix: runtime namespace write bug and delete devbox without service bug * chore: add debian svg * feat: quota support * feat: price * feat: devboxAffinityEnable * chore: build bug * feat: half succeed to mac * feat: release modal checkbox * perf: import order adjust * perf: api logic perf * perf: code perf * chore: vue svg name * chore: cr adjust * chore: remove unused code * chore: update yaml.tmpl * ci: update some build file * ci: build bug * fix: ts error * fix: ts error * chore: remove console.log code * fix: vscode transform other info * chore: export named to default * fix: ci error try * feat: sshinfo * fix: status error * fix: i18n error * feat: default version * fix: nodeports_exceeds_quota * fix: button show bug * fix: register_addr bug * fix: some i18n * feat: detail basic framework * feat: version devboxStatusTag * feat: devbox status * fix: adjust status * feat: download config * feat: monitor * fix: svg adjust * style: adjust style * feat: upTime * fix: get devbox upTime * fix: build error bug * fix:default 8080 port * fix: detail icon arrow-left adjust style * feat: runtimeVersion label show ui * fix: devbox deploy registry_addr bug * fix: ui some bug * feat: monitor cpu and memory * fix: monitorFetch bug * fix: monitor url to env * feat: new detail version * fix: version edit * feat: support transform params to launchpad * feat: adjust svg * fix: runtime version getSSHRuntimeInfo * fix: monitor will not show * fix: ugly fix ts bug * feat: transform kv to applaunchpad * feat: lastTerminatedState * style: detail page style perf * fix: delete then go to list * fix: cpu and memory adjust * style: some w h bug * fix: some bug * style: detail style perf * fix: button border * style: some style bug * fix: font size * fix: network null * feat: release to detail * chore: adjust import order * fix: router searchparams disappear bug * style: some detail ui bug * fix: pause and restart bug * chore: some i18n adjust * fix: memory ui bug * chore: note a bug * fix: download private key without name suffix * feat: add cursor and vscode-insiders support * fix: update cpu and memory bug * chore: adjust cpu and memory list * fix: some ui adjust * feat: add sealos coin * fix: wrong frontend/packages/ui modified * fix: console log security bug * fix: devbox svg show bug * chore: transform some Chinese to English * style: boxShadow bug * style: detail ui adjust again * fix: devboxList ui bug * chore: add react svg * fix: deploy bug * feat: deploy only success * feat: delModal adjust ui * fix: disable vscode select menuButton * fix: cpu and memory chart adjust * fix: update activebar bug * feat: empty status ui * style: status tag ui update * style: releaseModal ui update * style: version button h adjust * style: basicInfo detail update * feat: add squash env * style: status tag ui adjust * style: list color adjust * style: adjust shutdown svg * feat: add start button and logic * fix: box w * feat: small screen adapt * feat: monitol modal * fix: style button * chore: delete some console.log * fix: menubutton boxShadow * style: border radius bug * fix: default add port 8080 * chore: remove unused file * fix: release appname * fix: cpu and memory monitor 100 * chore: remove console * feat: version add time --- .github/workflows/frontend.yml | 2 + frontend/Makefile | 1 + .../ui/src/components/RangeInput/index.tsx | 2 + .../ui/src/components/Select/index.tsx | 2 + .../ui/src/components/icons/AddIcon.tsx | 1 + .../components/icons/ArrowDownSLineIcon.tsx | 1 + .../components/icons/ArrowExchangeIcon.tsx | 1 + .../ui/src/components/icons/BucketIcon.tsx | 1 + .../ui/src/components/icons/CancelIcon.tsx | 2 + .../ui/src/components/icons/ChangeIcon.tsx | 1 + .../src/components/icons/ClearOutlineIcon.tsx | 1 + .../ui/src/components/icons/CloseIcon.tsx | 2 + .../ui/src/components/icons/CopyIcon.tsx | 1 + .../src/components/icons/CreateFolderIcon.tsx | 1 + .../ui/src/components/icons/DeleteIcon.tsx | 1 + .../src/components/icons/DetailsMoreIcon.tsx | 1 + .../ui/src/components/icons/DnsIcon.tsx | 1 + .../ui/src/components/icons/DocsIcon.tsx | 2 + .../ui/src/components/icons/DownloadIcon.tsx | 1 + .../ui/src/components/icons/EditIcon.tsx | 1 + .../src/components/icons/ExpandMoreIcon.tsx | 1 + .../ui/src/components/icons/FileIcon.tsx | 1 + .../ui/src/components/icons/FolderIcon.tsx | 1 + .../ui/src/components/icons/GroupAdd.tsx | 1 + .../src/components/icons/InfoCircleIcon.tsx | 1 + .../ui/src/components/icons/InfoIcon.tsx | 1 + .../ui/src/components/icons/LeftArrowIcon.tsx | 1 + .../ui/src/components/icons/LinkIcon.tsx | 1 + .../ui/src/components/icons/ListCheckIcon.tsx | 1 + .../ui/src/components/icons/ListIcon.tsx | 1 + .../ui/src/components/icons/MoreIcon.tsx | 1 + .../ui/src/components/icons/PortIcon.tsx | 1 + .../ui/src/components/icons/ProviderIcon.tsx | 1 + .../ui/src/components/icons/RefreshIcon.tsx | 1 + .../ui/src/components/icons/SearchIcon.tsx | 1 + .../components/icons/SortPolygonDownIcon.tsx | 1 + .../components/icons/SortPolygonUpIcon.tsx | 1 + .../ui/src/components/icons/StorageIcon.tsx | 1 + .../ui/src/components/icons/UploadIcon.tsx | 1 + .../src/components/icons/VisibilityIcon.tsx | 1 + .../ui/src/components/icons/WebHostIcon.tsx | 1 + .../src/components/icons/line/WarnTriange.tsx | 1 + frontend/pnpm-lock.yaml | 670 +++++++++++++--- .../dbprovider/src/pages/db/edit/index.tsx | 1 + frontend/providers/devbox/.dockerignore | 10 + frontend/providers/devbox/.env.template | 0 frontend/providers/devbox/.eslintrc.json | 3 + frontend/providers/devbox/.gitignore | 36 + frontend/providers/devbox/.prettierignore | 4 + frontend/providers/devbox/.prettierrc.json | 18 + .../providers/devbox/.vscode/extensions.json | 10 + .../providers/devbox/.vscode/settings.json | 81 ++ frontend/providers/devbox/Dockerfile | 58 ++ frontend/providers/devbox/Makefile | 37 + frontend/providers/devbox/README.md | 39 + frontend/providers/devbox/api/devbox.ts | 67 ++ frontend/providers/devbox/api/platform.ts | 20 + .../(home)/components/DevboxList.tsx | 555 +++++++++++++ .../(platform)/(home)/components/Empty.tsx | 35 + .../(home)/components/empty.module.scss | 6 + .../app/[lang]/(platform)/(home)/page.tsx | 35 + .../devbox/create/components/Form.tsx | 734 ++++++++++++++++++ .../devbox/create/components/Header.tsx | 56 ++ .../devbox/create/components/Yaml.tsx | 122 +++ .../create/components/index.module.scss | 49 ++ .../[lang]/(platform)/devbox/create/page.tsx | 313 ++++++++ .../detail/[name]/components/BasicInfo.tsx | 271 +++++++ .../detail/[name]/components/Header.tsx | 392 ++++++++++ .../detail/[name]/components/MainBody.tsx | 157 ++++ .../detail/[name]/components/Version.tsx | 273 +++++++ .../(platform)/devbox/detail/[name]/page.tsx | 108 +++ .../devbox/app/[lang]/(platform)/layout.tsx | 134 ++++ .../providers/devbox/app/[lang]/globals.css | 149 ++++ .../providers/devbox/app/[lang]/layout.tsx | 40 + .../devbox/app/api/createDevbox/route.ts | 45 ++ .../devbox/app/api/delDevbox/route.ts | 66 ++ .../app/api/delDevboxVersionByName/route.ts | 37 + .../devbox/app/api/editDevboxVersion/route.ts | 44 ++ .../devbox/app/api/getDevboxList/route.ts | 119 +++ .../api/getDevboxPodsByDevboxName/route.ts | 41 + .../app/api/getDevboxVersionList/route.ts | 38 + .../providers/devbox/app/api/getEnv/route.ts | 23 + .../app/api/getSSHConnectionInfo/route.ts | 44 ++ .../devbox/app/api/getSSHRuntimeInfo/route.ts | 42 + .../app/api/monitor/getMonitorData/route.ts | 135 ++++ .../devbox/app/api/pauseDevbox/route.ts | 45 ++ .../app/api/platform/authCname/route.ts | 34 + .../app/api/platform/getNamespace/route.ts | 24 + .../devbox/app/api/platform/getQuota/route.ts | 28 + .../app/api/platform/getRuntime/route.ts | 149 ++++ .../app/api/platform/resourcePrice/route.ts | 86 ++ .../devbox/app/api/releaseDevbox/route.ts | 34 + .../devbox/app/api/restartDevbox/route.ts | 61 ++ .../devbox/app/api/startDevbox/route.ts | 45 ++ .../devbox/app/api/updateDevbox/route.ts | 184 +++++ .../devbox/components/DevboxStatusTag.tsx | 51 ++ .../devbox/components/Icon/icons/analyze.svg | 1 + .../components/Icon/icons/arrowDown.svg | 3 + .../components/Icon/icons/arrowLeft.svg | 10 + .../devbox/components/Icon/icons/arrowUp.svg | 3 + .../devbox/components/Icon/icons/book.svg | 3 + .../devbox/components/Icon/icons/change.svg | 5 + .../devbox/components/Icon/icons/check.svg | 3 + .../components/Icon/icons/chevronDown.svg | 3 + .../components/Icon/icons/codeServer.svg | 4 + .../components/Icon/icons/connection.svg | 3 + .../devbox/components/Icon/icons/continue.svg | 3 + .../devbox/components/Icon/icons/copy.svg | 1 + .../devbox/components/Icon/icons/currency.svg | 3 + .../devbox/components/Icon/icons/cursor.svg | 9 + .../devbox/components/Icon/icons/delete.svg | 3 + .../devbox/components/Icon/icons/detail.svg | 3 + .../devbox/components/Icon/icons/docs.svg | 3 + .../devbox/components/Icon/icons/download.svg | 3 + .../devbox/components/Icon/icons/edit.svg | 5 + .../devbox/components/Icon/icons/empty.svg | 5 + .../devbox/components/Icon/icons/error.svg | 3 + .../devbox/components/Icon/icons/export.svg | 3 + .../components/Icon/icons/formAdvanced.svg | 5 + .../devbox/components/Icon/icons/formInfo.svg | 3 + .../components/Icon/icons/formNetwork.svg | 3 + .../devbox/components/Icon/icons/info.svg | 3 + .../components/Icon/icons/infoCircle.svg | 4 + .../devbox/components/Icon/icons/link.svg | 3 + .../devbox/components/Icon/icons/list.svg | 8 + .../devbox/components/Icon/icons/loading.svg | 3 + .../devbox/components/Icon/icons/log.svg | 3 + .../devbox/components/Icon/icons/logo.svg | 9 + .../devbox/components/Icon/icons/maximize.svg | 3 + .../devbox/components/Icon/icons/monitor.svg | 3 + .../devbox/components/Icon/icons/more.svg | 1 + .../devbox/components/Icon/icons/network.svg | 3 + .../devbox/components/Icon/icons/noEvents.svg | 1 + .../devbox/components/Icon/icons/pause.svg | 4 + .../devbox/components/Icon/icons/plus.svg | 3 + .../devbox/components/Icon/icons/podList.svg | 1 + .../devbox/components/Icon/icons/pods.svg | 6 + .../devbox/components/Icon/icons/read.svg | 3 + .../devbox/components/Icon/icons/response.svg | 7 + .../devbox/components/Icon/icons/restart.svg | 3 + .../devbox/components/Icon/icons/restore.svg | 1 + .../devbox/components/Icon/icons/search.svg | 3 + .../devbox/components/Icon/icons/settings.svg | 4 + .../devbox/components/Icon/icons/shutdown.svg | 4 + .../devbox/components/Icon/icons/start.svg | 3 + .../components/Icon/icons/statusDetail.svg | 1 + .../devbox/components/Icon/icons/success.svg | 3 + .../devbox/components/Icon/icons/target.svg | 10 + .../devbox/components/Icon/icons/terminal.svg | 1 + .../devbox/components/Icon/icons/unread.svg | 7 + .../devbox/components/Icon/icons/upload.svg | 3 + .../components/Icon/icons/upperRight.svg | 3 + .../devbox/components/Icon/icons/version.svg | 3 + .../devbox/components/Icon/icons/vscode.svg | 5 + .../components/Icon/icons/vscodeInsider.svg | 35 + .../devbox/components/Icon/icons/warning.svg | 11 + .../components/Icon/icons/warningInfo.svg | 10 + .../devbox/components/Icon/index.tsx | 79 ++ .../providers/devbox/components/MyTable.tsx | 73 ++ .../providers/devbox/components/MyTooltip.tsx | 22 + .../devbox/components/PodLineChart.tsx | 226 ++++++ .../providers/devbox/components/PriceBox.tsx | 82 ++ .../providers/devbox/components/QuotaBox.tsx | 86 ++ .../devbox/components/YamlCode/hljs.ts | 121 +++ .../components/YamlCode/index.module.scss | 6 + .../devbox/components/YamlCode/index.tsx | 48 ++ .../components/modals/CustomAccessModal.tsx | 112 +++ .../devbox/components/modals/DelModal.tsx | 120 +++ .../components/modals/EditVersionDesModal.tsx | 90 +++ .../devbox/components/modals/ErrorModal.tsx | 44 ++ .../devbox/components/modals/MonitorModal.tsx | 55 ++ .../devbox/components/modals/releaseModal.tsx | 156 ++++ .../components/providers/MyChakraProvider.tsx | 9 + .../components/providers/MyIntlProvider.tsx | 10 + .../components/providers/MyQueryProvider.tsx | 23 + .../providers/MyRouteHandlerProvider.tsx | 41 + frontend/providers/devbox/constants/devbox.ts | 255 ++++++ frontend/providers/devbox/constants/theme.ts | 17 + frontend/providers/devbox/deploy/Kubefile | 19 + .../devbox/deploy/manifests/appcr.yaml.tmpl | 18 + .../devbox/deploy/manifests/deploy.yaml.tmpl | 104 +++ .../devbox/deploy/manifests/ingress.yaml.tmpl | 31 + .../devbox/deploy/manifests/rbac.yaml | 30 + .../deploy/scripts/update-backup-label.sh | 30 + .../providers/devbox/hooks/useConfirm.tsx | 104 +++ .../providers/devbox/hooks/useLoading.tsx | 39 + .../providers/devbox/hooks/useRequest.tsx | 37 + frontend/providers/devbox/i18n.ts | 17 + frontend/providers/devbox/message/en.json | 155 ++++ frontend/providers/devbox/message/zh.json | 158 ++++ frontend/providers/devbox/middleware.ts | 15 + frontend/providers/devbox/next.config.js | 29 + frontend/providers/devbox/package.json | 63 ++ .../providers/devbox/public/images/cpp.svg | 5 + .../providers/devbox/public/images/custom.svg | 9 + .../devbox/public/images/debian-ssh.svg | 10 + .../providers/devbox/public/images/flask.svg | 9 + .../providers/devbox/public/images/gin.svg | 9 + .../providers/devbox/public/images/go.svg | 30 + .../providers/devbox/public/images/hertz.svg | 9 + .../providers/devbox/public/images/java.svg | 7 + .../devbox/public/images/next.js.svg | 10 + .../devbox/public/images/node.js.svg | 26 + .../providers/devbox/public/images/php.svg | 10 + .../providers/devbox/public/images/python.svg | 14 + .../providers/devbox/public/images/react.svg | 1 + .../providers/devbox/public/images/rust.svg | 3 + .../devbox/public/images/spring-boot.svg | 12 + .../providers/devbox/public/images/vue.svg | 11 + frontend/providers/devbox/public/logo.svg | 9 + .../providers/devbox/services/backend/auth.ts | 16 + .../devbox/services/backend/kubernetes.ts | 317 ++++++++ .../devbox/services/backend/response.ts | 34 + frontend/providers/devbox/services/error.ts | 34 + .../providers/devbox/services/kubernet.ts | 9 + .../providers/devbox/services/monitorFetch.ts | 25 + frontend/providers/devbox/services/request.ts | 143 ++++ frontend/providers/devbox/stores/devbox.ts | 192 +++++ frontend/providers/devbox/stores/global.ts | 57 ++ frontend/providers/devbox/stores/static.ts | 156 ++++ frontend/providers/devbox/stores/user.ts | 65 ++ frontend/providers/devbox/tsconfig.json | 18 + frontend/providers/devbox/types/devbox.d.ts | 167 ++++ frontend/providers/devbox/types/index.d.ts | 8 + frontend/providers/devbox/types/k8s.d.ts | 164 ++++ frontend/providers/devbox/types/monitor.d.ts | 46 ++ frontend/providers/devbox/types/user.d.ts | 34 + frontend/providers/devbox/utils/adapt.ts | 119 +++ frontend/providers/devbox/utils/cookie.ts | 15 + frontend/providers/devbox/utils/json2Yaml.ts | 295 +++++++ frontend/providers/devbox/utils/tools.ts | 318 ++++++++ frontend/providers/devbox/utils/user.ts | 26 + 232 files changed, 11055 insertions(+), 88 deletions(-) create mode 100644 frontend/providers/devbox/.dockerignore create mode 100644 frontend/providers/devbox/.env.template create mode 100644 frontend/providers/devbox/.eslintrc.json create mode 100644 frontend/providers/devbox/.gitignore create mode 100644 frontend/providers/devbox/.prettierignore create mode 100644 frontend/providers/devbox/.prettierrc.json create mode 100644 frontend/providers/devbox/.vscode/extensions.json create mode 100644 frontend/providers/devbox/.vscode/settings.json create mode 100644 frontend/providers/devbox/Dockerfile create mode 100644 frontend/providers/devbox/Makefile create mode 100644 frontend/providers/devbox/README.md create mode 100644 frontend/providers/devbox/api/devbox.ts create mode 100644 frontend/providers/devbox/api/platform.ts create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/(home)/components/Empty.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/(home)/components/empty.module.scss create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Header.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/index.module.scss create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx create mode 100644 frontend/providers/devbox/app/[lang]/(platform)/layout.tsx create mode 100644 frontend/providers/devbox/app/[lang]/globals.css create mode 100644 frontend/providers/devbox/app/[lang]/layout.tsx create mode 100644 frontend/providers/devbox/app/api/createDevbox/route.ts create mode 100644 frontend/providers/devbox/app/api/delDevbox/route.ts create mode 100644 frontend/providers/devbox/app/api/delDevboxVersionByName/route.ts create mode 100644 frontend/providers/devbox/app/api/editDevboxVersion/route.ts create mode 100644 frontend/providers/devbox/app/api/getDevboxList/route.ts create mode 100644 frontend/providers/devbox/app/api/getDevboxPodsByDevboxName/route.ts create mode 100644 frontend/providers/devbox/app/api/getDevboxVersionList/route.ts create mode 100644 frontend/providers/devbox/app/api/getEnv/route.ts create mode 100644 frontend/providers/devbox/app/api/getSSHConnectionInfo/route.ts create mode 100644 frontend/providers/devbox/app/api/getSSHRuntimeInfo/route.ts create mode 100644 frontend/providers/devbox/app/api/monitor/getMonitorData/route.ts create mode 100644 frontend/providers/devbox/app/api/pauseDevbox/route.ts create mode 100644 frontend/providers/devbox/app/api/platform/authCname/route.ts create mode 100644 frontend/providers/devbox/app/api/platform/getNamespace/route.ts create mode 100644 frontend/providers/devbox/app/api/platform/getQuota/route.ts create mode 100644 frontend/providers/devbox/app/api/platform/getRuntime/route.ts create mode 100644 frontend/providers/devbox/app/api/platform/resourcePrice/route.ts create mode 100644 frontend/providers/devbox/app/api/releaseDevbox/route.ts create mode 100644 frontend/providers/devbox/app/api/restartDevbox/route.ts create mode 100644 frontend/providers/devbox/app/api/startDevbox/route.ts create mode 100644 frontend/providers/devbox/app/api/updateDevbox/route.ts create mode 100644 frontend/providers/devbox/components/DevboxStatusTag.tsx create mode 100644 frontend/providers/devbox/components/Icon/icons/analyze.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/arrowDown.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/arrowLeft.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/arrowUp.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/book.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/change.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/check.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/chevronDown.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/codeServer.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/connection.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/continue.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/copy.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/currency.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/cursor.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/delete.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/detail.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/docs.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/download.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/edit.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/empty.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/error.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/export.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/formAdvanced.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/formInfo.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/formNetwork.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/info.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/infoCircle.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/link.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/list.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/loading.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/log.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/logo.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/maximize.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/monitor.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/more.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/network.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/noEvents.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/pause.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/plus.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/podList.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/pods.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/read.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/response.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/restart.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/restore.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/search.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/settings.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/shutdown.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/start.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/statusDetail.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/success.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/target.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/terminal.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/unread.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/upload.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/upperRight.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/version.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/vscode.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/vscodeInsider.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/warning.svg create mode 100644 frontend/providers/devbox/components/Icon/icons/warningInfo.svg create mode 100644 frontend/providers/devbox/components/Icon/index.tsx create mode 100644 frontend/providers/devbox/components/MyTable.tsx create mode 100644 frontend/providers/devbox/components/MyTooltip.tsx create mode 100644 frontend/providers/devbox/components/PodLineChart.tsx create mode 100644 frontend/providers/devbox/components/PriceBox.tsx create mode 100644 frontend/providers/devbox/components/QuotaBox.tsx create mode 100644 frontend/providers/devbox/components/YamlCode/hljs.ts create mode 100644 frontend/providers/devbox/components/YamlCode/index.module.scss create mode 100644 frontend/providers/devbox/components/YamlCode/index.tsx create mode 100644 frontend/providers/devbox/components/modals/CustomAccessModal.tsx create mode 100644 frontend/providers/devbox/components/modals/DelModal.tsx create mode 100644 frontend/providers/devbox/components/modals/EditVersionDesModal.tsx create mode 100644 frontend/providers/devbox/components/modals/ErrorModal.tsx create mode 100644 frontend/providers/devbox/components/modals/MonitorModal.tsx create mode 100644 frontend/providers/devbox/components/modals/releaseModal.tsx create mode 100644 frontend/providers/devbox/components/providers/MyChakraProvider.tsx create mode 100644 frontend/providers/devbox/components/providers/MyIntlProvider.tsx create mode 100644 frontend/providers/devbox/components/providers/MyQueryProvider.tsx create mode 100644 frontend/providers/devbox/components/providers/MyRouteHandlerProvider.tsx create mode 100644 frontend/providers/devbox/constants/devbox.ts create mode 100644 frontend/providers/devbox/constants/theme.ts create mode 100644 frontend/providers/devbox/deploy/Kubefile create mode 100644 frontend/providers/devbox/deploy/manifests/appcr.yaml.tmpl create mode 100644 frontend/providers/devbox/deploy/manifests/deploy.yaml.tmpl create mode 100644 frontend/providers/devbox/deploy/manifests/ingress.yaml.tmpl create mode 100644 frontend/providers/devbox/deploy/manifests/rbac.yaml create mode 100644 frontend/providers/devbox/deploy/scripts/update-backup-label.sh create mode 100644 frontend/providers/devbox/hooks/useConfirm.tsx create mode 100644 frontend/providers/devbox/hooks/useLoading.tsx create mode 100644 frontend/providers/devbox/hooks/useRequest.tsx create mode 100644 frontend/providers/devbox/i18n.ts create mode 100644 frontend/providers/devbox/message/en.json create mode 100644 frontend/providers/devbox/message/zh.json create mode 100644 frontend/providers/devbox/middleware.ts create mode 100644 frontend/providers/devbox/next.config.js create mode 100644 frontend/providers/devbox/package.json create mode 100644 frontend/providers/devbox/public/images/cpp.svg create mode 100644 frontend/providers/devbox/public/images/custom.svg create mode 100644 frontend/providers/devbox/public/images/debian-ssh.svg create mode 100644 frontend/providers/devbox/public/images/flask.svg create mode 100644 frontend/providers/devbox/public/images/gin.svg create mode 100644 frontend/providers/devbox/public/images/go.svg create mode 100644 frontend/providers/devbox/public/images/hertz.svg create mode 100644 frontend/providers/devbox/public/images/java.svg create mode 100644 frontend/providers/devbox/public/images/next.js.svg create mode 100644 frontend/providers/devbox/public/images/node.js.svg create mode 100644 frontend/providers/devbox/public/images/php.svg create mode 100644 frontend/providers/devbox/public/images/python.svg create mode 100644 frontend/providers/devbox/public/images/react.svg create mode 100644 frontend/providers/devbox/public/images/rust.svg create mode 100644 frontend/providers/devbox/public/images/spring-boot.svg create mode 100644 frontend/providers/devbox/public/images/vue.svg create mode 100644 frontend/providers/devbox/public/logo.svg create mode 100644 frontend/providers/devbox/services/backend/auth.ts create mode 100644 frontend/providers/devbox/services/backend/kubernetes.ts create mode 100644 frontend/providers/devbox/services/backend/response.ts create mode 100644 frontend/providers/devbox/services/error.ts create mode 100644 frontend/providers/devbox/services/kubernet.ts create mode 100644 frontend/providers/devbox/services/monitorFetch.ts create mode 100644 frontend/providers/devbox/services/request.ts create mode 100644 frontend/providers/devbox/stores/devbox.ts create mode 100644 frontend/providers/devbox/stores/global.ts create mode 100644 frontend/providers/devbox/stores/static.ts create mode 100644 frontend/providers/devbox/stores/user.ts create mode 100644 frontend/providers/devbox/tsconfig.json create mode 100644 frontend/providers/devbox/types/devbox.d.ts create mode 100644 frontend/providers/devbox/types/index.d.ts create mode 100644 frontend/providers/devbox/types/k8s.d.ts create mode 100644 frontend/providers/devbox/types/monitor.d.ts create mode 100644 frontend/providers/devbox/types/user.d.ts create mode 100644 frontend/providers/devbox/utils/adapt.ts create mode 100644 frontend/providers/devbox/utils/cookie.ts create mode 100644 frontend/providers/devbox/utils/json2Yaml.ts create mode 100644 frontend/providers/devbox/utils/tools.ts create mode 100644 frontend/providers/devbox/utils/user.ts diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index f2c1226133f..b0bd2d59652 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -67,6 +67,7 @@ jobs: providers/objectstorage, providers/kubepanel, providers/workorder, + providers/devbox, desktop, ] steps: @@ -165,6 +166,7 @@ jobs: providers/objectstorage, providers/kubepanel, providers/workorder, + providers/devbox, desktop, ] steps: diff --git a/frontend/Makefile b/frontend/Makefile index abaa889d94e..f90110ff168 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -26,6 +26,7 @@ buildTargets := \ providers/license \ providers/kubepanel \ providers/objectstorage \ + providers/devbox \ providers/workorder buildTargets-all := $(addprefix image-build-,$(buildTargets)) diff --git a/frontend/packages/ui/src/components/RangeInput/index.tsx b/frontend/packages/ui/src/components/RangeInput/index.tsx index db018a64c7f..6fafa009727 100644 --- a/frontend/packages/ui/src/components/RangeInput/index.tsx +++ b/frontend/packages/ui/src/components/RangeInput/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import React, { useState } from 'react'; import { HStack, Input, useNumberInput, IconButton, InputProps } from '@chakra-ui/react'; import { AddIcon, MinusIcon } from '@chakra-ui/icons'; diff --git a/frontend/packages/ui/src/components/Select/index.tsx b/frontend/packages/ui/src/components/Select/index.tsx index c023a0ff48c..6443f93c5b1 100644 --- a/frontend/packages/ui/src/components/Select/index.tsx +++ b/frontend/packages/ui/src/components/Select/index.tsx @@ -1,3 +1,5 @@ +'use client'; + import React, { useRef, forwardRef, useMemo } from 'react'; import { Menu, diff --git a/frontend/packages/ui/src/components/icons/AddIcon.tsx b/frontend/packages/ui/src/components/icons/AddIcon.tsx index dc8caf26110..94ea5913cba 100644 --- a/frontend/packages/ui/src/components/icons/AddIcon.tsx +++ b/frontend/packages/ui/src/components/icons/AddIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const AddIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/ArrowDownSLineIcon.tsx b/frontend/packages/ui/src/components/icons/ArrowDownSLineIcon.tsx index be5119cdd73..9a287ebf135 100644 --- a/frontend/packages/ui/src/components/icons/ArrowDownSLineIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ArrowDownSLineIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ArrowDownSLineIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/ArrowExchangeIcon.tsx b/frontend/packages/ui/src/components/icons/ArrowExchangeIcon.tsx index 1eca1750673..c082982de57 100644 --- a/frontend/packages/ui/src/components/icons/ArrowExchangeIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ArrowExchangeIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ArrowExchangeIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/BucketIcon.tsx b/frontend/packages/ui/src/components/icons/BucketIcon.tsx index 8b6026fa863..caa352ad5c2 100644 --- a/frontend/packages/ui/src/components/icons/BucketIcon.tsx +++ b/frontend/packages/ui/src/components/icons/BucketIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const BucketIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/CancelIcon.tsx b/frontend/packages/ui/src/components/icons/CancelIcon.tsx index 964bd32d926..69e05e16988 100644 --- a/frontend/packages/ui/src/components/icons/CancelIcon.tsx +++ b/frontend/packages/ui/src/components/icons/CancelIcon.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Icon } from '@chakra-ui/react'; export default function CancelIcon(props: Parameters[0]) { return ( diff --git a/frontend/packages/ui/src/components/icons/ChangeIcon.tsx b/frontend/packages/ui/src/components/icons/ChangeIcon.tsx index 38cce25ffa3..1b4ac946d2b 100644 --- a/frontend/packages/ui/src/components/icons/ChangeIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ChangeIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ChangeIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/ClearOutlineIcon.tsx b/frontend/packages/ui/src/components/icons/ClearOutlineIcon.tsx index 31ad86134b9..d6a51945ce8 100644 --- a/frontend/packages/ui/src/components/icons/ClearOutlineIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ClearOutlineIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ClearOutlineIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/CloseIcon.tsx b/frontend/packages/ui/src/components/icons/CloseIcon.tsx index 7d28e0e61cd..ddb31cd1e8b 100644 --- a/frontend/packages/ui/src/components/icons/CloseIcon.tsx +++ b/frontend/packages/ui/src/components/icons/CloseIcon.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Icon, IconProps } from '@chakra-ui/react'; export default function CloseIcon(props: IconProps) { diff --git a/frontend/packages/ui/src/components/icons/CopyIcon.tsx b/frontend/packages/ui/src/components/icons/CopyIcon.tsx index 086cd3dae2e..09ac6023b87 100644 --- a/frontend/packages/ui/src/components/icons/CopyIcon.tsx +++ b/frontend/packages/ui/src/components/icons/CopyIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const CopyIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/CreateFolderIcon.tsx b/frontend/packages/ui/src/components/icons/CreateFolderIcon.tsx index 922e6977f86..a8dad7e8108 100644 --- a/frontend/packages/ui/src/components/icons/CreateFolderIcon.tsx +++ b/frontend/packages/ui/src/components/icons/CreateFolderIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const CreateFolderIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/DeleteIcon.tsx b/frontend/packages/ui/src/components/icons/DeleteIcon.tsx index a1c3e8ee41c..6f5f8c8e04e 100644 --- a/frontend/packages/ui/src/components/icons/DeleteIcon.tsx +++ b/frontend/packages/ui/src/components/icons/DeleteIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const DeleteIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/DetailsMoreIcon.tsx b/frontend/packages/ui/src/components/icons/DetailsMoreIcon.tsx index a8b9447e250..9cea1cf06f4 100644 --- a/frontend/packages/ui/src/components/icons/DetailsMoreIcon.tsx +++ b/frontend/packages/ui/src/components/icons/DetailsMoreIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ListIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/DnsIcon.tsx b/frontend/packages/ui/src/components/icons/DnsIcon.tsx index 26c616cc82c..8e7b10bf8a2 100644 --- a/frontend/packages/ui/src/components/icons/DnsIcon.tsx +++ b/frontend/packages/ui/src/components/icons/DnsIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const DnsIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/DocsIcon.tsx b/frontend/packages/ui/src/components/icons/DocsIcon.tsx index 6ce35387d3e..b123f7d1d82 100644 --- a/frontend/packages/ui/src/components/icons/DocsIcon.tsx +++ b/frontend/packages/ui/src/components/icons/DocsIcon.tsx @@ -1,3 +1,5 @@ +'use client'; + import { Icon, IconProps } from '@chakra-ui/react'; export default function DocsIcon(props: IconProps) { diff --git a/frontend/packages/ui/src/components/icons/DownloadIcon.tsx b/frontend/packages/ui/src/components/icons/DownloadIcon.tsx index d135bd7abf0..5e433d14415 100644 --- a/frontend/packages/ui/src/components/icons/DownloadIcon.tsx +++ b/frontend/packages/ui/src/components/icons/DownloadIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const DownloadIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/EditIcon.tsx b/frontend/packages/ui/src/components/icons/EditIcon.tsx index 6b8c5f36419..3892fbb1cb9 100644 --- a/frontend/packages/ui/src/components/icons/EditIcon.tsx +++ b/frontend/packages/ui/src/components/icons/EditIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const EditIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/ExpandMoreIcon.tsx b/frontend/packages/ui/src/components/icons/ExpandMoreIcon.tsx index a5e468994f7..6f97f749fef 100644 --- a/frontend/packages/ui/src/components/icons/ExpandMoreIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ExpandMoreIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ExpanMoreIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/FileIcon.tsx b/frontend/packages/ui/src/components/icons/FileIcon.tsx index 5dce25ce66c..28c11ac2d9f 100644 --- a/frontend/packages/ui/src/components/icons/FileIcon.tsx +++ b/frontend/packages/ui/src/components/icons/FileIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const FileIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/FolderIcon.tsx b/frontend/packages/ui/src/components/icons/FolderIcon.tsx index 7100a4c1397..6ba7889e12b 100644 --- a/frontend/packages/ui/src/components/icons/FolderIcon.tsx +++ b/frontend/packages/ui/src/components/icons/FolderIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const FolderIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/GroupAdd.tsx b/frontend/packages/ui/src/components/icons/GroupAdd.tsx index 29c278bef8f..09a7109ee30 100644 --- a/frontend/packages/ui/src/components/icons/GroupAdd.tsx +++ b/frontend/packages/ui/src/components/icons/GroupAdd.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const GroupAddIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/InfoCircleIcon.tsx b/frontend/packages/ui/src/components/icons/InfoCircleIcon.tsx index c651d20655c..299e69ef027 100644 --- a/frontend/packages/ui/src/components/icons/InfoCircleIcon.tsx +++ b/frontend/packages/ui/src/components/icons/InfoCircleIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const InfoCircleIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/InfoIcon.tsx b/frontend/packages/ui/src/components/icons/InfoIcon.tsx index 87d80f16840..42642d7f6cf 100644 --- a/frontend/packages/ui/src/components/icons/InfoIcon.tsx +++ b/frontend/packages/ui/src/components/icons/InfoIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const InfoIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/LeftArrowIcon.tsx b/frontend/packages/ui/src/components/icons/LeftArrowIcon.tsx index f060116043e..1ddc936ba0c 100644 --- a/frontend/packages/ui/src/components/icons/LeftArrowIcon.tsx +++ b/frontend/packages/ui/src/components/icons/LeftArrowIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const LeftArrowIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/LinkIcon.tsx b/frontend/packages/ui/src/components/icons/LinkIcon.tsx index 6d54f65f534..7306d03a762 100644 --- a/frontend/packages/ui/src/components/icons/LinkIcon.tsx +++ b/frontend/packages/ui/src/components/icons/LinkIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const LinkIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/ListCheckIcon.tsx b/frontend/packages/ui/src/components/icons/ListCheckIcon.tsx index e37ca27ba29..6f2924e6ad8 100644 --- a/frontend/packages/ui/src/components/icons/ListCheckIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ListCheckIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ListCheckIcon = createIcon({ displayName: 'ListCheckIcon', diff --git a/frontend/packages/ui/src/components/icons/ListIcon.tsx b/frontend/packages/ui/src/components/icons/ListIcon.tsx index d3af67ef60b..c0a2230acd1 100644 --- a/frontend/packages/ui/src/components/icons/ListIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ListIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ListIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/MoreIcon.tsx b/frontend/packages/ui/src/components/icons/MoreIcon.tsx index 6bcc0b5b797..818f3eb7778 100644 --- a/frontend/packages/ui/src/components/icons/MoreIcon.tsx +++ b/frontend/packages/ui/src/components/icons/MoreIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const MoreIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/PortIcon.tsx b/frontend/packages/ui/src/components/icons/PortIcon.tsx index c8b08384985..f2c1ae37e31 100644 --- a/frontend/packages/ui/src/components/icons/PortIcon.tsx +++ b/frontend/packages/ui/src/components/icons/PortIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const PortIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/ProviderIcon.tsx b/frontend/packages/ui/src/components/icons/ProviderIcon.tsx index f2c2cc6699a..75d9c85ddce 100644 --- a/frontend/packages/ui/src/components/icons/ProviderIcon.tsx +++ b/frontend/packages/ui/src/components/icons/ProviderIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const ProviderIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/RefreshIcon.tsx b/frontend/packages/ui/src/components/icons/RefreshIcon.tsx index 0409b03267a..c49e22a875d 100644 --- a/frontend/packages/ui/src/components/icons/RefreshIcon.tsx +++ b/frontend/packages/ui/src/components/icons/RefreshIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const RefreshIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/SearchIcon.tsx b/frontend/packages/ui/src/components/icons/SearchIcon.tsx index b81b6dd9289..e0d7ba4c75e 100644 --- a/frontend/packages/ui/src/components/icons/SearchIcon.tsx +++ b/frontend/packages/ui/src/components/icons/SearchIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const SearchIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/SortPolygonDownIcon.tsx b/frontend/packages/ui/src/components/icons/SortPolygonDownIcon.tsx index e8d3303328b..a002b6522b0 100644 --- a/frontend/packages/ui/src/components/icons/SortPolygonDownIcon.tsx +++ b/frontend/packages/ui/src/components/icons/SortPolygonDownIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const SortPolygonUpIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/SortPolygonUpIcon.tsx b/frontend/packages/ui/src/components/icons/SortPolygonUpIcon.tsx index 52fe0f979ee..ca3447ea7ba 100644 --- a/frontend/packages/ui/src/components/icons/SortPolygonUpIcon.tsx +++ b/frontend/packages/ui/src/components/icons/SortPolygonUpIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const SortPolygonUpIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/StorageIcon.tsx b/frontend/packages/ui/src/components/icons/StorageIcon.tsx index 6cdfee5163f..a30c1cabe4c 100644 --- a/frontend/packages/ui/src/components/icons/StorageIcon.tsx +++ b/frontend/packages/ui/src/components/icons/StorageIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const StorageIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/UploadIcon.tsx b/frontend/packages/ui/src/components/icons/UploadIcon.tsx index 32f0ffc3b24..17a4611255f 100644 --- a/frontend/packages/ui/src/components/icons/UploadIcon.tsx +++ b/frontend/packages/ui/src/components/icons/UploadIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const UploadIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/VisibilityIcon.tsx b/frontend/packages/ui/src/components/icons/VisibilityIcon.tsx index 264dce6c134..8d97c46513d 100644 --- a/frontend/packages/ui/src/components/icons/VisibilityIcon.tsx +++ b/frontend/packages/ui/src/components/icons/VisibilityIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const VisibityIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/WebHostIcon.tsx b/frontend/packages/ui/src/components/icons/WebHostIcon.tsx index 6a598f44318..d1c64aefba2 100644 --- a/frontend/packages/ui/src/components/icons/WebHostIcon.tsx +++ b/frontend/packages/ui/src/components/icons/WebHostIcon.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const WebHostIcon = createIcon({ diff --git a/frontend/packages/ui/src/components/icons/line/WarnTriange.tsx b/frontend/packages/ui/src/components/icons/line/WarnTriange.tsx index 18e811ed36b..961e69aaaac 100644 --- a/frontend/packages/ui/src/components/icons/line/WarnTriange.tsx +++ b/frontend/packages/ui/src/components/icons/line/WarnTriange.tsx @@ -1,3 +1,4 @@ +'use client'; import { createIcon } from '@chakra-ui/react'; const WarnTriangeIcon = createIcon({ diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index d2c7e029a1d..00c0dfd2852 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 2.1.1(@chakra-ui/system@2.6.2)(react@18.2.0) '@chakra-ui/next-js': specifier: ^2.1.5 - version: 2.2.0(@chakra-ui/react@2.8.2)(@emotion/react@11.11.1)(next@13.5.6)(react@18.2.0) + version: 2.2.0(@chakra-ui/react@2.8.2)(@emotion/react@11.11.1)(next@14.2.5)(react@18.2.0) '@chakra-ui/react': specifier: ^2.8.1 version: 2.8.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.37)(framer-motion@10.16.5)(react-dom@18.2.0)(react@18.2.0) @@ -37,7 +37,7 @@ importers: version: 23.12.1 next-i18next: specifier: ^15.3.0 - version: 15.3.0(i18next@23.12.1)(next@13.5.6)(react-i18next@14.1.2)(react@18.2.0) + version: 15.3.0(i18next@23.12.1)(next@14.2.5)(react-i18next@14.1.2)(react@18.2.0) react-i18next: specifier: ^14.1.2 version: 14.1.2(i18next@23.12.1)(react-dom@18.2.0)(react@18.2.0) @@ -1340,6 +1340,145 @@ importers: specifier: ^4.9.1 version: 4.10.1 + providers/devbox: + dependencies: + '@chakra-ui/react': + specifier: ^2.8.1 + version: 2.8.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.37)(framer-motion@10.16.5)(react-dom@18.2.0)(react@18.2.0) + '@emotion/react': + specifier: ^11.11.1 + version: 11.11.1(@types/react@18.2.37)(react@18.2.0) + '@emotion/styled': + specifier: ^11.11.0 + version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.37)(react@18.2.0) + '@kubernetes/client-node': + specifier: ^0.21.0 + version: 0.21.0 + '@sealos/ui': + specifier: workspace:^ + version: link:../../packages/ui + '@tanstack/react-query': + specifier: ^4.35.3 + version: 4.36.1(react-dom@18.2.0)(react@18.2.0) + axios: + specifier: ^1.7.3 + version: 1.7.3 + date-fns: + specifier: ^2.30.0 + version: 2.30.0 + dayjs: + specifier: ^1.11.10 + version: 1.11.10 + dns: + specifier: ^0.2.2 + version: 0.2.2 + echarts: + specifier: ^5.4.3 + version: 5.4.3 + fast-json-patch: + specifier: ^3.1.1 + version: 3.1.1 + framer-motion: + specifier: ^10.16.4 + version: 10.16.5(react-dom@18.2.0)(react@18.2.0) + immer: + specifier: ^10.1.1 + version: 10.1.1 + ini: + specifier: ^4.1.3 + version: 4.1.3 + js-cookie: + specifier: ^3.0.5 + version: 3.0.5 + js-yaml: + specifier: ^4.1.0 + version: 4.1.0 + jszip: + specifier: ^3.10.1 + version: 3.10.1 + lodash: + specifier: ^4.17.21 + version: 4.17.21 + nanoid: + specifier: ^4.0.2 + version: 4.0.2 + next: + specifier: 14.2.5 + version: 14.2.5(@babel/core@7.23.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5) + next-intl: + specifier: ^3.17.2 + version: 3.17.2(next@14.2.5)(react@18.2.0) + nprogress: + specifier: ^0.2.0 + version: 0.2.0 + react: + specifier: ^18 + version: 18.2.0 + react-dom: + specifier: ^18 + version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.46.2 + version: 7.48.2(react@18.2.0) + react-i18next: + specifier: ^14.1.2 + version: 14.1.2(i18next@23.12.1)(react-dom@18.2.0)(react@18.2.0) + react-markdown: + specifier: ^8.0.7 + version: 8.0.7(@types/react@18.2.37)(react@18.2.0) + react-syntax-highlighter: + specifier: ^15.5.0 + version: 15.5.0(react@18.2.0) + sass: + specifier: ^1.68.0 + version: 1.69.5 + sealos-desktop-sdk: + specifier: workspace:* + version: link:../../packages/client-sdk + zustand: + specifier: ^4.5.4 + version: 4.5.4(@types/react@18.2.37)(immer@10.1.1)(react@18.2.0) + devDependencies: + '@svgr/webpack': + specifier: ^6.5.1 + version: 6.5.1 + '@types/ini': + specifier: ^4.1.1 + version: 4.1.1 + '@types/js-cookie': + specifier: ^3.0.4 + version: 3.0.6 + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 + '@types/lodash': + specifier: ^4.14.199 + version: 4.14.202 + '@types/node': + specifier: ^20 + version: 20.10.0 + '@types/nprogress': + specifier: ^0.2.3 + version: 0.2.3 + '@types/react': + specifier: ^18 + version: 18.2.37 + '@types/react-dom': + specifier: ^18 + version: 18.0.11 + eslint: + specifier: ^8 + version: 8.57.0 + eslint-config-next: + specifier: 14.2.5 + version: 14.2.5(eslint@8.57.0)(typescript@5.2.2) + prettier: + specifier: ^2.8.8 + version: 2.8.8 + typescript: + specifier: ^5 + version: 5.2.2 + providers/invite: dependencies: '@chakra-ui/anatomy': @@ -4127,16 +4266,6 @@ packages: transitivePeerDependencies: - supports-color - /@babel/generator@7.23.4: - resolution: {integrity: sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.5 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 - jsesc: 2.5.2 - dev: true - /@babel/generator@7.23.5: resolution: {integrity: sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==} engines: {node: '>=6.9.0'} @@ -4150,13 +4279,13 @@ packages: resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 /@babel/helper-compilation-targets@7.22.15: resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} @@ -4164,7 +4293,7 @@ packages: dependencies: '@babel/compat-data': 7.23.3 '@babel/helper-validator-option': 7.22.15 - browserslist: 4.22.2 + browserslist: 4.23.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -4237,7 +4366,7 @@ packages: resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.5): resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} @@ -4288,19 +4417,19 @@ packages: resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 /@babel/helper-skip-transparent-expression-wrappers@7.22.5: resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 /@babel/helper-split-export-declaration@7.22.6: resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 /@babel/helper-string-parser@7.23.4: resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} @@ -4320,7 +4449,7 @@ packages: dependencies: '@babel/helper-function-name': 7.23.0 '@babel/template': 7.22.15 - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 /@babel/helpers@7.23.5: resolution: {integrity: sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==} @@ -4340,13 +4469,6 @@ packages: chalk: 2.4.2 js-tokens: 4.0.0 - /@babel/parser@7.23.4: - resolution: {integrity: sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.23.4 - /@babel/parser@7.23.5: resolution: {integrity: sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==} engines: {node: '>=6.0.0'} @@ -4993,7 +5115,7 @@ packages: '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5) - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 dev: true /@babel/plugin-transform-react-pure-annotations@7.23.3(@babel/core@7.23.5): @@ -5273,8 +5395,8 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.23.5 - '@babel/parser': 7.23.4 - '@babel/types': 7.23.4 + '@babel/parser': 7.23.5 + '@babel/types': 7.23.5 /@babel/traverse@7.23.5: resolution: {integrity: sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==} @@ -5293,14 +5415,6 @@ packages: transitivePeerDependencies: - supports-color - /@babel/types@7.23.4: - resolution: {integrity: sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.23.4 - '@babel/helper-validator-identifier': 7.22.20 - to-fast-properties: 2.0.0 - /@babel/types@7.23.5: resolution: {integrity: sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==} engines: {node: '>=6.9.0'} @@ -5767,6 +5881,21 @@ packages: react: 18.2.0 dev: false + /@chakra-ui/next-js@2.2.0(@chakra-ui/react@2.8.2)(@emotion/react@11.11.1)(next@14.2.5)(react@18.2.0): + resolution: {integrity: sha512-brCz0UEOlImX4Np2jDIaljZJkW6kiDSuXG5erxvYjZlklLhmti1zj0o1sSjt5yff1xndfgHoOJb+BYG5wx+vDg==} + peerDependencies: + '@chakra-ui/react': '>=2.4.0' + '@emotion/react': '>=11' + next: '>=13' + react: '>=18' + dependencies: + '@chakra-ui/react': 2.8.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.37)(framer-motion@10.16.5)(react-dom@18.2.0)(react@18.2.0) + '@emotion/cache': 11.11.0 + '@emotion/react': 11.11.1(@types/react@18.2.37)(react@18.2.0) + next: 14.2.5(@babel/core@7.23.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5) + react: 18.2.0 + dev: false + /@chakra-ui/number-input@2.1.2(@chakra-ui/system@2.6.2)(react@18.2.0): resolution: {integrity: sha512-pfOdX02sqUN0qC2ysuvgVDiws7xZ20XDIlcNhva55Jgm095xjm8eVdIBfNm3SFbSUNxyXvLTW/YQanX74tKmuA==} peerDependencies: @@ -6565,7 +6694,7 @@ packages: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: '@babel/helper-module-imports': 7.22.15 - '@babel/runtime': 7.23.4 + '@babel/runtime': 7.24.0 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.2 @@ -6647,7 +6776,7 @@ packages: '@emotion/memoize': 0.8.1 '@emotion/unitless': 0.8.1 '@emotion/utils': 1.2.1 - csstype: 3.1.2 + csstype: 3.1.3 dev: false /@emotion/sheet@1.2.2: @@ -7009,6 +7138,46 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@formatjs/ecma402-abstract@2.0.0: + resolution: {integrity: sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==} + dependencies: + '@formatjs/intl-localematcher': 0.5.4 + tslib: 2.6.2 + dev: false + + /@formatjs/fast-memoize@2.2.0: + resolution: {integrity: sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==} + dependencies: + tslib: 2.6.2 + dev: false + + /@formatjs/icu-messageformat-parser@2.7.8: + resolution: {integrity: sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==} + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/icu-skeleton-parser': 1.8.2 + tslib: 2.6.2 + dev: false + + /@formatjs/icu-skeleton-parser@1.8.2: + resolution: {integrity: sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==} + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + tslib: 2.6.2 + dev: false + + /@formatjs/intl-localematcher@0.2.32: + resolution: {integrity: sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==} + dependencies: + tslib: 2.6.2 + dev: false + + /@formatjs/intl-localematcher@0.5.4: + resolution: {integrity: sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==} + dependencies: + tslib: 2.6.2 + dev: false + /@fortaine/fetch-event-source@3.0.6: resolution: {integrity: sha512-621GAuLMvKtyZQ3IA6nlDWhV1V/7PGOTNIGLUifxt0KzM+dZIweJ6F3XvQF3QnqeNfS1N7WQ0Kil1Di/lhChEw==} engines: {node: '>=16.15'} @@ -7035,6 +7204,7 @@ packages: /@humanwhocodes/config-array@0.11.14: resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4 @@ -7052,6 +7222,7 @@ packages: /@humanwhocodes/object-schema@2.0.2: resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + deprecated: Use @eslint/object-schema instead dev: true /@icons/material@0.2.4(react@18.2.0): @@ -7072,7 +7243,13 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true + + /@isaacs/fs-minipass@4.0.1: + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + dependencies: + minipass: 7.1.2 + dev: false /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -7412,6 +7589,30 @@ packages: - utf-8-validate dev: false + /@kubernetes/client-node@0.21.0: + resolution: {integrity: sha512-yYRbgMeyQbvZDHt/ZqsW3m4lRefzhbbJEuj8sVXM+bufKrgmzriA2oq7lWPH/k/LQIicAME9ixPUadTrxIF6dQ==} + dependencies: + '@types/js-yaml': 4.0.9 + '@types/node': 20.10.0 + '@types/request': 2.48.12 + '@types/ws': 8.5.10 + byline: 5.0.0 + isomorphic-ws: 5.0.0(ws@8.14.2) + js-yaml: 4.1.0 + jsonpath-plus: 8.1.0 + request: 2.88.2 + rfc4648: 1.5.3 + stream-buffers: 3.0.2 + tar: 7.4.3 + tslib: 2.6.2 + ws: 8.14.2 + optionalDependencies: + openid-client: 5.6.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@lezer/common@1.1.1: resolution: {integrity: sha512-aAPB9YbvZHqAW+bIwiuuTDGB4DG0sYNRObGLxud8cW7osw1ZQxfDuTZ8KQiqfZ0QJGcR34CvpTMDXEyo/+Htgg==} dev: false @@ -7587,6 +7788,10 @@ packages: resolution: {integrity: sha512-Yac/bV5sBGkkEXmAX5FWPS9Mmo2rthrOPRQQNfycJPkjUAUclomCPH7QFVCDQ4Mp2k2K1SSM6m0zrxYrOwtFQw==} dev: false + /@next/env@14.2.5: + resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} + dev: false + /@next/eslint-plugin-next@13.1.6: resolution: {integrity: sha512-o7cauUYsXjzSJkay8wKjpKJf2uLzlggCsGUkPu3lP09Pv97jYlekTC20KJrjQKmSv5DXV0R/uks2ZXhqjNkqAw==} dependencies: @@ -7616,6 +7821,12 @@ packages: glob: 7.1.7 dev: true + /@next/eslint-plugin-next@14.2.5: + resolution: {integrity: sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==} + dependencies: + glob: 10.3.10 + dev: true + /@next/font@13.1.6: resolution: {integrity: sha512-AITjmeb1RgX1HKMCiA39ztx2mxeAyxl4ljv2UoSBUGAbFFMg8MO7YAvjHCgFhD39hL7YTbFjol04e/BPBH5RzQ==} dev: false @@ -7710,6 +7921,15 @@ packages: dev: false optional: true + /@next/swc-darwin-arm64@14.2.5: + resolution: {integrity: sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-darwin-x64@13.1.6: resolution: {integrity: sha512-/uOky5PaZDoaU99ohjtNcDTJ6ks/gZ5ykTQDvNZDjIoCxFe3+t06bxsTPY6tAO6uEAw5f6vVFX5H5KLwhrkZCA==} engines: {node: '>= 10'} @@ -7764,6 +7984,15 @@ packages: dev: false optional: true + /@next/swc-darwin-x64@14.2.5: + resolution: {integrity: sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: false + optional: true + /@next/swc-freebsd-x64@13.1.6: resolution: {integrity: sha512-qaEALZeV7to6weSXk3Br80wtFQ7cFTpos/q+m9XVRFggu+8Ib895XhMWdJBzew6aaOcMvYR6KQ6JmHA2/eMzWw==} engines: {node: '>= 10'} @@ -7860,6 +8089,16 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-gnu@14.2.5: + resolution: {integrity: sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-arm64-musl@13.1.6: resolution: {integrity: sha512-ECagB8LGX25P9Mrmlc7Q/TQBb9rGScxHbv/kLqqIWs2fIXy6Y/EiBBiM72NTwuXUFCNrWR4sjUPSooVBJJ3ESQ==} engines: {node: '>= 10'} @@ -7920,6 +8159,16 @@ packages: dev: false optional: true + /@next/swc-linux-arm64-musl@14.2.5: + resolution: {integrity: sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-gnu@13.1.6: resolution: {integrity: sha512-GT5w2mruk90V/I5g6ScuueE7fqj/d8Bui2qxdw6lFxmuTgMeol5rnzAv4uAoVQgClOUO/MULilzlODg9Ib3Y4Q==} engines: {node: '>= 10'} @@ -7980,6 +8229,16 @@ packages: dev: false optional: true + /@next/swc-linux-x64-gnu@14.2.5: + resolution: {integrity: sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + requiresBuild: true + dev: false + optional: true + /@next/swc-linux-x64-musl@13.1.6: resolution: {integrity: sha512-keFD6KvwOPzmat4TCnlnuxJCQepPN+8j3Nw876FtULxo8005Y9Ghcl7ACcR8GoiKoddAq8gxNBrpjoxjQRHeAQ==} engines: {node: '>= 10'} @@ -8040,6 +8299,16 @@ packages: dev: false optional: true + /@next/swc-linux-x64-musl@14.2.5: + resolution: {integrity: sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-arm64-msvc@13.1.6: resolution: {integrity: sha512-OwertslIiGQluFvHyRDzBCIB07qJjqabAmINlXUYt7/sY7Q7QPE8xVi5beBxX/rxTGPIbtyIe3faBE6Z2KywhQ==} engines: {node: '>= 10'} @@ -8094,6 +8363,15 @@ packages: dev: false optional: true + /@next/swc-win32-arm64-msvc@14.2.5: + resolution: {integrity: sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-ia32-msvc@13.1.6: resolution: {integrity: sha512-g8zowiuP8FxUR9zslPmlju7qYbs2XBtTLVSxVikPtUDQedhcls39uKYLvOOd1JZg0ehyhopobRoH1q+MHlIN/w==} engines: {node: '>= 10'} @@ -8148,6 +8426,15 @@ packages: dev: false optional: true + /@next/swc-win32-ia32-msvc@14.2.5: + resolution: {integrity: sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@next/swc-win32-x64-msvc@13.1.6: resolution: {integrity: sha512-Ls2OL9hi3YlJKGNdKv8k3X/lLgc3VmLG3a/DeTkAd+lAituJp8ZHmRmm9f9SL84fT3CotlzcgbdaCDfFwFA6bA==} engines: {node: '>= 10'} @@ -8202,6 +8489,15 @@ packages: dev: false optional: true + /@next/swc-win32-x64-msvc@14.2.5: + resolution: {integrity: sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: false + optional: true + /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -8460,7 +8756,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@polka/url@1.0.0-next.23: @@ -9424,6 +9719,10 @@ packages: - supports-color dev: true + /@swc/counter@0.1.3: + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + dev: false + /@swc/helpers@0.4.14: resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} dependencies: @@ -9442,6 +9741,13 @@ packages: tslib: 2.6.2 dev: false + /@swc/helpers@0.5.5: + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + dependencies: + '@swc/counter': 0.1.3 + tslib: 2.6.2 + dev: false + /@tanstack/query-core@4.36.1: resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} dev: false @@ -9870,7 +10176,7 @@ packages: /@types/mdast@4.0.3: resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} dependencies: - '@types/unist': 2.0.10 + '@types/unist': 3.0.2 dev: false /@types/mime@1.3.5: @@ -10575,7 +10881,6 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles@2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} @@ -10602,7 +10907,6 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /ansi_up@5.2.1: resolution: {integrity: sha512-5bz5T/7FRmlxA37zDXhG6cAwlcZtfnmNLDJra66EEIT3kYlw5aPJdbkJEhm59D6kA4Wi5ict6u6IDYHJaQlH+g==} @@ -10933,6 +11237,16 @@ packages: - debug dev: false + /axios@1.7.3: + resolution: {integrity: sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==} + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -11215,16 +11529,6 @@ packages: /browser-or-node@2.1.1: resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} - /browserslist@4.22.2: - resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001594 - electron-to-chromium: 1.4.603 - node-releases: 2.0.14 - update-browserslist-db: 1.0.13(browserslist@4.22.2) - /browserslist@4.23.0: resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -11453,6 +11757,11 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} + /chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + dev: false + /chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} engines: {node: '>=6.0'} @@ -11771,7 +12080,7 @@ packages: /core-js-compat@3.33.3: resolution: {integrity: sha512-cNzGqFsh3Ot+529GIXacjTJ7kegdt5fPXxCBVS1G0iaZpuo/tBz399ymceLJveQhFFZ8qThHiP3fzuoQjKN2ow==} dependencies: - browserslist: 4.22.2 + browserslist: 4.23.0 /core-js@3.33.3: resolution: {integrity: sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==} @@ -11976,6 +12285,7 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + dev: true /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -12535,7 +12845,6 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -12580,9 +12889,6 @@ packages: jake: 10.8.7 dev: false - /electron-to-chromium@1.4.603: - resolution: {integrity: sha512-Dvo5OGjnl7AZTU632dFJtWj0uJK835eeOVQIuRcmBmsFsTNn3cL05FqOyHAfGQDIoHfLhyJ1Tya3PJ0ceMz54g==} - /electron-to-chromium@1.4.692: resolution: {integrity: sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==} @@ -12603,7 +12909,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -13019,6 +13324,31 @@ packages: - supports-color dev: true + /eslint-config-next@14.2.5(eslint@8.57.0)(typescript@5.2.2): + resolution: {integrity: sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@next/eslint-plugin-next': 14.2.5 + '@rushstack/eslint-patch': 1.6.0 + '@typescript-eslint/parser': 6.13.1(eslint@8.57.0)(typescript@5.2.2) + eslint: 8.57.0 + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.57.0) + eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.1)(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0) + eslint-plugin-react: 7.33.2(eslint@8.57.0) + eslint-plugin-react-hooks: 4.6.0(eslint@8.57.0) + typescript: 5.2.2 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - supports-color + dev: true + /eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} dependencies: @@ -14284,7 +14614,6 @@ packages: /extsprintf@1.4.1: resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} engines: {'0': node >=0.6.0} - dev: false /eyes@0.1.8: resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} @@ -14490,6 +14819,16 @@ packages: optional: true dev: false + /follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -14501,7 +14840,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /forever-agent@0.2.0: resolution: {integrity: sha512-IasWSRIlfPnBZY1K9jEUK3PwsScR4mrcK+aNBJzGoPnW+S9b6f8I8ScyH4cehEOFNqnjGpP2gCaA22gqSV1xQA==} @@ -14795,7 +15133,6 @@ packages: minimatch: 9.0.3 minipass: 5.0.0 path-scurry: 1.10.1 - dev: true /glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} @@ -14809,6 +15146,7 @@ packages: /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -15262,6 +15600,10 @@ packages: resolution: {integrity: sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==} dev: false + /immer@10.1.1: + resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} + dev: false + /immer@9.0.21: resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} dev: false @@ -15343,6 +15685,15 @@ packages: engines: {node: '>= 0.10'} dev: false + /intl-messageformat@10.5.14: + resolution: {integrity: sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==} + dependencies: + '@formatjs/ecma402-abstract': 2.0.0 + '@formatjs/fast-memoize': 2.2.0 + '@formatjs/icu-messageformat-parser': 2.7.8 + tslib: 2.6.2 + dev: false + /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -15489,7 +15840,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-fullwidth-code-point@4.0.0: resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} @@ -15779,7 +16129,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jake@10.8.7: resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} @@ -16201,10 +16550,10 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.23.5 - '@babel/generator': 7.23.4 + '@babel/generator': 7.23.5 '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5) '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.5) - '@babel/types': 7.23.4 + '@babel/types': 7.23.5 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -16528,6 +16877,12 @@ packages: resolution: {integrity: sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==} engines: {node: '>=12.0.0'} + /jsonpath-plus@8.1.0: + resolution: {integrity: sha512-qVTiuKztFGw0dGhYi3WNqvddx3/SHtyDT0xJaeyz4uP0d1tkpG+0y5uYQ4OcIo1TLAz3PE/qDOW9F0uDt3+CTw==} + engines: {node: '>=14.0.0'} + hasBin: true + dev: false + /jsonpointer@5.0.1: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} @@ -17492,7 +17847,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -17526,6 +17880,10 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -17533,6 +17891,14 @@ packages: minipass: 3.3.6 yallist: 4.0.0 + /minizlib@3.0.1: + resolution: {integrity: sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==} + engines: {node: '>= 18'} + dependencies: + minipass: 7.1.2 + rimraf: 5.0.10 + dev: false + /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: false @@ -17549,6 +17915,12 @@ packages: engines: {node: '>=10'} hasBin: true + /mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + dev: false + /ml-array-max@1.2.4: resolution: {integrity: sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==} dependencies: @@ -17769,6 +18141,11 @@ packages: resolution: {integrity: sha512-ujxWwyRfZ6udAgHGECQC3JDO9e6UAsuItfUMcqA0Xf2OLNQTveFVFx+fHGIJ5p0MJaJrZyGQqPwzuN0NxJzEKA==} dev: false + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + /neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: false @@ -17893,6 +18270,39 @@ packages: react-i18next: 14.1.2(i18next@23.12.1)(react-dom@18.2.0)(react@18.2.0) dev: false + /next-i18next@15.3.0(i18next@23.12.1)(next@14.2.5)(react-i18next@14.1.2)(react@18.2.0): + resolution: {integrity: sha512-bq7Cc9XJFcmGOCLnyEtHaeJ3+JJNsI/8Pkj9BaHAnhm4sZ9vNNC4ZsaqYnlRZ7VH5ypSo73fEqLK935jLsmCvQ==} + engines: {node: '>=14'} + peerDependencies: + i18next: '>= 23.7.13' + next: '>= 12.0.0' + react: '>= 17.0.2' + react-i18next: '>= 13.5.0' + dependencies: + '@babel/runtime': 7.24.0 + '@types/hoist-non-react-statics': 3.3.5 + core-js: 3.33.3 + hoist-non-react-statics: 3.3.2 + i18next: 23.12.1 + i18next-fs-backend: 2.3.1 + next: 14.2.5(@babel/core@7.23.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5) + react: 18.2.0 + react-i18next: 14.1.2(i18next@23.12.1)(react-dom@18.2.0)(react@18.2.0) + dev: false + + /next-intl@3.17.2(next@14.2.5)(react@18.2.0): + resolution: {integrity: sha512-X2ly23e1lC5vdWHaJFBDZi/0iornEdFQQtqJmmPOb7WD+LDssm9vAnx+hJshYGjddaP3rUmyWaPgePCQqaxm1g==} + peerDependencies: + next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@formatjs/intl-localematcher': 0.2.32 + negotiator: 0.6.3 + next: 14.2.5(@babel/core@7.23.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5) + react: 18.2.0 + use-intl: 3.17.2(react@18.2.0) + dev: false + /next-pwa@5.6.0(@babel/core@7.23.5)(next@13.3.0)(webpack@5.89.0): resolution: {integrity: sha512-XV8g8C6B7UmViXU8askMEYhWwQ4qc/XqJGnexbLV68hzKaGHZDMtHsm2TNxFcbR7+ypVuth/wwpiIlMwpRJJ5A==} peerDependencies: @@ -18175,6 +18585,49 @@ packages: - babel-plugin-macros dev: false + /next@14.2.5(@babel/core@7.23.5)(react-dom@18.2.0)(react@18.2.0)(sass@1.69.5): + resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + dependencies: + '@next/env': 14.2.5 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001594 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + sass: 1.69.5 + styled-jsx: 5.1.1(@babel/core@7.23.5)(react@18.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.5 + '@next/swc-darwin-x64': 14.2.5 + '@next/swc-linux-arm64-gnu': 14.2.5 + '@next/swc-linux-arm64-musl': 14.2.5 + '@next/swc-linux-x64-gnu': 14.2.5 + '@next/swc-linux-x64-musl': 14.2.5 + '@next/swc-win32-arm64-msvc': 14.2.5 + '@next/swc-win32-ia32-msvc': 14.2.5 + '@next/swc-win32-x64-msvc': 14.2.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + dev: false + /node-abi@3.54.0: resolution: {integrity: sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==} engines: {node: '>=10'} @@ -18565,8 +19018,7 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: lru-cache: 10.1.0 - minipass: 5.0.0 - dev: true + minipass: 7.1.2 /path-to-regexp@0.1.3: resolution: {integrity: sha512-sd4vSOW+DCM6A5aRICI1CWaC7nufnzVpZfuh5T0VXshxxzFWuaFcvqKovAFLNGReOc+uZRptpcpPmn7CDvzLuA==} @@ -20264,6 +20716,7 @@ packages: /rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true dependencies: glob: 7.2.3 @@ -20271,10 +20724,18 @@ packages: /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true dependencies: glob: 7.2.3 + /rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + dependencies: + glob: 10.3.10 + dev: false + /robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} dev: false @@ -20614,7 +21075,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -20991,7 +21451,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -21000,7 +21459,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true /string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} @@ -21084,7 +21542,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} @@ -21362,6 +21819,18 @@ packages: mkdirp: 1.0.4 yallist: 4.0.0 + /tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.1 + mkdirp: 3.0.1 + yallist: 5.0.0 + dev: false + /temp-dir@2.0.0: resolution: {integrity: sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==} engines: {node: '>=8'} @@ -21955,16 +22424,6 @@ packages: engines: {node: '>=4'} dev: false - /update-browserslist-db@1.0.13(browserslist@4.22.2): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.22.2 - escalade: 3.1.1 - picocolors: 1.0.0 - /update-browserslist-db@1.0.13(browserslist@4.23.0): resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} hasBin: true @@ -22002,6 +22461,16 @@ packages: tslib: 2.6.2 dev: false + /use-intl@3.17.2(react@18.2.0): + resolution: {integrity: sha512-9lPgt41nS8x4AYCLfIC9VKCmamnVxzPM2nze7lpp/I1uaSSQvIz5MQpYUFikv08cMUsCwAWahU0e+arHInpdcw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@formatjs/fast-memoize': 2.2.0 + intl-messageformat: 10.5.14 + react: 18.2.0 + dev: false + /use-sidecar@1.1.2(@types/react@18.2.37)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} @@ -22103,7 +22572,7 @@ packages: dependencies: assert-plus: 1.0.0 core-util-is: 1.0.2 - extsprintf: 1.3.0 + extsprintf: 1.4.1 /verror@1.10.1: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} @@ -22595,7 +23064,6 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} @@ -22604,7 +23072,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true /wrap-ansi@9.0.0: resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} @@ -22724,6 +23191,12 @@ packages: /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + requiresBuild: true + + /yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + dev: false /yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} @@ -22838,6 +23311,27 @@ packages: use-sync-external-store: 1.2.0(react@18.2.0) dev: false + /zustand@4.5.4(@types/react@18.2.37)(immer@10.1.1)(react@18.2.0): + resolution: {integrity: sha512-/BPMyLKJPtFEvVL0E9E9BTUM63MNyhPGlvxk1XjrfWTUlV+BR8jufjsovHzrtR6YNcBEcL7cMHovL1n9xHawEg==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + dependencies: + '@types/react': 18.2.37 + immer: 10.1.1 + react: 18.2.0 + use-sync-external-store: 1.2.0(react@18.2.0) + dev: false + /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: false diff --git a/frontend/providers/dbprovider/src/pages/db/edit/index.tsx b/frontend/providers/dbprovider/src/pages/db/edit/index.tsx index 76845d7d2af..cfb5497bd79 100644 --- a/frontend/providers/dbprovider/src/pages/db/edit/index.tsx +++ b/frontend/providers/dbprovider/src/pages/db/edit/index.tsx @@ -94,6 +94,7 @@ const EditApp = ({ dbName, tabType }: { dbName?: string; tabType?: 'form' | 'yam }, 200), [] ); + // watch form change, compute new yaml formHook.watch((data) => { data && formOnchangeDebounce(data as DBEditType); diff --git a/frontend/providers/devbox/.dockerignore b/frontend/providers/devbox/.dockerignore new file mode 100644 index 00000000000..599da9cd7bf --- /dev/null +++ b/frontend/providers/devbox/.dockerignore @@ -0,0 +1,10 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +.git + +.yalc/ +yalc.lock diff --git a/frontend/providers/devbox/.env.template b/frontend/providers/devbox/.env.template new file mode 100644 index 00000000000..e69de29bb2d diff --git a/frontend/providers/devbox/.eslintrc.json b/frontend/providers/devbox/.eslintrc.json new file mode 100644 index 00000000000..bffb357a712 --- /dev/null +++ b/frontend/providers/devbox/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/frontend/providers/devbox/.gitignore b/frontend/providers/devbox/.gitignore new file mode 100644 index 00000000000..fd3dbb571a1 --- /dev/null +++ b/frontend/providers/devbox/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/providers/devbox/.prettierignore b/frontend/providers/devbox/.prettierignore new file mode 100644 index 00000000000..ab9a493f2ce --- /dev/null +++ b/frontend/providers/devbox/.prettierignore @@ -0,0 +1,4 @@ +dist +.vscode +**/.DS_Store +node_modules diff --git a/frontend/providers/devbox/.prettierrc.json b/frontend/providers/devbox/.prettierrc.json new file mode 100644 index 00000000000..c3bc434d271 --- /dev/null +++ b/frontend/providers/devbox/.prettierrc.json @@ -0,0 +1,18 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": false, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "none", + "bracketSpacing": true, + "jsxBracketSameLine": true, + "bracketSameLine": true, + "arrowParens": "always", + "requirePragma": false, + "insertPragma": false, + "proseWrap": "preserve", + "htmlWhitespaceSensitivity": "ignore", + "endOfLine": "auto" +} diff --git a/frontend/providers/devbox/.vscode/extensions.json b/frontend/providers/devbox/.vscode/extensions.json new file mode 100644 index 00000000000..f9f15658591 --- /dev/null +++ b/frontend/providers/devbox/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "streetsidesoftware.code-spell-checker", + "dsznajder.es7-react-js-snippets", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "burkeholland.simple-react-snippets", + "gruntfuggly.todo-tree" + ] +} diff --git a/frontend/providers/devbox/.vscode/settings.json b/frontend/providers/devbox/.vscode/settings.json new file mode 100644 index 00000000000..a553a498618 --- /dev/null +++ b/frontend/providers/devbox/.vscode/settings.json @@ -0,0 +1,81 @@ +{ + "i18n-ally.localesPaths": [ + "message" + ], + "i18n-ally.enabledParsers": [ + "json", + "yaml", + "js", + "ts" + ], + "i18n-ally.keystyle": "nested", + "i18n-ally.sortKeys": true, + "i18n-ally.keepFulfilled": false, + "i18n-ally.sourceLanguage": "zh", + "i18n-ally.displayLanguage": "zh", + "i18n-ally.pathMatcher": "{locale}.json", + "i18n-ally.extract.targetPickingStrategy": "most-similar-by-key", + "i18n-ally.translate.engines": [ + "deepl", + "google", + ], + "cSpell.words": [ + "apecloud", + "applaunchpad", + "Autoscaler", + "backuprepos", + "bdstatic", + "chakra", + "clusterdefinition", + "clusterversion", + "clusterversions", + "dataprotection", + "devbox", + "devboxes", + "devboxreleases", + "echarts", + "formatjs", + "grpcs", + "hljs", + "immer", + "jsonpatch", + "kbcli", + "kubeblocks", + "kubeconfig", + "kubernet", + "letsencrypt", + "localematcher", + "milvus", + "minio", + "mlhiter", + "nextjs", + "nextline", + "nodeports", + "nprogress", + "oname", + "openai", + "Parens", + "pitr", + "pricequeries", + "qdrant", + "Reconfig", + "rolebinding", + "runtimeclasses", + "runtimes", + "sailos", + "sealos", + "SSHDOMAIN", + "statefulset", + "svgr", + "Tailnet", + "tanstack", + "tolerations", + "weaviate", + "weixin", + "yalc", + "Yamls", + "zustand" + ], + "typescript.tsdk": "node_modules/typescript/lib", + "svg.preview.background": "transparent", +} diff --git a/frontend/providers/devbox/Dockerfile b/frontend/providers/devbox/Dockerfile new file mode 100644 index 00000000000..ac15573a4b2 --- /dev/null +++ b/frontend/providers/devbox/Dockerfile @@ -0,0 +1,58 @@ +# Install dependencies only when needed +FROM node:current-alpine AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat && npm install -g pnpm +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json pnpm-lock.yaml* ./ +RUN \ + [ -f pnpm-lock.yaml ] && pnpm install || \ + (echo "Lockfile not found." && exit 1) + +# Rebuild the source code only when needed +FROM node:current-alpine AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +ENV NEXT_TELEMETRY_DISABLED 1 +ENV NEXT_PUBLIC_MOCK_USER '' + +RUN npm install -g pnpm && pnpm run build + +# Production image, copy all the files and run next +FROM node:current-alpine AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +RUN sed -i 's/https/http/' /etc/apk/repositories +RUN apk add curl \ + && apk add ca-certificates \ + && update-ca-certificates + +# You only need to copy next.config.js if you are NOT using the default configuration +# COPY --from=builder /app/next.config.js ./ +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json +# COPY --from=builder /app/.env* . + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +CMD ["node", "server.js"] diff --git a/frontend/providers/devbox/Makefile b/frontend/providers/devbox/Makefile new file mode 100644 index 00000000000..c9a46bcadbc --- /dev/null +++ b/frontend/providers/devbox/Makefile @@ -0,0 +1,37 @@ +SERVICE_NAME=sealos-devbox +# Image URL to use all building/pushing image targets +IMG ?= $(SERVICE_NAME):latest + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Build + +.PHONY: build +build: ## Build desktop-frontend binary. + pnpm run build + +.PHONY: run +run: ## Run a dev service from host. + pnpm run start + +.PHONY: docker-build +docker-build: ## Build docker image with the desktop-frontend. + docker build -t sealos-devbox:latest . --network host --build-arg HTTP_PROXY=http://127.0.0.1:7890 --build-arg HTTPS_PROXY=http://127.0.0.1:7890 diff --git a/frontend/providers/devbox/README.md b/frontend/providers/devbox/README.md new file mode 100644 index 00000000000..6786987033b --- /dev/null +++ b/frontend/providers/devbox/README.md @@ -0,0 +1,39 @@ +## How to dev + +1. First,you should refer to `frontend/README.md` ’s `How to dev` part. + +2. Then you should config your env. + + 1. Create a new file `.env.local` in frontend/providers/kubepanel directory. + + > `SEALOS_DOMAIN` is anyone website you use in sealos. + + 2. ``` + NEXT_PUBLIC_MOCK_USER="" + SEALOS_DOMAIN="bja.sealos.run" + NODE_ENV="development" + ``` + + 3. Then we should get our `NEXT_PUBLIC_MOCK_USER` + + 4. go to `bja.sealos.run` and login (firstly you goto this website,sign up,so go on.) + + 5. Refer this picture,you should open `Console-application`,and get your own `session.state.session.kubeconfig`,copy as JSON string. + + ![image-20240423105724369](https://raw.githubusercontent.com/mlhiter/typora-images/master/202404231101028.png) + +3. After that,have your own `test 3000 `page + + > Why you should have that? + > + > If you open your own dev in `localhost:3000` directly,you cannot have sealos desktop border,which maybe influence your style. + + 1. This url:[website](https://cloud.sealos.run/?openapp=system-template%3FtemplateName%3Done-step-shortcuts) + + 2. ![image-20240423111024336](https://raw.githubusercontent.com/mlhiter/typora-images/master/202404231110609.png) + + 3. Refresh website. + + 4. Then you can get your own dev in this. + + ![image-20240423111123308](https://raw.githubusercontent.com/mlhiter/typora-images/master/202404231111720.png) diff --git a/frontend/providers/devbox/api/devbox.ts b/frontend/providers/devbox/api/devbox.ts new file mode 100644 index 00000000000..72a0d468c77 --- /dev/null +++ b/frontend/providers/devbox/api/devbox.ts @@ -0,0 +1,67 @@ +import { V1Pod } from '@kubernetes/client-node' + +import { + DevboxEditType, + DevboxListItemType, + DevboxPatchPropsType, + DevboxVersionListItemType, + runtimeNamespaceMapType +} from '@/types/devbox' +import { GET, POST, DELETE } from '@/services/request' +import { KBDevboxType, KBDevboxReleaseType } from '@/types/k8s' +import { MonitorDataResult, MonitorQueryKey } from '@/types/monitor' +import { adaptDevboxListItem, adaptDevboxVersionListItem, adaptPod } from '@/utils/adapt' + +export const getMyDevboxList = () => + GET('/api/getDevboxList').then((data): DevboxListItemType[] => + data.map(adaptDevboxListItem) + ) + +export const applyYamlList = (yamlList: string[], type: 'create' | 'replace' | 'update') => + POST('/api/applyYamlList', { yamlList, type }) + +export const createDevbox = (payload: { + devboxForm: DevboxEditType + runtimeNamespaceMap: runtimeNamespaceMapType +}) => POST(`/api/createDevbox`, payload) + +export const updateDevbox = (payload: { patch: DevboxPatchPropsType; devboxName: string }) => + POST(`/api/updateDevbox`, payload) + +export const delDevbox = (devboxName: string, networks: string[]) => + DELETE('/api/delDevbox', { devboxName, networks: JSON.stringify(networks) }) + +export const restartDevbox = (data: { devboxName: string }) => POST('/api/restartDevbox', data) + +export const startDevbox = (data: { devboxName: string }) => POST('/api/startDevbox', data) + +export const pauseDevbox = (data: { devboxName: string }) => POST('/api/pauseDevbox', data) + +export const getDevboxVersionList = (devboxName: string) => + GET('/api/getDevboxVersionList', { devboxName }).then( + (data): DevboxVersionListItemType[] => data.map(adaptDevboxVersionListItem) + ) + +export const releaseDevbox = (data: { devboxName: string; tag: string; releaseDes: string }) => + POST('/api/releaseDevbox', data) + +export const editDevboxVersion = (data: { name: string; releaseDes: string }) => + POST('/api/editDevboxVersion', data) + +export const delDevboxVersionByName = (versionName: string) => + DELETE('/api/delDevboxVersionByName', { versionName }) + +export const getSSHConnectionInfo = (data: { devboxName: string; runtimeName: string }) => + GET('/api/getSSHConnectionInfo', data) + +export const getDevboxPodsByDevboxName = (name: string) => + GET('/api/getDevboxPodsByDevboxName', { name }).then((item) => item.map(adaptPod)) + +export const getDevboxMonitorData = (payload: { + queryName: string + queryKey: keyof MonitorQueryKey + step: string +}) => GET(`/api/monitor/getMonitorData`, payload) + +export const getSSHRuntimeInfo = (runtimeName: string) => + GET('/api/getSSHRuntimeInfo', { runtimeName }) diff --git a/frontend/providers/devbox/api/platform.ts b/frontend/providers/devbox/api/platform.ts new file mode 100644 index 00000000000..1d1e6bd0bf7 --- /dev/null +++ b/frontend/providers/devbox/api/platform.ts @@ -0,0 +1,20 @@ +import { GET, POST } from '@/services/request' +import type { UserQuotaItemType } from '@/types/user' +import { SystemEnvResponse } from '@/app/api/getEnv/route' +import type { Response as resourcePriceResponse } from '@/app/api/platform/resourcePrice/route' + +export const getAppEnv = () => GET('/api/getEnv') + +export const getUserQuota = () => + GET<{ + quota: UserQuotaItemType[] + }>('/api/platform/getQuota') + +export const getRuntime = () => GET('/api/platform/getRuntime') + +export const getResourcePrice = () => GET('/api/platform/resourcePrice') + +export const postAuthCname = (data: { publicDomain: string; customDomain: string }) => + POST('/api/platform/authCname', data) + +export const getNamespace = () => GET('/api/platform/getNamespace') diff --git a/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx new file mode 100644 index 00000000000..8685bd78da4 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/DevboxList.tsx @@ -0,0 +1,555 @@ +import { + Box, + Button, + Center, + Flex, + Image, + MenuButton, + useTheme, + Text, + Tooltip, + Menu, + MenuList, + MenuItem, + IconButton +} from '@chakra-ui/react' +import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' +import { useCallback, useState } from 'react' +import { sealosApp } from 'sealos-desktop-sdk/app' +import { SealosMenu, MyTable, useMessage } from '@sealos/ui' + +import { + getSSHConnectionInfo, + getSSHRuntimeInfo, + pauseDevbox, + restartDevbox, + startDevbox +} from '@/api/devbox' +import { useRouter } from '@/i18n' +import MyIcon from '@/components/Icon' +import { IDEType, useGlobalStore } from '@/stores/global' +import { DevboxListItemType } from '@/types/devbox' +import PodLineChart from '@/components/PodLineChart' +import DevboxStatusTag from '@/components/DevboxStatusTag' +import { NAMESPACE, SEALOS_DOMAIN } from '@/stores/static' +import ReleaseModal from '@/components/modals/releaseModal' + +const DelModal = dynamic(() => import('@/components/modals/DelModal')) + +const DevboxList = ({ + devboxList = [], + refetchDevboxList +}: { + devboxList: DevboxListItemType[] + refetchDevboxList: () => void +}) => { + const theme = useTheme() + const router = useRouter() + const t = useTranslations() + const { message: toast } = useMessage() + const { setLoading, setCurrentIDE, currentIDE } = useGlobalStore() + const [onOpenRelease, setOnOpenRelease] = useState(false) + const [delDevbox, setDelDevbox] = useState(null) + const [currentDevboxListItem, setCurrentDevboxListItem] = useState( + null + ) + + const handleOpenRelease = (devbox: DevboxListItemType) => { + setCurrentDevboxListItem(devbox) + setOnOpenRelease(true) + } + + const handlePauseDevbox = useCallback( + async (devbox: DevboxListItemType) => { + try { + setLoading(true) + await pauseDevbox({ devboxName: devbox.name }) + toast({ + title: t('pause_success'), + status: 'success' + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('pause_error'), + status: 'error' + }) + console.error(error) + } + setLoading(false) + refetchDevboxList() + }, + [refetchDevboxList, setLoading, t, toast] + ) + const handleRestartDevbox = useCallback( + async (devbox: DevboxListItemType) => { + try { + setLoading(true) + await restartDevbox({ devboxName: devbox.name }) + toast({ + title: t('restart_success'), + status: 'success' + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('restart_error'), + status: 'error' + }) + console.error(error, '==') + } + setLoading(false) + }, + [setLoading, t, toast] + ) + const handleStartDevbox = useCallback( + async (devbox: DevboxListItemType) => { + try { + setLoading(true) + await startDevbox({ devboxName: devbox.name }) + toast({ + title: t('start_success'), + status: 'success' + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('start_error'), + status: 'error' + }) + console.error(error, '==') + } + setLoading(false) + }, + [setLoading, t, toast] + ) + + const getCurrentIDELabelAndIcon = useCallback( + ( + currentIDE: IDEType + ): { + label: string + icon: IDEType + } => { + switch (currentIDE) { + case 'vscode': + return { + label: 'VSCode', + icon: 'vscode' + } + case 'cursor': + return { + label: 'Cursor', + icon: 'cursor' + } + case 'vscodeInsider': + return { + label: 'VSCode Insider', + icon: 'vscodeInsider' + } + default: + return { + label: 'VSCode', + icon: 'vscode' + } + } + }, + [] + ) + + const handleGoToTerminal = useCallback( + async (devbox: DevboxListItemType) => { + const defaultCommand = `kubectl exec -it $(kubectl get po -l app.kubernetes.io/name=${devbox.name} -oname) -- sh -c "clear; (bash || ash || sh)"` + try { + sealosApp.runEvents('openDesktopApp', { + appKey: 'system-terminal', + query: { + defaultCommand + }, + messageData: { type: 'new terminal', command: defaultCommand } + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('jump_terminal_error'), + status: 'error' + }) + console.error(error) + } + refetchDevboxList() + }, + [refetchDevboxList, t, toast] + ) + + const handleGotoIDE = useCallback( + async (devbox: DevboxListItemType, currentIDE: string = 'vscode') => { + try { + const { base64PrivateKey, userName } = await getSSHConnectionInfo({ + devboxName: devbox.name, + runtimeName: devbox.runtimeVersion + }) + const { workingDir } = await getSSHRuntimeInfo(devbox.runtimeVersion) + + let editorUri = '' + switch (currentIDE) { + case 'cursor': + editorUri = `cursor://` + break + case 'vscodeInsider': + editorUri = `vscode-insiders://` + break + case 'vscode': + editorUri = `vscode://` + break + default: + editorUri = `vscode://` + } + + const fullUri = `${editorUri}mlhiter.devbox-sealos?sshDomain=${encodeURIComponent( + `${userName}@${SEALOS_DOMAIN}` + )}&sshPort=${encodeURIComponent(devbox.sshPort)}&base64PrivateKey=${encodeURIComponent( + base64PrivateKey + )}&sshHostLabel=${encodeURIComponent( + `${SEALOS_DOMAIN}/${NAMESPACE}/${devbox.name}` + )}&workingDir=${encodeURIComponent(workingDir)}` + + window.location.href = fullUri + } catch (error: any) { + console.error(error, '==') + } + }, + [] + ) + + const columns: { + title: string + dataIndex?: keyof DevboxListItemType + key: string + render?: (item: DevboxListItemType) => JSX.Element + }[] = [ + { + title: t('name'), + key: 'name', + render: (item: DevboxListItemType) => { + return ( + + {item.id} + + {item.name} + + + ) + } + }, + { + title: t('status'), + key: 'status', + render: (item: DevboxListItemType) => + }, + { + title: t('create_time'), + dataIndex: 'createTime', + key: 'createTime', + render: (item: DevboxListItemType) => { + return {item.createTime} + } + }, + { + title: t('cpu'), + key: 'cpu', + render: (item: DevboxListItemType) => ( + + + + + {item?.usedCpu?.yData[item?.usedCpu?.yData?.length - 1]}% + + + + ) + }, + { + title: t('memory'), + key: 'storage', + render: (item: DevboxListItemType) => ( + + + + + {item?.usedMemory?.yData[item?.usedMemory?.yData?.length - 1]}% + + + + ) + }, + { + title: t('control'), + key: 'control', + render: (item: DevboxListItemType) => ( + + + + + + } + _before={{ + content: '""', + position: 'absolute', + left: 0, + top: '50%', + transform: 'translateY(-50%)', + width: '1px', + height: '20px', + backgroundColor: 'grayModern.250' + }} + /> + + {[ + { value: 'vscode' as IDEType, label: 'VSCode' }, + { value: 'cursor' as IDEType, label: 'Cursor' }, + { value: 'vscodeInsider' as IDEType, label: 'VSCode Insider' } + ].map((item) => ( + setCurrentIDE(item.value)} + icon={} + _hover={{ + bg: '#1118240D', + borderRadius: 4 + }} + _focus={{ + bg: '#1118240D', + borderRadius: 4 + }}> + + {item.label} + {currentIDE === item.value && } + + + ))} + + + + + + + } + menuList={[ + { + child: ( + <> + + {t('publish')} + + ), + onClick: () => handleOpenRelease(item) + }, + { + child: ( + <> + + {t('terminal')} + + ), + onClick: () => handleGoToTerminal(item), + menuItemStyle: { + borderBottomLeftRadius: '0px', + borderBottomRightRadius: '0px', + borderBottom: '1px solid #F0F1F6' + } + }, + { + child: ( + <> + + {t('update')} + + ), + onClick: () => router.push(`/devbox/create?name=${item.name}`) + }, + ...(item.status.value === 'Stopped' + ? [ + { + child: ( + <> + + {t('boot')} + + ), + onClick: () => handleStartDevbox(item) + } + ] + : []), + // maybe Error or other status,all can restart + ...(item.status.value !== 'Stopped' + ? [ + { + child: ( + <> + + {t('restart')} + + ), + onClick: () => handleRestartDevbox(item) + } + ] + : []), + ...(item.status.value === 'Running' + ? [ + { + child: ( + <> + + {t('shutdown')} + + ), + onClick: () => handlePauseDevbox(item) + } + ] + : []), + { + child: ( + <> + + {t('delete')} + + ), + menuItemStyle: { + _hover: { + color: 'red.600', + bg: 'rgba(17, 24, 36, 0.05)' + } + }, + onClick: () => setDelDevbox(item) + } + ]} + /> + + ) + } + ] + + return ( + + +
+ +
+ + {t('devbox_list')} + + + ( {devboxList.length} ) + + + +
+ + {!!delDevbox && ( + setDelDevbox(null)} + onSuccess={refetchDevboxList} + /> + )} + {!!onOpenRelease && !!currentDevboxListItem && ( + { + router.push(`/devbox/detail/${currentDevboxListItem?.name}`) + }} + onClose={() => { + setOnOpenRelease(false) + setCurrentDevboxListItem(null) + }} + devbox={currentDevboxListItem} + /> + )} +
+ ) +} + +export default DevboxList diff --git a/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/Empty.tsx b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/Empty.tsx new file mode 100644 index 00000000000..5aa415a7da4 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/Empty.tsx @@ -0,0 +1,35 @@ +import { useTranslations } from 'next-intl' +import { Button, Box } from '@chakra-ui/react' + +import { useRouter } from '@/i18n' +import MyIcon from '@/components/Icon' + +import styles from './empty.module.scss' + +const Empty = () => { + const router = useRouter() + const t = useTranslations() + + return ( + + + {t('devbox_empty')} + + + ) +} + +export default Empty diff --git a/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/empty.module.scss b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/empty.module.scss new file mode 100644 index 00000000000..2e1a78a0b3c --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/(home)/components/empty.module.scss @@ -0,0 +1,6 @@ +.empty { + width: 100vw; + height: 100vh; + background-color: #fff; + user-select: none; +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx b/frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx new file mode 100644 index 00000000000..11a9b0c16ea --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/(home)/page.tsx @@ -0,0 +1,35 @@ +'use client' + +import { useState } from 'react' +import { useQuery } from '@tanstack/react-query' + +import Empty from './components/Empty' +import { useLoading } from '@/hooks/useLoading' +import { useDevboxStore } from '@/stores/devbox' +import DevboxList from './components/DevboxList' + +const EmptyPage = () => { + const { Loading } = useLoading() + const { devboxList, setDevboxList } = useDevboxStore() + const [initialized, setInitialized] = useState(false) + const { refetch } = useQuery(['initDevboxData'], setDevboxList, { + refetchInterval: 3000, + onSettled() { + setInitialized(true) + } + }) + return ( + <> + {devboxList.length === 0 && initialized ? ( + + ) : ( + <> + + + )} + + + ) +} + +export default EmptyPage diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx new file mode 100644 index 00000000000..c7fa3c05f47 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Form.tsx @@ -0,0 +1,734 @@ +'use client' + +import { + Box, + Button, + Center, + Flex, + FormControl, + FormErrorMessage, + Grid, + IconButton, + Image, + Input, + Switch, + Text, + useTheme +} from '@chakra-ui/react' +import { throttle } from 'lodash' +import dynamic from 'next/dynamic' +import { customAlphabet } from 'nanoid' +import { useEffect, useState } from 'react' +import { useTranslations } from 'next-intl' +import { UseFormReturn, useFieldArray } from 'react-hook-form' +import { MySelect, MySlider, Tabs, useMessage } from '@sealos/ui' + +import { + INSTALL_ACCOUNT, + SEALOS_DOMAIN, + frameworkTypeList, + frameworkVersionMap, + languageTypeList, + languageVersionMap, + osTypeList, + osVersionMap, + getRuntimeVersionList +} from '@/stores/static' +import { useRouter } from '@/i18n' +import MyIcon from '@/components/Icon' +import PriceBox from '@/components/PriceBox' +import QuotaBox from '@/components/QuotaBox' +import { useDevboxStore } from '@/stores/devbox' +import { ProtocolList } from '@/constants/devbox' +import type { DevboxEditType } from '@/types/devbox' +import { getValueDefault, obj2Query } from '@/utils/tools' +import { CpuSlideMarkList, MemorySlideMarkList } from '@/constants/devbox' + +const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 12) + +export type CustomAccessModalParams = { + publicDomain: string + customDomain: string +} + +const CustomAccessModal = dynamic(() => import('@/components/modals/CustomAccessModal')) + +const Form = ({ + formHook, + pxVal, + isEdit +}: { + formHook: UseFormReturn + pxVal: number + isEdit: boolean +}) => { + const theme = useTheme() + const router = useRouter() + const t = useTranslations() + const { + control, + register, + setValue, + getValues, + formState: { errors } + } = formHook + const { + fields: networks, + append: appendNetworks, + remove: removeNetworks, + update: updateNetworks + } = useFieldArray({ + control, + name: 'networks' + }) + + const [customAccessModalData, setCustomAccessModalData] = useState() + const navList: { id: string; label: string; icon: string }[] = [ + { + id: 'baseInfo', + label: t('basic_configuration'), + icon: 'formInfo' + }, + { + id: 'network', + label: t('Network Configuration'), + icon: 'network' + } + ] + const { message: toast } = useMessage() + const [activeNav, setActiveNav] = useState(navList[0].id) + const { devboxList } = useDevboxStore() + + // listen scroll and set activeNav + useEffect(() => { + const scrollFn = throttle((e: Event) => { + if (!e.target) return + const doms = navList.map((item) => ({ + dom: document.getElementById(item.id), + id: item.id + })) + + const dom = e.target as HTMLDivElement + const scrollTop = dom.scrollTop + + for (let i = doms.length - 1; i >= 0; i--) { + const offsetTop = doms[i].dom?.offsetTop || 0 + if (scrollTop + 500 >= offsetTop) { + setActiveNav(doms[i].id) + break + } + } + }, 200) + document.getElementById('form-container')?.addEventListener('scroll', scrollFn) + return () => { + document.getElementById('form-container')?.removeEventListener('scroll', scrollFn) + } + // eslint-disable-next-line + }, []) + + if (!formHook) return null + + const Label = ({ + children, + w = 'auto', + ...props + }: { + children: string + w?: number | 'auto' + [key: string]: any + }) => ( + + {children} + + ) + + const boxStyles = { + border: theme.borders.base, + borderRadius: 'lg', + mb: 4, + bg: 'white' + } + + const headerStyles = { + py: 4, + pl: '42px', + borderTopRadius: 'lg', + fontSize: 'xl', + color: 'grayModern.900', + fontWeight: 'bold', + display: 'flex', + alignItems: 'center', + backgroundColor: 'grayModern.50' + } + + return ( + <> + + {/* left sidebar */} + + + router.replace( + `/devbox/create?${obj2Query({ + type: 'yaml' + })}` + ) + } + /> + + {navList.map((item) => ( + { + setActiveNav(item.id) + window.location.hash = item.id + }}> + + + + {item.label} + + + ))} + + + + + {INSTALL_ACCOUNT && ( + + + + )} + + {/* right content */} + + {/* base info */} + + + + {t('basic_configuration')} + + + {/* Devbox Name */} + + + + + /^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/.test( + value + ) || t('devbox_name_invalid') + } + })} + /> + + + {errors.name && errors.name.message} + + + + + {/* Runtime Type */} + + + + {/* Language */} + {languageTypeList.length !== 0 && {t('language')}} + + {languageTypeList && + languageTypeList?.map((item) => { + return ( +
{ + if (isEdit) return + setValue('runtimeType', item.id) + setValue( + 'runtimeVersion', + languageVersionMap[getValues('runtimeType')][0].id + ) + }}> + {item.id} + + {item.label} + +
+ ) + })} +
+ {/* framework */} + {frameworkTypeList.length !== 0 && {t('framework')}} + + {frameworkTypeList && + frameworkTypeList?.map((item) => { + return ( +
{ + if (isEdit) return + setValue('runtimeType', item.id) + setValue( + 'runtimeVersion', + frameworkVersionMap[getValues('runtimeType')][0].id + ) + }}> + {item.id} + + {item.label} + +
+ ) + })} +
+ {/* os */} + {osTypeList.length !== 0 && {t('os')}} + + {osTypeList && + osTypeList?.map((item) => { + return ( +
{ + if (isEdit) return + setValue('runtimeType', item.id) + setValue( + 'runtimeVersion', + osVersionMap[getValues('runtimeType')][0].id + ) + }}> + {item.id} + + {item.label} + +
+ ) + })} +
+
+
+ {/* Runtime Version */} + + + {isEdit ? ( + {getValues('runtimeVersion')} + ) : ( + { + if (isEdit) return + setValue('runtimeVersion', val) + }} + /> + )} + + {/* CPU */} + + + { + setValue('cpu', CpuSlideMarkList[e].value) + }} + max={CpuSlideMarkList.length - 1} + min={0} + step={1} + /> + + {t('core')} + + + {/* Memory */} + + + { + setValue('memory', MemorySlideMarkList[e].value) + }} + max={MemorySlideMarkList.length - 1} + min={0} + step={1} + /> + +
+
+ {/* network */} + + + + {t('Network Configuration')} + + + {networks.length === 0 && ( + + )} + {networks.map((network, i) => ( + + + + {t('Container Port')} + + + {i === networks.length - 1 && networks.length < 5 && ( + + + + )} + + + + {t('Open Public Access')} + + + { + const devboxName = getValues('name') + if (!devboxName) { + toast({ + title: t('Please enter the devbox name first'), + status: 'warning' + }) + return + } + updateNetworks(i, { + ...getValues('networks')[i], + networkName: network.networkName || `${devboxName}-${nanoid()}`, + protocol: network.protocol || 'HTTP', + openPublicDomain: e.target.checked, + publicDomain: network.publicDomain || nanoid() + }) + }} + /> + + + {network.openPublicDomain && ( + <> + + + + { + updateNetworks(i, { + ...getValues('networks')[i], + protocol: val + }) + }} + /> + + + {network.customDomain + ? network.customDomain + : `${network.publicDomain}.${SEALOS_DOMAIN}`} + + + setCustomAccessModalData({ + publicDomain: network.publicDomain, + customDomain: network.customDomain + }) + }> + {t('Custom Domain')} + + + + + + )} + {networks.length >= 1 && ( + + + } + onClick={() => removeNetworks(i)} + /> + + )} + + ))} + + + {!!customAccessModalData && ( + setCustomAccessModalData(undefined)} + onSuccess={(e) => { + const i = networks.findIndex( + (item) => item.publicDomain === customAccessModalData.publicDomain + ) + if (i === -1) return + updateNetworks(i, { + ...networks[i], + customDomain: e + }) + + setCustomAccessModalData(undefined) + }} + /> + )} +
+
+ + ) +} + +export default Form diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Header.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Header.tsx new file mode 100644 index 00000000000..8b3b97a83ef --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Header.tsx @@ -0,0 +1,56 @@ +import JSZip from 'jszip' +import dayjs from 'dayjs' +import React, { useCallback } from 'react' +import { useTranslations } from 'next-intl' +import { Box, Flex, Button } from '@chakra-ui/react' + +import { useRouter } from '@/i18n' +import MyIcon from '@/components/Icon' +import { downLoadBlob } from '@/utils/tools' +import { useGlobalStore } from '@/stores/global' +import type { YamlItemType } from '@/types/index' + +const Header = ({ + title, + yamlList, + applyCb, + applyBtnText +}: { + yamlList: YamlItemType[] + applyCb: () => void + title: string + applyBtnText: string +}) => { + const router = useRouter() + const { lastRoute } = useGlobalStore() + const t = useTranslations() + + const handleExportYaml = useCallback(async () => { + const zip = new JSZip() + yamlList.forEach((item) => { + zip.file(item.filename, item.value) + }) + const res = await zip.generateAsync({ type: 'blob' }) + downLoadBlob(res, 'application/zip', `yaml${dayjs().format('YYYYMMDDHHmmss')}.zip`) + }, [yamlList]) + + return ( + + router.replace(lastRoute)}> + + + {t(title)} + + + + + + + ) +} + +export default Header diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx new file mode 100644 index 00000000000..059147e1500 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/Yaml.tsx @@ -0,0 +1,122 @@ +import { useState } from 'react' +import { Tabs } from '@sealos/ui' +import { useTranslations } from 'next-intl' +import { useSearchParams } from 'next/navigation' +import { Box, Center, Flex, Grid, useTheme } from '@chakra-ui/react' + +import { useRouter } from '@/i18n' +import MyIcon from '@/components/Icon' +import { obj2Query } from '@/utils/tools' +import type { YamlItemType } from '@/types' +import { useCopyData } from '@/utils/tools' +import YamlCode from '@/components/YamlCode/index' + +import styles from './index.module.scss' + +const Yaml = ({ yamlList = [], pxVal }: { yamlList: YamlItemType[]; pxVal: number }) => { + const theme = useTheme() + const router = useRouter() + const t = useTranslations() + const { copyData } = useCopyData() + const searchParams = useSearchParams() + const [selectedIndex, setSelectedIndex] = useState(0) + + const devboxName = searchParams.get('name') as string + + return ( + + + + router.replace( + `/devbox/create?${obj2Query({ + type: 'form', + name: devboxName + })}` + ) + } + /> + + {yamlList.map((file, index) => ( + setSelectedIndex(index)}> + + {file.filename} + + ))} + + + {!!yamlList[selectedIndex] && ( + + + + {yamlList[selectedIndex].filename} + +
copyData(yamlList[selectedIndex].value)}> + +
+
+ + + +
+ )} +
+ ) +} + +export default Yaml diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/index.module.scss b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/index.module.scss new file mode 100644 index 00000000000..9000ac3b5d7 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/components/index.module.scss @@ -0,0 +1,49 @@ +.codeBox { + .code { + height: 100%; + div { + background-color: transparent !important; + padding: 0 5px 0 0 !important; + } + * { + word-wrap: break-all !important; + } + } +} + +.deleteIcon svg { + fill: #8f949a; + &:hover { + fill: #d92a2a; + } +} + +.formSecondTitle { + color: #333333; + font-size: 16px; + font-weight: bold; + margin-bottom: 10px; +} +.textEllipsis { + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} +.table { + border-radius: 2px; + overflow: hidden; + table-layout: fixed; + width: 100%; + + th { + border: 1px solid #eff0f1; + text-align: left; + padding: 8px; + user-select: all; + font-weight: 500; + } + + tr:nth-child(even) { + background-color: #fbfbfc; + } +} diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx new file mode 100644 index 00000000000..293d0552c6a --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/create/page.tsx @@ -0,0 +1,313 @@ +'use client' + +import { useRouter } from '@/i18n' +import dynamic from 'next/dynamic' +import debounce from 'lodash/debounce' +import { useMessage } from '@sealos/ui' +import { useForm } from 'react-hook-form' +import { Box, Flex } from '@chakra-ui/react' +import { useTranslations } from 'next-intl' +import { useQuery } from '@tanstack/react-query' +import { useSearchParams } from 'next/navigation' +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' + +import Form from './components/Form' +import Yaml from './components/Yaml' +import Header from './components/Header' +import type { YamlItemType } from '@/types' +import { useUserStore } from '@/stores/user' +import { patchYamlList } from '@/utils/tools' +import { useConfirm } from '@/hooks/useConfirm' +import { useLoading } from '@/hooks/useLoading' +import { useDevboxStore } from '@/stores/devbox' +import { useGlobalStore } from '@/stores/global' +import { createDevbox, updateDevbox } from '@/api/devbox' +import type { DevboxEditType, DevboxKindsType } from '@/types/devbox' +import { LanguageTypeEnum, defaultDevboxEditValue, editModeMap } from '@/constants/devbox' +import { json2Devbox, json2Ingress, json2Service } from '@/utils/json2Yaml' +import { + DEVBOX_AFFINITY_ENABLE, + SQUASH_ENABLE, + languageVersionMap, + runtimeNamespaceMap +} from '@/stores/static' + +const ErrorModal = dynamic(() => import('@/components/modals/ErrorModal')) + +const defaultEdit = { + ...defaultDevboxEditValue, + runtimeVersion: languageVersionMap[LanguageTypeEnum.go][0].id +} + +const formData2Yamls = (data: DevboxEditType) => [ + { + filename: 'service.yaml', + value: json2Service(data) + }, + { + filename: 'devbox.yaml', + value: json2Devbox(data, runtimeNamespaceMap, DEVBOX_AFFINITY_ENABLE, SQUASH_ENABLE) + }, + ...(data.networks.find((item) => item.openPublicDomain) + ? [ + { + filename: 'ingress.yaml', + value: json2Ingress(data) + } + ] + : []) +] + +const DevboxCreatePage = () => { + const router = useRouter() + const t = useTranslations() + + const searchParams = useSearchParams() + const { message: toast } = useMessage() + const { checkQuotaAllow } = useUserStore() + const { setDevboxDetail, devboxList } = useDevboxStore() + + const crOldYamls = useRef([]) + const formOldYamls = useRef([]) + const oldDevboxEditData = useRef() + const { Loading, setIsLoading } = useLoading() + const [errorMessage, setErrorMessage] = useState('') + const [forceUpdate, setForceUpdate] = useState(false) + const [yamlList, setYamlList] = useState([]) + + const tabType = searchParams.get('type') || 'form' + const devboxName = searchParams.get('name') || '' + + // NOTE: need to explain why this is needed + // fix a bug: searchParams will disappear when go into this page + const [captureDevboxName, setCaptureDevboxName] = useState('') + + useEffect(() => { + const name = searchParams.get('name') + if (name) { + setCaptureDevboxName(name) + router.replace(`/devbox/create?name=${captureDevboxName}`, undefined) + } + }, [searchParams, router, captureDevboxName]) + + // eslint-disable-next-line react-hooks/exhaustive-deps + const isEdit = useMemo(() => !!devboxName, []) + + const { title, applyBtnText, applyMessage, applySuccess, applyError } = editModeMap(isEdit) + + const { openConfirm, ConfirmChild } = useConfirm({ + content: applyMessage + }) + + // compute container width + const { screenWidth, lastRoute } = useGlobalStore() + + const pxVal = useMemo(() => { + const val = Math.floor((screenWidth - 1050) / 2) + if (val < 20) { + return 20 + } + return val + }, [screenWidth]) + + const generateYamlList = (data: DevboxEditType) => { + return [ + { + filename: 'devbox.yaml', + value: json2Devbox(data, runtimeNamespaceMap, DEVBOX_AFFINITY_ENABLE, SQUASH_ENABLE) + }, + { + filename: 'service.yaml', + value: json2Service(data) + }, + { + filename: 'ingress.yaml', + value: json2Ingress(data) + } + ] + } + + const formHook = useForm({ + defaultValues: defaultEdit + }) + + // eslint-disable-next-line react-hooks/exhaustive-deps + const formOnchangeDebounce = useCallback( + debounce((data: DevboxEditType) => { + try { + setYamlList(generateYamlList(data)) + } catch (error) { + console.log(error) + } + }, 200), + [] + ) + + // watch form change, compute new yaml + formHook.watch((data) => { + data && formOnchangeDebounce(data as DevboxEditType) + setForceUpdate(!forceUpdate) + }) + + useQuery( + ['initDevboxCreateData'], + () => { + if (!devboxName) { + setYamlList([ + { + filename: 'devbox.yaml', + value: json2Devbox( + defaultEdit, + runtimeNamespaceMap, + DEVBOX_AFFINITY_ENABLE, + SQUASH_ENABLE + ) + }, + { + filename: 'service.yaml', + value: json2Service(defaultEdit) + }, + { + filename: 'ingress.yaml', + value: json2Ingress(defaultEdit) + } + ]) + return null + } + setIsLoading(true) + return setDevboxDetail(devboxName) + }, + { + onSuccess(res) { + if (!res) return + oldDevboxEditData.current = res + formOldYamls.current = formData2Yamls(res) + crOldYamls.current = generateYamlList(res) + formHook.reset(res) + }, + onError(err) { + toast({ + title: String(err), + status: 'error' + }) + }, + onSettled() { + setIsLoading(false) + } + } + ) + + const submitSuccess = async (formData: DevboxEditType) => { + setIsLoading(true) + + try { + // quote check + const quoteCheckRes = checkQuotaAllow( + { ...formData, nodeports: devboxList.length + 1 } as DevboxEditType & { nodeports: number }, + { ...oldDevboxEditData.current, nodeports: devboxList.length } as DevboxEditType & { + nodeports: number + } + ) + if (quoteCheckRes) { + setIsLoading(false) + return toast({ + status: 'warning', + title: t(quoteCheckRes), + duration: 5000, + isClosable: true + }) + } + const parsedNewYamlList = yamlList.map((item) => item.value) + const parsedOldYamlList = formOldYamls.current.map((item) => item.value) + + const areYamlListsEqual = + new Set(parsedNewYamlList).size === new Set(parsedOldYamlList).size && + [...new Set(parsedNewYamlList)].every((item) => new Set(parsedOldYamlList).has(item)) + if (areYamlListsEqual) { + setIsLoading(false) + return toast({ + status: 'info', + title: t('No changes detected'), + duration: 5000, + isClosable: true + }) + } + // create or update + if (isEdit) { + const patch = patchYamlList({ + parsedOldYamlList: parsedOldYamlList, + parsedNewYamlList: parsedNewYamlList, + originalYamlList: crOldYamls.current + }) + + await updateDevbox({ + patch, + devboxName: formData.name + }) + } else { + await createDevbox({ devboxForm: formData, runtimeNamespaceMap }) + } + toast({ + title: t(applySuccess), + status: 'success' + }) + router.push(lastRoute) + } catch (error) { + console.error(error) + setErrorMessage(JSON.stringify(error)) + } + setIsLoading(false) + } + + const submitError = useCallback(() => { + // deep search message + const deepSearch = (obj: any): string => { + if (!obj || typeof obj !== 'object') return t('submit_form_error') + if (!!obj.message) { + return obj.message + } + return deepSearch(Object.values(obj)[0]) + } + toast({ + title: deepSearch(formHook.formState.errors), + status: 'error', + position: 'top', + duration: 3000, + isClosable: true + }) + }, [formHook.formState.errors, toast, t]) + + return ( + <> + +
+ formHook.handleSubmit((data) => openConfirm(() => submitSuccess(data))(), submitError)() + } + /> + + {tabType === 'form' ? ( +
+ ) : ( + + )} + + + + + {!!errorMessage && ( + setErrorMessage('')} /> + )} + + ) +} + +export default DevboxCreatePage diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx new file mode 100644 index 00000000000..8669dbd3b2e --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/BasicInfo.tsx @@ -0,0 +1,271 @@ +import { useMessage } from '@sealos/ui' +import { useTranslations } from 'next-intl' +import React, { useCallback, useState } from 'react' +import { Box, Text, Flex, Image, Spinner, Tooltip } from '@chakra-ui/react' + +import MyIcon from '@/components/Icon' +import { useDevboxStore } from '@/stores/devbox' +import { DevboxDetailType } from '@/types/devbox' +import { getRuntimeVersionItem, NAMESPACE, REGISTRY_ADDR, SEALOS_DOMAIN } from '@/stores/static' + +const BasicInfo = () => { + const { devboxDetail } = useDevboxStore() + const [loading, setLoading] = useState(false) + const t = useTranslations() + const { message: toast } = useMessage() + + const handleCopySSHCommand = useCallback(() => { + const sshCommand = `ssh -i yourPrivateKeyPath ${devboxDetail?.sshConfig?.sshUser}@${SEALOS_DOMAIN} -p ${devboxDetail.sshPort}` + navigator.clipboard.writeText(sshCommand).then(() => { + toast({ + title: t('copy_success'), + status: 'success', + duration: 2000, + isClosable: true + }) + }) + }, [devboxDetail, toast, t]) + + const handleDownloadConfig = useCallback( + async (config: DevboxDetailType['sshConfig']) => { + setLoading(true) + + const privateKey = config?.sshPrivateKey as string + + const blob = new Blob([privateKey], { type: 'application/octet-stream' }) + const url = window.URL.createObjectURL(blob) + const a = document.createElement('a') + a.style.display = 'none' + a.href = url + a.download = devboxDetail.name + document.body.appendChild(a) + a.click() + window.URL.revokeObjectURL(url) + document.body.removeChild(a) + + setLoading(false) + }, + [devboxDetail] + ) + + return ( + + {/* basic info */} + + + + {t('basic_info')} + + + + + + {t('name')} + + + {devboxDetail?.name} + {devboxDetail?.runtimeType} + + + + + {t('image_info')} + + + {`${REGISTRY_ADDR}/${NAMESPACE}/${devboxDetail?.name}`} + + + + + {t('create_time')} + + + {devboxDetail?.createTime} + + + + + {t('start_runtime')} + + + + {getRuntimeVersionItem(devboxDetail?.runtimeType, devboxDetail?.runtimeVersion)} + + + + + + {t('start_time')} + + + {devboxDetail?.upTime} + + + + + Limit CPU + + + {devboxDetail?.cpu / 1000} Core + + + + + Limit Memory + + + {devboxDetail?.memory / 1024} G + + + + {/* ssh config */} + + + + {t('ssh_config')} + + + + + + {t('ssh_connect_info')} + + + + + {`ssh -i yourPrivateKeyPath ${devboxDetail?.sshConfig?.sshUser}@${SEALOS_DOMAIN} -p ${devboxDetail.sshPort}`} + + + + + + + {t('private_key')} + + + {loading ? ( + + ) : ( + + + handleDownloadConfig(devboxDetail?.sshConfig)} + /> + + + )} + + + + {/* event */} + + + + {t('event')} + + + + + + {t('recent_error')} + + + {devboxDetail?.lastTerminatedState?.reason ? ( + {devboxDetail?.lastTerminatedState?.reason} + ) : ( + {t('none')} + )} + + + { + console.log('click') + }} + /> + + + + + + + ) +} + +export default BasicInfo diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx new file mode 100644 index 00000000000..e9b69b28262 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Header.tsx @@ -0,0 +1,392 @@ +import { useMessage } from '@sealos/ui' +import { useTranslations } from 'next-intl' +import { Dispatch, useCallback, useState } from 'react' +import { + Flex, + Button, + Box, + Menu, + MenuButton, + IconButton, + MenuList, + MenuItem +} from '@chakra-ui/react' + +import { useRouter } from '@/i18n' +import MyIcon from '@/components/Icon' +import { useDevboxStore } from '@/stores/devbox' +import { IDEType, useGlobalStore } from '@/stores/global' +import { DevboxDetailType } from '@/types/devbox' +import DelModal from '@/components/modals/DelModal' +import DevboxStatusTag from '@/components/DevboxStatusTag' +import { NAMESPACE, SEALOS_DOMAIN } from '@/stores/static' +import { + getSSHConnectionInfo, + getSSHRuntimeInfo, + pauseDevbox, + restartDevbox, + startDevbox +} from '@/api/devbox' + +const Header = ({ + refetchDevboxDetail, + setShowSlider, + isLargeScreen = true +}: { + refetchDevboxDetail: () => void + setShowSlider: Dispatch + isLargeScreen: boolean +}) => { + const router = useRouter() + const t = useTranslations() + const { message: toast } = useMessage() + const { setLoading, setCurrentIDE, currentIDE } = useGlobalStore() + const { devboxDetail } = useDevboxStore() + const [delDevbox, setDelDevbox] = useState(null) + + const getCurrentIDELabelAndIcon = useCallback( + ( + currentIDE: IDEType + ): { + label: string + icon: IDEType + } => { + switch (currentIDE) { + case 'vscode': + return { + label: 'VSCode', + icon: 'vscode' + } + case 'cursor': + return { + label: 'Cursor', + icon: 'cursor' + } + case 'vscodeInsider': + return { + label: 'VSCode Insider', + icon: 'vscodeInsider' + } + default: + return { + label: 'VSCode', + icon: 'vscode' + } + } + }, + [] + ) + const handleGotoIDE = useCallback( + async (devbox: DevboxDetailType, currentIDE: string = 'vscode') => { + try { + const { base64PrivateKey, userName } = await getSSHConnectionInfo({ + devboxName: devbox.name, + runtimeName: devbox.runtimeVersion + }) + const { workingDir } = await getSSHRuntimeInfo(devbox.runtimeVersion) + + let editorUri = '' + switch (currentIDE) { + case 'cursor': + editorUri = `cursor://` + break + case 'vscodeInsider': + editorUri = `vscode-insiders://` + break + case 'vscode': + editorUri = `vscode://` + break + default: + editorUri = `vscode://` + } + + const fullUri = `${editorUri}mlhiter.devbox-sealos?sshDomain=${encodeURIComponent( + `${userName}@${SEALOS_DOMAIN}` + )}&sshPort=${encodeURIComponent( + devbox.sshPort as number + )}&base64PrivateKey=${encodeURIComponent( + base64PrivateKey + )}&sshHostLabel=${encodeURIComponent( + `${SEALOS_DOMAIN}/${NAMESPACE}/${devbox.name}` + )}&workingDir=${encodeURIComponent(workingDir)}` + + window.location.href = fullUri + } catch (error: any) { + console.error(error, '==') + } + }, + [] + ) + + const handlePauseDevbox = useCallback( + async (devbox: DevboxDetailType) => { + try { + setLoading(true) + await pauseDevbox({ devboxName: devbox.name }) + toast({ + title: t('pause_success'), + status: 'success' + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('pause_error'), + status: 'error' + }) + console.error(error) + } + refetchDevboxDetail() + setLoading(false) + }, + [refetchDevboxDetail, setLoading, t, toast] + ) + const handleRestartDevbox = useCallback( + async (devbox: DevboxDetailType) => { + try { + setLoading(true) + await restartDevbox({ devboxName: devbox.name }) + toast({ + title: t('restart_success'), + status: 'success' + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('restart_error'), + status: 'error' + }) + console.error(error, '==') + } + refetchDevboxDetail() + setLoading(false) + }, + [setLoading, t, toast, refetchDevboxDetail] + ) + const handleStartDevbox = useCallback( + async (devbox: DevboxDetailType) => { + try { + setLoading(true) + await startDevbox({ devboxName: devbox.name }) + toast({ + title: t('start_success'), + status: 'success' + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('start_error'), + status: 'error' + }) + console.error(error, '==') + } + refetchDevboxDetail() + setLoading(false) + }, + [setLoading, t, toast, refetchDevboxDetail] + ) + return ( + + + router.push('/')} + cursor={'pointer'} + mt={1} + ml={1} + /> + + {devboxDetail.name} + + + + {!isLargeScreen && ( + + + + )} + + + + + + } + _before={{ + content: '""', + position: 'absolute', + left: 0, + top: '50%', + transform: 'translateY(-50%)', + width: '1px', + height: '20px', + backgroundColor: 'grayModern.250' + }} + /> + + {[ + { value: 'vscode' as IDEType, label: 'VSCode' }, + { value: 'cursor' as IDEType, label: 'Cursor' }, + { value: 'vscodeInsider' as IDEType, label: 'VSCode Insider' } + ].map((item) => ( + setCurrentIDE(item.value)} + icon={} + _hover={{ + bg: '#1118240D', + borderRadius: 4 + }} + _focus={{ + bg: '#1118240D', + borderRadius: 4 + }}> + + {item.label} + {currentIDE === item.value && } + + + ))} + + + {devboxDetail.status.value === 'Running' && ( + + )} + {devboxDetail.status.value === 'Stopped' && ( + + )} + + + + + {delDevbox && ( + setDelDevbox(null)} + onSuccess={() => { + setDelDevbox(null) + refetchDevboxDetail() + router.push('/') + }} + /> + )} + + ) +} + +export default Header diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx new file mode 100644 index 00000000000..44fd348bd6f --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/MainBody.tsx @@ -0,0 +1,157 @@ +import dayjs from 'dayjs' +import dynamic from 'next/dynamic' +import { useTranslations } from 'next-intl' +import { Box, Button, Flex, Text, Tooltip, useDisclosure } from '@chakra-ui/react' + +import MyIcon from '@/components/Icon' +import MyTable from '@/components/MyTable' +import { useCopyData } from '@/utils/tools' +import { NetworkType } from '@/types/devbox' +import { useDevboxStore } from '@/stores/devbox' +import PodLineChart from '@/components/PodLineChart' +import { NAMESPACE, SEALOS_DOMAIN } from '@/stores/static' + +const MonitorModal = dynamic(() => import('@/components/modals/MonitorModal')) + +const MainBody = () => { + const t = useTranslations() + const { copyData } = useCopyData() + const { devboxDetail } = useDevboxStore() + const { isOpen, onOpen, onClose } = useDisclosure() + + const networkColumn: { + title: string + dataIndex?: keyof NetworkType + key: string + render?: (item: NetworkType) => JSX.Element + }[] = [ + { + title: t('internal_address'), + key: 'internalAddress', + render: (item: NetworkType) => { + return ( + + + copyData(`http://${devboxDetail?.name}.${NAMESPACE}.svc.cluster.local:${item.port}`) + }>{`http://${devboxDetail?.name}.${NAMESPACE}.svc.cluster.local:${item.port}`} + + ) + } + }, + { + title: t('external_address'), + key: 'externalAddress', + render: (item: NetworkType) => { + if (item.openPublicDomain) { + const address = item.customDomain || `${item.publicDomain}.${SEALOS_DOMAIN}` + return ( + + window.open(`https://${address}`, '_blank')}> + https://{address} + + + ) + } + return - + } + } + ] + return ( + + {/* monitor */} + + + + + {t('monitor')} + + + ({t('update Time')}  + {dayjs().format('HH:mm')}) + + + + + + {t('cpu')} {devboxDetail?.usedCpu?.yData[devboxDetail?.usedCpu?.yData?.length - 1]}% + + + + + + + + + + + {t('memory')}{' '} + {devboxDetail?.usedMemory?.yData[devboxDetail?.usedMemory?.yData?.length - 1]}% + + + + + + + + + + {/* network */} + + + + + {t('network')} ( {devboxDetail?.networks?.length} ) + + + {devboxDetail?.networks?.length > 0 ? ( + + ) : ( + + {t('no_network')} + + )} + + + + ) +} + +export default MainBody diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx new file mode 100644 index 00000000000..62914a83de1 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/components/Version.tsx @@ -0,0 +1,273 @@ +import { useTranslations } from 'next-intl' +import { useCallback, useState } from 'react' +import { useQuery } from '@tanstack/react-query' +import { sealosApp } from 'sealos-desktop-sdk/app' +import { SealosMenu, useMessage } from '@sealos/ui' +import { Box, Button, Flex, MenuButton, Text, useDisclosure } from '@chakra-ui/react' + +import MyIcon from '@/components/Icon' +import MyTable from '@/components/MyTable' +import { useLoading } from '@/hooks/useLoading' +import { useDevboxStore } from '@/stores/devbox' +import DevboxStatusTag from '@/components/DevboxStatusTag' +import { DevboxVersionListItemType } from '@/types/devbox' +import ReleaseModal from '@/components/modals/releaseModal' +import { delDevboxVersionByName, getSSHRuntimeInfo } from '@/api/devbox' +import EditVersionDesModal from '@/components/modals/EditVersionDesModal' +import { NAMESPACE, REGISTRY_ADDR, SEALOS_DOMAIN } from '@/stores/static' +import { DevboxReleaseStatusEnum } from '@/constants/devbox' + +const Version = () => { + const t = useTranslations() + const { devboxDetail: devbox } = useDevboxStore() + const { message: toast } = useMessage() + const { Loading, setIsLoading } = useLoading() + const [initialized, setInitialized] = useState(false) + const [onOpenRelease, setOnOpenRelease] = useState(false) + const { devboxVersionList, setDevboxVersionList } = useDevboxStore() + const { isOpen: isOpenEdit, onOpen: onOpenEdit, onClose: onCloseEdit } = useDisclosure() + const [currentVersion, setCurrentVersion] = useState(null) + + const { refetch } = useQuery(['initDevboxVersionList'], () => setDevboxVersionList(devbox.name), { + refetchInterval: 3000, + onSettled() { + setInitialized(true) + } + }) + + const handleDeploy = useCallback( + async (version: DevboxVersionListItemType) => { + const { releaseCommand, releaseArgs } = await getSSHRuntimeInfo(devbox.runtimeVersion) + const { cpu, memory, networks, name } = devbox + const newNetworks = networks.map((network) => { + return { + port: network.port, + protocol: network.protocol, + openPublicDomain: network.openPublicDomain, + domain: SEALOS_DOMAIN + } + }) + + const transformData = { + appName: `${name}-release`, + cpu: cpu, + memory: memory, + imageName: `${REGISTRY_ADDR}/${NAMESPACE}/${devbox.name}:${version.tag}`, + networks: + newNetworks.length > 0 + ? newNetworks + : [ + { + port: 80, + protocol: 'http', + openPublicDomain: false, + domain: SEALOS_DOMAIN + } + ], + runCMD: releaseCommand, + cmdParam: releaseArgs + } + console.log('transformData', transformData) + + const formData = encodeURIComponent(JSON.stringify(transformData)) + + sealosApp.runEvents('openDesktopApp', { + appKey: 'system-applaunchpad', + pathname: '/app/edit', + query: { formData }, + messageData: { + type: 'InternalAppCall', + formData: formData + } + }) + }, + [devbox] + ) + + const handleDelDevboxVersion = useCallback( + async (versionName: string) => { + try { + setIsLoading(true) + await delDevboxVersionByName(versionName) + toast({ + title: t('delete_successful'), + status: 'success' + }) + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('delete_failed'), + status: 'error' + }) + console.error(error) + } + setIsLoading(false) + }, + [setIsLoading, toast, t] + ) + const columns: { + title: string + dataIndex?: keyof DevboxVersionListItemType + key: string + render?: (item: DevboxVersionListItemType) => JSX.Element + }[] = [ + { + title: t('version_number'), + key: 'tag', + render: (item: DevboxVersionListItemType) => ( + + {item.tag} + + ) + }, + { + title: t('status'), + key: 'status', + render: (item: DevboxVersionListItemType) => ( + + ) + }, + { + title: t('create_time'), + dataIndex: 'createTime', + key: 'createTime', + render: (item: DevboxVersionListItemType) => { + return {item.createTime} + } + }, + { + title: t('version_description'), + key: 'description', + render: (item: DevboxVersionListItemType) => ( + + + {item.description} + + + ) + }, + { + title: t('control'), + key: 'control', + render: (item: DevboxVersionListItemType) => ( + + + + + + } + menuList={[ + { + child: ( + <> + + {t('edit')} + + ), + onClick: () => { + setCurrentVersion(item) + onOpenEdit() + } + }, + { + child: ( + <> + + {t('delete')} + + ), + menuItemStyle: { + _hover: { + color: 'red.600', + bg: 'rgba(17, 24, 36, 0.05)' + } + }, + onClick: () => handleDelDevboxVersion(item.name) + } + ]} + /> + + ) + } + ] + return ( + + + + + + {t('version_history')} + + + + + + {devboxVersionList.length === 0 && initialized ? ( + + + + {t('no_versions')} + + + ) : ( + + )} + {!!currentVersion && ( + + )} + {!!onOpenRelease && ( + { + setOnOpenRelease(false) + }} + devbox={{ ...devbox, sshPort: devbox.sshPort || 0 }} + /> + )} + + ) +} + +export default Version diff --git a/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx new file mode 100644 index 00000000000..a9f6a0f7578 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/devbox/detail/[name]/page.tsx @@ -0,0 +1,108 @@ +'use client' + +import { useMemo, useState } from 'react' +import { useQuery } from '@tanstack/react-query' +import { Box, Flex } from '@chakra-ui/react' + +import Header from './components/Header' +import Version from './components/Version' +import MainBody from './components/MainBody' +import BasicInfo from './components/BasicInfo' +import { useLoading } from '@/hooks/useLoading' +import { useDevboxStore } from '@/stores/devbox' +import { useGlobalStore } from '@/stores/global' + +const DevboxDetailPage = ({ params }: { params: { name: string } }) => { + const devboxName = params.name + const { Loading } = useLoading() + const [initialized, setInitialized] = useState(false) + const { devboxDetail, setDevboxDetail, loadDetailMonitorData } = useDevboxStore() + const { screenWidth } = useGlobalStore() + const [showSlider, setShowSlider] = useState(false) + const isLargeScreen = useMemo(() => screenWidth > 1280, [screenWidth]) + + const { refetch } = useQuery(['initDevboxDetail'], () => setDevboxDetail(devboxName), { + refetchOnMount: true, + refetchInterval: 1 * 60 * 1000, + onSettled() { + setInitialized(true) + }, + onSuccess: (data) => { + if (data) { + loadDetailMonitorData(data.name) + } + } + }) + + useQuery( + ['loadDetailMonitorData', devboxName, devboxDetail?.isPause], + () => { + if (devboxDetail?.isPause) return null + return loadDetailMonitorData(devboxName) + }, + { + refetchOnMount: true, + refetchInterval: 2 * 60 * 1000 + } + ) + + return ( + + + {devboxDetail !== null && initialized && ( + <> + +
+ + + + + + + + + + + + + + + {/* mask */} + {!isLargeScreen && showSlider && ( + setShowSlider(false)} + /> + )} + + )} + + ) +} + +export default DevboxDetailPage diff --git a/frontend/providers/devbox/app/[lang]/(platform)/layout.tsx b/frontend/providers/devbox/app/[lang]/(platform)/layout.tsx new file mode 100644 index 00000000000..305f9f1aa5c --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/(platform)/layout.tsx @@ -0,0 +1,134 @@ +'use client' + +import throttle from 'lodash/throttle' +import { useEffect, useState } from 'react' +import { usePathname, useRouter } from '@/i18n' +import { EVENT_NAME } from 'sealos-desktop-sdk' +import { createSealosApp, sealosApp } from 'sealos-desktop-sdk/app' + +import { + getEnv, + getGlobalNamespace, + getRuntime, + getUserPrice, + SEALOS_DOMAIN +} from '@/stores/static' +import { useGlobalStore } from '@/stores/global' +import { useLoading } from '@/hooks/useLoading' +import { useConfirm } from '@/hooks/useConfirm' +import { getLangStore, setLangStore } from '@/utils/cookie' +import QueryProvider from '@/components/providers/MyQueryProvider' +import ChakraProvider from '@/components/providers/MyChakraProvider' +import RouteHandlerProvider from '@/components/providers/MyRouteHandlerProvider' + +export default function PlatformLayout({ children }: { children: React.ReactNode }) { + const router = useRouter() + const pathname = usePathname() + const { Loading } = useLoading() + const [refresh, setRefresh] = useState(false) + const { setScreenWidth, loading, setLastRoute } = useGlobalStore() + const { openConfirm, ConfirmChild } = useConfirm({ + title: 'jump_prompt', + content: 'not_allow_standalone_use' + }) + + // init session + useEffect(() => { + const response = createSealosApp() + ;(async () => { + try { + const newSession = JSON.stringify(await sealosApp.getSession()) + const oldSession = localStorage.getItem('session') + if (newSession && newSession !== oldSession) { + localStorage.setItem('session', newSession) + window.location.reload() + } + console.log('devbox: app init success') + } catch (err) { + console.log('devbox: app is not running in desktop') + if (!process.env.NEXT_PUBLIC_MOCK_USER) { + localStorage.removeItem('session') + openConfirm(() => { + window.open(`https://${SEALOS_DOMAIN}`, '_self') + })() + } + } + })() + return response + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + getUserPrice() + getRuntime() + getEnv() + getGlobalNamespace() + const changeI18n = async (data: any) => { + const lastLang = getLangStore() + const newLang = data.currentLanguage + if (lastLang !== newLang) { + router.push(pathname, { locale: newLang }) + setLangStore(newLang) + setRefresh((state) => !state) + } + } + + ;(async () => { + try { + const lang = await sealosApp.getLanguage() + changeI18n({ + currentLanguage: lang.lng + }) + } catch (error) { + changeI18n({ + currentLanguage: 'en' + }) + } + })() + + return sealosApp?.addAppEventListen(EVENT_NAME.CHANGE_I18N, changeI18n) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + // add resize event + useEffect(() => { + const resize = throttle((e: Event) => { + const documentWidth = document.documentElement.clientWidth || document.body.clientWidth + setScreenWidth(documentWidth) + }, 200) + window.addEventListener('resize', resize) + const documentWidth = document.documentElement.clientWidth || document.body.clientWidth + setScreenWidth(documentWidth) + + return () => { + window.removeEventListener('resize', resize) + } + }, [setScreenWidth]) + + // record route + useEffect(() => { + return () => { + setLastRoute(pathname) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pathname]) + + useEffect(() => { + const lang = getLangStore() || 'zh' + router.push(pathname, { locale: lang }) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [refresh, pathname]) + + return ( + + + + + + {children} + + + + ) +} diff --git a/frontend/providers/devbox/app/[lang]/globals.css b/frontend/providers/devbox/app/[lang]/globals.css new file mode 100644 index 00000000000..2e0812804a1 --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/globals.css @@ -0,0 +1,149 @@ +body, +h1, +h2, +h3, +h4, +hr, +p, +blockquote, +dl, +dt, +dd, +ul, +ol, +li, +pre, +form, +fieldset, +legend, +button, +input, +textarea, +th, +td { + margin: 0; +} +body, +button, +input, +select, +textarea { + font: 12px/1.5tahoma, arial, \5b8b\4f53; +} +// h1, h2, h3, h4, h5, h6{ font-size:100%; } +address, +cite, +dfn, +em, +var { + font-style: normal; +} +body { + background-color: #f4f4f7 !important; +} +small { + font-size: 12px; +} +ul, +ol { + list-style: none; + padding: 0; +} +a { + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +sup { + vertical-align: text-top; +} +sub { + vertical-align: text-bottom; +} +legend { + color: #000; +} +fieldset, +img { + border: 0; +} +button, +input, +select, +textarea { + font-size: 100%; +} +table { + border-collapse: collapse; + border-spacing: 0; +} + +* { + box-sizing: border-box; +} + +.icon { + width: 1em; + height: 1em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +} + +input::placeholder, +textarea::placeholder { + color: var(--chakra-colors-blackAlpha-400); +} + +#__next { + height: 100%; +} + +::-webkit-scrollbar, +::-webkit-scrollbar { + width: 8px; + height: 8px; + border-radius: 8px; +} +::-webkit-scrollbar-track, +::-webkit-scrollbar-track { + background: transparent !important; + border-radius: 2px; +} +::-webkit-scrollbar-thumb, +::-webkit-scrollbar-thumb { + background: rgba(189, 193, 197, 1) !important; + border-radius: 2px; +} +::-webkit-scrollbar-thumb:hover, +::-webkit-scrollbar-thumb:hover { + background: rgba(189, 193, 197, 1) !important; +} + +div { + &::-webkit-scrollbar-thumb, + &::-webkit-scrollbar-thumb { + background: transparent !important; + border-radius: 2px; + transition: 1s; + } + &:hover { + &::-webkit-scrollbar-thumb, + &::-webkit-scrollbar-thumb { + background: rgba(189, 193, 197, 0.5) !important; + } + &::-webkit-scrollbar-thumb:hover, + &::-webkit-scrollbar-thumb:hover { + background: rgba(189, 193, 197, 1) !important; + } + } +} + +.hover-button { + display: none; +} + +.hover-container:hover .hover-button { + display: block; +} diff --git a/frontend/providers/devbox/app/[lang]/layout.tsx b/frontend/providers/devbox/app/[lang]/layout.tsx new file mode 100644 index 00000000000..90e01a049bd --- /dev/null +++ b/frontend/providers/devbox/app/[lang]/layout.tsx @@ -0,0 +1,40 @@ +import { Inter } from 'next/font/google' +import type { Metadata, Viewport } from 'next' + +import IntlProvider from '@/components/providers/MyIntlProvider' + +import './globals.css' + +const inter = Inter({ subsets: ['latin'] }) + +export const metadata: Metadata = { + title: 'Sealos Devbox', + description: 'Generated a development and production environment for you', + icons: [ + { + url: '/logo.svg', + href: '/logo.svg' + } + ] +} + +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1 +} + +export default function RootLayout({ + children, + params: { lang } +}: Readonly<{ + children: React.ReactNode + params: { lang: string } +}>) { + return ( + + + {children} + + + ) +} diff --git a/frontend/providers/devbox/app/api/createDevbox/route.ts b/frontend/providers/devbox/app/api/createDevbox/route.ts new file mode 100644 index 00000000000..6a52b87a3a5 --- /dev/null +++ b/frontend/providers/devbox/app/api/createDevbox/route.ts @@ -0,0 +1,45 @@ +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' +import { DevboxEditType, runtimeNamespaceMapType } from '@/types/devbox' +import { json2Devbox, json2Ingress, json2Service } from '@/utils/json2Yaml' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const { devboxForm, runtimeNamespaceMap } = (await req.json()) as { + devboxForm: DevboxEditType + runtimeNamespaceMap: runtimeNamespaceMapType + } + + const headerList = req.headers + + const { applyYamlList } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const { SEALOS_DOMAIN, INGRESS_SECRET, DEVBOX_AFFINITY_ENABLE, SQUASH_ENABLE } = process.env + const devbox = json2Devbox( + devboxForm, + runtimeNamespaceMap, + DEVBOX_AFFINITY_ENABLE, + SQUASH_ENABLE + ) + const service = json2Service(devboxForm) + const ingress = json2Ingress(devboxForm, SEALOS_DOMAIN as string, INGRESS_SECRET as string) + + await applyYamlList([devbox, service, ingress], 'create') + + return jsonRes({ + data: 'success create devbox' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/delDevbox/route.ts b/frontend/providers/devbox/app/api/delDevbox/route.ts new file mode 100644 index 00000000000..445a822f7f3 --- /dev/null +++ b/frontend/providers/devbox/app/api/delDevbox/route.ts @@ -0,0 +1,66 @@ +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function DELETE(req: NextRequest) { + try { + const { searchParams } = req.nextUrl + const devboxName = searchParams.get('devboxName') as string + const networks = JSON.parse(searchParams.get('networks') as string) as string[] + const headerList = req.headers + + const { k8sCustomObjects, k8sCore, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + await k8sCustomObjects.deleteNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes', + devboxName + ) + + // delete service and ingress at the same time + if (networks.length > 0) { + await k8sCore.deleteNamespacedService(devboxName, namespace) + networks.forEach(async (networkName: string) => { + await k8sCustomObjects.deleteNamespacedCustomObject( + 'networking.k8s.io', + 'v1', + namespace, + 'ingresses', + networkName + ) + // delete issuer and certificate at the same time + await k8sCustomObjects.deleteNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'issuers', + networkName + ) + await k8sCustomObjects.deleteNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'certificates', + networkName + ) + }) + } + + return jsonRes({ + data: 'success delete devbox' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/delDevboxVersionByName/route.ts b/frontend/providers/devbox/app/api/delDevboxVersionByName/route.ts new file mode 100644 index 00000000000..836d5a12af8 --- /dev/null +++ b/frontend/providers/devbox/app/api/delDevboxVersionByName/route.ts @@ -0,0 +1,37 @@ +import { NextRequest } from 'next/server' + +import { ApiResp } from '@/services/kubernet' +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function DELETE(req: NextRequest) { + try { + const { searchParams } = req.nextUrl + const versionName = searchParams.get('versionName') as string + const headerList = req.headers + + const { k8sCustomObjects, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + await k8sCustomObjects.deleteNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxreleases', + versionName + ) + + return jsonRes({ + data: 'success delete devbox version' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/editDevboxVersion/route.ts b/frontend/providers/devbox/app/api/editDevboxVersion/route.ts new file mode 100644 index 00000000000..f591fe56633 --- /dev/null +++ b/frontend/providers/devbox/app/api/editDevboxVersion/route.ts @@ -0,0 +1,44 @@ +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const { name, releaseDes } = (await req.json()) as { name: string; releaseDes: string } + const headerList = req.headers + + const { k8sCustomObjects, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + await k8sCustomObjects.patchNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxreleases', + name, + { spec: { notes: releaseDes } }, + undefined, + undefined, + undefined, + { + headers: { + 'Content-Type': 'application/merge-patch+json' + } + } + ) + + return jsonRes({ + data: 'success edit devbox version description' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/getDevboxList/route.ts b/frontend/providers/devbox/app/api/getDevboxList/route.ts new file mode 100644 index 00000000000..410a5a6f708 --- /dev/null +++ b/frontend/providers/devbox/app/api/getDevboxList/route.ts @@ -0,0 +1,119 @@ +import { NextRequest } from 'next/server' + +import { runtimeNamespace } from '@/stores/static' +import { authSession } from '@/services/backend/auth' +import { jsonRes } from '@/services/backend/response' +import { getK8s } from '@/services/backend/kubernetes' +import { KBDevboxType, KBRuntimeType } from '@/types/k8s' +import { devboxKey, publicDomainKey } from '@/constants/devbox' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const headerList = req.headers + + const { k8sCustomObjects, namespace, k8sCore } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const { body: devboxBody } = (await k8sCustomObjects.listNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes' + )) as { body: { items: KBDevboxType[] } } + + const { body: runtimeBody } = (await k8sCustomObjects.listNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + runtimeNamespace, + 'runtimes' + )) as { body: { items: KBRuntimeType[] } } + + // add runtimeType, runtimeVersion, runtimeNamespace, networks to devbox yaml + const res = devboxBody.items.map(async (item: any) => { + const devboxName = item.metadata.name + const runtimeName = item.spec.runtimeRef.name + const runtime = runtimeBody.items.find((item: any) => item.metadata.name === runtimeName) + + item.spec.runtimeType = runtime?.spec.classRef + item.spec.runtimeVersion = runtime?.metadata.name + item.spec.runtimeNamespace = runtime?.metadata.namespace + + const { body: ingresses }: any = await k8sCustomObjects.listNamespacedCustomObject( + 'networking.k8s.io', + 'v1', + namespace, + 'ingresses', + undefined, + undefined, + undefined, + undefined, + `${devboxKey}=${devboxName}` + ) + const { body: certificates }: any = await k8sCustomObjects.listNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'certificates', + undefined, + undefined, + undefined, + undefined, + `${devboxKey}=${devboxName}` + ) + const customDomain = certificates.items[0]?.spec.dnsNames[0] + const ingressList = ingresses.items.map((item: any) => { + return { + networkName: item.metadata.name, + port: item.spec.rules[0].http.paths[0].backend.service.port.number, + protocol: item.metadata.annotations['nginx.ingress.kubernetes.io/backend-protocol'], + openPublicDomain: !!item.metadata.labels[publicDomainKey], + publicDomain: item.metadata.labels[publicDomainKey], + customDomain: customDomain || '' + } + }) + if (item.spec.network.extraPorts.length !== 0) { + const { body: service } = await k8sCore.readNamespacedService(devboxName, namespace) + const portInfos = item.spec.network.extraPorts.map(async (network: any) => { + const matchingIngress = ingressList.find( + (ingress: any) => ingress.port === network.containerPort + ) + + const servicePort = service.spec?.ports?.find( + (port: any) => port.port === network.containerPort + ) + const servicePortName = servicePort?.name + + if (matchingIngress) { + return { + networkName: matchingIngress.networkName, + port: matchingIngress.port, + portName: servicePortName, + protocol: matchingIngress.protocol, + openPublicDomain: matchingIngress.openPublicDomain, + publicDomain: matchingIngress.publicDomain, + customDomain: matchingIngress.customDomain + } + } + + return { + ...network, + port: network.containerPort + } + }) + item.portInfos = await Promise.all(portInfos) + } + + return item + }) + const resp = await Promise.all(res) + return jsonRes({ data: resp }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/getDevboxPodsByDevboxName/route.ts b/frontend/providers/devbox/app/api/getDevboxPodsByDevboxName/route.ts new file mode 100644 index 00000000000..8ee28c0475a --- /dev/null +++ b/frontend/providers/devbox/app/api/getDevboxPodsByDevboxName/route.ts @@ -0,0 +1,41 @@ +import { NextRequest } from 'next/server' + +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' +import { jsonRes } from '@/services/backend/response' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const { searchParams } = req.nextUrl + const devboxName = searchParams.get('name') as string + + const headerList = req.headers + + const { namespace, k8sCore } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + // get pods + const { + body: { items: pods } + } = await k8sCore.listNamespacedPod( + namespace, + undefined, + undefined, + undefined, + undefined, + `app.kubernetes.io/name=${devboxName}` + ) + + return jsonRes({ + data: pods + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/getDevboxVersionList/route.ts b/frontend/providers/devbox/app/api/getDevboxVersionList/route.ts new file mode 100644 index 00000000000..f6bef592fbe --- /dev/null +++ b/frontend/providers/devbox/app/api/getDevboxVersionList/route.ts @@ -0,0 +1,38 @@ +import { NextRequest } from 'next/server' + +import { KBDevboxReleaseType } from '@/types/k8s' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' +import { jsonRes } from '@/services/backend/response' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const { searchParams } = req.nextUrl + const devboxName = searchParams.get('devboxName') + const headerList = req.headers + + const { k8sCustomObjects, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const { body: releaseBody } = (await k8sCustomObjects.listNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxreleases' + )) as { body: { items: KBDevboxReleaseType[] } } + + const matchingDevboxVersions = releaseBody.items.filter((item: any) => { + return item.spec && item.spec.devboxName === devboxName + }) + + return jsonRes({ data: matchingDevboxVersions }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/getEnv/route.ts b/frontend/providers/devbox/app/api/getEnv/route.ts new file mode 100644 index 00000000000..ae527a037ba --- /dev/null +++ b/frontend/providers/devbox/app/api/getEnv/route.ts @@ -0,0 +1,23 @@ +import { jsonRes } from '@/services/backend/response' + +export type SystemEnvResponse = { + domain: string + ingressSecret: string + registryAddr: string + devboxAffinityEnable: string + squashEnable: string +} + +export const dynamic = 'force-dynamic' + +export async function GET() { + return jsonRes({ + data: { + domain: process.env.SEALOS_DOMAIN || 'dev.sealos.plus', + ingressSecret: process.env.INGRESS_SECRET || 'wildcard-cert', + registryAddr: process.env.REGISTRY_ADDR || 'hub.dev.sealos.plus', + devboxAffinityEnable: process.env.DEVBOX_AFFINITY_ENABLE || 'true', + squashEnable: process.env.SQUASH_ENABLE || 'true' + } + }) +} diff --git a/frontend/providers/devbox/app/api/getSSHConnectionInfo/route.ts b/frontend/providers/devbox/app/api/getSSHConnectionInfo/route.ts new file mode 100644 index 00000000000..260e302ae6f --- /dev/null +++ b/frontend/providers/devbox/app/api/getSSHConnectionInfo/route.ts @@ -0,0 +1,44 @@ +import { NextRequest } from 'next/server' + +import { KBRuntimeType } from '@/types/k8s' +import { runtimeNamespace } from '@/stores/static' +import { authSession } from '@/services/backend/auth' +import { jsonRes } from '@/services/backend/response' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const { searchParams } = req.nextUrl + const devboxName = searchParams.get('devboxName') as string + const runtimeName = searchParams.get('runtimeName') as string + const headerList = req.headers + + const { k8sCore, namespace, k8sCustomObjects } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const response = await k8sCore.readNamespacedSecret(devboxName, namespace) + + const base64PublicKey = response.body.data?.['SEALOS_DEVBOX_PUBLIC_KEY'] as string + const base64PrivateKey = response.body.data?.['SEALOS_DEVBOX_PRIVATE_KEY'] as string + + const { body: runtime } = (await k8sCustomObjects.getNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + runtimeNamespace, + 'runtimes', + runtimeName + )) as { body: KBRuntimeType } + + const userName = runtime.spec.config.user + + return jsonRes({ data: { base64PublicKey, base64PrivateKey, userName } }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/getSSHRuntimeInfo/route.ts b/frontend/providers/devbox/app/api/getSSHRuntimeInfo/route.ts new file mode 100644 index 00000000000..a6d6b4b8aaa --- /dev/null +++ b/frontend/providers/devbox/app/api/getSSHRuntimeInfo/route.ts @@ -0,0 +1,42 @@ +import { NextRequest } from 'next/server' + +import { KBRuntimeType } from '@/types/k8s' +import { runtimeNamespace } from '@/stores/static' +import { authSession } from '@/services/backend/auth' +import { jsonRes } from '@/services/backend/response' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const { searchParams } = req.nextUrl + const runtimeName = searchParams.get('runtimeName') as string + const headerList = req.headers + + const { k8sCustomObjects } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const { body: runtime } = (await k8sCustomObjects.getNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + runtimeNamespace, + 'runtimes', + runtimeName + )) as { body: KBRuntimeType } + + const data = { + workingDir: runtime.spec.config.workingDir, + releaseCommand: runtime.spec.config.releaseCommand.join(' '), + releaseArgs: runtime.spec.config.releaseArgs.join(' ') + } + + return jsonRes({ data }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/monitor/getMonitorData/route.ts b/frontend/providers/devbox/app/api/monitor/getMonitorData/route.ts new file mode 100644 index 00000000000..901d615ea35 --- /dev/null +++ b/frontend/providers/devbox/app/api/monitor/getMonitorData/route.ts @@ -0,0 +1,135 @@ +import { NextRequest } from 'next/server' + +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' +import { jsonRes } from '@/services/backend/response' +import { monitorFetch } from '@/services/monitorFetch' +import { MonitorServiceResult, MonitorDataResult, MonitorQueryKey } from '@/types/monitor' + +const AdapterChartData: Record< + keyof MonitorQueryKey, + (data: MonitorServiceResult) => MonitorDataResult[] +> = { + disk: (data: MonitorServiceResult) => { + const newDataArray = data.data.result.map((item) => { + let name = item.metric.pod + let xData = item.values.map((value) => value[0]) + let yData = item.values.map((value) => (parseFloat(value[1]) * 100).toFixed(2)) + return { + name: name, + xData: xData, + yData: yData + } + }) + return newDataArray + }, + cpu: (data: MonitorServiceResult) => { + const newDataArray = data.data.result.map((item) => { + let name = item.metric.pod + let xData = item.values.map((value) => value[0]) + let yData = item.values.map((value) => (parseFloat(value[1]) * 100).toFixed(2)) + return { + name: name, + xData: xData, + yData: yData + } + }) + return newDataArray + }, + memory: (data: MonitorServiceResult) => { + const newDataArray = data.data.result.map((item) => { + let name = item.metric.pod + let xData = item.values.map((value) => value[0]) + let yData = item.values.map((value) => (parseFloat(value[1]) * 100).toFixed(2)) + return { + name: name, + xData: xData, + yData: yData + } + }) + return newDataArray + }, + average_cpu: (data: MonitorServiceResult) => { + const newDataArray = data.data.result.map((item) => { + let name = item.metric.pod + let xData = item.values.map((value) => value[0]) + let yData = item.values.map((value) => parseFloat(value[1]).toFixed(2)) + return { + name: name, + xData: xData, + yData: yData + } + }) + return newDataArray + }, + average_memory: (data: MonitorServiceResult) => { + const newDataArray = data.data.result.map((item) => { + let name = item.metric.pod + let xData = item.values.map((value) => value[0]) + let yData = item.values.map((value) => parseFloat(value[1]).toFixed(2)) + return { + name: name, + xData: xData, + yData: yData + } + }) + return newDataArray + } +} + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const headerList = req.headers + const { searchParams } = req.nextUrl + const queryName = searchParams.get('queryName') as string + const queryKey = searchParams.get('queryKey') as keyof MonitorQueryKey + const start = searchParams.get('start') as string + const end = searchParams.get('end') as string + const step = searchParams.get('step') as string | '1m' + + const kubeconfig = await authSession(headerList) + + const { namespace } = await getK8s({ + kubeconfig: kubeconfig + }) + + // One hour of monitoring data + const endTime = Date.now() + const startTime = endTime - 60 * 60 * 1000 + + const params = { + type: queryKey, + launchPadName: queryName, + namespace: namespace, + start: startTime / 1000, + end: endTime / 1000, + step: step + } + + const result: MonitorDataResult = await monitorFetch( + { + url: '/query', + params: params + }, + kubeconfig + ).then((res) => { + // @ts-ignore + return AdapterChartData[queryKey] + ? // @ts-ignore + AdapterChartData[queryKey](res as MonitorDataResult) + : res + }) + + return jsonRes({ + code: 200, + data: result + }) + } catch (error) { + return jsonRes({ + code: 500, + error: error + }) + } +} diff --git a/frontend/providers/devbox/app/api/pauseDevbox/route.ts b/frontend/providers/devbox/app/api/pauseDevbox/route.ts new file mode 100644 index 00000000000..baf8134ac79 --- /dev/null +++ b/frontend/providers/devbox/app/api/pauseDevbox/route.ts @@ -0,0 +1,45 @@ +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const { devboxName } = (await req.json()) as { devboxName: string } + + const headerList = req.headers + + const { k8sCustomObjects, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + await k8sCustomObjects.patchNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes', + devboxName, + { spec: { state: 'Stopped' } }, + undefined, + undefined, + undefined, + { + headers: { + 'Content-Type': 'application/merge-patch+json' + } + } + ) + + return jsonRes({ + data: 'success pause devbox' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/platform/authCname/route.ts b/frontend/providers/devbox/app/api/platform/authCname/route.ts new file mode 100644 index 00000000000..037e610250a --- /dev/null +++ b/frontend/providers/devbox/app/api/platform/authCname/route.ts @@ -0,0 +1,34 @@ +import dns from 'dns' +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const { publicDomain, customDomain } = (await req.json()) as { + publicDomain: string + customDomain: string + } + + await (async () => + new Promise((resolve, reject) => { + dns.resolveCname(customDomain, (err, address) => { + console.log(err, address) + if (err) return reject(err) + + if (address[0] !== publicDomain) + return reject("Cname auth error: customDomain's cname is not equal to publicDomain") + resolve('') + }) + }))() + + return jsonRes({}) + } catch (error) { + return jsonRes({ + code: 500, + error + }) + } +} diff --git a/frontend/providers/devbox/app/api/platform/getNamespace/route.ts b/frontend/providers/devbox/app/api/platform/getNamespace/route.ts new file mode 100644 index 00000000000..f30c460cb0a --- /dev/null +++ b/frontend/providers/devbox/app/api/platform/getNamespace/route.ts @@ -0,0 +1,24 @@ +import { NextRequest } from 'next/server' + +import { authSession } from '@/services/backend/auth' +import { jsonRes } from '@/services/backend/response' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const headerList = req.headers + + const { namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + return jsonRes({ data: namespace }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/platform/getQuota/route.ts b/frontend/providers/devbox/app/api/platform/getQuota/route.ts new file mode 100644 index 00000000000..eedfd8e6c31 --- /dev/null +++ b/frontend/providers/devbox/app/api/platform/getQuota/route.ts @@ -0,0 +1,28 @@ +import type { NextRequest } from 'next/server' + +import { getK8s } from '@/services/backend/kubernetes' +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const headerList = req.headers + + const { getUserQuota } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const quota = await getUserQuota() + + return jsonRes({ + data: { + quota + } + }) + } catch (error) { + console.log(error) + return jsonRes({ code: 500, message: 'get price error' }) + } +} diff --git a/frontend/providers/devbox/app/api/platform/getRuntime/route.ts b/frontend/providers/devbox/app/api/platform/getRuntime/route.ts new file mode 100644 index 00000000000..85a613aa18f --- /dev/null +++ b/frontend/providers/devbox/app/api/platform/getRuntime/route.ts @@ -0,0 +1,149 @@ +import { NextRequest } from 'next/server' + +import { runtimeNamespace } from '@/stores/static' +import { authSession } from '@/services/backend/auth' +import { jsonRes } from '@/services/backend/response' +import { getK8s } from '@/services/backend/kubernetes' +import { KBRuntimeClassType, KBRuntimeType } from '@/types/k8s' +import { VersionMapType, runtimeNamespaceMapType, valueType } from '@/types/devbox' + +export const dynamic = 'force-dynamic' + +export async function GET(req: NextRequest) { + try { + const languageTypeList: valueType[] = [] + const frameworkTypeList: valueType[] = [] + const osTypeList: valueType[] = [] + const languageVersionMap: VersionMapType = {} + const frameworkVersionMap: VersionMapType = {} + const osVersionMap: VersionMapType = {} + const runtimeNamespaceMap: runtimeNamespaceMapType = {} + + const headerList = req.headers + + const { k8sCustomObjects } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const { body: runtimeClasses } = (await k8sCustomObjects.listNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + runtimeNamespace, + 'runtimeclasses' + )) as { body: { items: KBRuntimeClassType[] } } + const { body: runtimes } = (await k8sCustomObjects.listNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + runtimeNamespace, + 'runtimes' + )) as { body: { items: KBRuntimeType[] } } + + // runtimeClasses + const languageList = runtimeClasses?.items.filter((item: any) => item.spec.kind === 'Language') + languageTypeList.push( + ...languageList.map((item: any) => { + return { + id: item.metadata.name, + label: item.spec.title + } + }) + ) + const frameworkList = runtimeClasses?.items.filter( + (item: any) => item.spec.kind === 'Framework' + ) + frameworkTypeList.push( + ...frameworkList.map((item: any) => { + return { + id: item.metadata.name, + label: item.spec.title + } + }) + ) + const osList = runtimeClasses?.items.filter((item: any) => item.spec.kind === 'OS') + osTypeList.push( + ...osList.map((item: any) => { + return { + id: item.metadata.name, + label: item.spec.title + } + }) + ) + + // runtimeVersions and runtimeNamespaceMap + languageList.forEach((item: any) => { + const language = item.metadata.name + const versions = runtimes?.items.filter((runtime: any) => runtime.spec.classRef === language) + languageVersionMap[language] = [] + versions.forEach((version: any) => { + runtimeNamespaceMap[version.metadata.name] = item.metadata.namespace + languageVersionMap[language].push({ + id: version.metadata.name, + label: version.spec.version + }) + }) + if (languageVersionMap[language].length === 0) { + delete languageVersionMap[language] + const index = languageTypeList.findIndex((item) => item.id === language) + if (index !== -1) { + languageTypeList.splice(index, 1) + } + } + }) + + frameworkList.forEach((item: any) => { + const framework = item.metadata.name + const versions = runtimes?.items.filter((runtime: any) => runtime.spec.classRef === framework) + frameworkVersionMap[framework] = [] + versions.forEach((version: any) => { + runtimeNamespaceMap[version.metadata.name] = item.metadata.namespace + frameworkVersionMap[framework].push({ + id: version.metadata.name, + label: version.spec.version + }) + }) + if (frameworkVersionMap[framework].length === 0) { + delete frameworkVersionMap[framework] + const index = frameworkTypeList.findIndex((item) => item.id === framework) + if (index !== -1) { + frameworkTypeList.splice(index, 1) + } + } + }) + osList.forEach((item: any) => { + const os = item.metadata.name + const versions = runtimes?.items.filter((runtime: any) => runtime.spec.classRef === os) + osVersionMap[os] = [] + versions.forEach((version: any) => { + runtimeNamespaceMap[version.metadata.name] = item.metadata.namespace + osVersionMap[os].push({ + id: version.metadata.name, + label: version.spec.version + }) + }) + if (osVersionMap[os].length === 0) { + delete osVersionMap[os] + const index = osTypeList.findIndex((item) => item.id === os) + if (index !== -1) { + frameworkTypeList.splice(index, 1) + } + } + }) + + return jsonRes({ + data: { + languageVersionMap, + frameworkVersionMap, + osVersionMap, + languageTypeList, + frameworkTypeList, + osTypeList, + runtimeNamespaceMap + } + }) + } catch (error) { + return jsonRes({ + code: 500, + error: error + }) + } +} diff --git a/frontend/providers/devbox/app/api/platform/resourcePrice/route.ts b/frontend/providers/devbox/app/api/platform/resourcePrice/route.ts new file mode 100644 index 00000000000..c921e88f894 --- /dev/null +++ b/frontend/providers/devbox/app/api/platform/resourcePrice/route.ts @@ -0,0 +1,86 @@ +import { NextRequest } from 'next/server' + +import { userPriceType } from '@/types/user' +import { jsonRes } from '@/services/backend/response' + +export const dynamic = 'force-dynamic' + +export type Response = { + cpu: number + memory: number + nodeports: number +} + +type ResourcePriceType = { + data: { + properties: { + name: string + unit_price: number + unit: string + }[] + } +} + +type ResourceType = + | 'cpu' + | 'memory' + | 'storage' + | 'disk' + | 'mongodb' + | 'minio' + | 'infra-cpu' + | 'infra-memory' + | 'infra-disk' + | 'services.nodeports' + +const PRICE_SCALE = 1000000 + +const valuationMap: Record = { + cpu: 1000, + memory: 1024, + storage: 1024, + ['services.nodeports']: 1000 +} + +export async function GET(req: NextRequest) { + try { + const { SEALOS_DOMAIN } = process.env + const getResourcePrice = async () => { + try { + const res = await fetch( + `https://account-api.${SEALOS_DOMAIN}/account/v1alpha1/properties`, + { + method: 'POST' + } + ) + + const data: ResourcePriceType = await res.json() + + return data.data.properties + } catch (error) { + console.log(error) + } + } + + const resp = (await getResourcePrice()) as ResourcePriceType['data']['properties'] + + const data: userPriceType = { + cpu: countSourcePrice(resp, 'cpu'), + memory: countSourcePrice(resp, 'memory'), + nodeports: countSourcePrice(resp, 'services.nodeports') + } + + return jsonRes({ + data + }) + } catch (error) { + return jsonRes({ code: 500, message: 'get price error' }) + } +} + +function countSourcePrice(rawData: ResourcePriceType['data']['properties'], type: ResourceType) { + const rawPrice = rawData.find((item) => item.name === type)?.unit_price || 1 + const sourceScale = rawPrice * (valuationMap[type] || 1) + const unitScale = sourceScale / PRICE_SCALE + return unitScale +} diff --git a/frontend/providers/devbox/app/api/releaseDevbox/route.ts b/frontend/providers/devbox/app/api/releaseDevbox/route.ts new file mode 100644 index 00000000000..8637937ec53 --- /dev/null +++ b/frontend/providers/devbox/app/api/releaseDevbox/route.ts @@ -0,0 +1,34 @@ +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' +import { json2DevboxRelease } from '@/utils/json2Yaml' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const releaseForm = (await req.json()) as { + devboxName: string + tag: string + releaseDes: string + } + const headerList = req.headers + + const { applyYamlList } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + const devbox = json2DevboxRelease(releaseForm) + await applyYamlList([devbox], 'create') + + return jsonRes({ + data: 'success create devbox release' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/restartDevbox/route.ts b/frontend/providers/devbox/app/api/restartDevbox/route.ts new file mode 100644 index 00000000000..0a20cf8c014 --- /dev/null +++ b/frontend/providers/devbox/app/api/restartDevbox/route.ts @@ -0,0 +1,61 @@ +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const { devboxName } = (await req.json()) as { devboxName: string } + const headerList = req.headers + + const { k8sCustomObjects, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + // restart = stopped + running + await k8sCustomObjects.patchNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes', + devboxName, + { spec: { state: 'Stopped' } }, + undefined, + undefined, + undefined, + { + headers: { + 'Content-Type': 'application/merge-patch+json' + } + } + ) + await k8sCustomObjects.patchNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes', + devboxName, + { spec: { state: 'Running' } }, + undefined, + undefined, + undefined, + { + headers: { + 'Content-Type': 'application/merge-patch+json' + } + } + ) + + return jsonRes({ + data: 'success pause devbox' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/startDevbox/route.ts b/frontend/providers/devbox/app/api/startDevbox/route.ts new file mode 100644 index 00000000000..2e89ba1b9a8 --- /dev/null +++ b/frontend/providers/devbox/app/api/startDevbox/route.ts @@ -0,0 +1,45 @@ +import { NextRequest } from 'next/server' + +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const { devboxName } = (await req.json()) as { devboxName: string } + + const headerList = req.headers + + const { k8sCustomObjects, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + await k8sCustomObjects.patchNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes', + devboxName, + { spec: { state: 'Running' } }, + undefined, + undefined, + undefined, + { + headers: { + 'Content-Type': 'application/merge-patch+json' + } + } + ) + + return jsonRes({ + data: 'success start devbox' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/app/api/updateDevbox/route.ts b/frontend/providers/devbox/app/api/updateDevbox/route.ts new file mode 100644 index 00000000000..647c144e0b1 --- /dev/null +++ b/frontend/providers/devbox/app/api/updateDevbox/route.ts @@ -0,0 +1,184 @@ +import { NextRequest } from 'next/server' +import { infoLog } from 'sealos-desktop-sdk' +import { PatchUtils } from '@kubernetes/client-node' + +import { YamlKindEnum } from '@/constants/devbox' +import { jsonRes } from '@/services/backend/response' +import { authSession } from '@/services/backend/auth' +import { getK8s } from '@/services/backend/kubernetes' +import type { DevboxPatchPropsType } from '@/types/devbox' + +export const dynamic = 'force-dynamic' + +export async function POST(req: NextRequest) { + try { + const { patch, devboxName } = (await req.json()) as { + patch: DevboxPatchPropsType + devboxName: string + } + if (!patch || patch.length === 0 || !devboxName) { + return jsonRes({ + code: 500, + error: 'params error' + }) + } + + const headerList = req.headers + + const { applyYamlList, k8sCore, k8sNetworkingApp, k8sCustomObjects, namespace } = await getK8s({ + kubeconfig: await authSession(headerList) + }) + + const crMap: Record< + `${YamlKindEnum}`, + { + patch: (jsonPatch: Object) => Promise + delete: (name: string) => Promise + } + > = { + [YamlKindEnum.Devbox]: { + patch: (jsonPatch: Object) => { + // @ts-ignore + const name = jsonPatch?.metadata?.name + return k8sCustomObjects.patchNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes', + name, + jsonPatch, + undefined, + undefined, + undefined, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_MERGE_PATCH } } + ) + }, + delete: (name) => + k8sCustomObjects.deleteNamespacedCustomObject( + 'devbox.sealos.io', + 'v1alpha1', + namespace, + 'devboxes', + name + ) + }, + [YamlKindEnum.Service]: { + patch: (jsonPatch: Object) => + k8sCore.replaceNamespacedService(devboxName, namespace, jsonPatch), + delete: (name) => k8sCore.deleteNamespacedService(name, namespace) + }, + [YamlKindEnum.Ingress]: { + patch: (jsonPatch: any) => + k8sNetworkingApp.patchNamespacedIngress( + jsonPatch?.metadata?.name, + namespace, + jsonPatch, + undefined, + undefined, + undefined, + undefined, + undefined, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_MERGE_PATCH } } + ), + delete: (name) => k8sNetworkingApp.deleteNamespacedIngress(name, namespace) + }, + [YamlKindEnum.Issuer]: { + patch: (jsonPatch: Object) => { + // @ts-ignore + const name = jsonPatch?.metadata?.name + return k8sCustomObjects.patchNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'issuers', + name, + jsonPatch, + undefined, + undefined, + undefined, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_MERGE_PATCH } } + ) + }, + delete: (name) => + k8sCustomObjects.deleteNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'issuers', + name + ) + }, + [YamlKindEnum.Certificate]: { + patch: (jsonPatch: Object) => { + // @ts-ignore + const name = jsonPatch?.metadata?.name + return k8sCustomObjects.patchNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'certificates', + name, + jsonPatch, + undefined, + undefined, + undefined, + { headers: { 'Content-type': PatchUtils.PATCH_FORMAT_JSON_MERGE_PATCH } } + ) + }, + delete: (name) => + k8sCustomObjects.deleteNamespacedCustomObject( + 'cert-manager.io', + 'v1', + namespace, + 'certificates', + name + ) + } + } + + // patch + await Promise.all( + patch.map((item) => { + const cr = crMap[item.kind] + if (!cr || item.type !== 'patch' || !item.value?.metadata) { + return + } + infoLog('patch cr', { kind: item.kind, name: item.value?.metadata?.name }) + return cr.patch(item.value) + }) + ) + + // create + const createYamlList = patch + .map((item) => { + const cr = crMap[item.kind] + if (!cr || item.type !== 'create') { + return + } + return item.value + }) + .filter((item) => item) + await applyYamlList(createYamlList as string[], 'create') + + // delete + await Promise.all( + patch.map((item) => { + const cr = crMap[item.kind] + if (!cr || item.type !== 'delete' || !item?.name) { + return + } + infoLog('delete cr', { kind: item.kind, name: item?.name }) + return cr.delete(item.name) + }) + ) + + return jsonRes({ + data: 'success update devbox' + }) + } catch (err: any) { + return jsonRes({ + code: 500, + error: err + }) + } +} diff --git a/frontend/providers/devbox/components/DevboxStatusTag.tsx b/frontend/providers/devbox/components/DevboxStatusTag.tsx new file mode 100644 index 00000000000..ec9f9ab0d6e --- /dev/null +++ b/frontend/providers/devbox/components/DevboxStatusTag.tsx @@ -0,0 +1,51 @@ +import React from 'react' +import { useTranslations } from 'next-intl' +import { Flex, Box } from '@chakra-ui/react' + +import type { DevboxReleaseStatusMapType, DevboxStatusMapType } from '@/types/devbox' + +const DevboxStatusTag = ({ + status, + showBorder = false, + thinMode = false, + ...props +}: { + status: DevboxStatusMapType | DevboxReleaseStatusMapType + showBorder?: boolean + size?: 'sm' | 'md' | 'lg' + thinMode?: boolean + w?: string + h?: string +}) => { + const label = status.label + const t = useTranslations() + + return ( + + + + {t(label)} + + + ) +} + +export default DevboxStatusTag diff --git a/frontend/providers/devbox/components/Icon/icons/analyze.svg b/frontend/providers/devbox/components/Icon/icons/analyze.svg new file mode 100644 index 00000000000..50317864733 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/analyze.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/arrowDown.svg b/frontend/providers/devbox/components/Icon/icons/arrowDown.svg new file mode 100644 index 00000000000..029c097a241 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/arrowDown.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/arrowLeft.svg b/frontend/providers/devbox/components/Icon/icons/arrowLeft.svg new file mode 100644 index 00000000000..9e246b28759 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/arrowLeft.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/arrowUp.svg b/frontend/providers/devbox/components/Icon/icons/arrowUp.svg new file mode 100644 index 00000000000..b9caedf354d --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/arrowUp.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/book.svg b/frontend/providers/devbox/components/Icon/icons/book.svg new file mode 100644 index 00000000000..fc017e15b37 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/book.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/change.svg b/frontend/providers/devbox/components/Icon/icons/change.svg new file mode 100644 index 00000000000..2ffbb98e154 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/change.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/check.svg b/frontend/providers/devbox/components/Icon/icons/check.svg new file mode 100644 index 00000000000..5c655c410ed --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/chevronDown.svg b/frontend/providers/devbox/components/Icon/icons/chevronDown.svg new file mode 100644 index 00000000000..38f7ead3eb3 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/chevronDown.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/codeServer.svg b/frontend/providers/devbox/components/Icon/icons/codeServer.svg new file mode 100644 index 00000000000..d3954a5c470 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/codeServer.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/connection.svg b/frontend/providers/devbox/components/Icon/icons/connection.svg new file mode 100644 index 00000000000..dc3a8f65168 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/connection.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/continue.svg b/frontend/providers/devbox/components/Icon/icons/continue.svg new file mode 100644 index 00000000000..d38b29ef09e --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/continue.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/copy.svg b/frontend/providers/devbox/components/Icon/icons/copy.svg new file mode 100644 index 00000000000..bfd38df8ede --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/currency.svg b/frontend/providers/devbox/components/Icon/icons/currency.svg new file mode 100644 index 00000000000..5d4afdf3092 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/currency.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/cursor.svg b/frontend/providers/devbox/components/Icon/icons/cursor.svg new file mode 100644 index 00000000000..7770b5ee252 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/cursor.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/delete.svg b/frontend/providers/devbox/components/Icon/icons/delete.svg new file mode 100644 index 00000000000..7f07bdcd4f5 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/delete.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/detail.svg b/frontend/providers/devbox/components/Icon/icons/detail.svg new file mode 100644 index 00000000000..63bb1eda521 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/detail.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/docs.svg b/frontend/providers/devbox/components/Icon/icons/docs.svg new file mode 100644 index 00000000000..027ca872a00 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/docs.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/download.svg b/frontend/providers/devbox/components/Icon/icons/download.svg new file mode 100644 index 00000000000..a2e40123a89 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/download.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/edit.svg b/frontend/providers/devbox/components/Icon/icons/edit.svg new file mode 100644 index 00000000000..031f0c1f310 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/edit.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/empty.svg b/frontend/providers/devbox/components/Icon/icons/empty.svg new file mode 100644 index 00000000000..f373506807e --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/empty.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/error.svg b/frontend/providers/devbox/components/Icon/icons/error.svg new file mode 100644 index 00000000000..4ee6d34ccb1 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/error.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/export.svg b/frontend/providers/devbox/components/Icon/icons/export.svg new file mode 100644 index 00000000000..f15d01604f0 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/export.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/formAdvanced.svg b/frontend/providers/devbox/components/Icon/icons/formAdvanced.svg new file mode 100644 index 00000000000..b8e60d5f173 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/formAdvanced.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/formInfo.svg b/frontend/providers/devbox/components/Icon/icons/formInfo.svg new file mode 100644 index 00000000000..a7c39c65f47 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/formInfo.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/formNetwork.svg b/frontend/providers/devbox/components/Icon/icons/formNetwork.svg new file mode 100644 index 00000000000..638352dea48 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/formNetwork.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/info.svg b/frontend/providers/devbox/components/Icon/icons/info.svg new file mode 100644 index 00000000000..ad93c387b58 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/info.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/infoCircle.svg b/frontend/providers/devbox/components/Icon/icons/infoCircle.svg new file mode 100644 index 00000000000..6cf4fd51465 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/infoCircle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/link.svg b/frontend/providers/devbox/components/Icon/icons/link.svg new file mode 100644 index 00000000000..13d0b61b384 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/link.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/list.svg b/frontend/providers/devbox/components/Icon/icons/list.svg new file mode 100644 index 00000000000..9277a2294dc --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/list.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/loading.svg b/frontend/providers/devbox/components/Icon/icons/loading.svg new file mode 100644 index 00000000000..2eb8dd726a3 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/loading.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/log.svg b/frontend/providers/devbox/components/Icon/icons/log.svg new file mode 100644 index 00000000000..798a41665eb --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/log.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/logo.svg b/frontend/providers/devbox/components/Icon/icons/logo.svg new file mode 100644 index 00000000000..6a62d79e2de --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/maximize.svg b/frontend/providers/devbox/components/Icon/icons/maximize.svg new file mode 100644 index 00000000000..451096ee93e --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/maximize.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/monitor.svg b/frontend/providers/devbox/components/Icon/icons/monitor.svg new file mode 100644 index 00000000000..ebf9fb01401 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/monitor.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/more.svg b/frontend/providers/devbox/components/Icon/icons/more.svg new file mode 100644 index 00000000000..bc3ddc9e9ac --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/network.svg b/frontend/providers/devbox/components/Icon/icons/network.svg new file mode 100644 index 00000000000..8ecd02dca03 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/network.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/noEvents.svg b/frontend/providers/devbox/components/Icon/icons/noEvents.svg new file mode 100644 index 00000000000..9665a702425 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/noEvents.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/pause.svg b/frontend/providers/devbox/components/Icon/icons/pause.svg new file mode 100644 index 00000000000..0c7f9ec92db --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/pause.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/plus.svg b/frontend/providers/devbox/components/Icon/icons/plus.svg new file mode 100644 index 00000000000..f21a34f9a77 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/plus.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/podList.svg b/frontend/providers/devbox/components/Icon/icons/podList.svg new file mode 100644 index 00000000000..6be85b62a28 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/podList.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/pods.svg b/frontend/providers/devbox/components/Icon/icons/pods.svg new file mode 100644 index 00000000000..7a7ebdc45a8 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/pods.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/read.svg b/frontend/providers/devbox/components/Icon/icons/read.svg new file mode 100644 index 00000000000..28c46f8300a --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/read.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/response.svg b/frontend/providers/devbox/components/Icon/icons/response.svg new file mode 100644 index 00000000000..cbd0a4d3bf8 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/response.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/restart.svg b/frontend/providers/devbox/components/Icon/icons/restart.svg new file mode 100644 index 00000000000..93cf5ef2f07 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/restart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/restore.svg b/frontend/providers/devbox/components/Icon/icons/restore.svg new file mode 100644 index 00000000000..2e89f43b666 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/restore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/search.svg b/frontend/providers/devbox/components/Icon/icons/search.svg new file mode 100644 index 00000000000..67426d97a70 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/search.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/settings.svg b/frontend/providers/devbox/components/Icon/icons/settings.svg new file mode 100644 index 00000000000..fa9f203e68e --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/settings.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/shutdown.svg b/frontend/providers/devbox/components/Icon/icons/shutdown.svg new file mode 100644 index 00000000000..f9cecf95d69 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/shutdown.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/start.svg b/frontend/providers/devbox/components/Icon/icons/start.svg new file mode 100644 index 00000000000..860c9c4b691 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/start.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/statusDetail.svg b/frontend/providers/devbox/components/Icon/icons/statusDetail.svg new file mode 100644 index 00000000000..9716b647188 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/statusDetail.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/success.svg b/frontend/providers/devbox/components/Icon/icons/success.svg new file mode 100644 index 00000000000..51c1b8f5949 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/success.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/target.svg b/frontend/providers/devbox/components/Icon/icons/target.svg new file mode 100644 index 00000000000..c9ed559074b --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/target.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/terminal.svg b/frontend/providers/devbox/components/Icon/icons/terminal.svg new file mode 100644 index 00000000000..db9147be96f --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/terminal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/unread.svg b/frontend/providers/devbox/components/Icon/icons/unread.svg new file mode 100644 index 00000000000..9e6a678deb2 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/unread.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/upload.svg b/frontend/providers/devbox/components/Icon/icons/upload.svg new file mode 100644 index 00000000000..ad7bf165347 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/upload.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/upperRight.svg b/frontend/providers/devbox/components/Icon/icons/upperRight.svg new file mode 100644 index 00000000000..f3299a16f8d --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/upperRight.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/version.svg b/frontend/providers/devbox/components/Icon/icons/version.svg new file mode 100644 index 00000000000..6d64d0f9dc0 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/version.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/providers/devbox/components/Icon/icons/vscode.svg b/frontend/providers/devbox/components/Icon/icons/vscode.svg new file mode 100644 index 00000000000..2be086f4c69 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/vscode.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/vscodeInsider.svg b/frontend/providers/devbox/components/Icon/icons/vscodeInsider.svg new file mode 100644 index 00000000000..bbf82fc6605 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/vscodeInsider.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/providers/devbox/components/Icon/icons/warning.svg b/frontend/providers/devbox/components/Icon/icons/warning.svg new file mode 100644 index 00000000000..dc84cf1b651 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/warning.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/icons/warningInfo.svg b/frontend/providers/devbox/components/Icon/icons/warningInfo.svg new file mode 100644 index 00000000000..100256a0fda --- /dev/null +++ b/frontend/providers/devbox/components/Icon/icons/warningInfo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/providers/devbox/components/Icon/index.tsx b/frontend/providers/devbox/components/Icon/index.tsx new file mode 100644 index 00000000000..46a78e85080 --- /dev/null +++ b/frontend/providers/devbox/components/Icon/index.tsx @@ -0,0 +1,79 @@ +import React from 'react' +import { Icon } from '@chakra-ui/react' +import type { IconProps } from '@chakra-ui/react' + +const map = { + codeServer: require('./icons/codeServer.svg').default, + version: require('./icons/version.svg').default, + terminal: require('./icons/terminal.svg').default, + + more: require('./icons/more.svg').default, + podList: require('./icons/podList.svg').default, + arrowLeft: require('./icons/arrowLeft.svg').default, + plus: require('./icons/plus.svg').default, + delete: require('./icons/delete.svg').default, + restart: require('./icons/restart.svg').default, + start: require('./icons/start.svg').default, + pause: require('./icons/pause.svg').default, + warningInfo: require('./icons/warningInfo.svg').default, + detail: require('./icons/detail.svg').default, + logo: require('./icons/logo.svg').default, + change: require('./icons/change.svg').default, + formInfo: require('./icons/formInfo.svg').default, + settings: require('./icons/settings.svg').default, + copy: require('./icons/copy.svg').default, + continue: require('./icons/continue.svg').default, + noEvents: require('./icons/noEvents.svg').default, + network: require('./icons/network.svg').default, + warning: require('./icons/warning.svg').default, + analyze: require('./icons/analyze.svg').default, + log: require('./icons/log.svg').default, + statusDetail: require('./icons/statusDetail.svg').default, + read: require('./icons/read.svg').default, + unread: require('./icons/unread.svg').default, + connection: require('./icons/connection.svg').default, + info: require('./icons/info.svg').default, + restore: require('./icons/restore.svg').default, + download: require('./icons/download.svg').default, + loading: require('./icons/loading.svg').default, + success: require('./icons/success.svg').default, + error: require('./icons/error.svg').default, + currency: require('./icons/currency.svg').default, + infoCircle: require('./icons/infoCircle.svg').default, + upperRight: require('./icons/upperRight.svg').default, + arrowUp: require('./icons/arrowUp.svg').default, + search: require('./icons/search.svg').default, + edit: require('./icons/edit.svg').default, + book: require('./icons/book.svg').default, + export: require('./icons/export.svg').default, + pods: require('./icons/pods.svg').default, + upload: require('./icons/upload.svg').default, + target: require('./icons/target.svg').default, + arrowDown: require('./icons/arrowDown.svg').default, + docs: require('./icons/docs.svg').default, + vscode: require('./icons/vscode.svg').default, + monitor: require('./icons/monitor.svg').default, + response: require('./icons/response.svg').default, + link: require('./icons/link.svg').default, + list: require('./icons/list.svg').default, + maximize: require('./icons/maximize.svg').default, + chevronDown: require('./icons/chevronDown.svg').default, + vscodeInsider: require('./icons/vscodeInsider.svg').default, + cursor: require('./icons/cursor.svg').default, + check: require('./icons/check.svg').default, + empty: require('./icons/empty.svg').default, + shutdown: require('./icons/shutdown.svg').default +} + +const MyIcon = ({ + name, + w = 'auto', + h = 'auto', + ...props +}: { name: keyof typeof map } & IconProps) => { + return map[name] ? ( + + ) : null +} + +export default MyIcon diff --git a/frontend/providers/devbox/components/MyTable.tsx b/frontend/providers/devbox/components/MyTable.tsx new file mode 100644 index 00000000000..fdc2db58f42 --- /dev/null +++ b/frontend/providers/devbox/components/MyTable.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import { Box, BoxProps, Grid, Flex } from '@chakra-ui/react' + +interface Props extends BoxProps { + columns: { + title: string + dataIndex?: string + key: string + render?: (item: any) => JSX.Element + minWidth?: string + }[] + data: any[] + itemClass?: string + alternateRowColors?: boolean +} + +const MyTable = ({ columns, data, itemClass = '', alternateRowColors = false }: Props) => { + return ( + <> + + {columns.map((item) => ( + + {item.title} + + ))} + + {data.map((item: any, index1) => ( + + {columns.map((col, index2) => ( + + {col.render ? col.render(item) : col.dataIndex ? `${item[col.dataIndex]}` : ''} + + ))} + + ))} + + ) +} + +export default MyTable diff --git a/frontend/providers/devbox/components/MyTooltip.tsx b/frontend/providers/devbox/components/MyTooltip.tsx new file mode 100644 index 00000000000..3d9679ef4e4 --- /dev/null +++ b/frontend/providers/devbox/components/MyTooltip.tsx @@ -0,0 +1,22 @@ +import { Tooltip, TooltipProps } from '@chakra-ui/react' + +const MyTooltip = ({ children, ...props }: TooltipProps) => { + return ( + + {children} + + ) +} + +export default MyTooltip diff --git a/frontend/providers/devbox/components/PodLineChart.tsx b/frontend/providers/devbox/components/PodLineChart.tsx new file mode 100644 index 00000000000..e14993ef4a4 --- /dev/null +++ b/frontend/providers/devbox/components/PodLineChart.tsx @@ -0,0 +1,226 @@ +'use client' + +import dayjs from 'dayjs' +import * as echarts from 'echarts' +import React, { useEffect, useMemo, useRef } from 'react' + +import { useGlobalStore } from '@/stores/global' +import { MonitorDataResult } from '@/types/monitor' + +const map = { + blue: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(3, 190, 232, 0.42)' + }, + { + offset: 1, + color: 'rgba(0, 182, 240, 0)' + } + ], + global: false + }, + lineColor: '#36ADEF' + }, + deepBlue: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(47, 112, 237, 0.42)' + }, + { + offset: 1, + color: 'rgba(94, 159, 235, 0)' + } + ], + global: false + }, + lineColor: '#3293EC' + }, + purple: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(211, 190, 255, 0.42)' + }, + { + offset: 1, + color: 'rgba(52, 60, 255, 0)' + } + ], + global: false // 缺省为 false + }, + lineColor: '#8172D8' + }, + green: { + backgroundColor: { + type: 'linear', + x: 0, + y: 0, + x2: 0, + y2: 1, + colorStops: [ + { + offset: 0, + color: 'rgba(4, 209, 148, 0.42)' + }, + { + offset: 1, + color: 'rgba(19, 217, 181, 0)' + } + ], + global: false + }, + lineColor: '#00A9A6', + max: 100 + } +} + +const PodLineChart = ({ + type, + data, + isShowLabel = false +}: { + type: 'blue' | 'deepBlue' | 'green' | 'purple' + data?: MonitorDataResult + isShowLabel?: boolean +}) => { + const { screenWidth } = useGlobalStore() + const xData = + data?.xData?.map((time) => (time ? dayjs(time * 1000).format('HH:mm') : '')) || + new Array(30).fill(0) + const yData = data?.yData || new Array(30).fill('') + + const Dom = useRef(null) + const myChart = useRef() + + const optionStyle = useMemo( + () => ({ + areaStyle: { + color: map[type].backgroundColor + }, + lineStyle: { + width: '1', + color: map[type].lineColor + }, + itemStyle: { + width: 1.5, + color: map[type].lineColor + } + }), + [type] + ) + const option = useRef({ + xAxis: { + type: 'category', + show: isShowLabel, + boundaryGap: false, + data: xData, + axisLabel: { + show: isShowLabel + }, + axisTick: { + show: false + }, + axisLine: { + show: false + } + }, + yAxis: { + type: 'value', + boundaryGap: false, + splitNumber: 2, + max: 100, + min: 0, + axisLabel: { + show: isShowLabel + } + }, + grid: { + containLabel: isShowLabel, + show: false, + left: 0, + right: isShowLabel ? 14 : 0, + top: 10, + bottom: 2 + }, + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'line' + }, + formatter: (params: any[]) => { + const axisValue = params[0]?.axisValue + return `${axisValue} ${params[0]?.value || 0}%` + } + }, + series: [ + { + data: yData, + type: 'line', + showSymbol: false, + smooth: true, + animationDuration: 300, + animationEasingUpdate: 'linear', + ...optionStyle, + emphasis: { + disabled: true + } + } + ] + }) + + // init chart + useEffect(() => { + if (!Dom.current || myChart?.current?.getOption()) return + myChart.current = echarts.init(Dom.current) + myChart.current && myChart.current.setOption(option.current) + }, [Dom]) + + // data changed, update + useEffect(() => { + if (!myChart.current || !myChart?.current?.getOption()) return + option.current.xAxis.data = xData + option.current.series[0].data = yData + myChart.current.setOption(option.current) + }, [xData, yData]) + + // type changed, update + useEffect(() => { + if (!myChart.current || !myChart?.current?.getOption()) return + option.current.series[0] = { + ...option.current.series[0], + ...optionStyle + } + myChart.current.setOption(option.current) + }, [optionStyle]) + + // resize chart + useEffect(() => { + if (!myChart.current || !myChart.current.getOption()) return + myChart.current.resize() + }, [screenWidth]) + + return
+} + +export default React.memo(PodLineChart) diff --git a/frontend/providers/devbox/components/PriceBox.tsx b/frontend/providers/devbox/components/PriceBox.tsx new file mode 100644 index 00000000000..82e82f57b49 --- /dev/null +++ b/frontend/providers/devbox/components/PriceBox.tsx @@ -0,0 +1,82 @@ +import { useMemo } from 'react' +import { useTranslations } from 'next-intl' +import { Box, Flex, useTheme, Text } from '@chakra-ui/react' + +import { SOURCE_PRICE } from '@/stores/static' +import { SealosCoin } from '@sealos/ui' + +export const colorMap = { + cpu: '#33BABB', + memory: '#36ADEF', + nodeports: '#8172D8' +} + +const PriceBox = ({ + components = [] +}: { + components: { + cpu: number + memory: number + nodeports: number + }[] +}) => { + const theme = useTheme() + const t = useTranslations() + const priceList: { + label: string + color: string + value: string + }[] = useMemo(() => { + let cp = 0 + let mp = 0 + let pp = 0 + let tp = 0 + + components.forEach(({ cpu, memory, nodeports }) => { + cp = (SOURCE_PRICE.cpu * cpu * 24) / 1000 + mp = (SOURCE_PRICE.memory * memory * 24) / 1024 + pp = SOURCE_PRICE.nodeports * nodeports * 24 + tp = cp + mp + pp + }) + + return [ + { + label: 'cpu', + color: '#33BABB', + value: cp.toFixed(2) + }, + { label: 'memory', color: '#36ADEF', value: mp.toFixed(2) }, + { + label: 'nodeports', + color: '#8172D8', + value: pp.toFixed(2) + }, + { label: 'total_price', color: '#485058', value: tp.toFixed(2) } + ] + }, [components]) + + return ( + + + + {t('estimated_price')} + + ({t('daily')}) + + + {priceList.map((item) => ( + + + {t(item.label)}: + + + {item.value} + + + ))} + + + ) +} + +export default PriceBox diff --git a/frontend/providers/devbox/components/QuotaBox.tsx b/frontend/providers/devbox/components/QuotaBox.tsx new file mode 100644 index 00000000000..57d5f603522 --- /dev/null +++ b/frontend/providers/devbox/components/QuotaBox.tsx @@ -0,0 +1,86 @@ +'use client' + +import { useMemo } from 'react' +import { useTranslations } from 'next-intl' +import { useQuery } from '@tanstack/react-query' +import { Box, Flex, Progress, css, useTheme } from '@chakra-ui/react' + +import { useUserStore } from '@/stores/user' +import MyTooltip from '@/components/MyTooltip' + +const sourceMap = { + cpu: { + color: '#33BABB', + unit: 'Core' + }, + memory: { + color: '#36ADEF', + unit: 'Gi' + }, + nodeports: { + color: '#FFA500', + unit: '' + } +} + +const QuotaBox = ({ showBorder = true }: { showBorder?: boolean }) => { + const theme = useTheme() + const t = useTranslations() + const { userQuota, loadUserQuota } = useUserStore() + + useQuery(['getUserQuota'], loadUserQuota) + const quotaList = useMemo(() => { + if (!userQuota) return [] + + return userQuota + .filter((item) => item.limit > 0) + .map((item) => { + const { limit, used, type } = item + const unit = sourceMap[type]?.unit + const color = sourceMap[type]?.color + const tip = `${t('total')}: ${limit} ${unit} +${t('used')}: ${used.toFixed(2)} ${unit} +${t('remaining')}: ${(limit - used).toFixed(2)} ${unit}` + + return { ...item, tip, color } + }) + }, [userQuota, t]) + + return userQuota.length === 0 ? null : ( + + + {t('resource_quota')} + + + {quotaList.map((item) => ( + + + + {t(item.type)} + + div': { + bg: item.color + } + })} + /> + + + ))} + + + ) +} + +export default QuotaBox diff --git a/frontend/providers/devbox/components/YamlCode/hljs.ts b/frontend/providers/devbox/components/YamlCode/hljs.ts new file mode 100644 index 00000000000..796b2e0de82 --- /dev/null +++ b/frontend/providers/devbox/components/YamlCode/hljs.ts @@ -0,0 +1,121 @@ +export const codeTheme = { + hljs: { + display: 'block', + overflowX: 'auto', + color: '#2f3337' + }, + linenumber: { + minWidth: '2em' + }, + 'hljs-comment': { + color: '#656e77' + }, + comment: { + color: '#A19F9D' + }, + 'hljs-keyword': { + color: '#0770BC' + }, + 'hljs-selector-tag': { + color: '#0770BC' + }, + 'hljs-meta-keyword': { + color: '#0770BC' + }, + 'hljs-doctag': { + color: '#0770BC' + }, + 'hljs-section': { + color: '#0770BC' + }, + 'hljs-selector-class': { + color: '#0770BC' + }, + 'hljs-meta': { + color: '#0770BC' + }, + 'hljs-selector-pseudo': { + color: '#0770BC' + }, + 'hljs-attr': { + color: '#0770BC' + }, + 'hljs-attribute': { + color: '#803378' + }, + 'hljs-name': { + color: '#b75501' + }, + 'hljs-type': { + color: '#b75501' + }, + 'hljs-number': { + color: '#b75501' + }, + 'hljs-selector-id': { + color: '#b75501' + }, + 'hljs-quote': { + color: '#b75501' + }, + 'hljs-template-tag': { + color: '#b75501' + }, + 'hljs-built_in': { + color: '#b75501' + }, + 'hljs-title': { + color: '#b75501' + }, + 'hljs-literal': { + color: '#b75501' + }, + 'hljs-string': { + color: '#9E53C1' + }, + 'hljs-regexp': { + color: '#9E53C1' + }, + 'hljs-symbol': { + color: '#9E53C1' + }, + 'hljs-variable': { + color: '#9E53C1' + }, + 'hljs-template-variable': { + color: '#9E53C1' + }, + 'hljs-link': { + color: '#9E53C1' + }, + 'hljs-selector-attr': { + color: '#9E53C1' + }, + 'hljs-meta-string': { + color: '#9E53C1' + }, + 'hljs-bullet': { + color: '#535a60' + }, + 'hljs-code': { + color: '#535a60' + }, + 'hljs-deletion': { + color: '#c02d2e' + }, + 'hljs-addition': { + color: '#2f6f44' + }, + 'hljs-emphasis': { + fontStyle: 'italic' + }, + 'hljs-strong': { + fontWeight: 'bold' + }, + '-webkit-touch-callout': 'none' /* iOS Safari */, + '-webkit-user-select': 'none' /* Safari */, + '-khtml-user-select': 'none' /* Konqueror HTML */, + '-moz-user-select': 'none' /* Old versions of Firefox */, + '-ms-user-select': 'none' /* Internet Explorer/Edge */, + 'user-select': 'none' /* Non-prefixed version, currently supported by Chrome, Opera and Firefox */ +}; diff --git a/frontend/providers/devbox/components/YamlCode/index.module.scss b/frontend/providers/devbox/components/YamlCode/index.module.scss new file mode 100644 index 00000000000..883b64a970c --- /dev/null +++ b/frontend/providers/devbox/components/YamlCode/index.module.scss @@ -0,0 +1,6 @@ +.markdown { + height: '100%'; + div { + overflow: visible !important; + } +} diff --git a/frontend/providers/devbox/components/YamlCode/index.tsx b/frontend/providers/devbox/components/YamlCode/index.tsx new file mode 100644 index 00000000000..7f9ce5cda8f --- /dev/null +++ b/frontend/providers/devbox/components/YamlCode/index.tsx @@ -0,0 +1,48 @@ +import { useMemo } from 'react' +import ReactMarkdown from 'react-markdown' +import SyntaxHighlighter from 'react-syntax-highlighter' + +import { codeTheme } from './hljs' + +import styles from './index.module.scss' + +type TMarkDown = { + content: string + [key: string]: any +} + +const YamlCode = ({ content, ...props }: TMarkDown) => { + const code = useMemo(() => '```yaml\n' + content + '```', [content]) + + return ( + + ) : ( + + {children} + + ) + } + }} + /> + ) +} + +export default YamlCode diff --git a/frontend/providers/devbox/components/modals/CustomAccessModal.tsx b/frontend/providers/devbox/components/modals/CustomAccessModal.tsx new file mode 100644 index 00000000000..063192325e6 --- /dev/null +++ b/frontend/providers/devbox/components/modals/CustomAccessModal.tsx @@ -0,0 +1,112 @@ +import { + Box, + ModalBody, + ModalCloseButton, + BoxProps, + Flex, + useTheme, + Input, + ModalFooter, + Button, + Modal, + ModalOverlay, + ModalContent, + ModalHeader +} from '@chakra-ui/react' +import { Tip } from '@sealos/ui' +import { useTranslations } from 'next-intl' +import React, { useMemo, useRef } from 'react' +import { InfoOutlineIcon } from '@chakra-ui/icons' + +import { postAuthCname } from '@/api/platform' +import { useRequest } from '@/hooks/useRequest' +import { SEALOS_DOMAIN } from '@/stores/static' + +export type CustomAccessModalParams = { + publicDomain: string + customDomain: string +} + +const CustomAccessModal = ({ + publicDomain, + customDomain, + onClose, + onSuccess +}: CustomAccessModalParams & { onClose: () => void; onSuccess: (e: string) => void }) => { + const ref = useRef(null) + const theme = useTheme() + const t = useTranslations() + + const titleStyles: BoxProps = { + fontWeight: 'bold', + mb: 2 + } + + const completePublicDomain = useMemo(() => `${publicDomain}.${SEALOS_DOMAIN}`, [publicDomain]) + + const { mutate: authCNAME, isLoading } = useRequest({ + mutationFn: async () => { + const val = ref.current?.value || '' + if (!val) { + return onSuccess('') + } + await postAuthCname({ + publicDomain: completePublicDomain, + customDomain: val + }) + return val + }, + onSuccess, + errorToast: 'Custom Domain Error' + }) + + return ( + <> + + + + {t('Custom Domain')} + + + CNAME + + {completePublicDomain} + + + {t('Custom Domain')} + + + + } + text={t('CNAME Tips', { domain: completePublicDomain })} + /> + + + + + + + + ) +} + +export default CustomAccessModal diff --git a/frontend/providers/devbox/components/modals/DelModal.tsx b/frontend/providers/devbox/components/modals/DelModal.tsx new file mode 100644 index 00000000000..61d28f09b40 --- /dev/null +++ b/frontend/providers/devbox/components/modals/DelModal.tsx @@ -0,0 +1,120 @@ +import { + Box, + Button, + Flex, + Input, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Text +} from '@chakra-ui/react' +import { useMessage } from '@sealos/ui' +import { useTranslations } from 'next-intl' +import { useCallback, useState } from 'react' + +import MyIcon from '@/components/Icon' +import { delDevbox } from '@/api/devbox' +import { DevboxDetailType, DevboxListItemType } from '@/types/devbox' + +const DelModal = ({ + devbox, + onClose, + onSuccess +}: { + devbox: DevboxListItemType | DevboxDetailType + onClose: () => void + onSuccess: () => void +}) => { + const t = useTranslations() + const { message: toast } = useMessage() + const [loading, setLoading] = useState(false) + const [inputValue, setInputValue] = useState('') + + const handleDelDevbox = useCallback(async () => { + try { + setLoading(true) + const networks = devbox.networks.map((item) => item.networkName) + await delDevbox(devbox.name, networks) + toast({ + title: t('delete_successful'), + status: 'success' + }) + onSuccess() + onClose() + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('delete_failed'), + status: 'error' + }) + console.error(error) + } + setLoading(false) + }, [devbox.networks, devbox.name, toast, t, onSuccess, onClose]) + + return ( + + + + + + + {t('delete_warning')} + + + + + {t('delete_warning_content')} + + {t('delete_warning_content_2')} + + + {t.rich('please_enter_devbox_name_confirm', { + name: devbox.name, + strong: (chunks) => ( + + {chunks} + + ) + })} + + setInputValue(e.target.value)} + mt={4} + w={'100%'} + border={'1px solid'} + borderColor={'grayModern.300'} + borderRadius={'4px'} + p={2} + /> + + + + + + + + ) +} + +export default DelModal diff --git a/frontend/providers/devbox/components/modals/EditVersionDesModal.tsx b/frontend/providers/devbox/components/modals/EditVersionDesModal.tsx new file mode 100644 index 00000000000..aece104f94b --- /dev/null +++ b/frontend/providers/devbox/components/modals/EditVersionDesModal.tsx @@ -0,0 +1,90 @@ +import { + Box, + Button, + Flex, + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, + Textarea +} from '@chakra-ui/react' +import { useMessage } from '@sealos/ui' +import { useTranslations } from 'next-intl' +import { useCallback, useState } from 'react' + +import { editDevboxVersion } from '@/api/devbox' +import { DevboxVersionListItemType } from '@/types/devbox' + +const EditVersionDesModal = ({ + version, + onClose, + isOpen, + onSuccess +}: { + version: DevboxVersionListItemType + isOpen: boolean + onClose: () => void + onSuccess: () => void +}) => { + const t = useTranslations() + const { message: toast } = useMessage() + const [loading, setLoading] = useState(false) + const [inputValue, setInputValue] = useState(version.description) + const handleEditVersionDes = useCallback(async () => { + try { + setLoading(true) + await editDevboxVersion({ + name: version.name, + releaseDes: inputValue + }) + toast({ + title: t('edit_successful'), + status: 'success' + }) + onSuccess() + onClose() + } catch (error: any) { + toast({ + title: typeof error === 'string' ? error : error.message || t('edit_failed'), + status: 'error' + }) + console.error(error) + } + setLoading(false) + }, [version.name, inputValue, toast, t, onSuccess, onClose]) + + return ( + + + + + + {t('edit_version_description')} + + + + + + {t('version_description')} +