├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── feature_request.yml │ └── question.yml └── workflows │ ├── build-scripts-updated.yml │ ├── build-sealos-cluster-image.yml │ ├── dockerize-server.yml │ ├── dockerize-web.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── deploy ├── Kubefile ├── etc │ └── sealaf │ │ └── .env.tmpl ├── manifests │ ├── appcr.yaml.tmpl │ ├── deploy.yaml.tmpl │ ├── ingress.yaml.tmpl │ ├── mongodb.yaml.tmpl │ └── serviceaccount.yaml └── scripts │ ├── gen-mongodb-uri.sh │ └── init.sh ├── lerna.json ├── package-lock.json ├── package.json ├── server ├── .dockerignore ├── .eslintrc ├── .gitignore ├── Dockerfile ├── README.md ├── build-image.sh ├── nest-cli.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.ts │ ├── app.interceptor.ts │ ├── app.module.ts │ ├── app.service.ts │ ├── application │ │ ├── application-task.service.ts │ │ ├── application.controller.ts │ │ ├── application.module.ts │ │ ├── application.service.ts │ │ ├── bundle.service.ts │ │ ├── configuration.service.ts │ │ ├── dto │ │ │ ├── bind-custom-domain.dto.ts │ │ │ ├── create-application.dto.ts │ │ │ ├── create-autoscaling.dto.ts │ │ │ ├── create-env.dto.ts │ │ │ ├── pod.dto.ts │ │ │ └── update-application.dto.ts │ │ ├── entities │ │ │ ├── application-bundle.ts │ │ │ ├── application-configuration.ts │ │ │ ├── application.ts │ │ │ └── runtime.ts │ │ ├── environment.controller.ts │ │ ├── environment.service.ts │ │ ├── events │ │ │ ├── application-bundle-updating.event.ts │ │ │ └── application-creating.event.ts │ │ ├── pod.controller.ts │ │ └── pod.service.ts │ ├── authentication │ │ ├── application.auth.guard.ts │ │ ├── authentication.controller.ts │ │ ├── authentication.module.ts │ │ ├── authentication.service.ts │ │ ├── dto │ │ │ ├── pat2token.dto.ts │ │ │ └── signin.dto.ts │ │ ├── jwt.auth.guard.ts │ │ ├── jwt.strategy.ts │ │ └── sealos-manager.guard.ts │ ├── billing │ │ ├── billing.module.ts │ │ ├── billing.service.ts │ │ ├── dto │ │ │ └── calculate-price.dto.ts │ │ ├── entities │ │ │ └── resource.ts │ │ ├── resource.controller.ts │ │ └── resource.service.ts │ ├── constants.ts │ ├── database │ │ ├── collection │ │ │ ├── collection.controller.ts │ │ │ └── collection.service.ts │ │ ├── database.controller.ts │ │ ├── database.module.ts │ │ ├── dedicated-database │ │ │ ├── dedicated-database-task.service.ts │ │ │ └── dedicated-database.service.ts │ │ ├── dto │ │ │ ├── create-collection.dto.ts │ │ │ ├── create-dedicated-database.dto.ts │ │ │ ├── import-database.dto.ts │ │ │ └── update-collection.dto.ts │ │ ├── entities │ │ │ ├── collection.ts │ │ │ ├── database-sync-record.ts │ │ │ └── dedicated-database.ts │ │ ├── listeners │ │ │ └── application.listener.ts │ │ ├── mongo.service.ts │ │ └── monitor │ │ │ ├── monitor.controller.ts │ │ │ └── monitor.service.ts │ ├── dependency │ │ ├── dependency.controller.ts │ │ ├── dependency.module.ts │ │ ├── dependency.service.ts │ │ └── dto │ │ │ ├── create-dependency.dto.ts │ │ │ ├── delete-dependency.dto.ts │ │ │ └── update-dependency.dto.ts │ ├── function-template │ │ ├── dto │ │ │ ├── create-function-template.dto.ts │ │ │ ├── function-template-usedBy.dto.ts │ │ │ ├── function-templates.dto.ts │ │ │ ├── update-function-template.dto.ts │ │ │ └── use-function-template.dto.ts │ │ ├── entities │ │ │ └── function-template.ts │ │ ├── function-template.controller.ts │ │ ├── function-template.module.ts │ │ └── function-template.service.ts │ ├── function │ │ ├── dto │ │ │ ├── compile-function.dto.ts │ │ │ ├── create-function.dto.ts │ │ │ ├── update-function-debug.dto.ts │ │ │ └── update-function.dto.ts │ │ ├── entities │ │ │ ├── cloud-function-history.ts │ │ │ └── cloud-function.ts │ │ ├── function.controller.ts │ │ ├── function.module.ts │ │ └── function.service.ts │ ├── gateway │ │ ├── certificate.service.ts │ │ ├── entities │ │ │ └── runtime-domain.ts │ │ ├── gateway.module.ts │ │ ├── ingress │ │ │ └── runtime-ingress.service.ts │ │ ├── runtime-domain-task.service.ts │ │ └── runtime-domain.service.ts │ ├── generated │ │ └── i18n.generated.ts │ ├── i18n │ │ ├── en │ │ │ ├── function.json │ │ │ ├── notification.json │ │ │ └── subscription.json │ │ ├── zh-CN │ │ │ ├── function.json │ │ │ ├── notification.json │ │ │ └── subscription.json │ │ └── zh │ │ │ ├── function.json │ │ │ ├── notification.json │ │ │ └── subscription.json │ ├── initializer │ │ ├── deploy-manifest │ │ │ ├── database.yaml │ │ │ └── databaseOpsRequest.yaml │ │ ├── initializer.module.ts │ │ └── initializer.service.ts │ ├── instance │ │ ├── instance-task.service.ts │ │ ├── instance.module.ts │ │ └── instance.service.ts │ ├── interceptor │ │ ├── dto │ │ │ └── http-interceptor.dto.ts │ │ ├── http-interceptor.service.ts │ │ └── interceptor.module.ts │ ├── log │ │ ├── entities │ │ │ └── function-log.ts │ │ ├── log.controller.ts │ │ └── log.module.ts │ ├── main.ts │ ├── monitor │ │ ├── dto │ │ │ └── query-metrics.dto.ts │ │ ├── monitor.controller.ts │ │ ├── monitor.module.ts │ │ └── monitor.service.ts │ ├── recycle-bin │ │ ├── cloud-function │ │ │ ├── dto │ │ │ │ ├── delete-recycle-bin-functions.dto.ts │ │ │ │ ├── get-recycle-bin-functions.dto.ts │ │ │ │ └── restore-recycle-bin-functions.dto.ts │ │ │ ├── function-recycle-bin.controller.ts │ │ │ ├── function-recycle-bin.service.ts │ │ │ └── interface │ │ │ │ └── function-recycle-bin-query.interface.ts │ │ ├── entities │ │ │ └── recycle-bin.ts │ │ └── recycle-bin.module.ts │ ├── region │ │ ├── cluster │ │ │ ├── cluster.service.spec.ts │ │ │ ├── cluster.service.ts │ │ │ └── types.ts │ │ ├── entities │ │ │ └── region.ts │ │ ├── region.controller.ts │ │ ├── region.module.ts │ │ └── region.service.ts │ ├── runtime-builtin-deps.ts │ ├── setting │ │ ├── entities │ │ │ └── setting.ts │ │ ├── setting.controller.ts │ │ ├── setting.module.ts │ │ └── setting.service.ts │ ├── storage │ │ ├── cloud-bin-bucket.service.ts │ │ └── storage.module.ts │ ├── system-database.ts │ ├── trigger │ │ ├── cron-job.service.ts │ │ ├── dto │ │ │ ├── create-trigger.dto.ts │ │ │ └── update-trigger.dto.ts │ │ ├── entities │ │ │ └── cron-trigger.ts │ │ ├── trigger-task.service.ts │ │ ├── trigger.controller.ts │ │ ├── trigger.module.ts │ │ └── trigger.service.ts │ ├── user │ │ ├── dto │ │ │ └── create-pat.dto.ts │ │ ├── entities │ │ │ ├── pat.ts │ │ │ └── user.ts │ │ ├── pat.controller.ts │ │ ├── pat.service.ts │ │ ├── user.controller.ts │ │ ├── user.module.ts │ │ └── user.service.ts │ └── utils │ │ ├── crypto.ts │ │ ├── decorator.ts │ │ ├── getter.ts │ │ ├── interface.ts │ │ ├── lang.ts │ │ ├── number.ts │ │ ├── random.ts │ │ └── response.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.build.json └── tsconfig.json └── web ├── .dockerignore ├── .env ├── .env.production ├── .eslintrc ├── .gitignore ├── .stylelintrc.json ├── .swagger.config.js ├── Dockerfile ├── README.md ├── index.html ├── nginx.conf ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public ├── apple-touch-icon.png ├── bg.png ├── favicon.ico ├── fonts │ └── NotoSansSC-Medium.ttf ├── homepage │ ├── Vector.svg │ ├── banner.png │ ├── bg.png │ ├── cancel_btn.svg │ ├── database.png │ ├── database.svg │ ├── discord.svg │ ├── discord_2.svg │ ├── forum.svg │ ├── forum1.svg │ ├── function.png │ ├── functions.svg │ ├── icon_01.svg │ ├── icon_02.svg │ ├── icon_03.svg │ ├── icon_04.svg │ ├── icon_05.svg │ ├── icon_06.png │ ├── joinbg.svg │ ├── laficon.jpg │ ├── logo_icon.svg │ ├── logo_text.png │ ├── p1.png │ ├── p2.png │ ├── p3.png │ ├── p4.png │ ├── p5.png │ ├── play.svg │ ├── storage.png │ ├── storage.svg │ ├── videobg.png │ ├── videomobile.png │ ├── wechat_01.svg │ └── wechat_02.svg ├── js │ ├── aws-sdk-2.1275.0.min.js │ └── monaco-editor.0.43.0 │ │ ├── base │ │ ├── browser │ │ │ └── ui │ │ │ │ └── codicons │ │ │ │ └── codicon │ │ │ │ └── codicon.ttf │ │ ├── common │ │ │ └── worker │ │ │ │ ├── simpleWorker.nls.de.js │ │ │ │ ├── simpleWorker.nls.es.js │ │ │ │ ├── simpleWorker.nls.fr.js │ │ │ │ ├── simpleWorker.nls.it.js │ │ │ │ ├── simpleWorker.nls.ja.js │ │ │ │ ├── simpleWorker.nls.js │ │ │ │ ├── simpleWorker.nls.ko.js │ │ │ │ ├── simpleWorker.nls.ru.js │ │ │ │ ├── simpleWorker.nls.zh-cn.js │ │ │ │ └── simpleWorker.nls.zh-tw.js │ │ └── worker │ │ │ └── workerMain.js │ │ ├── basic-languages │ │ ├── abap │ │ │ └── abap.js │ │ ├── apex │ │ │ └── apex.js │ │ ├── azcli │ │ │ └── azcli.js │ │ ├── bat │ │ │ └── bat.js │ │ ├── bicep │ │ │ └── bicep.js │ │ ├── cameligo │ │ │ └── cameligo.js │ │ ├── clojure │ │ │ └── clojure.js │ │ ├── coffee │ │ │ └── coffee.js │ │ ├── cpp │ │ │ └── cpp.js │ │ ├── csharp │ │ │ └── csharp.js │ │ ├── csp │ │ │ └── csp.js │ │ ├── css │ │ │ └── css.js │ │ ├── cypher │ │ │ └── cypher.js │ │ ├── dart │ │ │ └── dart.js │ │ ├── dockerfile │ │ │ └── dockerfile.js │ │ ├── ecl │ │ │ └── ecl.js │ │ ├── elixir │ │ │ └── elixir.js │ │ ├── flow9 │ │ │ └── flow9.js │ │ ├── freemarker2 │ │ │ └── freemarker2.js │ │ ├── fsharp │ │ │ └── fsharp.js │ │ ├── go │ │ │ └── go.js │ │ ├── graphql │ │ │ └── graphql.js │ │ ├── handlebars │ │ │ └── handlebars.js │ │ ├── hcl │ │ │ └── hcl.js │ │ ├── html │ │ │ └── html.js │ │ ├── ini │ │ │ └── ini.js │ │ ├── java │ │ │ └── java.js │ │ ├── javascript │ │ │ └── javascript.js │ │ ├── julia │ │ │ └── julia.js │ │ ├── kotlin │ │ │ └── kotlin.js │ │ ├── less │ │ │ └── less.js │ │ ├── lexon │ │ │ └── lexon.js │ │ ├── liquid │ │ │ └── liquid.js │ │ ├── lua │ │ │ └── lua.js │ │ ├── m3 │ │ │ └── m3.js │ │ ├── markdown │ │ │ └── markdown.js │ │ ├── mdx │ │ │ └── mdx.js │ │ ├── mips │ │ │ └── mips.js │ │ ├── msdax │ │ │ └── msdax.js │ │ ├── mysql │ │ │ └── mysql.js │ │ ├── objective-c │ │ │ └── objective-c.js │ │ ├── pascal │ │ │ └── pascal.js │ │ ├── pascaligo │ │ │ └── pascaligo.js │ │ ├── perl │ │ │ └── perl.js │ │ ├── pgsql │ │ │ └── pgsql.js │ │ ├── php │ │ │ └── php.js │ │ ├── pla │ │ │ └── pla.js │ │ ├── postiats │ │ │ └── postiats.js │ │ ├── powerquery │ │ │ └── powerquery.js │ │ ├── powershell │ │ │ └── powershell.js │ │ ├── protobuf │ │ │ └── protobuf.js │ │ ├── pug │ │ │ └── pug.js │ │ ├── python │ │ │ └── python.js │ │ ├── qsharp │ │ │ └── qsharp.js │ │ ├── r │ │ │ └── r.js │ │ ├── razor │ │ │ └── razor.js │ │ ├── redis │ │ │ └── redis.js │ │ ├── redshift │ │ │ └── redshift.js │ │ ├── restructuredtext │ │ │ └── restructuredtext.js │ │ ├── ruby │ │ │ └── ruby.js │ │ ├── rust │ │ │ └── rust.js │ │ ├── sb │ │ │ └── sb.js │ │ ├── scala │ │ │ └── scala.js │ │ ├── scheme │ │ │ └── scheme.js │ │ ├── scss │ │ │ └── scss.js │ │ ├── shell │ │ │ └── shell.js │ │ ├── solidity │ │ │ └── solidity.js │ │ ├── sophia │ │ │ └── sophia.js │ │ ├── sparql │ │ │ └── sparql.js │ │ ├── sql │ │ │ └── sql.js │ │ ├── st │ │ │ └── st.js │ │ ├── swift │ │ │ └── swift.js │ │ ├── systemverilog │ │ │ └── systemverilog.js │ │ ├── tcl │ │ │ └── tcl.js │ │ ├── twig │ │ │ └── twig.js │ │ ├── typescript │ │ │ └── typescript.js │ │ ├── vb │ │ │ └── vb.js │ │ ├── wgsl │ │ │ └── wgsl.js │ │ ├── xml │ │ │ └── xml.js │ │ └── yaml │ │ │ └── yaml.js │ │ ├── editor │ │ ├── editor.main.css │ │ ├── editor.main.js │ │ ├── editor.main.nls.de.js │ │ ├── editor.main.nls.es.js │ │ ├── editor.main.nls.fr.js │ │ ├── editor.main.nls.it.js │ │ ├── editor.main.nls.ja.js │ │ ├── editor.main.nls.js │ │ ├── editor.main.nls.ko.js │ │ ├── editor.main.nls.ru.js │ │ ├── editor.main.nls.zh-cn.js │ │ └── editor.main.nls.zh-tw.js │ │ ├── language │ │ ├── css │ │ │ ├── cssMode.js │ │ │ └── cssWorker.js │ │ ├── html │ │ │ ├── htmlMode.js │ │ │ └── htmlWorker.js │ │ ├── json │ │ │ ├── jsonMode.js │ │ │ └── jsonWorker.js │ │ └── typescript │ │ │ ├── tsMode.js │ │ │ └── tsWorker.js │ │ └── loader.js ├── locales │ ├── en │ │ └── translation.json │ ├── zh-CN │ │ └── translation.json │ └── zh │ │ └── translation.json ├── logo.png ├── logo_light.png ├── logo_text.png ├── masked-icon.png ├── node.svg ├── pwa-192x192.png ├── pwa-512x512.png ├── pwa-64x64.png └── robots.txt ├── src ├── App.css ├── App.tsx ├── apis │ ├── dependence.ts │ ├── typing.d.ts │ └── v1 │ │ ├── api-auto.d.ts │ │ ├── applications.ts │ │ ├── apps.ts │ │ ├── auth.ts │ │ ├── function-templates.ts │ │ ├── monitor.ts │ │ ├── pats.ts │ │ ├── recycle-bin.ts │ │ ├── regions.ts │ │ ├── resources.ts │ │ ├── runtimes.ts │ │ ├── settings.ts │ │ └── user.ts ├── assets │ ├── login_bg.svg │ └── react.svg ├── chakraTheme.ts ├── chakraThemeDark.ts ├── components │ ├── ColorModeSwitch │ │ └── index.tsx │ ├── CommonIcon │ │ └── index.tsx │ ├── ConfirmButton │ │ └── index.tsx │ ├── ConfirmDialog │ │ └── index.tsx │ ├── Content │ │ └── index.tsx │ ├── CopyText │ │ └── index.tsx │ ├── DateRangePicker │ │ ├── index.css │ │ └── index.tsx │ ├── DependenceList │ │ ├── index.module.scss │ │ └── index.tsx │ ├── DotBadge │ │ └── index.tsx │ ├── EditableTable │ │ ├── EditableTr │ │ │ └── index.tsx │ │ ├── NormalTr │ │ │ └── index.tsx │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Editor │ │ ├── CommonDiffEditor.tsx │ │ ├── ENVCodeEditor.tsx │ │ ├── ENVEditor.tsx │ │ ├── FunctionEditor.tsx │ │ ├── JSONEditor.tsx │ │ ├── JSONViewer.tsx │ │ ├── LanguageClient.ts │ │ ├── TSEditor.tsx │ │ ├── TextModel.tsx │ │ ├── index.css │ │ ├── index.scss │ │ ├── typesResolve │ │ │ ├── globals.ts │ │ │ ├── index.ts │ │ │ ├── objectidType.ts │ │ │ ├── requestType.ts │ │ │ ├── responseType.ts │ │ │ ├── streamType.ts │ │ │ └── websocketType.ts │ │ └── useWorker.ts │ ├── EmptyBox │ │ └── index.tsx │ ├── FileStatusIcon │ │ └── index.tsx │ ├── FileTypeIcon │ │ └── index.tsx │ ├── FileUpload │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Grid │ │ └── index.tsx │ ├── IconText │ │ ├── index.module.scss │ │ └── index.tsx │ ├── IconWrap │ │ └── index.tsx │ ├── InitSealosApp │ │ └── index.tsx │ ├── InputTag │ │ └── index.tsx │ ├── LanguageSwitch │ │ └── index.tsx │ ├── LogoIcon │ │ └── index.tsx │ ├── Markdown │ │ ├── codeLight.ts │ │ ├── formatLinkText.ts │ │ ├── index.module.scss │ │ └── index.tsx │ ├── MoreButton │ │ └── index.tsx │ ├── Pagination │ │ └── index.tsx │ ├── Panel │ │ ├── Header.tsx │ │ ├── index.module.scss │ │ └── index.tsx │ ├── PopConfirm │ │ └── index.tsx │ ├── Resize │ │ └── index.tsx │ ├── SectionList │ │ ├── index.module.scss │ │ └── index.tsx │ ├── SmsCodeInput │ │ └── index.tsx │ ├── TextButton │ │ └── index.tsx │ └── UpgradePrompt │ │ └── index.tsx ├── constants │ ├── .gitkeep │ └── index.ts ├── hooks │ ├── useAwsS3.ts │ ├── useCustomToast.ts │ ├── useDB.ts │ ├── useFunctionCache.ts │ ├── useHotKey.ts │ ├── useInviteCode.ts │ └── useResizable.ts ├── layouts │ ├── Auth │ │ ├── index.module.scss │ │ └── index.tsx │ ├── Basic │ │ └── index.tsx │ ├── Function.tsx │ ├── Header │ │ └── index.tsx │ └── Template.tsx ├── main.tsx ├── pages │ ├── 403.tsx │ ├── 404.tsx │ ├── app │ │ ├── database │ │ │ ├── BottomPanel │ │ │ │ └── index.tsx │ │ │ ├── CollectionDataList │ │ │ │ ├── index.tsx │ │ │ │ └── mods │ │ │ │ │ ├── ColPanel │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── DataPanel │ │ │ │ │ ├── index.module.scss │ │ │ │ │ └── index.tsx │ │ │ │ │ └── IndexPanel │ │ │ │ │ ├── addIndexModal │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ ├── CollectionListPanel │ │ │ │ └── index.tsx │ │ │ ├── RightComponent │ │ │ │ ├── DeleteButton.tsx │ │ │ │ ├── EditBox.tsx │ │ │ │ └── List.tsx │ │ │ ├── index.tsx │ │ │ ├── mods │ │ │ │ ├── AddDataModal │ │ │ │ │ └── index.tsx │ │ │ │ ├── AddIndexModal │ │ │ │ │ └── index.tsx │ │ │ │ ├── CreateCollectionModal │ │ │ │ │ └── index.tsx │ │ │ │ ├── DeleteCollectionModal │ │ │ │ │ └── index.tsx │ │ │ │ └── IndexModal │ │ │ │ │ └── index.tsx │ │ │ ├── service.ts │ │ │ └── store.ts │ │ ├── functions │ │ │ ├── index.tsx │ │ │ ├── mods │ │ │ │ ├── AIChatPanel │ │ │ │ │ └── index.tsx │ │ │ │ ├── BottomPanel │ │ │ │ │ └── index.tsx │ │ │ │ ├── ConsolePanel │ │ │ │ │ └── index.tsx │ │ │ │ ├── DebugPanel │ │ │ │ │ ├── BodyParamsTab │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── HeaderParamsTab │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── QueryParamsTab │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── DependencePanel │ │ │ │ │ ├── AddDependenceModal │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── service.ts │ │ │ │ ├── DeployButton │ │ │ │ │ └── index.tsx │ │ │ │ ├── EditorPanel │ │ │ │ │ └── index.tsx │ │ │ │ ├── FunctionPanel │ │ │ │ │ ├── ContextMenu │ │ │ │ │ │ ├── index.scss │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── CreateModal │ │ │ │ │ │ ├── functionTemplates.ts │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── HeadPanel │ │ │ │ │ ├── FunctionDetailPopOver.tsx │ │ │ │ │ ├── index.css │ │ │ │ │ └── index.tsx │ │ │ │ ├── RecycleBinModal │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── service.ts │ │ │ │ ├── TriggerModal │ │ │ │ │ ├── AddTriggerModal │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── service.ts │ │ │ │ └── VersionHistoryPanel │ │ │ │ │ ├── FetchModal │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ ├── service.ts │ │ │ └── store.ts │ │ ├── index.tsx │ │ ├── mods │ │ │ ├── SideBar │ │ │ │ ├── Icons.tsx │ │ │ │ ├── index.module.scss │ │ │ │ └── index.tsx │ │ │ └── StatusBar │ │ │ │ ├── LSPBar │ │ │ │ └── index.tsx │ │ │ │ ├── LogsModal │ │ │ │ ├── InitLog.tsx │ │ │ │ ├── index.scss │ │ │ │ ├── index.tsx │ │ │ │ └── initLog.scss │ │ │ │ ├── MonitorBar │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ └── setting │ │ │ ├── SysSetting │ │ │ ├── AppEnvList │ │ │ │ ├── EditTextarea │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── service.ts │ │ │ ├── AppInfoList │ │ │ │ ├── InfoDetail │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── AppMonitor │ │ │ │ ├── AreaCard │ │ │ │ │ └── index.tsx │ │ │ │ ├── PieCard │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── CommonSetting │ │ │ │ └── index.tsx │ │ │ ├── CustomDomain │ │ │ │ ├── index.tsx │ │ │ │ └── service.ts │ │ │ ├── DatabaseMonitor │ │ │ │ ├── AreaCard │ │ │ │ │ └── index.tsx │ │ │ │ ├── PieCard │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ │ ├── UserSetting │ │ │ ├── PATList │ │ │ │ ├── DateSelector │ │ │ │ │ ├── index.module.scss │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── service.ts │ │ │ ├── index.tsx │ │ │ └── useTabMatch.tsx │ │ │ ├── index.css │ │ │ └── index.tsx │ ├── auth │ │ ├── index.tsx │ │ └── store.ts │ ├── customSetting.ts │ ├── functionTemplate │ │ ├── AllTemplateList │ │ │ └── index.tsx │ │ ├── CreateFuncTemplate │ │ │ ├── Mods │ │ │ │ ├── AddDependenceModal.tsx │ │ │ │ ├── AddEnvironmentsModal.tsx │ │ │ │ └── AddFunctionModal.tsx │ │ │ └── index.tsx │ │ ├── FuncTemplateItem │ │ │ ├── TemplateFunctionInfo.tsx │ │ │ └── index.tsx │ │ ├── Mods │ │ │ ├── MonacoEditor │ │ │ │ ├── FunctionPopover.tsx │ │ │ │ └── index.tsx │ │ │ ├── TemplateCard │ │ │ │ └── index.tsx │ │ │ ├── TemplateInfo │ │ │ │ ├── SponsorModal │ │ │ │ │ └── index.tsx │ │ │ │ ├── UseTemplate │ │ │ │ │ ├── UseTemplateModal.tsx │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── TemplatePopover │ │ │ │ └── TemplatePopover.tsx │ │ ├── MyTemplateList │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── service.ts │ │ └── store.ts │ ├── globalStore.ts │ ├── home │ │ ├── index.tsx │ │ ├── mods │ │ │ ├── CreateAppModal │ │ │ │ ├── AutoscalingControl │ │ │ │ │ └── index.tsx │ │ │ │ ├── BundleControl │ │ │ │ │ └── index.tsx │ │ │ │ ├── BundleItem │ │ │ │ │ └── index.tsx │ │ │ │ ├── DatabaseBundleControl │ │ │ │ │ └── index.tsx │ │ │ │ ├── RuntimeItem │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── DeleteAppModal │ │ │ │ └── index.tsx │ │ │ ├── Empty │ │ │ │ ├── index.module.scss │ │ │ │ └── index.tsx │ │ │ ├── List │ │ │ │ ├── BundleInfo.tsx │ │ │ │ └── index.tsx │ │ │ └── StatusBadge │ │ │ │ ├── index.module.scss │ │ │ │ └── index.tsx │ │ └── service.ts │ └── siteSetting.ts ├── routes │ └── index.tsx ├── types │ └── index.d.ts ├── utils │ ├── .gitkeep │ ├── format.ts │ ├── getAvatarUrl.ts │ ├── getPageInfo.ts │ ├── getRegionById.ts │ ├── handleData.ts │ ├── i18n.ts │ ├── request.ts │ └── streamFetch.ts └── vite-env.d.ts ├── tailwind.config.cjs ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [!{node_modules}/**] 4 | end_of_line = lf 5 | charset = utf-8 6 | insert_final_newline = true 7 | 8 | [{*.js,*.ts}] 9 | indent_style = space 10 | indent_size = 2 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Join Our Discord 4 | url: https://discord.gg/uWZqAwwdvy 5 | about: Join our community, we will keep you in the loop. 6 | - name: Contact US / Business Inquiries 7 | url: https://docs.qq.com/form/page/DS0tCWXpQc2NpR3dR 8 | about: Please contact us. -------------------------------------------------------------------------------- /.github/workflows/build-scripts-updated.yml: -------------------------------------------------------------------------------- 1 | name: build-scripts-updated 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - "build/**" 10 | - ".github/workflows/build-scripts-updated.yml" 11 | - ".github/workflows/build-sealos-cluster-image.yml" 12 | - "!**/*.md" 13 | 14 | concurrency: 15 | group: build-scripts-updated-${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | trigger-workflow-build-cluster-image: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: trigger cluster image workflow 23 | uses: peter-evans/repository-dispatch@v2 24 | with: 25 | event-type: build-scripts-updated 26 | client-payload: '{"ref": "${{ github.ref }}", "sha": "${{ github.sha }}", "version": "latest"}' 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | 4 | upload 5 | data/* 6 | tmp 7 | 8 | ecosystem.config.js 9 | .DS_Store 10 | .idea 11 | 12 | coverage 13 | .nyc_output 14 | 15 | kubernetes-dev/mongo-data 16 | 17 | kubernetes-local/ 18 | .vscode/configurationCache.log 19 | .vscode/dryrun.log 20 | .vscode/targets.log 21 | 22 | .kube/ 23 | 24 | build/*/manifests 25 | build/*/registry 26 | build/*/charts 27 | build/registry 28 | build-dev 29 | docs/.vitepress/.temp 30 | .idea/ 31 | 32 | update-changelog.sh 33 | runtimes/nodejs-esm 34 | yarn.lock 35 | deploy/logs/sealos.log 36 | deploy/registry 37 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint-staged 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "christian-kohler.npm-intellisense", 5 | "streetsidesoftware.code-spell-checker", 6 | "pomdtr.excalidraw-editor", 7 | "Tim-Koehler.helm-intellisense", 8 | "lokalise.i18n-ally", 9 | "stylelint.vscode-stylelint", 10 | "EditorConfig.EditorConfig" 11 | ] 12 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## What is Sealaf? 2 | 3 | Sealaf is a product that deeply integrates [Laf](https://github.com/labring/laf) and [Sealos](https://github.com/labring/sealos). As a function computing application of Sealos, it can leverage the powerful BaaS (Backend as a Service) capabilities provided by Sealos, offering users out-of-the-box development capabilities. 4 | 5 | ## How to build 6 | 7 | ```bash 8 | # in web directory 9 | docker build -t docker.io/zacharywin/sealaf-web:latest -f Dockerfile . 10 | 11 | # in server directory 12 | docker build -t docker.io/zacharywin/sealaf-server:latest -f Dockerfile . 13 | 14 | # in deploy directory 15 | sealos build -t docker.io/zacharywin/sealaf:latest --platform linux/amd64 -f Kubefile . 16 | ``` 17 | 18 | ## How to deploy 19 | 20 | ```bash 21 | # 1. Make sure that object storage is installed 22 | curl -O https://raw.githubusercontent.com/minio/operator/master/helm-releases/operator-5.0.6.tgz 23 | helm install --namespace minio-system --create-namespace minio-operator operator-5.0.6.tgz 24 | sealos run ghcr.io/labring/sealos-cloud-objectstorage:latest -e cloudDomain="127.0.0.1.nip.io" 25 | 26 | # 2. Copy the wildcard-cert secret to sealaf-system 27 | 28 | # 3. Install sealaf 29 | sealos run docker.io/zacharywin/sealaf:latest --env cloudDomain="127.0.0.1.nip.io" 30 | ``` 31 | -------------------------------------------------------------------------------- /deploy/Kubefile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | USER 65532:65532 4 | 5 | COPY etc etc 6 | COPY scripts scripts 7 | COPY manifests manifests 8 | COPY registry registry 9 | 10 | ENV cloudDomain="127.0.0.1.nip.io" 11 | ENV cloudPort="" 12 | 13 | ENV certSecretName="wildcard-cert" 14 | 15 | ENV mongodbUri="" 16 | ENV appMonitorUrl="http://launchpad-monitor.sealos.svc.cluster.local:8428/query" 17 | ENV databaseMonitorUrl="http://database-monitor.sealos.svc.cluster.local:9090/query" 18 | ENV runtimeInitImage="docker.io/lafyun/runtime-node-init:latest" 19 | ENV runtimeImage="docker.io/lafyun/runtime-node:latest" 20 | 21 | CMD ["bash scripts/init.sh"] -------------------------------------------------------------------------------- /deploy/etc/sealaf/.env.tmpl: -------------------------------------------------------------------------------- 1 | mongodbUri={{ .mongodbUri }} 2 | appMonitorUrl={{ .appMonitorUrl }} 3 | databaseMonitorUrl={{ .databaseMonitorUrl }} 4 | certSecretName={{ .certSecretName }} -------------------------------------------------------------------------------- /deploy/manifests/appcr.yaml.tmpl: -------------------------------------------------------------------------------- 1 | apiVersion: app.sealos.io/v1 2 | kind: App 3 | metadata: 4 | name: sealaf 5 | namespace: app-system 6 | spec: 7 | data: 8 | desc: Cloud development 9 | url: "https://sealaf.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}" 10 | icon: "https://sealaf.{{ .cloudDomain }}{{ if .cloudPort }}:{{ .cloudPort }}{{ end }}/favicon.ico" 11 | i18n: 12 | zh: 13 | name: 云开发 14 | zh-Hans: 15 | name: 云开发 16 | menuData: 17 | helpDropDown: false 18 | nameColor: text-black 19 | name: Function Service 20 | type: iframe 21 | displayType: normal -------------------------------------------------------------------------------- /deploy/manifests/mongodb.yaml.tmpl: -------------------------------------------------------------------------------- 1 | apiVersion: apps.kubeblocks.io/v1alpha1 2 | kind: Cluster 3 | metadata: 4 | finalizers: 5 | - cluster.kubeblocks.io/finalizer 6 | generation: 1 7 | labels: 8 | clusterdefinition.kubeblocks.io/name: mongodb 9 | clusterversion.kubeblocks.io/name: mongodb-5.0 10 | name: sealaf-mongodb 11 | namespace: sealaf-system 12 | spec: 13 | clusterDefinitionRef: mongodb 14 | clusterVersionRef: mongodb-5.0 15 | componentSpecs: 16 | - componentDefRef: mongodb 17 | monitor: true 18 | name: mongodb 19 | replicas: 1 20 | resources: 21 | limits: 22 | cpu: "1" 23 | memory: 2Gi 24 | requests: 25 | cpu: "0.5" 26 | memory: 1Gi 27 | serviceAccountName: sealaf-mongodb 28 | volumeClaimTemplates: 29 | - name: data 30 | spec: 31 | accessModes: 32 | - ReadWriteOnce 33 | resources: 34 | requests: 35 | storage: 10Gi 36 | terminationPolicy: Delete 37 | -------------------------------------------------------------------------------- /deploy/scripts/gen-mongodb-uri.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | namespace="sealaf-system" 3 | secret_name="sealaf-mongodb-conn-credential" 4 | 5 | secret_data=$(kubectl get secret -n $namespace $secret_name -o go-template='{{range $k,$v := .data}}{{printf "%s: " $k}}{{if not $v}}{{$v}}{{else}}{{$v | base64decode}}{{end}}{{"\n"}}{{end}}') 6 | 7 | endpoint=$(echo "$secret_data" | awk -F': ' '/endpoint/ {print $2}') 8 | headlessEndpoint=$(echo "$secret_data" | awk -F': ' '/headlessEndpoint/ {print $2}') 9 | headlessHost=$(echo "$secret_data" | awk -F': ' '/headlessHost/ {print $2}') 10 | headlessPort=$(echo "$secret_data" | awk -F': ' '/headlessPort/ {print $2}') 11 | host=$(echo "$secret_data" | awk -F': ' '/host/ {print $2}') 12 | password=$(echo "$secret_data" | awk -F': ' '/password/ {print $2}') 13 | port=$(echo "$secret_data" | awk -F': ' '/port/ {print $2}') 14 | username=$(echo "$secret_data" | awk -F': ' '/username/ {print $2}') 15 | 16 | mongodb_uri="mongodb://$username:$password@$headlessEndpoint/sys_db?authSource=admin&replicaSet=sealaf-mongodb-mongodb&w=majority" 17 | 18 | echo "$mongodb_uri" -------------------------------------------------------------------------------- /deploy/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source etc/sealaf/.env 5 | 6 | NAMESPACE=sealaf-system 7 | kubectl create ns $NAMESPACE || true 8 | 9 | function gen_mongodbUri() { 10 | # if mongodbUri is empty then create mongodb and gen mongodb uri 11 | if [ -z "$mongodbUri" ]; then 12 | echo "no mongodb uri found, create mongodb and gen mongodb uri" 13 | kubectl apply -f manifests/mongodb.yaml 14 | echo "waiting for mongodb secret generated" 15 | message="Waiting for MongoDB ready" 16 | # if there is no sealos-mongodb-conn-credential secret then wait for mongodb ready 17 | while [ -z "$(kubectl get secret -n $NAMESPACE sealaf-mongodb-conn-credential 2>/dev/null)" ]; do 18 | echo -ne "\r$message \e[K" 19 | sleep 0.5 20 | echo -ne "\r$message . \e[K" 21 | sleep 0.5 22 | echo -ne "\r$message .. \e[K" 23 | sleep 0.5 24 | echo -ne "\r$message ...\e[K" 25 | sleep 0.5 26 | done 27 | echo "mongodb secret has been generated successfully." 28 | chmod +x scripts/gen-mongodb-uri.sh 29 | mongodbUri=$(scripts/gen-mongodb-uri.sh) 30 | fi 31 | } 32 | 33 | gen_mongodbUri 34 | 35 | SERVER_JWT_SECRET=$(tr -cd 'a-z0-9' ) 33 | - minio client installed (see ) 34 | 35 | ## Start service locally 36 | 37 | ```bash 38 | cd server/ 39 | 40 | # telepresence version v2.16.1 41 | # Install telepresence traffic manager 42 | telepresence helm install 43 | # Connect your computer to laf-dev cluster (namespace laf-system) 44 | telepresence connect -n laf-system 45 | # Connect local server to laf server cluster (namespace laf-system) 46 | telepresence intercept laf-server -p 3000:3000 -e $(pwd)/.env 47 | 48 | npm install 49 | npm run dev 50 | ``` 51 | 52 | > Clean up 53 | 54 | ```bash 55 | telepresence leave laf-server 56 | ``` -------------------------------------------------------------------------------- /server/build-image.sh: -------------------------------------------------------------------------------- 1 | # Warning: just for development phase, will be move to github action in future. 2 | 3 | 4 | 5 | docker buildx build --platform linux/amd64,linux/arm64 --push -t docker.io/lafyun/laf-server:debug-202306151521 -f Dockerfile . 6 | -------------------------------------------------------------------------------- /server/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "assets": [ 7 | { 8 | "include": "i18n/**/*", 9 | "watchAssets": true 10 | }, 11 | { 12 | "include": "initializer/deploy-manifest/*", 13 | "watchAssets": true 14 | } 15 | ] 16 | } 17 | } -------------------------------------------------------------------------------- /server/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Logger } from '@nestjs/common' 2 | import { ApiOperation, ApiTags } from '@nestjs/swagger' 3 | import { ApiResponseArray, ResponseUtil } from './utils/response' 4 | import { SystemDatabase } from './system-database' 5 | import { Runtime } from './application/entities/runtime' 6 | 7 | @ApiTags('Public') 8 | @Controller() 9 | export class AppController { 10 | private readonly logger = new Logger(AppController.name) 11 | private readonly db = SystemDatabase.db 12 | 13 | /** 14 | * Get runtime list 15 | * @returns 16 | */ 17 | @ApiOperation({ summary: 'Get application runtime list' }) 18 | @ApiResponseArray(Runtime) 19 | @Get('runtimes') 20 | async getRuntimes() { 21 | const data = await this.db.collection('Runtime').find({}).toArray() 22 | return ResponseUtil.ok(data) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | @Injectable() 4 | export class AppService {} 5 | -------------------------------------------------------------------------------- /server/src/application/bundle.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common' 2 | import { ApplicationBundle } from 'src/application/entities/application-bundle' 3 | import { SystemDatabase } from 'src/system-database' 4 | 5 | @Injectable() 6 | export class BundleService { 7 | private readonly logger = new Logger(BundleService.name) 8 | private readonly db = SystemDatabase.db 9 | 10 | async findOne(appid: string) { 11 | const bundle = await this.db 12 | .collection('ApplicationBundle') 13 | .findOne({ appid }) 14 | 15 | return bundle 16 | } 17 | 18 | async deleteOne(appid: string) { 19 | const res = await this.db 20 | .collection('ApplicationBundle') 21 | .findOneAndDelete({ appid }) 22 | 23 | return res.value 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /server/src/application/configuration.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common' 2 | import { CN_PUBLISHED_CONF } from 'src/constants' 3 | import { SystemDatabase } from 'src/system-database' 4 | import { ApplicationConfiguration } from './entities/application-configuration' 5 | import { DedicatedDatabaseService } from 'src/database/dedicated-database/dedicated-database.service' 6 | 7 | @Injectable() 8 | export class ApplicationConfigurationService { 9 | private readonly db = SystemDatabase.db 10 | private readonly logger = new Logger(ApplicationConfigurationService.name) 11 | 12 | constructor( 13 | private readonly dedicatedDatabaseService: DedicatedDatabaseService, 14 | ) {} 15 | 16 | async count(appid: string) { 17 | return this.db 18 | .collection('ApplicationConfiguration') 19 | .countDocuments({ appid }) 20 | } 21 | 22 | async remove(appid: string) { 23 | return this.db 24 | .collection('ApplicationConfiguration') 25 | .deleteOne({ appid }) 26 | } 27 | 28 | async publish(conf: ApplicationConfiguration) { 29 | const database = await this.dedicatedDatabaseService.findAndConnect( 30 | conf.appid, 31 | ) 32 | const { db, client } = database 33 | const session = client.startSession() 34 | try { 35 | await session.withTransaction(async () => { 36 | const coll = db.collection(CN_PUBLISHED_CONF) 37 | await coll.deleteMany({}, { session }) 38 | await coll.insertOne(conf, { session }) 39 | }) 40 | } finally { 41 | await session.endSession() 42 | await client.close() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/src/application/dto/bind-custom-domain.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, IsString } from 'class-validator' 3 | 4 | export class BindCustomDomainDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsString() 8 | domain: string 9 | } 10 | -------------------------------------------------------------------------------- /server/src/application/dto/create-application.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsIn, IsNotEmpty, IsString, Length } from 'class-validator' 3 | import { ApplicationState } from '../entities/application' 4 | import { UpdateApplicationBundleDto } from './update-application.dto' 5 | 6 | const STATES = [ApplicationState.Running] 7 | 8 | export class CreateApplicationDto extends UpdateApplicationBundleDto { 9 | @ApiProperty() 10 | @IsString() 11 | @Length(1, 64) 12 | name: string 13 | 14 | @ApiProperty({ 15 | default: ApplicationState.Running, 16 | enum: STATES, 17 | }) 18 | @IsIn(STATES) 19 | state: ApplicationState 20 | 21 | @ApiProperty() 22 | @IsNotEmpty() 23 | @IsString() 24 | regionId: string 25 | 26 | @ApiProperty() 27 | @IsNotEmpty() 28 | @IsString() 29 | runtimeId: string 30 | 31 | validate() { 32 | return super.validate() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /server/src/application/dto/create-env.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, IsString, Length, Matches } from 'class-validator' 3 | 4 | export class CreateEnvironmentDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @IsString() 8 | @Length(1, 64) 9 | @Matches(/^[a-zA-Z_][a-zA-Z0-9_]{1,64}$/) 10 | name: string 11 | 12 | @ApiProperty() 13 | @Length(0, 4096) 14 | @IsString() 15 | value: string 16 | } 17 | -------------------------------------------------------------------------------- /server/src/application/dto/pod.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | 3 | export class PodNameListDto { 4 | @ApiProperty() 5 | appid: string 6 | 7 | @ApiProperty({ 8 | description: 'List of pod identifiers', 9 | example: ['pod1', 'pod2'], 10 | }) 11 | podNameList: string[] 12 | } 13 | 14 | export class ContainerNameListDto { 15 | @ApiProperty() 16 | podName: string 17 | 18 | @ApiProperty({ 19 | description: 'List of container identifiers', 20 | example: ['container1', 'container2'], 21 | }) 22 | containerNameList: string[] 23 | } 24 | -------------------------------------------------------------------------------- /server/src/application/entities/application-configuration.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export class EnvironmentVariable { 5 | @ApiProperty() 6 | name: string 7 | 8 | @ApiProperty() 9 | value: string 10 | } 11 | 12 | export class Autoscaling { 13 | @ApiProperty() 14 | enable: boolean 15 | 16 | @ApiProperty() 17 | minReplicas: number 18 | 19 | @ApiProperty() 20 | maxReplicas: number 21 | 22 | @ApiProperty() 23 | targetCPUUtilizationPercentage?: number 24 | 25 | @ApiProperty() 26 | targetMemoryUtilizationPercentage?: number 27 | } 28 | 29 | export class ApplicationConfiguration { 30 | @ApiProperty({ type: String }) 31 | _id?: ObjectId 32 | 33 | @ApiProperty() 34 | appid: string 35 | 36 | @ApiProperty({ isArray: true, type: EnvironmentVariable }) 37 | environments: EnvironmentVariable[] 38 | 39 | @ApiProperty() 40 | dependencies: string[] 41 | 42 | @ApiProperty() 43 | createdAt: Date 44 | 45 | @ApiProperty() 46 | updatedAt: Date 47 | } 48 | -------------------------------------------------------------------------------- /server/src/application/entities/runtime.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export class RuntimeImageGroup { 5 | @ApiProperty() 6 | main: string 7 | 8 | @ApiProperty() 9 | init: string 10 | 11 | @ApiPropertyOptional() 12 | sidecar?: string 13 | } 14 | 15 | export class Runtime { 16 | @ApiProperty({ type: String }) 17 | _id?: ObjectId 18 | 19 | @ApiProperty() 20 | name: string 21 | 22 | @ApiProperty() 23 | type: string 24 | 25 | @ApiProperty() 26 | image: RuntimeImageGroup 27 | 28 | @ApiProperty() 29 | state: 'Active' | 'Inactive' 30 | 31 | @ApiProperty() 32 | version: string 33 | 34 | @ApiProperty() 35 | latest: boolean 36 | 37 | constructor(partial: Partial) { 38 | Object.assign(this, partial) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /server/src/application/events/application-bundle-updating.event.ts: -------------------------------------------------------------------------------- 1 | import { ClientSession } from 'mongodb' 2 | import { Region } from 'src/region/entities/region' 3 | import { UpdateApplicationBundleDto } from '../dto/update-application.dto' 4 | 5 | export class ApplicationBundleUpdatingEvent { 6 | region: Region 7 | appid: string 8 | session: ClientSession 9 | dto: UpdateApplicationBundleDto 10 | 11 | constructor(partial: Partial) { 12 | Object.assign(this, partial) 13 | } 14 | 15 | static get eventName() { 16 | return 'application.bundle.updating' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/src/application/events/application-creating.event.ts: -------------------------------------------------------------------------------- 1 | import { ClientSession } from 'mongodb' 2 | import { CreateApplicationDto } from '../dto/create-application.dto' 3 | import { Region } from 'src/region/entities/region' 4 | 5 | export class ApplicationCreatingEvent { 6 | region: Region 7 | appid: string 8 | session: ClientSession 9 | dto: CreateApplicationDto 10 | 11 | constructor(partial: Partial) { 12 | Object.assign(this, partial) 13 | } 14 | 15 | static get eventName() { 16 | return 'application.creating' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /server/src/authentication/application.auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | Injectable, 5 | Logger, 6 | } from '@nestjs/common' 7 | import { ApplicationService } from '../application/application.service' 8 | import { IRequest } from '../utils/interface' 9 | import { User } from 'src/user/entities/user' 10 | 11 | @Injectable() 12 | export class ApplicationAuthGuard implements CanActivate { 13 | logger = new Logger(ApplicationAuthGuard.name) 14 | constructor(private readonly appService: ApplicationService) {} 15 | 16 | async canActivate(context: ExecutionContext) { 17 | const request = context.switchToHttp().getRequest() as IRequest 18 | const appid = request.params.appid 19 | const user = request.user as User 20 | 21 | // check appid 22 | const app = await this.appService.findOne(appid) 23 | if (!app) { 24 | return false 25 | } 26 | 27 | if (!app.createdBy.equals(user._id)) { 28 | return false 29 | } 30 | 31 | // inject app to request 32 | request.application = app 33 | 34 | return true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/src/authentication/authentication.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common' 2 | import { JwtModule } from '@nestjs/jwt' 3 | import { PassportModule } from '@nestjs/passport' 4 | import { ServerConfig } from '../constants' 5 | import { UserModule } from '../user/user.module' 6 | import { JwtStrategy } from './jwt.strategy' 7 | import { HttpModule } from '@nestjs/axios' 8 | import { PatService } from 'src/user/pat.service' 9 | import { AuthenticationService } from './authentication.service' 10 | import { AuthenticationController } from './authentication.controller' 11 | import { SealosManagerGuard } from './sealos-manager.guard' 12 | 13 | @Global() 14 | @Module({ 15 | imports: [ 16 | PassportModule, 17 | JwtModule.register({ 18 | secret: ServerConfig.JWT_SECRET, 19 | signOptions: { expiresIn: ServerConfig.JWT_EXPIRES_IN }, 20 | }), 21 | UserModule, 22 | HttpModule, 23 | ], 24 | providers: [ 25 | JwtStrategy, 26 | PatService, 27 | AuthenticationService, 28 | SealosManagerGuard, 29 | ], 30 | controllers: [AuthenticationController], 31 | }) 32 | export class AuthenticationModule {} 33 | -------------------------------------------------------------------------------- /server/src/authentication/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { JwtService } from '@nestjs/jwt' 2 | import { Injectable, Logger } from '@nestjs/common' 3 | import { User } from 'src/user/entities/user' 4 | import { PatService } from 'src/user/pat.service' 5 | 6 | @Injectable() 7 | export class AuthenticationService { 8 | private readonly logger = new Logger(AuthenticationService.name) 9 | 10 | constructor( 11 | private readonly jwtService: JwtService, 12 | private readonly patService: PatService, 13 | ) {} 14 | 15 | /** 16 | * Get token by PAT 17 | * @param user 18 | * @param token 19 | * @returns 20 | */ 21 | async pat2token(token: string): Promise { 22 | const pat = await this.patService.findOneByToken(token) 23 | if (!pat) return null 24 | 25 | // check pat expired 26 | if (pat.expiredAt < new Date()) return null 27 | 28 | return this.getAccessTokenByUser(pat.user) 29 | } 30 | 31 | /** 32 | * Get access token by user 33 | * @param user 34 | * @returns 35 | */ 36 | getAccessTokenByUser(user: User): string { 37 | const payload = { sub: user._id.toString() } 38 | const token = this.jwtService.sign(payload) 39 | return token 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/src/authentication/dto/pat2token.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, IsString } from 'class-validator' 3 | 4 | export class Pat2TokenDto { 5 | @ApiProperty({ 6 | description: 'PAT', 7 | example: 8 | 'laf_1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', 9 | }) 10 | @IsString() 11 | @IsNotEmpty() 12 | pat: string 13 | } 14 | -------------------------------------------------------------------------------- /server/src/authentication/dto/signin.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, IsString, Length } from 'class-validator' 3 | 4 | export class SigninDto { 5 | @ApiProperty() 6 | @IsString() 7 | @IsNotEmpty() 8 | kubeconfig: string 9 | 10 | @ApiProperty() 11 | @IsString() 12 | @IsNotEmpty() 13 | @Length(1, 64) 14 | username: string 15 | 16 | @ApiProperty() 17 | @IsString() 18 | @IsNotEmpty() 19 | namespace: string 20 | } 21 | -------------------------------------------------------------------------------- /server/src/authentication/jwt.auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { ExecutionContext, Injectable } from '@nestjs/common' 2 | import { AuthGuard } from '@nestjs/passport' 3 | 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') { 6 | async canActivate(context: ExecutionContext) { 7 | const res = await super.canActivate(context) 8 | if (!res) { 9 | return false 10 | } 11 | const request = context.switchToHttp().getRequest() 12 | const kubeconfig = request.headers?.['credential'] 13 | if (kubeconfig) { 14 | request.user.kubeconfig = Buffer.from(kubeconfig, 'base64').toString( 15 | 'utf-8', 16 | ) 17 | } 18 | return true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/authentication/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { PassportStrategy } from '@nestjs/passport' 3 | import { ExtractJwt, Strategy } from 'passport-jwt' 4 | import { ServerConfig } from '../constants' 5 | import { UserService } from '../user/user.service' 6 | import { ObjectId } from 'mongodb' 7 | 8 | @Injectable() 9 | export class JwtStrategy extends PassportStrategy(Strategy) { 10 | constructor(private readonly userService: UserService) { 11 | super({ 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | ignoreExpiration: false, 14 | secretOrKey: ServerConfig.JWT_SECRET, 15 | }) 16 | } 17 | 18 | /** 19 | * Turn payload to user object 20 | * @param payload 21 | * @returns 22 | */ 23 | async validate(payload: any) { 24 | const id = new ObjectId(payload.sub as string) 25 | const user = await this.userService.findOneById(id) 26 | return user 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/src/authentication/sealos-manager.guard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CanActivate, 3 | ExecutionContext, 4 | Injectable, 5 | Logger, 6 | } from '@nestjs/common' 7 | import { ClusterService } from 'src/region/cluster/cluster.service' 8 | import { UserWithKubeconfig } from 'src/user/entities/user' 9 | import { IRequest } from 'src/utils/interface' 10 | 11 | @Injectable() 12 | export class SealosManagerGuard implements CanActivate { 13 | private readonly logger = new Logger(SealosManagerGuard.name) 14 | constructor(private readonly clusterService: ClusterService) {} 15 | 16 | async canActivate(context: ExecutionContext) { 17 | const request = context.switchToHttp().getRequest() as IRequest 18 | const user = request.user as UserWithKubeconfig 19 | 20 | const api = await this.clusterService.makeRbacAuthorizationApi(user) 21 | const kc = this.clusterService.loadKubeConfig(user) 22 | const username = kc.users[0].name 23 | 24 | try { 25 | const res = await api.readNamespacedRoleBinding(username, user.namespace) 26 | 27 | if (['Owner'].includes(res.body.roleRef.name)) { 28 | return true 29 | } 30 | } catch {} 31 | 32 | try { 33 | const res = await api.readNamespacedRoleBinding( 34 | 'rb-' + username, 35 | user.namespace, 36 | ) 37 | 38 | if (['Owner', 'Manager'].includes(res.body.roleRef.name)) { 39 | return true 40 | } 41 | } catch {} 42 | 43 | return false 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/src/billing/billing.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { BillingService } from './billing.service' 3 | import { ResourceService } from './resource.service' 4 | import { ResourceController } from './resource.controller' 5 | 6 | @Module({ 7 | controllers: [ResourceController], 8 | providers: [BillingService, ResourceService], 9 | exports: [BillingService, ResourceService], 10 | }) 11 | export class BillingModule {} 12 | -------------------------------------------------------------------------------- /server/src/billing/dto/calculate-price.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, OmitType } from '@nestjs/swagger' 2 | import { IsNotEmpty, IsString } from 'class-validator' 3 | import { UpdateApplicationBundleDto } from 'src/application/dto/update-application.dto' 4 | 5 | export class CalculatePriceDto extends OmitType(UpdateApplicationBundleDto, [ 6 | 'validate', 7 | ]) { 8 | @ApiProperty() 9 | @IsNotEmpty() 10 | @IsString() 11 | regionId: string 12 | } 13 | 14 | export class CalculatePriceResultDto { 15 | @ApiProperty({ example: 0.072 }) 16 | cpu: number 17 | 18 | @ApiProperty({ example: 0.036 }) 19 | memory: number 20 | 21 | @ApiProperty({ example: 0.18 }) 22 | total: number 23 | } 24 | -------------------------------------------------------------------------------- /server/src/database/database.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { CollectionService } from './collection/collection.service' 3 | import { CollectionController } from './collection/collection.controller' 4 | import { MongoService } from './mongo.service' 5 | import { ApplicationService } from 'src/application/application.service' 6 | import { BundleService } from 'src/application/bundle.service' 7 | import { DedicatedDatabaseService } from './dedicated-database/dedicated-database.service' 8 | import { DedicatedDatabaseTaskService } from './dedicated-database/dedicated-database-task.service' 9 | import { HttpModule } from '@nestjs/axios' 10 | import { ApplicationListener } from './listeners/application.listener' 11 | import { DedicatedDatabaseMonitorService } from './monitor/monitor.service' 12 | import { DedicatedDatabaseMonitorController } from './monitor/monitor.controller' 13 | import { DatabaseController } from './database.controller' 14 | 15 | @Module({ 16 | imports: [HttpModule], 17 | controllers: [ 18 | CollectionController, 19 | DatabaseController, 20 | DedicatedDatabaseMonitorController, 21 | ], 22 | providers: [ 23 | CollectionService, 24 | MongoService, 25 | ApplicationService, 26 | BundleService, 27 | DedicatedDatabaseService, 28 | DedicatedDatabaseTaskService, 29 | DedicatedDatabaseMonitorService, 30 | ApplicationListener, 31 | ], 32 | exports: [CollectionService, MongoService, DedicatedDatabaseService], 33 | }) 34 | export class DatabaseModule {} 35 | -------------------------------------------------------------------------------- /server/src/database/dto/create-collection.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, Length } from 'class-validator' 3 | 4 | export class CreateCollectionDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @Length(3, 32) 8 | name: string 9 | 10 | async validate() { 11 | return null 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server/src/database/dto/create-dedicated-database.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsInt, IsNotEmpty, Max } from 'class-validator' 3 | 4 | export class CreateDedicatedDatabaseDto { 5 | @ApiProperty({ example: 200 }) 6 | @IsNotEmpty() 7 | @IsInt() 8 | cpu: number 9 | 10 | @ApiProperty({ example: 256 }) 11 | @IsNotEmpty() 12 | @IsInt() 13 | memory: number 14 | 15 | @ApiProperty({ example: 1024 }) 16 | @IsNotEmpty() 17 | @IsInt() 18 | capacity: number 19 | 20 | @ApiProperty({ example: 3 }) 21 | @IsNotEmpty() 22 | @IsInt() 23 | @Max(9) 24 | replicas: number 25 | } 26 | -------------------------------------------------------------------------------- /server/src/database/dto/import-database.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | 3 | export class ImportDatabaseDto { 4 | @ApiProperty({ type: 'binary', format: 'binary' }) 5 | file: any 6 | 7 | @ApiProperty({ type: 'string', description: 'source appid' }) 8 | sourceAppid: string 9 | } 10 | -------------------------------------------------------------------------------- /server/src/database/dto/update-collection.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | import { IsObject, IsString } from 'class-validator' 3 | 4 | export class UpdateCollectionDto { 5 | @ApiPropertyOptional() 6 | @IsObject() 7 | validatorSchema: object 8 | 9 | @ApiPropertyOptional() 10 | @IsString() 11 | validationLevel: string 12 | } 13 | -------------------------------------------------------------------------------- /server/src/database/entities/collection.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { Binary, CollectionInfo, Document } from 'mongodb' 3 | 4 | export class Collection implements CollectionInfo { 5 | @ApiProperty() 6 | name: string 7 | 8 | @ApiProperty() 9 | type?: string 10 | 11 | @ApiProperty() 12 | options?: Document 13 | 14 | @ApiProperty() 15 | info?: { readOnly?: false; uuid?: Binary } 16 | 17 | @ApiProperty() 18 | idIndex?: Document 19 | } 20 | -------------------------------------------------------------------------------- /server/src/database/entities/database-sync-record.ts: -------------------------------------------------------------------------------- 1 | import { ObjectId } from 'mongodb' 2 | 3 | export enum DatabaseSyncState { 4 | Processing = 'Processing', 5 | Complete = 'Complete', 6 | } 7 | 8 | export class DatabaseSyncRecord { 9 | _id?: ObjectId 10 | appid: string 11 | uid: ObjectId 12 | createdAt: Date 13 | type: 'Export' | 'Import' 14 | state: DatabaseSyncState 15 | } 16 | -------------------------------------------------------------------------------- /server/src/database/entities/dedicated-database.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export enum DedicatedDatabasePhase { 5 | Starting = 'Starting', 6 | Started = 'Started', 7 | Stopping = 'Stopping', 8 | Stopped = 'Stopped', 9 | Deleting = 'Deleting', 10 | Deleted = 'Deleted', 11 | } 12 | 13 | export enum DedicatedDatabaseState { 14 | Running = 'Running', 15 | Stopped = 'Stopped', 16 | Restarting = 'Restarting', 17 | Deleted = 'Deleted', 18 | } 19 | 20 | export class DedicatedDatabaseSpec { 21 | @ApiProperty({ example: 500 }) 22 | limitCPU: number 23 | 24 | @ApiProperty({ example: 1024 }) 25 | limitMemory: number 26 | 27 | requestCPU: number 28 | 29 | requestMemory: number 30 | 31 | @ApiProperty({ example: 1024 }) 32 | capacity: number 33 | 34 | @ApiProperty({ example: 1 }) 35 | replicas: number 36 | } 37 | 38 | export class DedicatedDatabase { 39 | @ApiProperty({ type: String }) 40 | _id?: ObjectId 41 | 42 | @ApiProperty() 43 | appid: string 44 | 45 | @ApiProperty() 46 | name: string 47 | 48 | @ApiProperty({ enum: DedicatedDatabaseState }) 49 | state: DedicatedDatabaseState 50 | 51 | @ApiProperty({ enum: DedicatedDatabasePhase }) 52 | phase: DedicatedDatabasePhase 53 | 54 | lockedAt: Date 55 | 56 | @ApiProperty() 57 | createdAt: Date 58 | 59 | @ApiProperty() 60 | updatedAt: Date 61 | } 62 | -------------------------------------------------------------------------------- /server/src/database/listeners/application.listener.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { OnEvent } from '@nestjs/event-emitter' 3 | import { DedicatedDatabaseService } from '../dedicated-database/dedicated-database.service' 4 | import { ApplicationCreatingEvent } from 'src/application/events/application-creating.event' 5 | 6 | @Injectable() 7 | export class ApplicationListener { 8 | constructor( 9 | private readonly dedicatedDatabaseService: DedicatedDatabaseService, 10 | ) {} 11 | 12 | @OnEvent(ApplicationCreatingEvent.eventName, { 13 | promisify: true, 14 | async: true, 15 | }) 16 | handleApplicationCreatedEvent(event: ApplicationCreatingEvent) { 17 | if (event.dto.dedicatedDatabase) { 18 | return this.dedicatedDatabaseService.create(event.appid, event.session) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/src/database/mongo.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common' 2 | import { MongoClient } from 'mongodb' 3 | import * as assert from 'node:assert' 4 | 5 | @Injectable() 6 | export class MongoService { 7 | private readonly logger = new Logger(MongoService.name) 8 | 9 | /** 10 | * Connect to database 11 | */ 12 | async connectDatabase(connectionUri: string, dbName?: string) { 13 | assert(connectionUri, 'Database connection uri is required') 14 | 15 | const client = new MongoClient(connectionUri) 16 | try { 17 | this.logger.verbose(`Connecting to database ${dbName}`) 18 | await client.connect() 19 | this.logger.log(`Connected to database ${dbName}`) 20 | return client 21 | } catch { 22 | this.logger.error(`Failed to connect to database ${dbName}`) 23 | await client.close() 24 | return null 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/dependency/dependency.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { ApplicationModule } from 'src/application/application.module' 3 | import { DependencyController } from './dependency.controller' 4 | import { DependencyService } from './dependency.service' 5 | 6 | @Module({ 7 | imports: [ApplicationModule], 8 | controllers: [DependencyController], 9 | providers: [DependencyService], 10 | exports: [DependencyService], 11 | }) 12 | export class DependencyModule {} 13 | -------------------------------------------------------------------------------- /server/src/dependency/dto/create-dependency.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, Length } from 'class-validator' 3 | 4 | export class CreateDependencyDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @Length(1, 64) 8 | name: string 9 | 10 | @ApiProperty() 11 | @IsNotEmpty() 12 | @Length(1, 64) 13 | spec: string 14 | } 15 | -------------------------------------------------------------------------------- /server/src/dependency/dto/delete-dependency.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, Length } from 'class-validator' 3 | 4 | export class DeleteDependencyDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @Length(1, 64) 8 | name: string 9 | } 10 | -------------------------------------------------------------------------------- /server/src/dependency/dto/update-dependency.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, Length } from 'class-validator' 3 | 4 | export class UpdateDependencyDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @Length(1, 64) 8 | name: string 9 | 10 | @ApiProperty() 11 | @IsNotEmpty() 12 | @Length(1, 64) 13 | spec: string 14 | } 15 | -------------------------------------------------------------------------------- /server/src/function-template/dto/function-template-usedBy.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export class GetFunctionTemplateUsedByDto { 5 | @ApiProperty({ type: String }) 6 | uid: ObjectId 7 | } 8 | -------------------------------------------------------------------------------- /server/src/function-template/dto/use-function-template.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, IsString, Length } from 'class-validator' 3 | import { ObjectId } from 'mongodb' 4 | 5 | export class UseFunctionTemplateDto { 6 | @ApiProperty({ 7 | description: 'The ObjectId of function template', 8 | type: 'string', 9 | }) 10 | @IsNotEmpty() 11 | @Length(24, 24) 12 | functionTemplateId: ObjectId 13 | 14 | @ApiProperty() 15 | @IsString() 16 | @IsNotEmpty() 17 | appid: string 18 | } 19 | -------------------------------------------------------------------------------- /server/src/function-template/function-template.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { FunctionTemplateService } from './function-template.service' 3 | import { FunctionTemplateController } from './function-template.controller' 4 | import { ApplicationModule } from 'src/application/application.module' 5 | import { DatabaseModule } from 'src/database/database.module' 6 | import { FunctionModule } from 'src/function/function.module' 7 | import { DependencyModule } from '../dependency/dependency.module' 8 | 9 | @Module({ 10 | imports: [ 11 | ApplicationModule, 12 | DatabaseModule, 13 | FunctionModule, 14 | DependencyModule, 15 | ], 16 | controllers: [FunctionTemplateController], 17 | providers: [FunctionTemplateService], 18 | }) 19 | export class FunctionTemplateModule {} 20 | -------------------------------------------------------------------------------- /server/src/function/dto/compile-function.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, IsString } from 'class-validator' 3 | 4 | export class CompileFunctionDto { 5 | @ApiProperty({ 6 | description: 'The source code of the function', 7 | }) 8 | @IsNotEmpty() 9 | @IsString() 10 | code: string 11 | } 12 | -------------------------------------------------------------------------------- /server/src/function/dto/create-function.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { 3 | IsArray, 4 | IsIn, 5 | IsNotEmpty, 6 | IsString, 7 | Matches, 8 | MaxLength, 9 | } from 'class-validator' 10 | import { HTTP_METHODS } from '../../constants' 11 | import { HttpMethod } from '../entities/cloud-function' 12 | 13 | export class CreateFunctionDto { 14 | @ApiProperty({ 15 | description: 'Function name is unique in the application', 16 | }) 17 | @IsNotEmpty() 18 | @Matches(/^[a-zA-Z0-9_.\-](?:[a-zA-Z0-9_.\-/]{0,254}[a-zA-Z0-9_.\-])?$/) 19 | name: string 20 | 21 | @ApiPropertyOptional() 22 | @MaxLength(256) 23 | description: string 24 | 25 | @ApiProperty({ type: [String], enum: HttpMethod }) 26 | @IsIn(HTTP_METHODS, { each: true }) 27 | methods: HttpMethod[] = [] 28 | 29 | @ApiProperty({ description: 'The source code of the function' }) 30 | @IsNotEmpty() 31 | @IsString() 32 | @MaxLength(1024 * 512) 33 | code: string 34 | 35 | @ApiPropertyOptional({ type: [String] }) 36 | @IsString({ each: true }) 37 | @IsArray() 38 | @MaxLength(16, { each: true }) 39 | @IsNotEmpty({ each: true }) 40 | tags: string[] 41 | 42 | validate() { 43 | if (this.tags?.length >= 8) { 44 | return 'tags length must less than 8' 45 | } 46 | if (this.name.includes('./')) { 47 | return 'the relative path is not allowed in function name' 48 | } 49 | return null 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server/src/function/dto/update-function-debug.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsObject } from 'class-validator' 3 | 4 | export class UpdateFunctionDebugDto { 5 | @ApiProperty() 6 | @IsObject() 7 | params: any 8 | 9 | validate() { 10 | return null 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /server/src/function/dto/update-function.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { 3 | IsArray, 4 | IsIn, 5 | IsNotEmpty, 6 | IsOptional, 7 | IsString, 8 | Matches, 9 | MaxLength, 10 | } from 'class-validator' 11 | import { HTTP_METHODS } from '../../constants' 12 | import { HttpMethod } from '../entities/cloud-function' 13 | 14 | export class UpdateFunctionDto { 15 | @ApiProperty({ 16 | description: 'Function name is unique in the application', 17 | }) 18 | @IsOptional() 19 | @Matches(/^[a-zA-Z0-9_.\-](?:[a-zA-Z0-9_.\-/]{0,254}[a-zA-Z0-9_.\-])?$/) 20 | newName?: string 21 | 22 | @ApiPropertyOptional() 23 | @MaxLength(256) 24 | description: string 25 | 26 | @ApiProperty({ type: [String], enum: HttpMethod }) 27 | @IsIn(HTTP_METHODS, { each: true }) 28 | methods: HttpMethod[] = [] 29 | 30 | @ApiProperty({ description: 'The source code of the function' }) 31 | @IsNotEmpty() 32 | @IsString() 33 | @MaxLength(1024 * 512) 34 | code: string 35 | 36 | @ApiPropertyOptional({ type: [String] }) 37 | @IsString({ each: true }) 38 | @IsArray() 39 | @MaxLength(16, { each: true }) 40 | @IsNotEmpty({ each: true }) 41 | tags: string[] 42 | 43 | @ApiPropertyOptional() 44 | @MaxLength(256) 45 | @IsOptional() 46 | changelog?: string 47 | 48 | validate() { 49 | return null 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /server/src/function/entities/cloud-function-history.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export class CloudFunctionHistorySource { 5 | @ApiProperty() 6 | code: string 7 | } 8 | 9 | export class CloudFunctionHistory { 10 | @ApiProperty({ type: String }) 11 | _id?: ObjectId 12 | 13 | @ApiProperty() 14 | appid: string 15 | 16 | @ApiProperty({ type: String }) 17 | functionId: ObjectId 18 | 19 | @ApiProperty({ type: CloudFunctionHistorySource }) 20 | source: CloudFunctionHistorySource 21 | 22 | @ApiProperty({ type: String }) 23 | changelog?: string 24 | 25 | @ApiProperty() 26 | createdAt: Date 27 | } 28 | -------------------------------------------------------------------------------- /server/src/function/entities/cloud-function.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export class CloudFunctionSource { 5 | @ApiProperty() 6 | code: string 7 | 8 | @ApiProperty() 9 | compiled: string 10 | 11 | @ApiPropertyOptional() 12 | uri?: string 13 | 14 | @ApiProperty() 15 | version: number 16 | 17 | @ApiPropertyOptional() 18 | hash?: string 19 | 20 | @ApiPropertyOptional() 21 | lang?: string 22 | } 23 | 24 | export enum HttpMethod { 25 | GET = 'GET', 26 | POST = 'POST', 27 | PUT = 'PUT', 28 | DELETE = 'DELETE', 29 | PATCH = 'PATCH', 30 | HEAD = 'HEAD', 31 | } 32 | 33 | export class CloudFunction { 34 | @ApiProperty({ type: String }) 35 | _id?: ObjectId 36 | 37 | @ApiProperty() 38 | appid: string 39 | 40 | @ApiProperty() 41 | name: string 42 | 43 | @ApiProperty({ type: CloudFunctionSource }) 44 | source: CloudFunctionSource 45 | 46 | @ApiProperty() 47 | desc: string 48 | 49 | @ApiProperty({ type: String, isArray: true }) 50 | tags: string[] 51 | 52 | @ApiProperty({ enum: HttpMethod, isArray: true }) 53 | methods: HttpMethod[] 54 | 55 | @ApiPropertyOptional() 56 | params?: any 57 | 58 | @ApiProperty() 59 | createdAt: Date 60 | 61 | @ApiProperty() 62 | updatedAt: Date 63 | 64 | @ApiProperty({ type: String }) 65 | createdBy: ObjectId 66 | } 67 | -------------------------------------------------------------------------------- /server/src/function/function.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { JwtService } from '@nestjs/jwt' 3 | import { FunctionController } from './function.controller' 4 | import { FunctionService } from './function.service' 5 | import { DatabaseModule } from 'src/database/database.module' 6 | import { TriggerService } from 'src/trigger/trigger.service' 7 | import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' 8 | import { HttpModule } from '@nestjs/axios' 9 | import { ApplicationModule } from 'src/application/application.module' 10 | 11 | @Module({ 12 | imports: [ApplicationModule, DatabaseModule, HttpModule], 13 | controllers: [FunctionController], 14 | providers: [ 15 | FunctionService, 16 | FunctionRecycleBinService, 17 | JwtService, 18 | TriggerService, 19 | ], 20 | exports: [FunctionService], 21 | }) 22 | export class FunctionModule {} 23 | -------------------------------------------------------------------------------- /server/src/gateway/entities/runtime-domain.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export enum DomainPhase { 5 | Creating = 'Creating', 6 | Created = 'Created', 7 | Deleting = 'Deleting', 8 | Deleted = 'Deleted', 9 | } 10 | 11 | export enum DomainState { 12 | Active = 'Active', 13 | Inactive = 'Inactive', 14 | Deleted = 'Deleted', 15 | } 16 | 17 | export class RuntimeDomain { 18 | @ApiProperty({ type: String }) 19 | _id?: ObjectId 20 | 21 | @ApiProperty() 22 | appid: string 23 | 24 | @ApiProperty() 25 | domain: string 26 | 27 | @ApiPropertyOptional() 28 | customDomain?: string 29 | 30 | @ApiProperty({ enum: DomainState }) 31 | state: DomainState 32 | 33 | @ApiProperty({ enum: DomainPhase }) 34 | phase: DomainPhase 35 | 36 | lockedAt: Date 37 | 38 | @ApiProperty() 39 | createdAt: Date 40 | 41 | @ApiProperty() 42 | updatedAt: Date 43 | 44 | constructor(partial: Partial) { 45 | Object.assign(this, partial) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/src/gateway/gateway.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { RuntimeDomainService } from './runtime-domain.service' 3 | import { HttpModule } from '@nestjs/axios' 4 | import { RuntimeDomainTaskService } from './runtime-domain-task.service' 5 | import { CertificateService } from './certificate.service' 6 | import { RuntimeGatewayService } from './ingress/runtime-ingress.service' 7 | 8 | @Module({ 9 | imports: [HttpModule], 10 | providers: [ 11 | RuntimeDomainService, 12 | RuntimeDomainTaskService, 13 | CertificateService, 14 | RuntimeGatewayService, 15 | ], 16 | exports: [RuntimeDomainService], 17 | }) 18 | export class GatewayModule {} 19 | -------------------------------------------------------------------------------- /server/src/generated/i18n.generated.ts: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT, file generated by nestjs-i18n */ 2 | 3 | import { Path } from "nestjs-i18n"; 4 | export type I18nTranslations = { 5 | "function": { 6 | "create": { 7 | "nameExist": string; 8 | "maxCount": string; 9 | "error": string; 10 | }; 11 | "get": {}; 12 | "update": { 13 | "error": string; 14 | }; 15 | "delete": { 16 | "error": string; 17 | }; 18 | "compile": { 19 | "codeRequired": string; 20 | }; 21 | "common": { 22 | "notFound": string; 23 | }; 24 | }; 25 | "notification": { 26 | "InsufficientBalance": { 27 | "title": string; 28 | "content": string; 29 | }; 30 | }; 31 | }; 32 | export type I18nPath = Path; 33 | -------------------------------------------------------------------------------- /server/src/i18n/en/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "create": { 3 | "nameExist": "function {name} is already existed", 4 | "maxCount": "function count limit is {max}", 5 | "error": "create function error" 6 | }, 7 | "get": {}, 8 | "update": { 9 | "error": "update function error" 10 | }, 11 | "delete": { 12 | "error": "delete function error" 13 | }, 14 | "compile": { 15 | "codeRequired": "code is required" 16 | }, 17 | "common": { 18 | "notFound": "function {name} is not found" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/i18n/en/notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "InsufficientBalance": { 3 | "title": "Laf account is in arrears", 4 | "content": "Your account is in arrears, the app {appid} will stop running, please recharge your account promptly!" 5 | } 6 | } -------------------------------------------------------------------------------- /server/src/i18n/en/subscription.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /server/src/i18n/zh-CN/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "create": { 3 | "nameExist": "函数 {name} 已存在", 4 | "maxCount": "达到函数最大数量 {max}", 5 | "error": "创建函数失败" 6 | }, 7 | "get": {}, 8 | "update": { 9 | "error": "更新函数失败" 10 | }, 11 | "delete": { 12 | "error": "删除函数失败" 13 | }, 14 | "compile": { 15 | "codeRequired": "函数代码不能为空" 16 | }, 17 | "common": { 18 | "notFound": "函数 {name} 不存在" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/i18n/zh-CN/notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "InsufficientBalance": { 3 | "title": "laf 账户欠费", 4 | "content": "你的账户已欠费,应用 {appid} 将停止运行,请及时充值" 5 | } 6 | } -------------------------------------------------------------------------------- /server/src/i18n/zh-CN/subscription.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /server/src/i18n/zh/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "create": { 3 | "nameExist": "函数 {name} 已存在", 4 | "maxCount": "达到函数最大数量 {max}", 5 | "error": "创建函数失败" 6 | }, 7 | "get": {}, 8 | "update": { 9 | "error": "更新函数失败" 10 | }, 11 | "delete": { 12 | "error": "删除函数失败" 13 | }, 14 | "compile": { 15 | "codeRequired": "函数代码不能为空" 16 | }, 17 | "common": { 18 | "notFound": "函数 {name} 不存在" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /server/src/i18n/zh/notification.json: -------------------------------------------------------------------------------- 1 | { 2 | "InsufficientBalance": { 3 | "title": "laf 账户欠费", 4 | "content": "你的账户已欠费,应用 {appid} 将停止运行,请及时充值" 5 | } 6 | } -------------------------------------------------------------------------------- /server/src/i18n/zh/subscription.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /server/src/initializer/deploy-manifest/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.kubeblocks.io/v1alpha1 2 | kind: Cluster 3 | metadata: 4 | finalizers: 5 | - cluster.kubeblocks.io/finalizer 6 | labels: 7 | clusterdefinition.kubeblocks.io/name: mongodb 8 | clusterversion.kubeblocks.io/name: mongodb-5.0 9 | sealos-db-provider-cr: <%- name %> 10 | sealaf-app: <%- label %> 11 | annotations: {} 12 | name: <%- name %> 13 | spec: 14 | affinity: 15 | nodeLabels: {} 16 | podAntiAffinity: Preferred 17 | tenancy: SharedNode 18 | topologyKeys: [] 19 | clusterDefinitionRef: mongodb 20 | clusterVersionRef: mongodb-5.0 21 | componentSpecs: 22 | - componentDefRef: mongodb 23 | monitor: true 24 | name: mongodb 25 | replicas: <%- replicas %> 26 | resources: 27 | limits: 28 | cpu: <%- limitCPU %>m 29 | memory: <%- limitMemory %>Mi 30 | requests: 31 | cpu: <%- requestCPU %>m 32 | memory: <%- requestMemory %>Mi 33 | serviceAccountName: laf-mongodb-<%- name %> 34 | volumeClaimTemplates: 35 | - name: data 36 | spec: 37 | accessModes: 38 | - ReadWriteOnce 39 | resources: 40 | requests: 41 | storage: <%- capacity %>Gi 42 | terminationPolicy: Delete 43 | tolerations: [] 44 | -------------------------------------------------------------------------------- /server/src/initializer/deploy-manifest/databaseOpsRequest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps.kubeblocks.io/v1alpha1 2 | kind: OpsRequest 3 | metadata: 4 | name: <%- name %> 5 | namespace: <%- namespace %> 6 | spec: 7 | clusterRef: <%- clusterName %> 8 | type: Restart 9 | restart: 10 | - componentName: mongodb 11 | -------------------------------------------------------------------------------- /server/src/initializer/initializer.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { InitializerService } from './initializer.service' 3 | 4 | @Module({ 5 | providers: [InitializerService], 6 | }) 7 | export class InitializerModule {} 8 | -------------------------------------------------------------------------------- /server/src/instance/instance.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { InstanceService } from './instance.service' 3 | import { InstanceTaskService } from './instance-task.service' 4 | import { StorageModule } from '../storage/storage.module' 5 | import { DatabaseModule } from '../database/database.module' 6 | import { ApplicationModule } from 'src/application/application.module' 7 | 8 | @Module({ 9 | imports: [StorageModule, DatabaseModule, ApplicationModule], 10 | providers: [InstanceService, InstanceTaskService], 11 | }) 12 | export class InstanceModule {} 13 | -------------------------------------------------------------------------------- /server/src/interceptor/dto/http-interceptor.dto.ts: -------------------------------------------------------------------------------- 1 | export enum HttpInterceptorAction { 2 | ALLOW = 'allow', 3 | DENY = 'deny', 4 | } 5 | class HttpInterceptorRewrite { 6 | status: number 7 | data: any 8 | } 9 | class HttpInterceptorRedirect { 10 | status: number 11 | data: string 12 | } 13 | export class HttpInterceptorResponseDto { 14 | action: HttpInterceptorAction 15 | rewrite?: HttpInterceptorRewrite 16 | redirect?: HttpInterceptorRedirect 17 | denyMessage?: string 18 | } 19 | -------------------------------------------------------------------------------- /server/src/interceptor/interceptor.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { HttpInterceptorService } from './http-interceptor.service' 3 | import { HttpModule } from '@nestjs/axios' 4 | 5 | @Module({ 6 | imports: [HttpModule], 7 | providers: [HttpInterceptorService], 8 | exports: [HttpInterceptorService], 9 | }) 10 | export class InterceptorModule {} 11 | -------------------------------------------------------------------------------- /server/src/log/entities/function-log.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export class FunctionLog { 5 | @ApiProperty({ type: String }) 6 | _id?: ObjectId 7 | 8 | @ApiProperty() 9 | request_id: string 10 | 11 | // cloud function name 12 | @ApiProperty() 13 | func: string 14 | 15 | // log content 16 | @ApiProperty() 17 | data: string 18 | 19 | @ApiProperty() 20 | created_at: Date 21 | } 22 | -------------------------------------------------------------------------------- /server/src/log/log.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { FunctionModule } from 'src/function/function.module' 3 | import { ApplicationModule } from '../application/application.module' 4 | import { LogController } from './log.controller' 5 | 6 | @Module({ 7 | imports: [ApplicationModule, FunctionModule], 8 | controllers: [LogController], 9 | }) 10 | export class LogModule {} 11 | -------------------------------------------------------------------------------- /server/src/monitor/dto/query-metrics.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsArray, IsEnum, IsIn, IsString } from 'class-validator' 3 | import { MonitorMetric } from '../monitor.service' 4 | 5 | export class QueryMetricsDto { 6 | @ApiProperty({ isArray: true, enum: MonitorMetric }) 7 | @IsEnum(MonitorMetric, { each: true }) 8 | @IsArray() 9 | q: MonitorMetric[] 10 | 11 | @ApiProperty({ 12 | description: 'Query type', 13 | enum: ['range', 'instant'], 14 | }) 15 | @IsString() 16 | @IsIn(['range', 'instant']) 17 | type: 'range' | 'instant' 18 | } 19 | -------------------------------------------------------------------------------- /server/src/monitor/monitor.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common' 2 | import { MonitorService } from './monitor.service' 3 | import { ResponseUtil } from 'src/utils/response' 4 | import { JwtAuthGuard } from 'src/authentication/jwt.auth.guard' 5 | import { ApplicationAuthGuard } from 'src/authentication/application.auth.guard' 6 | import { 7 | ApiBearerAuth, 8 | ApiOperation, 9 | ApiResponse, 10 | ApiTags, 11 | } from '@nestjs/swagger' 12 | import { QueryMetricsDto } from './dto/query-metrics.dto' 13 | import { InjectUser } from 'src/utils/decorator' 14 | import { UserWithKubeconfig } from 'src/user/entities/user' 15 | 16 | @ApiTags('Monitor') 17 | @ApiBearerAuth('Authorization') 18 | @Controller('monitor') 19 | export class MonitorController { 20 | constructor(private readonly monitorService: MonitorService) {} 21 | 22 | @ApiOperation({ summary: 'Get monitor metrics data' }) 23 | @ApiResponse({ type: ResponseUtil }) 24 | @UseGuards(JwtAuthGuard, ApplicationAuthGuard) 25 | @Get(':appid/metrics') 26 | async getData( 27 | @Param('appid') appid: string, 28 | @Query() dto: QueryMetricsDto, 29 | @InjectUser() user: UserWithKubeconfig, 30 | ) { 31 | const { q: metrics, type } = dto 32 | const isRange = type === 'range' 33 | 34 | const res = await this.monitorService.getData(user, appid, metrics, isRange) 35 | 36 | return ResponseUtil.ok(res) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/monitor/monitor.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpModule } from '@nestjs/axios' 2 | import { Module } from '@nestjs/common' 3 | import { MonitorController } from './monitor.controller' 4 | import { MonitorService } from './monitor.service' 5 | import { ApplicationModule } from 'src/application/application.module' 6 | 7 | @Module({ 8 | imports: [HttpModule, ApplicationModule], 9 | controllers: [MonitorController], 10 | providers: [MonitorService], 11 | exports: [MonitorService], 12 | }) 13 | export class MonitorModule {} 14 | -------------------------------------------------------------------------------- /server/src/recycle-bin/cloud-function/dto/delete-recycle-bin-functions.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsArray, IsNotEmpty, IsString } from 'class-validator' 3 | 4 | export class DeleteRecycleBinItemsDto { 5 | @ApiProperty({ 6 | description: 'The list of item ids', 7 | type: [String], 8 | }) 9 | @IsNotEmpty() 10 | @IsArray() 11 | @IsString({ each: true }) 12 | ids: string[] 13 | } 14 | -------------------------------------------------------------------------------- /server/src/recycle-bin/cloud-function/dto/restore-recycle-bin-functions.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsArray, IsNotEmpty, IsString } from 'class-validator' 3 | 4 | export class RestoreRecycleBinItemsDto { 5 | @ApiProperty({ 6 | description: 'The list of item ids', 7 | type: [String], 8 | }) 9 | @IsNotEmpty() 10 | @IsArray() 11 | @IsString({ each: true }) 12 | ids: string[] 13 | } 14 | -------------------------------------------------------------------------------- /server/src/recycle-bin/cloud-function/interface/function-recycle-bin-query.interface.ts: -------------------------------------------------------------------------------- 1 | export interface CloudFunctionRecycleBinQuery { 2 | name?: string 3 | appid?: string 4 | startTime?: Date 5 | endTime?: Date 6 | page?: number 7 | pageSize?: number 8 | } 9 | -------------------------------------------------------------------------------- /server/src/recycle-bin/entities/recycle-bin.ts: -------------------------------------------------------------------------------- 1 | export enum DataType { 2 | FUNCTION = 'function', 3 | } 4 | 5 | export class RecycleBin { 6 | type: DataType 7 | data: any 8 | createdAt: Date 9 | } 10 | -------------------------------------------------------------------------------- /server/src/recycle-bin/recycle-bin.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { FunctionRecycleBinController } from './cloud-function/function-recycle-bin.controller' 3 | import { FunctionRecycleBinService } from './cloud-function/function-recycle-bin.service' 4 | import { FunctionService } from 'src/function/function.service' 5 | import { JwtService } from '@nestjs/jwt' 6 | import { TriggerService } from 'src/trigger/trigger.service' 7 | import { MongoService } from 'src/database/mongo.service' 8 | import { ApplicationService } from 'src/application/application.service' 9 | import { HttpModule } from '@nestjs/axios' 10 | import { DedicatedDatabaseService } from 'src/database/dedicated-database/dedicated-database.service' 11 | 12 | @Module({ 13 | imports: [HttpModule], 14 | controllers: [FunctionRecycleBinController], 15 | providers: [ 16 | ApplicationService, 17 | DedicatedDatabaseService, 18 | JwtService, 19 | TriggerService, 20 | FunctionRecycleBinService, 21 | FunctionService, 22 | MongoService, 23 | ], 24 | exports: [FunctionRecycleBinService], 25 | }) 26 | export class RecycleBinModule {} 27 | -------------------------------------------------------------------------------- /server/src/region/cluster/cluster.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { ClusterService } from './cluster.service' 3 | 4 | describe('ClusterService', () => { 5 | let service: ClusterService 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ClusterService], 10 | }).compile() 11 | 12 | service = module.get(ClusterService) 13 | }) 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /server/src/region/region.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Logger } from '@nestjs/common' 2 | import { ApiOperation, ApiTags } from '@nestjs/swagger' 3 | import { ResponseUtil } from '../utils/response' 4 | import { RegionService } from './region.service' 5 | 6 | @ApiTags('Public') 7 | @Controller('regions') 8 | export class RegionController { 9 | private readonly logger = new Logger(RegionController.name) 10 | constructor(private readonly regionService: RegionService) {} 11 | 12 | /** 13 | * Get region list 14 | * @returns 15 | */ 16 | @ApiOperation({ summary: 'Get region list' }) 17 | @Get() 18 | async getRegions() { 19 | const data = await this.regionService.findAllDesensitized() 20 | return ResponseUtil.ok(data) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server/src/region/region.module.ts: -------------------------------------------------------------------------------- 1 | import { Global, Module } from '@nestjs/common' 2 | import { RegionService } from './region.service' 3 | import { RegionController } from './region.controller' 4 | import { ClusterService } from './cluster/cluster.service' 5 | 6 | @Global() 7 | @Module({ 8 | providers: [RegionService, ClusterService], 9 | controllers: [RegionController], 10 | exports: [RegionService, ClusterService], 11 | }) 12 | export class RegionModule {} 13 | -------------------------------------------------------------------------------- /server/src/runtime-builtin-deps.ts: -------------------------------------------------------------------------------- 1 | export const RUNTIME_BUILTIN_DEPENDENCIES = { 2 | '@aws-sdk/client-s3': '^3.231.0', 3 | '@aws-sdk/client-sts': '^3.231.0', 4 | '@aws-sdk/s3-request-presigner': '^3.231.0', 5 | '@kubernetes/client-node': '^0.18.0', 6 | '@lafjs/cloud': '^1.0.0-beta.13', 7 | '@types/pako': '^2.0.2', 8 | axios: '^1.4.0', 9 | chalk: '^4.1.2', 10 | chatgpt: '^5.2.5', 11 | cors: '^2.8.5', 12 | 'database-proxy': '^1.0.0-beta.12', 13 | dayjs: '^1.11.7', 14 | dotenv: '^8.2.0', 15 | ejs: '^3.1.8', 16 | express: '^4.18.2', 17 | 'express-http-proxy': '^2.0.0', 18 | 'express-xml-bodyparser': '^0.3.0', 19 | jsonwebtoken: '^9.0.0', 20 | lodash: '^4.17.21', 21 | minio: '^7.0.32', 22 | mongodb: '^4.12.1', 23 | 'mongodb-uri': '^0.9.7', 24 | multer: '^1.4.5-lts.1', 25 | 'node-modules-utils': '^0.8.2', 26 | nodemailer: '^6.6.3', 27 | pako: '^2.1.0', 28 | validator: '^13.7.0', 29 | ws: '^8.11.0', 30 | // dev dependencies 31 | '@types/cors': '^2.8.13', 32 | '@types/dotenv': '^8.2.0', 33 | '@types/ejs': '^3.1.1', 34 | '@types/express': '^4.17.15', 35 | '@types/express-http-proxy': '^1.6.3', 36 | '@types/express-xml-bodyparser': '^0.3.2', 37 | '@types/jsonwebtoken': '^8.5.1', 38 | '@types/lodash': '^4.14.171', 39 | '@types/mongodb-uri': '^0.9.1', 40 | '@types/multer': '^1.4.5', 41 | '@types/node': '^18.11.15', 42 | '@types/nodemailer': '^6.4.4', 43 | '@types/validator': '^13.1.3', 44 | '@types/ws': '^8.5.3', 45 | typescript: '^5.0.2', 46 | } 47 | -------------------------------------------------------------------------------- /server/src/setting/entities/setting.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export enum SettingKey { 5 | SiteTitle = 'site_title', 6 | SiteName = 'site_name', 7 | SiteDescription = 'site_description', 8 | SiteKeywords = 'site_keywords', 9 | SiteLogo = 'site_logo', 10 | SiteFavicon = 'site_favicon', 11 | SiteUrl = 'site_url', 12 | SiteFooter = 'site_footer', 13 | 14 | AiPilotUrl = 'ai_pilot_url', 15 | LafForumUrl = 'laf_forum_url', 16 | LafBusinessUrl = 'laf_business_url', 17 | LafDiscordUrl = 'laf_discord_url', 18 | LafWeChatUrl = 'laf_wechat_url', 19 | LafAboutUsUrl = 'laf_about_us_url', 20 | LafDocUrl = 'laf_doc_url', 21 | 22 | AppCreateTimeOut = 'app_create_timeout', 23 | } 24 | 25 | export class Setting { 26 | @ApiProperty({ type: String }) 27 | _id?: ObjectId 28 | 29 | @ApiProperty() 30 | public: boolean 31 | 32 | @ApiProperty({ type: String, enum: SettingKey }) 33 | key: SettingKey | string 34 | 35 | @ApiProperty() 36 | value: string 37 | 38 | @ApiPropertyOptional() 39 | desc?: string 40 | 41 | @ApiPropertyOptional() 42 | metadata?: any 43 | } 44 | -------------------------------------------------------------------------------- /server/src/setting/setting.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Logger, Param } from '@nestjs/common' 2 | import { ApiOperation, ApiTags } from '@nestjs/swagger' 3 | import { ResponseUtil } from 'src/utils/response' 4 | import { SettingService } from './setting.service' 5 | 6 | @ApiTags('Public') 7 | @Controller('settings') 8 | export class SettingController { 9 | private readonly logger = new Logger(SettingController.name) 10 | 11 | constructor(private readonly settingService: SettingService) {} 12 | 13 | /** 14 | * Get site settings 15 | */ 16 | @ApiOperation({ summary: 'Get site settings' }) 17 | @Get() 18 | async getSettings() { 19 | const data = await this.settingService.findAllPublic() 20 | return ResponseUtil.ok(data) 21 | } 22 | 23 | /** 24 | * Get one site setting by key 25 | */ 26 | @ApiOperation({ summary: 'Get one site setting by key' }) 27 | @Get(':key') 28 | async getSettingByKey(@Param('key') key: string) { 29 | const data = await this.settingService.findOnePublic(key) 30 | if (!data) { 31 | return ResponseUtil.error('Setting not found') 32 | } 33 | return ResponseUtil.ok(data) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /server/src/setting/setting.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { SettingService } from './setting.service' 3 | import { SettingController } from './setting.controller' 4 | 5 | @Module({ 6 | providers: [SettingService], 7 | controllers: [SettingController], 8 | }) 9 | export class SettingModule {} 10 | -------------------------------------------------------------------------------- /server/src/setting/setting.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common' 2 | import { SystemDatabase } from 'src/system-database' 3 | import { Setting } from './entities/setting' 4 | 5 | @Injectable() 6 | export class SettingService { 7 | private readonly logger = new Logger(SettingService.name) 8 | private readonly db = SystemDatabase.db 9 | 10 | async findAllPublic() { 11 | return await this.db 12 | .collection('Setting') 13 | .find({ public: true }) 14 | .toArray() 15 | } 16 | 17 | async findOnePublic(key: string) { 18 | return await this.db.collection('Setting').findOne({ key }) 19 | } 20 | 21 | async findAll() { 22 | return await this.db.collection('Setting').find({}).toArray() 23 | } 24 | 25 | async findOne(key: string) { 26 | return await this.db.collection('Setting').findOne({ key }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/src/storage/storage.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { CloudBinBucketService } from './cloud-bin-bucket.service' 3 | 4 | @Module({ 5 | providers: [CloudBinBucketService], 6 | exports: [CloudBinBucketService], 7 | }) 8 | export class StorageModule {} 9 | -------------------------------------------------------------------------------- /server/src/system-database.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common' 2 | import { MongoClient } from 'mongodb' 3 | import { ServerConfig } from 'src/constants' 4 | import * as assert from 'node:assert' 5 | 6 | export class SystemDatabase { 7 | private static readonly logger = new Logger(SystemDatabase.name) 8 | private static _client: MongoClient 9 | static ready = this.initialize() 10 | 11 | static get client() { 12 | return this._client 13 | } 14 | 15 | static get db() { 16 | return this.client.db() 17 | } 18 | 19 | static async initialize() { 20 | assert.ok(ServerConfig.DATABASE_URL, 'DATABASE_URL is required') 21 | this._client = new MongoClient(ServerConfig.DATABASE_URL) 22 | try { 23 | const client = await this._client.connect() 24 | this.logger.log('Connected to system database') 25 | return client 26 | } catch (err) { 27 | this.logger.error('Failed to connect to system database') 28 | this.logger.error(err) 29 | process.exit(1) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /server/src/trigger/dto/create-trigger.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { IsNotEmpty, Length } from 'class-validator' 3 | 4 | export class CreateTriggerDto { 5 | @ApiProperty() 6 | @IsNotEmpty() 7 | @Length(1, 64) 8 | desc: string 9 | 10 | @ApiProperty() 11 | @IsNotEmpty() 12 | @Length(1, 64) 13 | cron: string 14 | 15 | @ApiProperty() 16 | @IsNotEmpty() 17 | @Length(1, 255) 18 | target: string 19 | } 20 | -------------------------------------------------------------------------------- /server/src/trigger/dto/update-trigger.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/swagger' 2 | import { CreateTriggerDto } from './create-trigger.dto' 3 | 4 | export class UpdateTriggerDto extends PartialType(CreateTriggerDto) {} 5 | -------------------------------------------------------------------------------- /server/src/trigger/entities/cron-trigger.ts: -------------------------------------------------------------------------------- 1 | import { ObjectId } from 'mongodb' 2 | 3 | export enum TriggerState { 4 | Active = 'Active', 5 | Inactive = 'Inactive', 6 | Deleted = 'Deleted', 7 | } 8 | 9 | export enum TriggerPhase { 10 | Creating = 'Creating', 11 | Created = 'Created', 12 | Deleting = 'Deleting', 13 | Deleted = 'Deleted', 14 | } 15 | 16 | export class CronTrigger { 17 | _id?: ObjectId 18 | appid: string 19 | desc: string 20 | cron: string 21 | target: string 22 | state: TriggerState 23 | phase: TriggerPhase 24 | lockedAt: Date 25 | createdAt: Date 26 | updatedAt: Date 27 | } 28 | -------------------------------------------------------------------------------- /server/src/trigger/trigger.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { TriggerService } from './trigger.service' 3 | import { TriggerController } from './trigger.controller' 4 | import { JwtService } from '@nestjs/jwt' 5 | import { ApplicationService } from 'src/application/application.service' 6 | import { StorageModule } from 'src/storage/storage.module' 7 | import { HttpModule } from '@nestjs/axios' 8 | import { CronJobService } from './cron-job.service' 9 | import { TriggerTaskService } from './trigger-task.service' 10 | import { FunctionService } from 'src/function/function.service' 11 | import { MongoService } from 'src/database/mongo.service' 12 | import { BundleService } from 'src/application/bundle.service' 13 | import { FunctionRecycleBinService } from 'src/recycle-bin/cloud-function/function-recycle-bin.service' 14 | import { DedicatedDatabaseService } from 'src/database/dedicated-database/dedicated-database.service' 15 | 16 | @Module({ 17 | imports: [StorageModule, HttpModule], 18 | controllers: [TriggerController], 19 | providers: [ 20 | TriggerService, 21 | JwtService, 22 | ApplicationService, 23 | FunctionRecycleBinService, 24 | CronJobService, 25 | TriggerTaskService, 26 | FunctionService, 27 | DedicatedDatabaseService, 28 | MongoService, 29 | BundleService, 30 | ], 31 | exports: [TriggerService, CronJobService], 32 | }) 33 | export class TriggerModule {} 34 | -------------------------------------------------------------------------------- /server/src/user/dto/create-pat.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { 3 | IsNotEmpty, 4 | IsNumber, 5 | IsString, 6 | Length, 7 | Max, 8 | Min, 9 | } from 'class-validator' 10 | 11 | export class CreatePATDto { 12 | @IsString() 13 | @IsNotEmpty() 14 | @Length(1, 255) 15 | @ApiProperty() 16 | name: string 17 | 18 | @IsNumber() 19 | @IsNotEmpty() 20 | @Min(60) 21 | @Max(3600 * 24 * 365) 22 | @ApiProperty({ minimum: 60 }) 23 | expiresIn: number 24 | } 25 | -------------------------------------------------------------------------------- /server/src/user/entities/pat.ts: -------------------------------------------------------------------------------- 1 | import { ObjectId } from 'mongodb' 2 | import { User } from './user' 3 | import { ApiProperty } from '@nestjs/swagger' 4 | 5 | export class PersonalAccessToken { 6 | @ApiProperty({ type: String }) 7 | _id?: ObjectId 8 | 9 | @ApiProperty({ type: String }) 10 | uid: ObjectId 11 | 12 | @ApiProperty() 13 | name: string 14 | 15 | token: string 16 | 17 | @ApiProperty() 18 | expiredAt: Date 19 | 20 | @ApiProperty() 21 | createdAt: Date 22 | } 23 | 24 | export class PersonalAccessTokenWithUser extends PersonalAccessToken { 25 | @ApiProperty({ type: User }) 26 | user: User 27 | } 28 | -------------------------------------------------------------------------------- /server/src/user/entities/user.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ObjectId } from 'mongodb' 3 | 4 | export class User { 5 | @ApiProperty({ type: String }) 6 | _id?: ObjectId 7 | 8 | @ApiProperty() 9 | username: string 10 | 11 | @ApiProperty() 12 | namespace: string 13 | 14 | @ApiProperty() 15 | createdAt: Date 16 | 17 | @ApiProperty() 18 | updatedAt: Date 19 | } 20 | 21 | export class UserWithKubeconfig extends User { 22 | @ApiProperty() 23 | kubeconfig: string 24 | } 25 | -------------------------------------------------------------------------------- /server/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, UseGuards } from '@nestjs/common' 2 | import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger' 3 | import { IRequest } from 'src/utils/interface' 4 | import { ApiResponseObject, ResponseUtil } from 'src/utils/response' 5 | import { JwtAuthGuard } from 'src/authentication/jwt.auth.guard' 6 | import { User } from './entities/user' 7 | 8 | @ApiTags('User') 9 | @ApiBearerAuth('Authorization') 10 | @Controller('user') 11 | export class UserController { 12 | /** 13 | * Get current user profile 14 | * @param request 15 | * @returns 16 | */ 17 | @UseGuards(JwtAuthGuard) 18 | @Get('profile') 19 | @ApiResponseObject(User) 20 | @ApiOperation({ summary: 'Get current user profile' }) 21 | @ApiBearerAuth('Authorization') 22 | async getProfile(@Req() request: IRequest) { 23 | const user = { 24 | _id: request.user._id, 25 | username: request.user.username, 26 | namespace: request.user.namespace, 27 | } 28 | return ResponseUtil.ok(user) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { UserService } from './user.service' 3 | import { PatService } from './pat.service' 4 | import { PatController } from './pat.controller' 5 | import { UserController } from './user.controller' 6 | import { ApplicationService } from 'src/application/application.service' 7 | 8 | @Module({ 9 | providers: [UserService, PatService, ApplicationService], 10 | exports: [UserService], 11 | controllers: [PatController, UserController], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /server/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { SystemDatabase } from 'src/system-database' 3 | import { User } from './entities/user' 4 | import { ObjectId } from 'mongodb' 5 | 6 | @Injectable() 7 | export class UserService { 8 | private readonly db = SystemDatabase.db 9 | 10 | async create(data: Partial) { 11 | const res = await this.db.collection('User').insertOne({ 12 | username: data.username, 13 | namespace: data.namespace, 14 | createdAt: new Date(), 15 | updatedAt: new Date(), 16 | }) 17 | 18 | return await this.findOneById(res.insertedId) 19 | } 20 | 21 | async findOneById(id: ObjectId) { 22 | return this.db.collection('User').findOne({ 23 | _id: id, 24 | }) 25 | } 26 | 27 | async findOneByNamespace(namespace: string) { 28 | return this.db.collection('User').findOne({ namespace }) 29 | } 30 | 31 | async updateUser(id: ObjectId, data: Partial) { 32 | await this.db 33 | .collection('User') 34 | .updateOne({ _id: id }, { $set: data }) 35 | 36 | return await this.findOneById(id) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /server/src/utils/crypto.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from 'crypto' 2 | 3 | // use sha256 to hash the password 4 | export function hashPassword(password: string): string { 5 | const hash = crypto.createHash('sha256') 6 | hash.update(password) 7 | return hash.digest('hex') 8 | } 9 | -------------------------------------------------------------------------------- /server/src/utils/decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common' 2 | 3 | export const InjectUser = createParamDecorator( 4 | (data: unknown, ctx: ExecutionContext) => { 5 | const request = ctx.switchToHttp().getRequest() 6 | return request.user 7 | }, 8 | ) 9 | 10 | export const InjectApplication = createParamDecorator( 11 | (data: unknown, ctx: ExecutionContext) => { 12 | const request = ctx.switchToHttp().getRequest() 13 | return request.application 14 | }, 15 | ) 16 | -------------------------------------------------------------------------------- /server/src/utils/getter.ts: -------------------------------------------------------------------------------- 1 | import { Condition } from 'src/region/cluster/types' 2 | import { IRequest } from './interface' 3 | 4 | export function isConditionTrue(type: string, conditions: Condition[] | any[]) { 5 | if (!conditions) return false 6 | 7 | for (const condition of conditions) { 8 | if (condition.type === type) { 9 | return condition.status === 'True' 10 | } 11 | } 12 | return false 13 | } 14 | 15 | export function GetClientIPFromRequest(req: IRequest) { 16 | // try to get ip from x-forwarded-for 17 | const ips_str = req.headers['x-forwarded-for'] as string 18 | if (ips_str) { 19 | const ips = ips_str.split(',') 20 | return ips[0] 21 | } 22 | 23 | // try to get ip from x-real-ip 24 | const ip = req.headers['x-real-ip'] as string 25 | if (ip) { 26 | return ip 27 | } 28 | 29 | return null 30 | } 31 | -------------------------------------------------------------------------------- /server/src/utils/interface.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express' 2 | import { Application } from 'src/application/entities/application' 3 | import { UserWithKubeconfig } from 'src/user/entities/user' 4 | 5 | export interface IRequest extends Request { 6 | user?: UserWithKubeconfig 7 | application?: Application 8 | [key: string]: any 9 | } 10 | 11 | export interface IResponse extends Response { 12 | [key: string]: any 13 | } 14 | -------------------------------------------------------------------------------- /server/src/utils/lang.ts: -------------------------------------------------------------------------------- 1 | import * as ts from 'typescript' 2 | 3 | /** 4 | * compile typescript code to javascript 5 | * @param source typescript source code 6 | */ 7 | export function compileTs2js(source: string, name: string) { 8 | const jscode = ts.transpile( 9 | source, 10 | { 11 | module: ts.ModuleKind.Node16, 12 | target: ts.ScriptTarget.ES2022, 13 | removeComments: true, 14 | inlineSourceMap: true, 15 | }, 16 | `${name}.ts`, 17 | undefined, 18 | name, 19 | ) 20 | 21 | return jscode 22 | } 23 | 24 | /** 25 | * Deeply freeze object recursively 26 | * @param object 27 | * @returns 28 | */ 29 | export function deepFreeze(object: T) { 30 | // Retrieve the property names defined on object 31 | const propNames = Object.getOwnPropertyNames(object) 32 | 33 | // Freeze properties before freezing self 34 | for (const name of propNames) { 35 | const value = object[name] 36 | 37 | if (value && typeof value === 'object') { 38 | deepFreeze(value) 39 | } 40 | } 41 | 42 | return Object.freeze(object) 43 | } 44 | -------------------------------------------------------------------------------- /server/src/utils/number.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * PriceRound() 3 | * - keep two decimals 4 | * - 1.234 => 1.23 5 | * - 1.235 => 1.24 6 | * 7 | * Special case: 8 | * - capitalize the first letter of this function name to make it like a constructor 9 | * @param price 10 | * @returns 11 | */ 12 | export function PriceRound(price: number | string) { 13 | const priceNum = Number(price) 14 | return Math.round(priceNum * 100) / 100 15 | } 16 | 17 | export function PriceAdd(price1: number | string, price2: number | string) { 18 | const price1Num = Number(price1) 19 | const price2Num = Number(price2) 20 | return PriceRound(price1Num + price2Num) 21 | } 22 | 23 | export function PriceSub(price1: number | string, price2: number | string) { 24 | const price1Num = Number(price1) 25 | const price2Num = Number(price2) 26 | return PriceRound(price1Num - price2Num) 27 | } 28 | 29 | export function PriceMul(price1: number | string, price2: number | string) { 30 | const price1Num = Number(price1) 31 | const price2Num = Number(price2) 32 | return PriceRound(price1Num * price2Num) 33 | } 34 | 35 | export function PriceDiv(price1: number | string, price2: number | string) { 36 | const price1Num = Number(price1) 37 | const price2Num = Number(price2) 38 | return PriceRound(price1Num / price2Num) 39 | } 40 | 41 | export function extractNumber(value: string): number { 42 | const match = value.match(/\d+/) 43 | return match ? parseInt(match[0], 10) : null 44 | } 45 | -------------------------------------------------------------------------------- /server/src/utils/random.ts: -------------------------------------------------------------------------------- 1 | import * as nanoid from 'nanoid' 2 | import * as dayjs from 'dayjs' 3 | 4 | export function GenerateAlphaNumericPassword(length: number) { 5 | const nano = nanoid.customAlphabet( 6 | '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 7 | length || 16, 8 | ) 9 | return nano() 10 | } 11 | 12 | export function GenerateInviteCode(length?: number) { 13 | const nano = nanoid.customAlphabet( 14 | '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 15 | length || 7, 16 | ) 17 | return nano() 18 | } 19 | 20 | export function GenerateRandomString(length: number) { 21 | return GenerateAlphaNumericPassword(length) 22 | } 23 | 24 | export function GenerateRandomNumericString(length: number) { 25 | const nano = nanoid.customAlphabet('0123456789', length || 16) 26 | return nano() 27 | } 28 | 29 | export function GenerateOrderNumber() { 30 | const dateStr = dayjs().format('YYYYMMDDHHMMSS') 31 | const randomStr = GenerateRandomNumericString(6) 32 | return `${dateStr}${randomStr}` 33 | } 34 | -------------------------------------------------------------------------------- /server/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing' 2 | import { INestApplication } from '@nestjs/common' 3 | import * as request from 'supertest' 4 | import { AppModule } from './../src/app.module' 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile() 13 | 14 | app = moduleFixture.createNestApplication() 15 | await app.init() 16 | }) 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /server/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /server/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": false, 16 | "noImplicitAny": false, 17 | "strictBindCallApply": false, 18 | "forceConsistentCasingInFileNames": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } -------------------------------------------------------------------------------- /web/.dockerignore: -------------------------------------------------------------------------------- 1 | # node_modules 2 | .env.local 3 | .env -------------------------------------------------------------------------------- /web/.env: -------------------------------------------------------------------------------- 1 | # keep it default "" in both dev or prod environment unless you must use a custom server api 2 | VITE_SERVER_BASE_URL= 3 | 4 | # this only used for dev environment, change it to your local api server url if needed 5 | # don't change it directly, you should create a .env.local file and set it there 6 | VITE_DEV_SERVER_URL=https://sealaf-api.bja.sealos.run/ 7 | -------------------------------------------------------------------------------- /web/.env.production: -------------------------------------------------------------------------------- 1 | VITE_SERVER_BASE_URL= -------------------------------------------------------------------------------- /web/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "laf/web" 4 | } 5 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | pnpm-debug.log* 7 | lerna-debug.log* 8 | 9 | node_modules 10 | dist 11 | dist-ssr 12 | dev-dist 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /web/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { "extends": ["stylelint-config-standard"] } -------------------------------------------------------------------------------- /web/.swagger.config.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | swaggerPath: "https://sealaf-api.dev.sealos.io/-json", 4 | typingFileName: "api-auto.d.ts", 5 | 6 | outDir: "src/apis/v1", 7 | request: "import request from '@/utils/request';", 8 | fileNameRule: function (url) { 9 | return url.split("/")[2]; 10 | }, 11 | }, 12 | ]; 13 | -------------------------------------------------------------------------------- /web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | 3 | COPY ./nginx.conf /etc/nginx/conf.d/default.conf 4 | COPY ./dist /usr/share/nginx/html -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | laf 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /web/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/apple-touch-icon.png -------------------------------------------------------------------------------- /web/public/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/bg.png -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/favicon.ico -------------------------------------------------------------------------------- /web/public/fonts/NotoSansSC-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/fonts/NotoSansSC-Medium.ttf -------------------------------------------------------------------------------- /web/public/homepage/Vector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/public/homepage/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/banner.png -------------------------------------------------------------------------------- /web/public/homepage/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/bg.png -------------------------------------------------------------------------------- /web/public/homepage/cancel_btn.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /web/public/homepage/database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/database.png -------------------------------------------------------------------------------- /web/public/homepage/discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/public/homepage/discord_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /web/public/homepage/forum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/public/homepage/forum1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /web/public/homepage/function.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/function.png -------------------------------------------------------------------------------- /web/public/homepage/icon_01.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/public/homepage/icon_02.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/public/homepage/icon_03.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/public/homepage/icon_05.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web/public/homepage/icon_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/icon_06.png -------------------------------------------------------------------------------- /web/public/homepage/laficon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/laficon.jpg -------------------------------------------------------------------------------- /web/public/homepage/logo_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/public/homepage/logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/logo_text.png -------------------------------------------------------------------------------- /web/public/homepage/p1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/p1.png -------------------------------------------------------------------------------- /web/public/homepage/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/p2.png -------------------------------------------------------------------------------- /web/public/homepage/p3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/p3.png -------------------------------------------------------------------------------- /web/public/homepage/p4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/p4.png -------------------------------------------------------------------------------- /web/public/homepage/p5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/p5.png -------------------------------------------------------------------------------- /web/public/homepage/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /web/public/homepage/storage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/storage.png -------------------------------------------------------------------------------- /web/public/homepage/videobg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/videobg.png -------------------------------------------------------------------------------- /web/public/homepage/videomobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/homepage/videomobile.png -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/browser/ui/codicons/codicon/codicon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/js/monaco-editor.0.43.0/base/browser/ui/codicons/codicon/codicon.ttf -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.de.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.de",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["Array","Boolescher Wert","Klasse","Konstante","Konstruktor","Enumeration","Enumerationsmember","Ereignis","Feld","Datei","Funktion","Schnittstelle","Schl\xFCssel","Methode","Modul","Namespace","NULL","Zahl","Objekt","Operator","Paket","Eigenschaft","Zeichenfolge","Struktur","Typparameter","Variable","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.de.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.es.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.es",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["matriz","booleano","clase","constante","constructor","enumeraci\xF3n","miembro de la enumeraci\xF3n","evento","campo","archivo","funci\xF3n","interfaz","clave","m\xE9todo","m\xF3dulo","espacio de nombres","NULL","n\xFAmero","objeto","operador","paquete","propiedad","cadena","estructura","par\xE1metro de tipo","variable","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.es.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.fr.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.fr",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["tableau","bool\xE9en","classe","constante","constructeur","\xE9num\xE9ration","membre d'\xE9num\xE9ration","\xE9v\xE9nement","champ","fichier","fonction","interface","cl\xE9","m\xE9thode","module","espace de noms","NULL","nombre","objet","op\xE9rateur","package","propri\xE9t\xE9","cha\xEEne","struct","param\xE8tre de type","variable","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.fr.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.it.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.it",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["matrice","valore booleano","classe","costante","costruttore","enumerazione","membro di enumerazione","evento","campo","file","funzione","interfaccia","chiave","metodo","modulo","spazio dei nomi","Null","numero","oggetto","operatore","pacchetto","propriet\xE0","stringa","struct","parametro di tipo","variabile","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.it.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.ja.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.ja",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\u914D\u5217","\u30D6\u30FC\u30EB\u5024","\u30AF\u30E9\u30B9","\u5B9A\u6570","\u30B3\u30F3\u30B9\u30C8\u30E9\u30AF\u30BF\u30FC","\u5217\u6319\u578B","\u5217\u6319\u578B\u30E1\u30F3\u30D0\u30FC","\u30A4\u30D9\u30F3\u30C8","\u30D5\u30A3\u30FC\u30EB\u30C9","\u30D5\u30A1\u30A4\u30EB","\u95A2\u6570","\u30A4\u30F3\u30BF\u30FC\u30D5\u30A7\u30A4\u30B9","\u30AD\u30FC","\u30E1\u30BD\u30C3\u30C9","\u30E2\u30B8\u30E5\u30FC\u30EB","\u540D\u524D\u7A7A\u9593","NULL","\u6570\u5024","\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8","\u6F14\u7B97\u5B50","\u30D1\u30C3\u30B1\u30FC\u30B8","\u30D7\u30ED\u30D1\u30C6\u30A3","\u6587\u5B57\u5217","\u69CB\u9020\u4F53","\u578B\u30D1\u30E9\u30E1\u30FC\u30BF\u30FC","\u5909\u6570","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.ja.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["array","boolean","class","constant","constructor","enumeration","enumeration member","event","field","file","function","interface","key","method","module","namespace","null","number","object","operator","package","property","string","struct","type parameter","variable","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.ko.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.ko",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\uBC30\uC5F4","\uBD80\uC6B8","\uD074\uB798\uC2A4","\uC0C1\uC218","\uC0DD\uC131\uC790","\uC5F4\uAC70\uD615","\uC5F4\uAC70\uD615 \uBA64\uBC84","\uC774\uBCA4\uD2B8","\uD544\uB4DC","\uD30C\uC77C","\uD568\uC218","\uC778\uD130\uD398\uC774\uC2A4","\uD0A4","\uBA54\uC11C\uB4DC","\uBAA8\uB4C8","\uB124\uC784\uC2A4\uD398\uC774\uC2A4","Null","\uC22B\uC790","\uAC1C\uCCB4","\uC5F0\uC0B0\uC790","\uD328\uD0A4\uC9C0","\uC18D\uC131","\uBB38\uC790\uC5F4","\uAD6C\uC870\uCCB4","\uD615\uC2DD \uB9E4\uAC1C \uBCC0\uC218","\uBCC0\uC218","{0}({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.ko.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.zh-cn.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.zh-cn",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\u6570\u7EC4","\u5E03\u5C14\u503C","\u7C7B","\u5E38\u6570","\u6784\u9020\u51FD\u6570","\u679A\u4E3E","\u679A\u4E3E\u6210\u5458","\u4E8B\u4EF6","\u5B57\u6BB5","\u6587\u4EF6","\u51FD\u6570","\u63A5\u53E3","\u952E","\u65B9\u6CD5","\u6A21\u5757","\u547D\u540D\u7A7A\u95F4","Null","\u6570\u5B57","\u5BF9\u8C61","\u8FD0\u7B97\u7B26","\u5305","\u5C5E\u6027","\u5B57\u7B26\u4E32","\u7ED3\u6784","\u7C7B\u578B\u53C2\u6570","\u53D8\u91CF","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.zh-cn.js.map -------------------------------------------------------------------------------- /web/public/js/monaco-editor.0.43.0/base/common/worker/simpleWorker.nls.zh-tw.js: -------------------------------------------------------------------------------- 1 | /*!----------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Version: 0.43.0(94c055bcbdd49f04a0fa15515e848542a79fb948) 4 | * Released under the MIT license 5 | * https://github.com/microsoft/vscode/blob/main/LICENSE.txt 6 | *-----------------------------------------------------------*/define("vs/base/common/worker/simpleWorker.nls.zh-tw",{"vs/base/common/platform":["_"],"vs/editor/common/languages":["\u9663\u5217","\u5E03\u6797\u503C","\u985E\u5225","\u5E38\u6578","\u5EFA\u69CB\u51FD\u5F0F","\u5217\u8209","\u5217\u8209\u6210\u54E1","\u4E8B\u4EF6","\u6B04\u4F4D","\u6A94\u6848","\u51FD\u5F0F","\u4ECB\u9762","\u7D22\u5F15\u9375","\u65B9\u6CD5","\u6A21\u7D44","\u547D\u540D\u7A7A\u9593","null","\u6578\u5B57","\u7269\u4EF6","\u904B\u7B97\u5B50","\u5957\u4EF6","\u5C6C\u6027","\u5B57\u4E32","\u7D50\u69CB","\u578B\u5225\u53C3\u6578","\u8B8A\u6578","{0} ({1})"]}); 7 | 8 | //# sourceMappingURL=../../../../../min-maps/vs/base/common/worker/simpleWorker.nls.zh-tw.js.map -------------------------------------------------------------------------------- /web/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/logo.png -------------------------------------------------------------------------------- /web/public/logo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/logo_light.png -------------------------------------------------------------------------------- /web/public/logo_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/logo_text.png -------------------------------------------------------------------------------- /web/public/masked-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/masked-icon.png -------------------------------------------------------------------------------- /web/public/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/pwa-192x192.png -------------------------------------------------------------------------------- /web/public/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/pwa-512x512.png -------------------------------------------------------------------------------- /web/public/pwa-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/public/pwa-64x64.png -------------------------------------------------------------------------------- /web/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /web/src/apis/dependence.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | interface KeyWord { 3 | q: String; 4 | } 5 | 6 | export async function DependencySearch(params: KeyWord | any): Promise { 7 | if (params.q === "") { 8 | return Promise.resolve([]); 9 | } else { 10 | return axios.get(`https://registry.npmjs.org/-/v1/search`, { 11 | params: { 12 | text: params.q, 13 | size: 4, 14 | }, 15 | }); 16 | } 17 | } 18 | 19 | export async function GetDependencyVersions(params: KeyWord | any): Promise { 20 | if (params.q === "") { 21 | return Promise.resolve([]); 22 | } else { 23 | return axios.get(`https://registry.npmjs.org/${params.q}`); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web/src/apis/v1/auth.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | /* eslint-disable */ 3 | /////////////////////////////////////////////////////////////////////// 4 | // // 5 | // this file is autogenerated by service-generate // 6 | // do not edit this file manually // 7 | // // 8 | /////////////////////////////////////////////////////////////////////// 9 | /// 10 | import request from '@/utils/request'; 11 | import useGlobalStore from "@/pages/globalStore"; 12 | 13 | /** 14 | * Signin by kubeconfig 15 | */ 16 | export async function AuthenticationControllerSignin( 17 | params: Definitions.SigninDto, 18 | ): Promise<{ 19 | error: string; 20 | data: Paths.AuthenticationControllerSignin.Responses 21 | }> { 22 | // /v1/auth/signin 23 | let _params: { [key: string]: any } = { 24 | appid: useGlobalStore.getState().currentApp?.appid || '', 25 | ...params, 26 | }; 27 | return request(`/v1/auth/signin`, { 28 | method: 'POST', 29 | data : params, 30 | }); 31 | } 32 | 33 | /** 34 | * Get user token by PAT 35 | */ 36 | export async function AuthenticationControllerPat2token( 37 | params: Definitions.Pat2TokenDto, 38 | ): Promise<{ 39 | error: string; 40 | data: Paths.AuthenticationControllerPat2token.Responses 41 | }> { 42 | // /v1/auth/pat2token 43 | let _params: { [key: string]: any } = { 44 | appid: useGlobalStore.getState().currentApp?.appid || '', 45 | ...params, 46 | }; 47 | return request(`/v1/auth/pat2token`, { 48 | method: 'POST', 49 | data : params, 50 | }); 51 | } 52 | 53 | -------------------------------------------------------------------------------- /web/src/apis/v1/monitor.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | /* eslint-disable */ 3 | /////////////////////////////////////////////////////////////////////// 4 | // // 5 | // this file is autogenerated by service-generate // 6 | // do not edit this file manually // 7 | // // 8 | /////////////////////////////////////////////////////////////////////// 9 | /// 10 | import request from '@/utils/request'; 11 | import useGlobalStore from "@/pages/globalStore"; 12 | 13 | /** 14 | * Get monitor metrics data 15 | */ 16 | export async function MonitorControllerGetData( 17 | params: Paths.MonitorControllerGetData.BodyParameters, 18 | ): Promise<{ 19 | error: string; 20 | data: Paths.MonitorControllerGetData.Responses 21 | }> { 22 | // /v1/monitor/{appid}/metrics 23 | let _params: { [key: string]: any } = { 24 | appid: useGlobalStore.getState().currentApp?.appid || '', 25 | ...params, 26 | }; 27 | return request(`/v1/monitor/${_params.appid}/metrics`, { 28 | method: 'GET', 29 | params : params, 30 | }); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /web/src/apis/v1/regions.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | /* eslint-disable */ 3 | /////////////////////////////////////////////////////////////////////// 4 | // // 5 | // this file is autogenerated by service-generate // 6 | // do not edit this file manually // 7 | // // 8 | /////////////////////////////////////////////////////////////////////// 9 | /// 10 | import request from '@/utils/request'; 11 | import useGlobalStore from "@/pages/globalStore"; 12 | 13 | /** 14 | * Get region list 15 | */ 16 | export async function RegionControllerGetRegions( 17 | params: Paths.RegionControllerGetRegions.BodyParameters, 18 | ): Promise<{ 19 | error: string; 20 | data: Paths.RegionControllerGetRegions.Responses 21 | }> { 22 | // /v1/regions 23 | let _params: { [key: string]: any } = { 24 | appid: useGlobalStore.getState().currentApp?.appid || '', 25 | ...params, 26 | }; 27 | return request(`/v1/regions`, { 28 | method: 'GET', 29 | params : params, 30 | }); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /web/src/apis/v1/runtimes.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | /* eslint-disable */ 3 | /////////////////////////////////////////////////////////////////////// 4 | // // 5 | // this file is autogenerated by service-generate // 6 | // do not edit this file manually // 7 | // // 8 | /////////////////////////////////////////////////////////////////////// 9 | /// 10 | import request from '@/utils/request'; 11 | import useGlobalStore from "@/pages/globalStore"; 12 | 13 | /** 14 | * Get application runtime list 15 | */ 16 | export async function AppControllerGetRuntimes( 17 | params: Paths.AppControllerGetRuntimes.BodyParameters, 18 | ): Promise<{ 19 | error: string; 20 | data: Paths.AppControllerGetRuntimes.Responses 21 | }> { 22 | // /v1/runtimes 23 | let _params: { [key: string]: any } = { 24 | appid: useGlobalStore.getState().currentApp?.appid || '', 25 | ...params, 26 | }; 27 | return request(`/v1/runtimes`, { 28 | method: 'GET', 29 | params : params, 30 | }); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /web/src/apis/v1/user.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | /* eslint-disable */ 3 | /////////////////////////////////////////////////////////////////////// 4 | // // 5 | // this file is autogenerated by service-generate // 6 | // do not edit this file manually // 7 | // // 8 | /////////////////////////////////////////////////////////////////////// 9 | /// 10 | import request from '@/utils/request'; 11 | import useGlobalStore from "@/pages/globalStore"; 12 | 13 | /** 14 | * Get current user profile 15 | */ 16 | export async function UserControllerGetProfile( 17 | params: Paths.UserControllerGetProfile.BodyParameters, 18 | ): Promise<{ 19 | error: string; 20 | data: Definitions.User 21 | }> { 22 | // /v1/user/profile 23 | let _params: { [key: string]: any } = { 24 | appid: useGlobalStore.getState().currentApp?.appid || '', 25 | ...params, 26 | }; 27 | return request(`/v1/user/profile`, { 28 | method: 'GET', 29 | params : params, 30 | }); 31 | } 32 | 33 | -------------------------------------------------------------------------------- /web/src/components/ColorModeSwitch/index.tsx: -------------------------------------------------------------------------------- 1 | import { MoonIcon, SunIcon } from "@chakra-ui/icons"; 2 | import { useColorMode } from "@chakra-ui/react"; 3 | import clsx from "clsx"; 4 | 5 | import { COLOR_MODE } from "@/constants"; 6 | 7 | export default function ColorModeSwitch(props: { className?: string; fontSize?: number }) { 8 | const { toggleColorMode } = useColorMode(); 9 | const { className, fontSize = 18 } = props; 10 | const darkMode = useColorMode().colorMode === COLOR_MODE.dark; 11 | 12 | return ( 13 |
{ 16 | toggleColorMode(); 17 | window.dispatchEvent(new Event("ColorModeChange")); 18 | }} 19 | > 20 | {darkMode ? : } 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /web/src/components/Content/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function Content(props: { children: React.ReactNode }) { 4 | return
{props.children}
; 5 | } 6 | -------------------------------------------------------------------------------- /web/src/components/CopyText/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { CopyIcon } from "@chakra-ui/icons"; 3 | import { Tooltip, useClipboard } from "@chakra-ui/react"; 4 | import clsx from "clsx"; 5 | import { t } from "i18next"; 6 | 7 | import useGlobalStore from "@/pages/globalStore"; 8 | 9 | export default function CopyText(props: { 10 | text?: string; 11 | tip?: string; 12 | className?: string; 13 | children?: React.ReactElement; 14 | hideToolTip?: boolean; 15 | }) { 16 | const { onCopy, setValue } = useClipboard(""); 17 | const { showSuccess } = useGlobalStore(); 18 | 19 | const { 20 | children = , 21 | text, 22 | tip, 23 | className, 24 | hideToolTip, 25 | } = props; 26 | 27 | useEffect(() => { 28 | setValue(text || ""); 29 | }, [setValue, text]); 30 | 31 | return ( 32 | 33 | {React.cloneElement(children, { 34 | className: clsx("cursor-pointer", className), 35 | onClick: () => { 36 | onCopy(); 37 | showSuccess(tip || t("Copied")); 38 | }, 39 | })} 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /web/src/components/DateRangePicker/index.css: -------------------------------------------------------------------------------- 1 | .rdp-day_selected { 2 | background-color: #00a9a6 !important; 3 | color: white !important; 4 | } 5 | -------------------------------------------------------------------------------- /web/src/components/DependenceList/index.module.scss: -------------------------------------------------------------------------------- 1 | .dependenceList { 2 | max-height: 60vh; 3 | overflow: auto; 4 | 5 | li { 6 | cursor: pointer; 7 | display: flex; 8 | min-height: 80px; 9 | align-items: center; 10 | justify-content: space-between; 11 | padding: 5px 8px; 12 | 13 | span { 14 | vertical-align: middle; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web/src/components/DependenceList/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useColorMode } from "@chakra-ui/react"; 3 | import clsx from "clsx"; 4 | 5 | import { COLOR_MODE } from "@/constants"; 6 | 7 | import styles from "./index.module.scss"; 8 | 9 | function DependenceList(props: { children: React.ReactNode; style?: React.CSSProperties }) { 10 | return ( 11 |
    12 | {props.children} 13 |
14 | ); 15 | } 16 | 17 | function Item(props: { 18 | children: React.ReactNode; 19 | isActive: boolean; 20 | className?: string; 21 | style?: React.CSSProperties; 22 | key: string; 23 | onClick: () => void; 24 | }) { 25 | const { children, isActive, onClick, className, style } = props; 26 | const { colorMode } = useColorMode(); 27 | const darkMode = colorMode === COLOR_MODE.dark; 28 | 29 | return ( 30 |
  • 40 | {children} 41 |
  • 42 | ); 43 | } 44 | 45 | DependenceList.Item = Item; 46 | 47 | export default DependenceList; 48 | -------------------------------------------------------------------------------- /web/src/components/DotBadge/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from "@chakra-ui/react"; 2 | 3 | export default function DotBadge({ 4 | text, 5 | type = "success", 6 | }: { 7 | text: React.ReactNode; 8 | type?: "success" | "warning" | "error" | "info"; 9 | }) { 10 | let color = ""; 11 | let bgColor = "bg-primary-600"; 12 | switch (type) { 13 | case "success": 14 | color = "text-primary-600"; 15 | bgColor = "bg-primary-600"; 16 | break; 17 | case "warning": 18 | color = "text-warn-600"; 19 | bgColor = "bg-warn-600"; 20 | break; 21 | case "error": 22 | color = "text-error-600"; 23 | bgColor = "bg-error-600"; 24 | break; 25 | case "info": 26 | color = "text-blue-500"; 27 | bgColor = "bg-blue-600"; 28 | break; 29 | default: 30 | break; 31 | } 32 | return ( 33 | 34 | 35 | {text} 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /web/src/components/EditableTable/index.module.scss: -------------------------------------------------------------------------------- 1 | .text { 2 | white-space: nowrap; 3 | overflow: hidden; 4 | text-overflow: ellipsis; 5 | font-family: Inter; 6 | } 7 | -------------------------------------------------------------------------------- /web/src/components/Editor/CommonDiffEditor.tsx: -------------------------------------------------------------------------------- 1 | import { useColorMode } from "@chakra-ui/react"; 2 | import { DiffEditor } from "@monaco-editor/react"; 3 | 4 | import { COLOR_MODE } from "@/constants"; 5 | 6 | export default function CommonDiffEditor(props: { original: string; modified: string }) { 7 | const { original, modified } = props; 8 | const { colorMode } = useColorMode(); 9 | 10 | const options = { 11 | readOnly: true, 12 | automaticLayout: true, 13 | minimap: { 14 | enabled: false, 15 | }, 16 | renderOverviewRuler: false, 17 | fontSize: 14, 18 | scrollBeyondLastLine: false, 19 | scrollbar: { 20 | verticalScrollbarSize: 4, 21 | horizontalScrollbarSize: 6, 22 | alwaysConsumeMouseWheel: false, 23 | }, 24 | }; 25 | return ( 26 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /web/src/components/Editor/JSONEditor.tsx: -------------------------------------------------------------------------------- 1 | import Editor from "@monaco-editor/react"; 2 | import { LineNumbersType } from "vscode/vscode/vs/editor/common/config/editorOptions"; 3 | 4 | import { COLOR_MODE } from "@/constants"; 5 | 6 | function JSONEditor(props: { 7 | value: string; 8 | height?: string; 9 | colorMode?: string; 10 | onChange?: (value: string | undefined) => void; 11 | }) { 12 | const { value, onChange, height = "90%", colorMode = COLOR_MODE.light } = props; 13 | 14 | const options = { 15 | lineNumbers: "off" as LineNumbersType, 16 | guides: { 17 | indentation: false, 18 | }, 19 | automaticLayout: true, 20 | minimap: { 21 | enabled: false, 22 | }, 23 | scrollbar: { 24 | verticalScrollbarSize: 4, 25 | horizontalScrollbarSize: 8, 26 | alwaysConsumeMouseWheel: false, 27 | }, 28 | lineNumbersMinChars: 0, 29 | fontSize: 12, 30 | scrollBeyondLastLine: false, 31 | folding: false, 32 | overviewRulerBorder: false, 33 | tabSize: 2, 34 | }; 35 | 36 | return ( 37 | 45 | ); 46 | } 47 | 48 | export default JSONEditor; 49 | -------------------------------------------------------------------------------- /web/src/components/Editor/index.css: -------------------------------------------------------------------------------- 1 | .monaco-editor .margin { 2 | background-color: #fff !important; 3 | } 4 | 5 | .monaco-editor { 6 | --vscode-editor-background: #fff !important; 7 | } 8 | 9 | [data-theme="dark"] .monaco-editor .margin { 10 | background-color: #202631 !important; 11 | } 12 | 13 | [data-theme="dark"] .monaco-editor { 14 | --vscode-editor-background: #202631 !important; 15 | } 16 | 17 | .monaco-editor .mtk5 { 18 | color: #00f !important; 19 | } 20 | 21 | .monaco-editor .mtk4 { 22 | color: #008000 !important; 23 | } 24 | 25 | .monaco-editor .mtk11 { 26 | color: #a31515 !important; 27 | } 28 | 29 | [data-theme="dark"] .monaco-editor .mtk5 { 30 | color: #569cd6 !important; 31 | } 32 | 33 | [data-theme="dark"] .monaco-editor .mtk4 { 34 | color: #6a9955 !important; 35 | } 36 | 37 | [data-theme="dark"] .monaco-editor .mtk11 { 38 | color: #ce9178 !important; 39 | } 40 | 41 | [data-theme="dark"] .monaco-editor .mtk1 { 42 | color: #d4d4d4 !important; 43 | } -------------------------------------------------------------------------------- /web/src/components/Editor/index.scss: -------------------------------------------------------------------------------- 1 | .code .property { 2 | color: #A31515 3 | } 4 | 5 | .code .number { 6 | color: #01a99d 7 | } 8 | 9 | .code .string { 10 | color: #0451a5 11 | } 12 | 13 | [data-theme="dark"] .code .property { 14 | color: #9BDCFE 15 | } 16 | 17 | [data-theme="dark"] .code .number { 18 | color: #B0CAA4 19 | } 20 | 21 | [data-theme="dark"] .code .string { 22 | color: #CE9178 23 | } -------------------------------------------------------------------------------- /web/src/components/Editor/useWorker.ts: -------------------------------------------------------------------------------- 1 | import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"; 2 | import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker"; 3 | import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"; 4 | import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"; 5 | import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"; 6 | 7 | // eslint-disable-next-line no-restricted-globals 8 | self.MonacoEnvironment = { 9 | getWorker(_: any, label: string) { 10 | if (label === "json") { 11 | return new jsonWorker(); 12 | } 13 | if (label === "css" || label === "scss" || label === "less") { 14 | return new cssWorker(); 15 | } 16 | if (label === "html" || label === "handlebars" || label === "razor") { 17 | return new htmlWorker(); 18 | } 19 | if (label === "typescript" || label === "javascript") { 20 | return new tsWorker(); 21 | } 22 | return new editorWorker(); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /web/src/components/FileUpload/index.module.scss: -------------------------------------------------------------------------------- 1 | .formFileUpload { 2 | height: 16rem; 3 | width: 29rem; 4 | max-width: 100%; 5 | text-align: center; 6 | position: relative; 7 | } 8 | 9 | .inputFileUpload { 10 | display: none; 11 | } 12 | 13 | .labelFileUpload { 14 | height: 100%; 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | border-width: 2px; 19 | border-radius: 1rem; 20 | border-style: dashed; 21 | } 22 | 23 | .uploadButton { 24 | cursor: pointer; 25 | padding: 0.25rem; 26 | font-size: 1rem; 27 | border: none; 28 | } 29 | 30 | .uploadButton:hover { 31 | text-decoration-line: underline; 32 | } 33 | 34 | .dragFileElement { 35 | position: absolute; 36 | width: 100%; 37 | height: 100%; 38 | border-radius: 1rem; 39 | top: 0px; 40 | right: 0px; 41 | bottom: 0px; 42 | left: 0px; 43 | } 44 | -------------------------------------------------------------------------------- /web/src/components/Grid/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { forwardRef } from "react"; 3 | import clsx from "clsx"; 4 | function Grid() { 5 | return
    Grid
    ; 6 | } 7 | 8 | const Row = forwardRef( 9 | ( 10 | props: { 11 | className?: string; 12 | style?: React.CSSProperties; 13 | children: React.ReactNode; 14 | id?: string; 15 | }, 16 | ref: any, 17 | ) => { 18 | const { className, style, id } = props; 19 | 20 | return ( 21 |
    31 | {props.children} 32 |
    33 | ); 34 | }, 35 | ); 36 | 37 | function Col(props: { 38 | className?: string; 39 | style?: React.CSSProperties; 40 | children: React.ReactNode; 41 | }) { 42 | const { className, style } = props; 43 | return ( 44 |
    52 | {props.children} 53 |
    54 | ); 55 | } 56 | 57 | export { Col, Row }; 58 | export default Grid; 59 | -------------------------------------------------------------------------------- /web/src/components/IconText/index.module.scss: -------------------------------------------------------------------------------- 1 | .iconText { 2 | color: #7b838b; 3 | 4 | svg { 5 | fill: #7b838b !important; 6 | } 7 | 8 | &:hover { 9 | color: var(--chakra-colors-grayModern-600); 10 | svg { 11 | fill: var(--chakra-colors-grayModern-600) !important; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /web/src/components/IconText/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | 4 | import styles from "./index.module.scss"; 5 | 6 | export default function IconText(props: { 7 | icon: React.ReactElement; 8 | text: string; 9 | className?: string; 10 | onClick?: () => void; 11 | }) { 12 | return ( 13 |
    17 | {React.cloneElement(props.icon, { 18 | height: "20px", 19 | })} 20 | {props.text} 21 |
    22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /web/src/components/IconWrap/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Center, Tooltip, useColorMode } from "@chakra-ui/react"; 3 | import clsx from "clsx"; 4 | 5 | import { COLOR_MODE } from "@/constants"; 6 | 7 | export default function IconWrap(props: { 8 | size?: number; 9 | children: React.ReactNode; 10 | placement?: "top" | "bottom" | "left" | "right"; 11 | tooltip?: string | undefined; 12 | onClick?: (data: any) => void; 13 | showBg?: boolean; 14 | className?: string; 15 | }) { 16 | const { size = 20, tooltip, placement = "top", showBg = false, className } = props; 17 | const { colorMode } = useColorMode(); 18 | const darkMode = colorMode === COLOR_MODE.dark; 19 | return ( 20 | 21 |
    31 | {props.children} 32 |
    33 |
    34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /web/src/components/LanguageSwitch/index.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "react-i18next"; 2 | import { Button, useColorMode } from "@chakra-ui/react"; 3 | import clsx from "clsx"; 4 | 5 | import { LanguageIcon } from "@/components/CommonIcon"; 6 | 7 | const LanguageSwitch = (props: { className?: string; size?: string; color?: string }) => { 8 | const { i18n, t } = useTranslation(); 9 | const { className, size = "20px", color = "#68686E" } = props; 10 | const darkMode = useColorMode().colorMode === "dark"; 11 | 12 | return ( 13 | 24 | ); 25 | }; 26 | 27 | export default LanguageSwitch; 28 | -------------------------------------------------------------------------------- /web/src/components/Markdown/formatLinkText.ts: -------------------------------------------------------------------------------- 1 | export const formatLinkText = (text: string) => { 2 | const httpReg = 3 | /(http|https|ftp):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?/gi; 4 | return text.replace(httpReg, ` $& `); 5 | }; 6 | -------------------------------------------------------------------------------- /web/src/components/MoreButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | Popover, 5 | PopoverContent, 6 | PopoverTrigger, 7 | Tooltip, 8 | useDisclosure, 9 | } from "@chakra-ui/react"; 10 | import clsx from "clsx"; 11 | import { t } from "i18next"; 12 | 13 | import { MoreIcon } from "@/components/CommonIcon/index"; 14 | 15 | export default function MoreButton(props: { 16 | children: React.ReactElement; 17 | isHidden: boolean; 18 | label: string; 19 | maxWidth?: string; 20 | className?: string; 21 | refItem?: React.RefObject; 22 | }) { 23 | const { children, isHidden, maxWidth, label = t("openPopover"), className } = props; 24 | const { isOpen, onOpen, onClose } = useDisclosure(); 25 | return ( 26 |
    27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 |
    {children}
    39 |
    40 |
    41 |
    42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /web/src/components/Panel/index.module.scss: -------------------------------------------------------------------------------- 1 | .sectionHeader { 2 | min-height: 32px; 3 | max-height: 32px; 4 | display: flex; 5 | align-items: center; 6 | 7 | h4 { 8 | padding-left: 10px; 9 | font-weight: 500; 10 | font-size: 12px; 11 | position: relative; 12 | 13 | &::before { 14 | content: " "; 15 | height: 12px; 16 | position: absolute; 17 | left: 0; 18 | top: 3px; 19 | width: 3px; 20 | background: #00a99d; 21 | border-radius: 24px; 22 | } 23 | } 24 | 25 | h4 svg { 26 | vertical-align: -4px !important; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web/src/components/Panel/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Box, useColorModeValue } from "@chakra-ui/react"; 3 | import clsx from "clsx"; 4 | 5 | import PanelHeader from "./Header"; 6 | 7 | const Panel = (props: { 8 | className?: string; 9 | style?: React.CSSProperties; 10 | children: React.ReactNode; 11 | onClick?: () => void; 12 | }) => { 13 | const { className, style = {}, onClick } = props; 14 | const bg = useColorModeValue("lafWhite.200", "lafDark.200"); 15 | return ( 16 | 22 | {props.children} 23 | 24 | ); 25 | }; 26 | 27 | Panel.Header = PanelHeader; 28 | 29 | export default Panel; 30 | -------------------------------------------------------------------------------- /web/src/components/SectionList/index.module.scss: -------------------------------------------------------------------------------- 1 | .sectionList { 2 | overflow-y: hidden; 3 | overflow-x: hidden; 4 | height: 100%; 5 | 6 | li { 7 | cursor: pointer; 8 | display: flex; 9 | min-height: 24px; 10 | align-items: center; 11 | justify-content: space-between; 12 | padding: 0px 4px 0px 12px; 13 | margin-bottom: 2px; 14 | 15 | span { 16 | vertical-align: middle; 17 | } 18 | 19 | svg { 20 | fill: #7b838b; 21 | } 22 | 23 | &.small { 24 | height: 32px; 25 | } 26 | 27 | &:hover, 28 | &.active { 29 | background-color: #00b3b01a; 30 | color: var(--chakra-colors-primary-600); 31 | 32 | svg { 33 | fill: var(--chakra-colors-primary-600); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /web/src/components/SectionList/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import clsx from "clsx"; 3 | import SimpleBar from "simplebar-react"; 4 | 5 | import styles from "./index.module.scss"; 6 | 7 | function SectionList( 8 | props: { children: React.ReactNode; className?: string } & Omit< 9 | React.HTMLAttributes, 10 | "children" 11 | >, 12 | ) { 13 | const { className, children, ...restProps } = props; 14 | 15 | return ( 16 | 20 | {children} 21 | 22 | ); 23 | } 24 | 25 | function Item( 26 | props: { 27 | children: React.ReactNode; 28 | isActive: boolean; 29 | className?: string; 30 | size?: "small" | "default"; 31 | } & Omit, "children">, 32 | ) { 33 | const { children, isActive, className, size = "default", ...restProps } = props; 34 | 35 | return ( 36 |
  • 43 | {children} 44 |
  • 45 | ); 46 | } 47 | 48 | SectionList.Item = Item; 49 | 50 | export default SectionList; 51 | -------------------------------------------------------------------------------- /web/src/components/TextButton/index.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from "react"; 2 | import { Button } from "@chakra-ui/react"; 3 | import clsx from "clsx"; 4 | const TextButton = function ( 5 | props: { 6 | text: string; 7 | onClick?: () => void; 8 | className?: string; 9 | type?: "submit" | undefined; 10 | }, 11 | ref: any, 12 | ) { 13 | const { text, type, onClick, className } = props; 14 | return ( 15 | 26 | ); 27 | }; 28 | export default forwardRef(TextButton); 29 | -------------------------------------------------------------------------------- /web/src/constants/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/labring/sealaf/bfb0ddfc1361929b69b4f5707dd67a1f5c2381e8/web/src/constants/.gitkeep -------------------------------------------------------------------------------- /web/src/hooks/useCustomToast.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useToast } from "@chakra-ui/react"; 3 | 4 | function useCustomToast() { 5 | const toast = useToast(); 6 | 7 | const showSuccess = (text: string | React.ReactNode) => { 8 | toast({ 9 | position: "top", 10 | title: text, 11 | status: "success", 12 | duration: 1000, 13 | }); 14 | }; 15 | 16 | const showError = (text: string | React.ReactNode) => { 17 | toast({ 18 | position: "top", 19 | title: text, 20 | status: "error", 21 | duration: 1000, 22 | }); 23 | }; 24 | return { 25 | showSuccess, 26 | showError, 27 | }; 28 | } 29 | 30 | export default useCustomToast; 31 | -------------------------------------------------------------------------------- /web/src/hooks/useDB.ts: -------------------------------------------------------------------------------- 1 | import { Cloud } from "laf-client-sdk"; 2 | 3 | import { VITE_SERVER_BASE_URL } from "../constants"; 4 | 5 | import useGlobalStore from "@/pages/globalStore"; 6 | 7 | function useDB() { 8 | const currentApp = useGlobalStore((state) => state.currentApp); 9 | const dbm_cloud = new Cloud({ 10 | baseUrl: VITE_SERVER_BASE_URL, 11 | dbProxyUrl: `/v1/apps/${currentApp?.appid}/databases/proxy`, 12 | getAccessToken: () => localStorage.getItem("token") as any, 13 | }); 14 | 15 | const db = dbm_cloud.database(); 16 | 17 | return { db }; 18 | } 19 | 20 | export default useDB; 21 | -------------------------------------------------------------------------------- /web/src/hooks/useFunctionCache.ts: -------------------------------------------------------------------------------- 1 | import { RUNTIMES_PATH } from "@/constants"; 2 | 3 | function useFunctionCache() { 4 | const CACHE_KEY_PREFIX = "$cached_function@"; 5 | 6 | function getCache(functionId: string, _default: string = ""): string { 7 | return localStorage.getItem(CACHE_KEY_PREFIX + functionId) || _default; 8 | } 9 | 10 | function setCache(functionId: string, value: string) { 11 | localStorage.setItem(CACHE_KEY_PREFIX + functionId, value); 12 | } 13 | 14 | function removeCache(functionId: string) { 15 | localStorage.removeItem(CACHE_KEY_PREFIX + functionId); 16 | } 17 | 18 | function getPositionCache(path: string, _default: string = ""): string { 19 | return localStorage.getItem(CACHE_KEY_PREFIX + path) || _default; 20 | } 21 | 22 | function setPositionCache(functionName: string, value: string) { 23 | localStorage.setItem(CACHE_KEY_PREFIX + `${RUNTIMES_PATH}/${functionName}.ts`, value); 24 | } 25 | 26 | return { 27 | getCache, 28 | setCache, 29 | removeCache, 30 | getPositionCache, 31 | setPositionCache, 32 | }; 33 | } 34 | 35 | export default useFunctionCache; 36 | -------------------------------------------------------------------------------- /web/src/hooks/useInviteCode.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | const useInviteCode = () => { 4 | const [inviteCode, setInviteCode] = useState(null); 5 | 6 | useEffect(() => { 7 | let code = new URLSearchParams(window.location.search).get("code"); 8 | 9 | if (code) { 10 | const now = new Date(); 11 | const expirationDays = 7; 12 | const expiration = new Date(now.getTime() + expirationDays * 24 * 60 * 60 * 1000); 13 | 14 | const item = { 15 | value: code, 16 | expiration: expiration.getTime(), 17 | }; 18 | 19 | localStorage.setItem("inviteCode", JSON.stringify(item)); 20 | } else { 21 | const item = localStorage.getItem("inviteCode"); 22 | 23 | if (item) { 24 | const data = JSON.parse(item); 25 | if (new Date().getTime() > data.expiration) { 26 | localStorage.removeItem("inviteCode"); 27 | } else { 28 | code = data.value; 29 | } 30 | } 31 | } 32 | 33 | setInviteCode(code); 34 | }, []); 35 | 36 | return inviteCode; 37 | }; 38 | 39 | export default useInviteCode; 40 | -------------------------------------------------------------------------------- /web/src/layouts/Auth/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | height: 100vh; 3 | width: 100vw; 4 | background-image: url("@/assets/login_bg.svg"); 5 | background-repeat: no-repeat; 6 | background-position: left bottom; 7 | background-size: 56%; 8 | } 9 | -------------------------------------------------------------------------------- /web/src/layouts/Auth/index.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import { useColorMode } from "@chakra-ui/react"; 3 | import clsx from "clsx"; 4 | 5 | import { COLOR_MODE } from "@/constants"; 6 | 7 | import styles from "./index.module.scss"; 8 | 9 | export default function LoginReg() { 10 | const { colorMode } = useColorMode(); 11 | const darkMode = colorMode === COLOR_MODE.dark; 12 | 13 | return ( 14 |
    17 |
    18 |
    24 | Welcome to Laf ! 25 |
    26 |
    32 | life is short, you need laf. 33 |
    34 |
    35 | 36 |
    37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /web/src/layouts/Basic/index.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { AiFillHeart } from "react-icons/ai"; 3 | import { Outlet } from "react-router-dom"; 4 | import { Center, Spinner } from "@chakra-ui/react"; 5 | 6 | import Header from "@/layouts/Header"; 7 | import useGlobalStore from "@/pages/globalStore"; 8 | 9 | export default function BasicLayout() { 10 | const { init, loading } = useGlobalStore((state) => state); 11 | 12 | useEffect(() => { 13 | init(); 14 | }, [init]); 15 | 16 | return ( 17 |
    18 |
    19 |
    20 | {loading ? ( 21 |
    22 | 23 |
    24 | ) : ( 25 | 26 | )} 27 |
    28 |
    29 | Made with by laf team 30 |
    31 |
    32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /web/src/layouts/Template.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { Outlet } from "react-router-dom"; 3 | import { useColorMode } from "@chakra-ui/react"; 4 | import { Center, Spinner } from "@chakra-ui/react"; 5 | import clsx from "clsx"; 6 | 7 | import Header from "./Header"; 8 | 9 | import useGlobalStore from "@/pages/globalStore"; 10 | 11 | export default function TemplateLayout() { 12 | const { colorMode } = useColorMode(); 13 | const darkMode = colorMode === "dark"; 14 | 15 | const { init, loading } = useGlobalStore((state) => state); 16 | useEffect(() => { 17 | init(); 18 | }, [init]); 19 | 20 | return ( 21 | <> 22 |
    28 |
    29 |
    30 |
    31 |
    34 | 35 |
    36 |
    37 | {loading ? ( 38 |
    39 | 40 |
    41 | ) : ( 42 | 43 | )} 44 |
    45 |
    46 |
    47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /web/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { 4 | createRoutesFromChildren, 5 | matchRoutes, 6 | useLocation, 7 | useNavigationType, 8 | } from "react-router-dom"; 9 | import * as Sentry from "@sentry/react"; 10 | 11 | import "focus-visible/dist/focus-visible"; 12 | 13 | import App from "./App"; 14 | 15 | if (["laf.run", "laf.dev"].includes(window.location.hostname)) { 16 | const commitId = import.meta.env.VITE_GITHUB_SHA; 17 | 18 | Sentry.init({ 19 | dsn: import.meta.env.VITE_SENTRY_DSN, 20 | release: `laf@${commitId}`, 21 | integrations: [ 22 | new Sentry.BrowserTracing({ 23 | routingInstrumentation: Sentry.reactRouterV6Instrumentation( 24 | useEffect, 25 | useLocation, 26 | useNavigationType, 27 | createRoutesFromChildren, 28 | matchRoutes, 29 | ), 30 | }), 31 | new Sentry.Replay(), 32 | // new HttpClient(), 33 | ], 34 | // Performance Monitoring 35 | tracesSampleRate: 1.0, 36 | // Session Replay 37 | replaysSessionSampleRate: 0.1, 38 | replaysOnErrorSampleRate: 1.0, 39 | }); 40 | } 41 | 42 | // polyfill for aws-sdk 43 | if (typeof (window as any).global === "undefined") { 44 | (window as any).global = window; 45 | } 46 | 47 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(); 48 | -------------------------------------------------------------------------------- /web/src/pages/403.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import { ArrowForwardIcon } from "@chakra-ui/icons"; 3 | import { Button, Center, HStack } from "@chakra-ui/react"; 4 | import { t } from "i18next"; 5 | 6 | import { Routes } from "@/constants"; 7 | 8 | export default function Index() { 9 | const navigate = useNavigate(); 10 | return ( 11 |
    12 |
    13 |

    403

    14 |

    15 | {t(`403Message`)} 16 |

    17 | 18 | 25 | 26 |
    27 |
    28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /web/src/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router-dom"; 2 | import { ArrowForwardIcon } from "@chakra-ui/icons"; 3 | import { Button, Center, HStack } from "@chakra-ui/react"; 4 | import { t } from "i18next"; 5 | 6 | import { Routes } from "@/constants"; 7 | 8 | export default function Index() { 9 | const navigate = useNavigate(); 10 | return ( 11 |
    12 |
    13 |

    404

    14 |

    15 | {t(`404Message`)} 16 |

    17 | 18 | 25 | 26 |
    27 |
    28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /web/src/pages/app/database/BottomPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, HStack } from "@chakra-ui/react"; 2 | import { t } from "i18next"; 3 | 4 | import Panel from "@/components/Panel"; 5 | 6 | import useCustomSettingStore from "@/pages/customSetting"; 7 | 8 | function BottomPanel() { 9 | const store = useCustomSettingStore(); 10 | 11 | return ( 12 | 13 | 14 | 21 | 22 | 23 | ); 24 | } 25 | 26 | export default BottomPanel; 27 | -------------------------------------------------------------------------------- /web/src/pages/app/database/CollectionDataList/index.tsx: -------------------------------------------------------------------------------- 1 | import { useTranslation } from "react-i18next"; 2 | 3 | import EmptyBox from "@/components/EmptyBox"; 4 | 5 | import CreateCollectionModal from "../mods/CreateCollectionModal"; 6 | import useDBMStore from "../store"; 7 | 8 | import DataPanel from "./mods/DataPanel"; 9 | 10 | export default function CollectionDataList() { 11 | const { t } = useTranslation(); 12 | const store = useDBMStore((state) => state); 13 | return ( 14 | <> 15 | {store.currentDB === undefined ? ( 16 | 17 |
    18 | {t("CollectionPanel.EmptyCollectionText")} 19 | 20 | 21 | {t("CreateNow")} 22 | 23 | 24 |
    25 |
    26 | ) : ( 27 | 28 | )} 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /web/src/pages/app/database/CollectionDataList/mods/ColPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@chakra-ui/react"; 2 | import { t } from "i18next"; 3 | 4 | import JSONEditor from "@/components/Editor/JSONEditor"; 5 | export default function ColPanel() { 6 | return ( 7 |
    8 |
    9 | 12 |
    13 |
    14 | 15 |
    16 |
    17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /web/src/pages/app/database/CollectionDataList/mods/DataPanel/index.module.scss: -------------------------------------------------------------------------------- 1 | .simplebar-scrollbar::before { 2 | background: #7b838b; 3 | width: 4px; 4 | transform: translateX(100%); 5 | } 6 | -------------------------------------------------------------------------------- /web/src/pages/app/database/CollectionDataList/mods/IndexPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from "@chakra-ui/react"; 2 | import { t } from "i18next"; 3 | 4 | import AddIndexModal from "./addIndexModal"; 5 | 6 | export default function IndexPanel() { 7 | return ( 8 |
    9 |
    10 | 11 |
    12 |
    13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | {[1, 2, 3, 4].map((i) => ( 27 | 28 | 29 | 30 | 31 | 36 | 37 | ))} 38 | 39 |
    索引名称索引属性索引字段 21 | 操作 22 |
    Mark ChandlerMark Chandlerdeveloper 32 | 35 |
    40 |
    41 |
    42 |
    43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /web/src/pages/app/database/RightComponent/EditBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Text, useColorMode } from "@chakra-ui/react"; 3 | import { clsx } from "clsx"; 4 | import { t } from "i18next"; 5 | 6 | import { COLOR_MODE } from "@/constants"; 7 | 8 | const RightPanelEditBox: React.FC<{ 9 | show?: boolean; 10 | children?: React.ReactNode; 11 | className?: string; 12 | isLoading: boolean; 13 | title?: React.ReactNode | string; 14 | onSave: () => void; 15 | }> = (props) => { 16 | const { title, isLoading, children, onSave, show } = props; 17 | const { colorMode } = useColorMode(); 18 | const darkMode = colorMode === COLOR_MODE.dark; 19 | 20 | return ( 21 |
    30 |
    35 | 36 | {title} 37 | 38 | 48 |
    49 | {children} 50 |
    51 | ); 52 | }; 53 | 54 | export default RightPanelEditBox; 55 | -------------------------------------------------------------------------------- /web/src/pages/app/database/index.tsx: -------------------------------------------------------------------------------- 1 | /**************************** 2 | * cloud functions database page 3 | ***************************/ 4 | import { useRef } from "react"; 5 | 6 | import Content from "@/components/Content"; 7 | import { Col, Row } from "@/components/Grid"; 8 | import Panel from "@/components/Panel"; 9 | import Resize from "@/components/Resize"; 10 | 11 | import StatusBar from "../mods/StatusBar"; 12 | 13 | import CollectionDataList from "./CollectionDataList"; 14 | import CollectionListPanel from "./CollectionListPanel"; 15 | 16 | import useCustomSettingStore from "@/pages/customSetting"; 17 | function DatabasePage() { 18 | const containerRef = useRef(null); 19 | const settingStore = useCustomSettingStore(); 20 | 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | 39 | export default DatabasePage; 40 | -------------------------------------------------------------------------------- /web/src/pages/app/database/store.ts: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | import { devtools } from "zustand/middleware"; 3 | import { immer } from "zustand/middleware/immer"; 4 | 5 | import { TDB } from "@/apis/typing"; 6 | 7 | type State = { 8 | currentDB?: TDB | undefined; 9 | currentPolicy?: any; 10 | setCurrentDB: (currentDB: TDB | undefined) => void; 11 | }; 12 | 13 | const useDBMStore = create()( 14 | devtools( 15 | immer((set) => ({ 16 | currentShow: "DB", 17 | currentDB: undefined, 18 | currentPolicy: undefined, 19 | setCurrentDB: async (currentDB: any) => { 20 | set((state) => { 21 | state.currentDB = currentDB; 22 | }); 23 | }, 24 | })), 25 | ), 26 | ); 27 | 28 | export default useDBMStore; 29 | -------------------------------------------------------------------------------- /web/src/pages/app/functions/mods/BottomPanel/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, HStack } from "@chakra-ui/react"; 2 | import { t } from "i18next"; 3 | 4 | import Panel from "@/components/Panel"; 5 | 6 | import useCustomSettingStore from "@/pages/customSetting"; 7 | 8 | function BottomPanel() { 9 | const store = useCustomSettingStore(); 10 | 11 | return ( 12 | 13 | 14 | 21 | 28 | 29 | 30 | 37 | 38 | 39 | ); 40 | } 41 | 42 | export default BottomPanel; 43 | -------------------------------------------------------------------------------- /web/src/pages/app/functions/mods/FunctionPanel/ContextMenu/index.scss: -------------------------------------------------------------------------------- 1 | .contexify { 2 | box-shadow: none !important; 3 | border: 1px solid #e2e8f0 !important; 4 | min-width: 0 !important; 5 | justify-content: center; 6 | width: 120px; 7 | padding: 2px !important; 8 | } 9 | 10 | .contexify_theme-dark { 11 | background-color: #212630 !important; 12 | border: 1px solid rgba(255, 255, 255, 0.16) !important; 13 | } 14 | 15 | .contexify_itemContent { 16 | background: none !important; 17 | width: 100% !important; 18 | height: 100% !important; 19 | } -------------------------------------------------------------------------------- /web/src/pages/app/functions/mods/FunctionPanel/index.css: -------------------------------------------------------------------------------- 1 | .funcList .simplebar-scrollbar::before { 2 | background: #dee0e2; 3 | width: 4px; 4 | transform: translateX(100%); 5 | } 6 | -------------------------------------------------------------------------------- /web/src/pages/app/functions/mods/HeadPanel/index.css: -------------------------------------------------------------------------------- 1 | .recentList .simplebar-scrollbar::before { 2 | background: #dee0e2; 3 | height: 6px; 4 | transform: translateY(50%); 5 | } 6 | -------------------------------------------------------------------------------- /web/src/pages/app/index.tsx: -------------------------------------------------------------------------------- 1 | import { Pages, SideBarWidth } from "@/constants/index"; 2 | 3 | import useGlobalStore from "../globalStore"; 4 | 5 | import SideBar from "./mods/SideBar"; 6 | import DatabasePage from "./database"; 7 | import FunctionPage from "./functions"; 8 | 9 | function AppDetail() { 10 | const { visitedViews, currentPageId } = useGlobalStore(); 11 | return ( 12 | <> 13 | 14 |
    15 | {[ 16 | { 17 | pageId: Pages.function, 18 | component: FunctionPage, 19 | }, 20 | { 21 | pageId: Pages.database, 22 | component: DatabasePage, 23 | }, 24 | ].map((item) => 25 | visitedViews.includes(item.pageId) ? ( 26 |
    34 | 35 |
    36 | ) : null, 37 | )} 38 |
    39 | 40 | ); 41 | } 42 | 43 | export default AppDetail; 44 | -------------------------------------------------------------------------------- /web/src/pages/app/mods/SideBar/index.module.scss: -------------------------------------------------------------------------------- 1 | .icon { 2 | color: #888d97; 3 | cursor: pointer; 4 | border-radius: 4px; 5 | height: 52px; 6 | width: 42px; 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: center; 10 | font-weight: 500; 11 | 12 | svg { 13 | fill: #9ca2a8; 14 | } 15 | } 16 | 17 | .icon:hover, 18 | .icon.current { 19 | background-color: var(--chakra-colors-primary-200); 20 | color: var(--chakra-colors-primary-700); 21 | 22 | svg { 23 | fill: var(--chakra-colors-primary-600); 24 | } 25 | } 26 | 27 | [data-theme="dark"] .icon:hover, 28 | [data-theme="dark"] .icon.current { 29 | background: none; 30 | 31 | svg { 32 | fill: var(--chakra-colors-primary-600); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /web/src/pages/app/mods/StatusBar/LogsModal/index.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable selector-class-pattern */ 2 | #log-viewer-container { 3 | .pf-v5-c-text-input-group__icon { 4 | visibility: hidden; 5 | } 6 | 7 | .pf-v5-c-text-input-group__text-input:focus { 8 | outline: none !important; 9 | color: #000; 10 | } 11 | 12 | .pf-m-current { 13 | background: #91ded9 !important; 14 | } 15 | 16 | .pf-m-match { 17 | background: #daf4f2 !important; 18 | } 19 | 20 | [data-theme="dark"] & .pf-v5-c-text-input-group__text-input:focus { 21 | outline: none !important; 22 | color: #fff; 23 | } 24 | 25 | [data-theme="dark"] & .pf-m-current { 26 | background: #47c8bf !important; 27 | } 28 | 29 | [data-theme="dark"] & .pf-m-match { 30 | background: #2b7873 !important; 31 | } 32 | } 33 | 34 | .log-viewer-container-hide-scrollbar { 35 | &, 36 | & * { 37 | &::-webkit-scrollbar { 38 | width: 0 !important; 39 | height: 0 !important; 40 | } 41 | 42 | -ms-overflow-style: none; /* IE and Edge */ 43 | scrollbar-width: none; /* Firefox */ 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /web/src/pages/app/mods/StatusBar/LogsModal/initLog.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable selector-class-pattern */ 2 | #log-viewer-cover-container { 3 | .pf-v5-c-text-input-group__icon { 4 | visibility: hidden; 5 | } 6 | 7 | .pf-v5-c-text-input-group__text-input:focus { 8 | outline: none !important; 9 | color: #000; 10 | } 11 | 12 | .pf-m-current { 13 | background: #91ded9 !important; 14 | } 15 | 16 | .pf-m-match { 17 | background: #daf4f2 !important; 18 | } 19 | 20 | [data-theme="dark"] & .pf-v5-c-text-input-group__text-input:focus { 21 | outline: none !important; 22 | color: #fff; 23 | } 24 | 25 | [data-theme="dark"] & .pf-m-current { 26 | background: #47c8bf !important; 27 | } 28 | 29 | [data-theme="dark"] & .pf-m-match { 30 | background: #2b7873 !important; 31 | } 32 | } 33 | 34 | .log-viewer-cover-container-hide-scrollbar { 35 | &, 36 | & * { 37 | &::-webkit-scrollbar { 38 | width: 0 !important; 39 | height: 0 !important; 40 | } 41 | 42 | -ms-overflow-style: none; /* IE and Edge */ 43 | scrollbar-width: none; /* Firefox */ 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /web/src/pages/app/setting/SysSetting/AppEnvList/EditTextarea/index.tsx: -------------------------------------------------------------------------------- 1 | import { Textarea } from "@chakra-ui/react"; 2 | import { t } from "i18next"; 3 | 4 | const EditTextarea = function (props: { 5 | text: string; 6 | value: number; 7 | onChange: (data: any) => any; 8 | onBlur: (data: any) => any; 9 | disabled: boolean; 10 | }) { 11 | const { text, value, onBlur, onChange, disabled } = props; 12 | return ( 13 |