├── .dockerignore
├── .editorconfig
├── .env
├── .github
└── workflows
│ └── check.yaml
├── .gitignore
├── .npmrc
├── .tool-versions
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── bun.lock
├── eslint.config.js
├── jsconfig.json
├── package.json
├── playwright.config.js
├── src
├── app.d.ts
├── app.html
├── hooks.server.js
├── lib
│ ├── api
│ │ └── index.js
│ ├── components
│ │ ├── Chart.svelte
│ │ ├── DeploymentStatusIcon.svelte
│ │ ├── ErrorRow.svelte
│ │ ├── LoadingRow.svelte
│ │ ├── NoDataRow.svelte
│ │ ├── Secret.svelte
│ │ └── StatusIcon.svelte
│ ├── format
│ │ └── index.js
│ ├── hc
│ │ └── index.js
│ └── modal
│ │ └── index.js
├── routes
│ ├── (auth)
│ │ ├── (project)
│ │ │ ├── +layout.js
│ │ │ ├── +layout.server.js
│ │ │ ├── +layout.svelte
│ │ │ ├── +page.js
│ │ │ ├── +page.svelte
│ │ │ ├── deployment
│ │ │ │ ├── (detail)
│ │ │ │ │ ├── +layout.js
│ │ │ │ │ ├── +layout.svelte
│ │ │ │ │ ├── detail
│ │ │ │ │ │ ├── +page.js
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ │ ├── events
│ │ │ │ │ │ ├── +page.js
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ │ ├── logs
│ │ │ │ │ │ ├── +page.js
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ │ ├── metrics
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ │ └── revision
│ │ │ │ │ │ ├── +page.js
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── _components
│ │ │ │ │ └── Header.svelte
│ │ │ │ └── deploy
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ ├── disk
│ │ │ │ ├── (detail)
│ │ │ │ │ ├── +layout.js
│ │ │ │ │ ├── +layout.svelte
│ │ │ │ │ ├── detail
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ │ └── metrics
│ │ │ │ │ │ └── +page.svelte
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ └── create
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ ├── domain
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── cdn-downgrade
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ │ ├── create
│ │ │ │ │ └── +page.svelte
│ │ │ │ └── detail
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ ├── dropbox
│ │ │ │ ├── +layout.js
│ │ │ │ └── +page.svelte
│ │ │ ├── email
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ └── +page.svelte
│ │ │ ├── pull-secret
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── create
│ │ │ │ │ └── +page.svelte
│ │ │ │ └── detail
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ ├── registry
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ └── detail
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ ├── role
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── bind
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ │ ├── create
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ │ └── users
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ ├── route
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ └── create
│ │ │ │ │ └── +page.svelte
│ │ │ ├── service-account
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── create
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ │ └── detail
│ │ │ │ │ ├── +page.js
│ │ │ │ │ └── +page.svelte
│ │ │ └── workload-identity
│ │ │ │ ├── +layout.js
│ │ │ │ ├── +page.js
│ │ │ │ ├── +page.svelte
│ │ │ │ ├── create
│ │ │ │ └── +page.svelte
│ │ │ │ └── detail
│ │ │ │ ├── +page.js
│ │ │ │ └── +page.svelte
│ │ ├── +layout.js
│ │ ├── +layout.svelte
│ │ ├── ModalSelectProject.svelte
│ │ ├── Navbar.svelte
│ │ ├── Sidebar.svelte
│ │ ├── billing
│ │ │ ├── +page.js
│ │ │ ├── +page.svelte
│ │ │ ├── create
│ │ │ │ ├── +page.js
│ │ │ │ └── +page.svelte
│ │ │ ├── detail
│ │ │ │ ├── +page.js
│ │ │ │ └── +page.svelte
│ │ │ └── report
│ │ │ │ ├── +page.js
│ │ │ │ └── +page.svelte
│ │ └── project
│ │ │ ├── +page.js
│ │ │ ├── +page.svelte
│ │ │ └── create
│ │ │ ├── +page.js
│ │ │ └── +page.svelte
│ ├── +layout.js
│ ├── +layout.svelte
│ ├── api
│ │ ├── [fn]
│ │ │ └── +server.js
│ │ ├── dropbox
│ │ │ └── +server.js
│ │ └── registry
│ │ │ └── [fn]
│ │ │ └── +server.js
│ └── auth
│ │ ├── callback
│ │ └── +server.js
│ │ ├── signin
│ │ └── +server.js
│ │ └── signout
│ │ └── +server.js
├── style
│ ├── _theme.scss
│ └── main.scss
└── types
│ └── api.d.ts
├── static
├── favicon.png
├── favicon.webp
└── images
│ ├── logo.png
│ └── logo.webp
├── svelte.config.js
├── vite.config.js
└── wrangler.toml
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .svelte-kit/
3 | build/
4 | .idea/
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.svelte]
4 | indent_style = tab
5 |
6 | [*.{js,cjs}]
7 | indent_style = tab
8 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | API_ENDPOINT=https://api.deploys.app
2 | OAUTH2_CLIENT_ID=localhost
3 | OAUTH2_CLIENT_SECRET=localhost
4 | PUBLIC_API_ENDPOINT=https://api.deploys.app
5 | PUBLIC_SENTRY_DSN=
6 |
--------------------------------------------------------------------------------
/.github/workflows/check.yaml:
--------------------------------------------------------------------------------
1 | name: Check
2 | on:
3 | push:
4 | pull_request:
5 | jobs:
6 | lint:
7 | runs-on: ubuntu-latest
8 | name: Lint
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: asdf-vm/actions/install@v2
12 | - run: bun install --frozen-lockfile
13 | - run: bun run lint
14 | - run: bun run check
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env.*
7 | !.env.example
8 | .pnp.*
9 | .yarn/*
10 | !.yarn/patches
11 | !.yarn/plugins
12 | !.yarn/releases
13 | !.yarn/sdks
14 | !.yarn/versions
15 | .idea
16 | .vscode
17 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | bun 1.2.15
2 | nodejs 22.15.1
3 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM registry.moonrhythm.io/builder
2 |
3 | WORKDIR /workspace
4 | ADD .tool-versions ./
5 | RUN asdf install
6 |
7 | ENV ADAPTER=node
8 |
9 | ADD package.json bun.lock ./
10 | ADD svelte.config.js ./
11 | RUN bun install --frozen-lockfile
12 | ADD . .
13 | RUN bun -b run build
14 | #RUN sed -i'' -e "s/import http from 'http'/import http from 'http2'/g" build/index.js
15 |
16 | FROM gcr.io/distroless/nodejs22-debian11
17 |
18 | ENV NODE_ENV=production
19 | ENV BODY_SIZE_LIMIT=Infinity
20 | ENV ADDRESS_HEADER=X-Real-Ip
21 |
22 | WORKDIR /app
23 | ADD package.json ./
24 | COPY --from=0 /workspace/build .
25 | CMD ["index.js"]
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 deploys-app
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | REGISTRY=registry.deploys.app/deploys-app/console
2 | TAG=$(shell git rev-parse HEAD)
3 |
4 | .PHONY: build
5 | build:
6 | buildctl build \
7 | --frontend dockerfile.v0 \
8 | --local dockerfile=. \
9 | --local context=. \
10 | --output type=image,name=$(REGISTRY):$(TAG),push=true
11 |
12 | deploy: build
13 | deploys deployment set image console -project=deploys-app -image=$(REGISTRY):$(TAG)
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Console
2 |
3 | Web console for manage resources on deploys.app.
4 |
5 | ## Developing
6 |
7 | Install dependencies with `bun install`.
8 |
9 | Start development server:
10 |
11 | ```bash
12 | bun -b run dev
13 |
14 | # or start the server and open the app in a new browser tab
15 | bun -b run dev --open
16 | ```
17 |
18 | ## Building
19 |
20 | To create a production version:
21 |
22 | ```bash
23 | bun -b run build
24 | ```
25 |
26 | ## License
27 |
28 | MIT
29 |
30 | This project uses font-awesome pro, you need font-awesome pro license to editing source code.
31 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import svelte from 'eslint-plugin-svelte'
3 | import globals from 'globals'
4 |
5 | /** @type {import('eslint').Linter.FlatConfig[]} */
6 | export default [
7 | js.configs.recommended,
8 | ...svelte.configs['flat/recommended'],
9 | {
10 | languageOptions: {
11 | globals: {
12 | ...globals.browser,
13 | ...globals.node
14 | }
15 | }
16 | },
17 | {
18 | ignores: ['build/', '.svelte-kit/', 'package/']
19 | },
20 | {
21 | rules: {
22 | indent: ['error', 'tab'],
23 | 'no-var': 'warn',
24 | 'object-shorthand': ['warn', 'properties'],
25 |
26 | 'accessor-pairs': ['error', { setWithoutGet: true, enforceForClassMembers: true }],
27 | 'array-bracket-spacing': ['error', 'never'],
28 | 'array-callback-return': ['error', {
29 | allowImplicit: false,
30 | checkForEach: false
31 | }],
32 | 'arrow-spacing': ['error', { before: true, after: true }],
33 | 'block-spacing': ['error', 'always'],
34 | 'brace-style': ['error', '1tbs', { allowSingleLine: true }],
35 | camelcase: ['error', {
36 | allow: ['^UNSAFE_'],
37 | properties: 'never',
38 | ignoreGlobals: true,
39 | ignoreImports: true
40 | }],
41 | 'comma-dangle': ['error', {
42 | arrays: 'never',
43 | objects: 'never',
44 | imports: 'never',
45 | exports: 'never',
46 | functions: 'never'
47 | }],
48 | 'comma-spacing': ['error', { before: false, after: true }],
49 | 'comma-style': ['error', 'last'],
50 | 'computed-property-spacing': ['error', 'never', { enforceForClassMembers: true }],
51 | 'constructor-super': 'error',
52 | curly: ['error', 'multi-line'],
53 | 'default-case-last': 'error',
54 | 'dot-location': ['error', 'property'],
55 | 'dot-notation': ['error', { allowKeywords: true }],
56 | 'eol-last': 'error',
57 | eqeqeq: ['error', 'always', { null: 'ignore' }],
58 | 'func-call-spacing': ['error', 'never'],
59 | 'generator-star-spacing': ['error', { before: true, after: true }],
60 | 'key-spacing': ['error', { beforeColon: false, afterColon: true }],
61 | 'keyword-spacing': ['error', { before: true, after: true }],
62 | 'lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: true }],
63 | 'multiline-ternary': ['error', 'always-multiline'],
64 | 'new-cap': ['error', { newIsCap: true, capIsNew: false, properties: true }],
65 | 'new-parens': 'error',
66 | 'no-array-constructor': 'error',
67 | 'no-async-promise-executor': 'error',
68 | 'no-caller': 'error',
69 | 'no-case-declarations': 'error',
70 | 'no-class-assign': 'error',
71 | 'no-compare-neg-zero': 'error',
72 | 'no-cond-assign': 'error',
73 | 'no-const-assign': 'error',
74 | 'no-constant-condition': ['error', { checkLoops: false }],
75 | 'no-control-regex': 'error',
76 | 'no-debugger': 'error',
77 | 'no-delete-var': 'error',
78 | 'no-dupe-args': 'error',
79 | 'no-dupe-class-members': 'error',
80 | 'no-dupe-keys': 'error',
81 | 'no-duplicate-case': 'error',
82 | 'no-useless-backreference': 'error',
83 | 'no-empty': ['error', { allowEmptyCatch: true }],
84 | 'no-empty-character-class': 'error',
85 | 'no-empty-pattern': 'error',
86 | 'no-eval': 'error',
87 | 'no-ex-assign': 'error',
88 | 'no-extend-native': 'error',
89 | 'no-extra-bind': 'error',
90 | 'no-extra-boolean-cast': 'error',
91 | 'no-extra-parens': ['error', 'functions'],
92 | 'no-fallthrough': 'error',
93 | 'no-floating-decimal': 'error',
94 | 'no-func-assign': 'error',
95 | 'no-global-assign': 'error',
96 | 'no-implied-eval': 'error',
97 | 'no-import-assign': 'error',
98 | 'no-invalid-regexp': 'error',
99 | 'no-irregular-whitespace': 'error',
100 | 'no-iterator': 'error',
101 | 'no-labels': ['error', { allowLoop: false, allowSwitch: false }],
102 | 'no-lone-blocks': 'error',
103 | 'no-loss-of-precision': 'error',
104 | 'no-misleading-character-class': 'error',
105 | 'no-prototype-builtins': 'error',
106 | 'no-useless-catch': 'error',
107 | 'no-mixed-operators': ['error', {
108 | groups: [
109 | ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
110 | ['&&', '||'],
111 | ['in', 'instanceof']
112 | ],
113 | allowSamePrecedence: true
114 | }],
115 | 'no-mixed-spaces-and-tabs': ['error', 'smart-tabs'],
116 | 'no-multi-spaces': 'error',
117 | 'no-multi-str': 'error',
118 | 'no-new': 'error',
119 | 'no-new-func': 'error',
120 | 'no-new-object': 'error',
121 | 'no-new-symbol': 'error',
122 | 'no-new-wrappers': 'error',
123 | 'no-obj-calls': 'error',
124 | 'no-octal': 'error',
125 | 'no-octal-escape': 'error',
126 | 'no-proto': 'error',
127 | 'no-redeclare': ['error', { builtinGlobals: false }],
128 | 'no-regex-spaces': 'error',
129 | 'no-self-assign': ['error', { props: true }],
130 | 'no-self-compare': 'error',
131 | 'no-shadow-restricted-names': 'error',
132 | 'no-sparse-arrays': 'error',
133 | 'no-template-curly-in-string': 'error',
134 | 'no-this-before-super': 'error',
135 | 'no-throw-literal': 'error',
136 | 'no-trailing-spaces': 'error',
137 | 'no-undef': 'error',
138 | 'no-undef-init': 'error',
139 | 'no-unexpected-multiline': 'error',
140 | 'no-unmodified-loop-condition': 'error',
141 | 'no-unneeded-ternary': ['error', { defaultAssignment: false }],
142 | 'no-unreachable': 'error',
143 | 'no-unreachable-loop': 'error',
144 | 'no-unsafe-finally': 'error',
145 | 'no-unsafe-negation': 'error',
146 | 'no-unused-vars': ['error', {
147 | args: 'none',
148 | caughtErrors: 'none',
149 | ignoreRestSiblings: true,
150 | vars: 'all'
151 | }],
152 | 'no-use-before-define': ['error', { functions: false, classes: false, variables: false }],
153 | 'no-useless-call': 'error',
154 | 'no-useless-computed-key': 'error',
155 | 'no-useless-constructor': 'error',
156 | 'no-useless-escape': 'error',
157 | 'no-useless-rename': 'error',
158 | 'no-useless-return': 'error',
159 | 'no-void': 'error',
160 | 'no-whitespace-before-property': 'error',
161 | 'no-with': 'error',
162 | 'object-curly-newline': ['error', { multiline: true, consistent: true }],
163 | 'object-curly-spacing': ['error', 'always'],
164 | 'object-property-newline': ['error', { allowMultiplePropertiesPerLine: true }],
165 | 'one-var': ['error', { initialized: 'never' }],
166 | 'operator-linebreak': ['error', 'after', { overrides: { '?': 'before', ':': 'before', '|>': 'before' } }],
167 | 'padded-blocks': ['error', { blocks: 'never', switches: 'never', classes: 'never' }],
168 | 'prefer-const': ['error', { destructuring: 'all' }],
169 | 'prefer-promise-reject-errors': 'error',
170 | 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
171 | 'quote-props': ['error', 'as-needed'],
172 | quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }],
173 | 'rest-spread-spacing': ['error', 'never'],
174 | semi: ['error', 'never'],
175 | 'semi-spacing': ['error', { before: false, after: true }],
176 | 'space-before-blocks': ['error', 'always'],
177 | 'space-before-function-paren': ['error', 'always'],
178 | 'space-in-parens': ['error', 'never'],
179 | 'space-infix-ops': 'error',
180 | 'space-unary-ops': ['error', { words: true, nonwords: false }],
181 | 'spaced-comment': ['error', 'always', {
182 | line: { markers: ['*package', '!', '/', ',', '='] },
183 | block: {
184 | balanced: true,
185 | markers: ['*package', '!', ',', ':', '::', 'flow-include'],
186 | exceptions: ['*']
187 | }
188 | }],
189 | 'symbol-description': 'error',
190 | 'template-curly-spacing': ['error', 'never'],
191 | 'template-tag-spacing': ['error', 'never'],
192 | 'unicode-bom': ['error', 'never'],
193 | 'use-isnan': ['error', {
194 | enforceForSwitchCase: true,
195 | enforceForIndexOf: true
196 | }],
197 | 'valid-typeof': ['error', { requireStringLiterals: true }],
198 | 'wrap-iife': ['error', 'any', { functionPrototypeMethods: true }],
199 | 'yield-star-spacing': ['error', 'both'],
200 | yoda: ['error', 'never']
201 | }
202 | }
203 | ]
204 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "strict": true,
7 | "noImplicitAny": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "console",
3 | "version": "0.0.1",
4 | "scripts": {
5 | "dev": "vite dev",
6 | "build": "vite build",
7 | "preview": "vite preview",
8 | "prepare": "svelte-kit sync",
9 | "check": "svelte-check --tsconfig ./jsconfig.json",
10 | "check:watch": "svelte-check --tsconfig ./jsconfig.json --watch",
11 | "test": "playwright test",
12 | "lint": "eslint ."
13 | },
14 | "devDependencies": {
15 | "@nomimono/nomimono-css": "^0.6.0",
16 | "@sveltejs/adapter-cloudflare": "^5.0.3",
17 | "@sveltejs/adapter-node": "^5.2.12",
18 | "@sveltejs/kit": "=2.17.2",
19 | "@typescript-eslint/eslint-plugin": "^8.24.1",
20 | "@typescript-eslint/parser": "^8.24.1",
21 | "clipboard": "^2.0.11",
22 | "dayjs": "^1.11.13",
23 | "eslint": "^9.20.1",
24 | "eslint-plugin-svelte": "^2.46.1",
25 | "global": "^4.4.0",
26 | "gravatar-url": "^4.0.1",
27 | "highcharts": "=11.4.8",
28 | "js-cookie": "^3.0.5",
29 | "sass": "^1.84.0",
30 | "svelte": "^5.20.2",
31 | "svelte-check": "^4.1.4",
32 | "svelte-eslint-parser": "^0.43.0",
33 | "svelte-preprocess": "^6.0.3",
34 | "sweetalert2": "^11.17.2",
35 | "typescript": "~5.7.3",
36 | "valid-url": "^1.0.9"
37 | },
38 | "type": "module"
39 | }
40 |
--------------------------------------------------------------------------------
/playwright.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@playwright/test').PlaywrightTestConfig} */
2 | const config = {
3 | webServer: {
4 | command: 'npm run build && npm run preview',
5 | port: 3000
6 | }
7 | }
8 |
9 | export default config
10 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://kit.svelte.dev/docs/types#app
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | interface Locals {
6 | token: string
7 | project: string
8 | }
9 | }
10 | }
11 |
12 | export {}
13 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Console | Deploys.app
6 |
7 |
8 |
9 | %sveltekit.head%
10 |
11 |
12 |
13 | %sveltekit.body%
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/hooks.server.js:
--------------------------------------------------------------------------------
1 | import { sequence } from '@sveltejs/kit/hooks'
2 |
3 | const allowTheme = {
4 | dark: true,
5 | light: true
6 | }
7 |
8 | /** @type {import('@sveltejs/kit').Handle} */
9 | async function theme ({ event, resolve }) {
10 | let t = event.cookies.get('theme')
11 | if (!allowTheme[t]) {
12 | t = 'dark'
13 | }
14 | return resolve(event, {
15 | transformPageChunk: ({ html }) => html.replace('data-theme=""', `data-theme="${t}"`)
16 | })
17 | }
18 |
19 | /** @type {import('@sveltejs/kit').Handle} */
20 | async function handleCookie ({ event, resolve }) {
21 | const { cookies, locals } = event
22 |
23 | locals.token = cookies.get('token') || ''
24 | locals.project = cookies.get('project') || ''
25 |
26 | return resolve(event)
27 | }
28 |
29 | /** @type {import('@sveltejs/kit').Handle} */
30 | function storeProject ({ event, resolve }) {
31 | const { url, locals } = event
32 |
33 | if (url.pathname.startsWith('/api/')) {
34 | return resolve(event)
35 | }
36 |
37 | const project = url.searchParams.get('project')
38 | if (project && project !== locals.project) {
39 | locals.project = project
40 | }
41 |
42 | return resolve(event)
43 | }
44 |
45 | export const handle = sequence(
46 | theme,
47 | handleCookie,
48 | storeProject
49 | )
50 |
--------------------------------------------------------------------------------
/src/lib/api/index.js:
--------------------------------------------------------------------------------
1 | import { invalidate } from '$app/navigation'
2 |
3 | const endpoint = '/api'
4 |
5 | /** @type {Function} */
6 | let onUnauth
7 |
8 | /**
9 | * @template T
10 | * @param {string} fn
11 | * @param {Object} args
12 | * @param {fetch} fetch
13 | * @returns {Promise>}
14 | */
15 | async function invoke (fn, args, fetch) {
16 | const resp = await fetch(`${endpoint}/${fn}`, {
17 | method: 'POST',
18 | body: JSON.stringify(args || {}),
19 | headers: {
20 | 'content-type': 'application/json'
21 | }
22 | })
23 |
24 | const body = await resp.json()
25 | if (!body.ok) {
26 | const msg = body.error?.message || ''
27 | switch (msg) {
28 | case 'api: unauthorized':
29 | body.error.unauth = true
30 | onUnauth?.()
31 | break
32 | case 'api: forbidden':
33 | body.error.forbidden = true
34 | break
35 | case 'iam: forbidden':
36 | body.error.forbidden = true
37 | break
38 | case 'api: validate error':
39 | body.error.validate = body.error.items
40 | break
41 | default:
42 | if (msg.includes('api: ') && msg.includes('not found')) {
43 | body.error.notFound = true
44 | }
45 | break
46 | }
47 | }
48 | return body
49 | }
50 |
51 | /**
52 | * @param {Function} callback
53 | */
54 | function setOnUnauth (callback) {
55 | onUnauth = callback
56 | }
57 |
58 | /**
59 | * @param {string} fn
60 | * @returns {Promise}
61 | */
62 | function wrapInvalidate (fn) {
63 | return invalidate(`${endpoint}/${fn}`)
64 | }
65 |
66 | /**
67 | * intervalInvalidate calls callback every interval milliseconds
68 | * must be called in onMount
69 | * callback can return a number to override the interval for only the next call
70 | * @param {() => Promise} callback
71 | * @param {number} interval
72 | * @returns {() => void}
73 | */
74 | function intervalInvalidate (callback, interval) {
75 | let p
76 |
77 | const f = async () => {
78 | let newInterval = await callback()
79 | if (!newInterval) {
80 | newInterval = interval
81 | }
82 | p = setTimeout(f, newInterval)
83 | }
84 | p = setTimeout(f, interval)
85 |
86 | return () => {
87 | clearTimeout(p)
88 | }
89 | }
90 |
91 | export default {
92 | invoke,
93 | setOnUnauth,
94 | invalidate: wrapInvalidate,
95 | intervalInvalidate
96 | }
97 |
--------------------------------------------------------------------------------
/src/lib/components/Chart.svelte:
--------------------------------------------------------------------------------
1 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/lib/components/DeploymentStatusIcon.svelte:
--------------------------------------------------------------------------------
1 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/lib/components/ErrorRow.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 | {#if error}
13 |
14 |
15 | {#if error.forbidden}
16 | You don't have permission to view data
17 | {:else if error.message}
18 | {error.message}
19 | {:else}
20 | {error}
21 | {/if}
22 | |
23 |
24 | {/if}
25 |
--------------------------------------------------------------------------------
/src/lib/components/LoadingRow.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 | Loading...
13 | |
14 |
--------------------------------------------------------------------------------
/src/lib/components/NoDataRow.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 | {#if !list?.length}
13 |
14 |
15 | No data
16 | |
17 |
18 | {/if}
19 |
--------------------------------------------------------------------------------
/src/lib/components/Secret.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | {#if isHidden}
21 |
22 | {:else}
23 |
24 | {/if}
25 |
26 | {#if isHidden}
27 | •••••••••••••••
28 | {:else}
29 | {value}
30 | {/if}
31 |
32 |
33 |
43 |
--------------------------------------------------------------------------------
/src/lib/components/StatusIcon.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/lib/format/index.js:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs'
2 |
3 | /**
4 | * @param {string} v
5 | * @returns {string}
6 | */
7 | export function cpu (v) {
8 | if (v === '0') {
9 | return 'Shared'
10 | }
11 | return `${v} vCPU`
12 | }
13 |
14 | /**
15 | * @param {string} v
16 | * @returns {string}
17 | */
18 | export function cpuLimited (v) {
19 | if (v === '0' || !v) {
20 | return 'Cluster Default'
21 | }
22 | return `${v} vCPU`
23 | }
24 |
25 | /**
26 | * @param {string} v
27 | * @returns {string}
28 | */
29 | export function memory (v) {
30 | if (v === '0') {
31 | return 'Shared'
32 | }
33 | const m = v.match(/^(\d+)(\w+)$/) ?? []
34 | if (m.length !== 3) {
35 | return v
36 | }
37 | return `${m[1]} ${m[2]}B`
38 | }
39 |
40 | /**
41 | * @param {number} v
42 | * @returns {string}
43 | */
44 | export function storage (v) {
45 | return (v / 1024 / 1024 / 1024).toLocaleString(undefined, { maximumFractionDigits: 2 }) + ' GiB'
46 | }
47 |
48 | /**
49 | * @param {string} v
50 | * @returns {string}
51 | */
52 | export function datetime (v) {
53 | if (!v) {
54 | return ''
55 | }
56 | return dayjs(v).format('YYYY-MM-DD HH:mm:ss')
57 | }
58 |
59 | /**
60 | * @param {string} project
61 | * @param {string} name
62 | * @param {string} gsa
63 | * @param {string} locationProject
64 | * @returns {string}
65 | */
66 | export function gsaBinding (project, name, gsa, locationProject) {
67 | const namespace = 'deploys'
68 | const matched = gsa.match(/^.*@([^.]+)\.iam.gserviceaccount.com$/) ?? []
69 | const googleProject = matched.length > 1 ? `\n --project ${matched[1]} \\` : ''
70 | return `gcloud iam service-accounts add-iam-policy-binding \\
71 | --role roles/iam.workloadIdentityUser \\
72 | --member "serviceAccount:${locationProject}.svc.id.goog[${namespace}/${name}-${project}]" \\${googleProject}
73 | ${gsa}`
74 | }
75 |
76 | /**
77 | * @param {Api.DeploymentType} t
78 | * @returns {string}
79 | */
80 | export function deploymentType (t) {
81 | return {
82 | WebService: 'Web Service',
83 | TCPService: 'TCP Service',
84 | InternalTCPService: 'Internal TCP Service',
85 | Worker: 'Worker',
86 | CronJob: 'Cron Job'
87 | }[t] || t
88 | }
89 |
90 | /**
91 | * @param {string} s
92 | * @returns {string}
93 | */
94 | export function shortDigest (s) {
95 | return s.replace(/^sha256:/, '').slice(0, 12)
96 | }
97 |
--------------------------------------------------------------------------------
/src/lib/hc/index.js:
--------------------------------------------------------------------------------
1 | import Highcharts from 'highcharts'
2 |
3 | let inited = false
4 |
5 | export function init () {
6 | if (inited) {
7 | return
8 | }
9 | inited = true
10 |
11 | Highcharts.setOptions({
12 | accessibility: {
13 | enabled: false
14 | },
15 | colors: ['#2b908f', '#90ee7e', '#f45b5b', '#7798bf', '#aaeeee', '#ff0066',
16 | '#eeaaee', '#55bf3b', '#df5353', '#7798bf', '#aaeeee'],
17 | chart: {
18 | backgroundColor: 'hsl(var(--hsl-base-200))',
19 | plotBorderColor: '#606063'
20 | },
21 | title: {
22 | style: {
23 | color: 'hsl(var(--hsl-content))',
24 | fontSize: '20px'
25 | }
26 | },
27 | subtitle: {
28 | style: {
29 | color: 'hsl(var(--hsl-content))',
30 | textTransform: 'uppercase'
31 | }
32 | },
33 | xAxis: {
34 | gridLineColor: '#707073',
35 | labels: {
36 | style: {
37 | color: 'hsl(var(--hsl-content))'
38 | }
39 | },
40 | lineColor: '#707073',
41 | minorGridLineColor: '#505053',
42 | tickColor: '#707073',
43 | title: {
44 | style: {
45 | color: '#a0a0a3'
46 | }
47 | },
48 | gridLineWidth: 1
49 | },
50 | yAxis: {
51 | gridLineColor: '#707073',
52 | labels: {
53 | style: {
54 | color: 'hsl(var(--hsl-content))'
55 | }
56 | },
57 | lineColor: '#707073',
58 | minorGridLineColor: '#505053',
59 | tickColor: '#707073',
60 | tickWidth: 1,
61 | title: {
62 | text: '',
63 | style: {
64 | color: '#a0a0a3'
65 | }
66 | }
67 | },
68 | tooltip: {
69 | backgroundColor: 'rgba(0, 0, 0, 0.85)',
70 | style: {
71 | color: '#f0f0f0'
72 | }
73 | },
74 | plotOptions: {
75 | series: {
76 | dataLabels: {
77 | color: '#f0f0f3',
78 | style: {
79 | fontSize: '13px'
80 | }
81 | },
82 | marker: {
83 | lineColor: '#333'
84 | }
85 | },
86 | boxplot: {
87 | fillColor: '#505053'
88 | },
89 | candlestick: {
90 | lineColor: 'white'
91 | },
92 | errorbar: {
93 | color: 'white'
94 | }
95 | },
96 | legend: {
97 | itemStyle: {
98 | color: 'hsl(var(--hsl-content))'
99 | },
100 | itemHoverStyle: {
101 | color: 'hsl(var(--hsl-content)/0.8)'
102 | },
103 | itemHiddenStyle: {
104 | color: 'hsl(var(--hsl-content)/0.4)'
105 | },
106 | title: {
107 | style: {
108 | color: '#c0c0c0'
109 | }
110 | }
111 | },
112 | drilldown: {
113 | activeAxisLabelStyle: {
114 | color: '#f0f0f3'
115 | },
116 | activeDataLabelStyle: {
117 | color: '#f0f0f3'
118 | }
119 | },
120 | navigation: {
121 | buttonOptions: {
122 | symbolStroke: '#dddddd',
123 | theme: {
124 | fill: '#505053'
125 | }
126 | }
127 | },
128 | // scroll charts
129 | rangeSelector: {
130 | buttonTheme: {
131 | fill: '#505053',
132 | stroke: '#000000',
133 | style: {
134 | color: '#ccc'
135 | },
136 | states: {
137 | hover: {
138 | fill: '#707073',
139 | stroke: '#000000',
140 | style: {
141 | color: 'white'
142 | }
143 | },
144 | select: {
145 | fill: '#000003',
146 | stroke: '#000000',
147 | style: {
148 | color: 'white'
149 | }
150 | }
151 | }
152 | },
153 | inputBoxBorderColor: '#505053',
154 | inputStyle: {
155 | backgroundColor: '#333',
156 | color: 'silver'
157 | },
158 | labelStyle: {
159 | color: 'silver'
160 | }
161 | },
162 | navigator: {
163 | handles: {
164 | backgroundColor: '#666',
165 | borderColor: '#aaa'
166 | },
167 | outlineColor: '#ccc',
168 | maskFill: 'rgba(255,255,255,0.1)',
169 | series: {
170 | color: '#7798bf',
171 | lineColor: '#a6c7ed'
172 | },
173 | xAxis: {
174 | gridLineColor: '#505053'
175 | }
176 | },
177 | scrollbar: {
178 | barBackgroundColor: '#808083',
179 | barBorderColor: '#808083',
180 | buttonArrowColor: '#ccc',
181 | buttonBackgroundColor: '#606063',
182 | buttonBorderColor: '#606063',
183 | rifleColor: '#fff',
184 | trackBackgroundColor: '#404043',
185 | trackBorderColor: '#404043'
186 | },
187 | time: {
188 | useUTC: false
189 | },
190 | credits: {
191 | enabled: false
192 | }
193 | })
194 | }
195 |
--------------------------------------------------------------------------------
/src/lib/modal/index.js:
--------------------------------------------------------------------------------
1 | import Swal from 'sweetalert2'
2 | import 'sweetalert2/dist/sweetalert2.min.css'
3 |
4 | /**
5 | * @typedef {Object} ModalConfirmOptions
6 | * @property {string} [title]
7 | * @property {string} [html]
8 | * @property {string} [yes]
9 | * @property {function?} [callback]
10 | */
11 |
12 | /**
13 | * @param {ModalConfirmOptions} options
14 | * @returns {Promise}
15 | */
16 | export async function confirm ({ title, html, yes, callback }) {
17 | const result = await Swal.fire({
18 | title: 'Are you sure ?',
19 | text: title,
20 | html,
21 | icon: 'warning',
22 | showCancelButton: true,
23 | buttonsStyling: false,
24 | background: 'var(--modal-panel-background)',
25 | color: 'var(--modal-panel-color)',
26 | confirmButtonText: yes || 'Yes',
27 | customClass: {
28 | confirmButton: 'nm-button is-variant-negative _mgr-6',
29 | cancelButton: 'nm-button is-variant-tertiary',
30 | actions: '_mgt-7'
31 | }
32 | })
33 | if (!result.value) {
34 | return false
35 | }
36 |
37 | callback?.()
38 | return true
39 | }
40 |
41 | /**
42 | * @typedef {Object} ModalErrorOptions
43 | * @property {string | Api.Error | unknown} [error]
44 | * @property {Function} [callback]
45 | */
46 |
47 | /**
48 | * @param {ModalErrorOptions} options
49 | * @returns {Promise}
50 | */
51 | export async function error ({ error, callback }) {
52 | if (!error) {
53 | callback?.()
54 | return
55 | }
56 |
57 | let msg = ''
58 | if (typeof error === 'string') {
59 | msg = error
60 | } else if (error instanceof Error) {
61 | msg = error.message
62 | } else if (typeof error === 'object') {
63 | if ('message' in error && typeof error.message === 'string') {
64 | msg = error.message
65 | }
66 | if ('validate' in error && Array.isArray(error.validate)) {
67 | msg = error.validate.join('
')
68 | }
69 | }
70 |
71 | await Swal.fire({
72 | title: 'Oops...',
73 | html: msg,
74 | icon: 'error',
75 | background: 'var(--modal-panel-background)',
76 | color: 'var(--modal-panel-color)',
77 | customClass: {
78 | actions: '_mgt-7',
79 | confirmButton: 'nm-button is-variant-negative'
80 | }
81 | })
82 | callback?.()
83 | }
84 |
85 | /**
86 | * @typedef {Object} ModalSuccessOptions
87 | * @property {string} [content]
88 | */
89 |
90 | /**
91 | * @param {ModalSuccessOptions} options
92 | * @returns {Promise}
93 | */
94 | export async function success ({ content }) {
95 | await Swal.fire({
96 | title: 'Success',
97 | text: content,
98 | icon: 'success',
99 | background: 'var(--modal-panel-background)',
100 | color: 'var(--modal-panel-color)',
101 | customClass: {
102 | actions: '_mgt-7',
103 | confirmButton: 'nm-button is-variant-negative'
104 | }
105 | })
106 | }
107 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/+layout.js:
--------------------------------------------------------------------------------
1 | import { error, redirect } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 | import { browser } from '$app/environment'
4 |
5 | /**
6 | * @typedef {Object} BrowserCache
7 | * @property {string} project
8 | * @property {Api.Project} projectInfo
9 | * @property {Api.Location[]} locations
10 | */
11 |
12 | /** @type {BrowserCache | null} */
13 | let browserCache = null
14 |
15 | export async function load ({ url, data, fetch }) {
16 | const project = url.searchParams.get('project')
17 | const { restoreProject } = data || {}
18 |
19 | if (!project && restoreProject) {
20 | const q = new URLSearchParams(url.search)
21 | q.set('project', restoreProject)
22 | redirect(302, `?${q.toString()}`)
23 | }
24 | if (!project) redirect(302, '/project')
25 |
26 | if (browser && browserCache?.project === project) {
27 | return {
28 | project,
29 | projectInfo: browserCache.projectInfo,
30 | locations: browserCache.locations
31 | }
32 | }
33 |
34 | /** @type {Api.Response} */
35 | const projectInfo = await api.invoke('project.get', { project }, fetch)
36 | if (!projectInfo.ok) {
37 | // not allow to access if user don't have permission 'project.get'
38 | if (projectInfo.error?.forbidden || projectInfo.error?.notFound) {
39 | redirect(302, '/project')
40 | }
41 | error(500, `project: ${projectInfo.error?.message}`)
42 | }
43 | if (!projectInfo.result) redirect(302, '/project')
44 |
45 | /** @type {Api.Response>} */
46 | const locations = await api.invoke('location.list', { project }, fetch)
47 | if (!locations.ok) error(500, `locations: ${locations.error?.message}`)
48 | if (!locations.result) redirect(302, '/project')
49 |
50 | if (browser) {
51 | browserCache = {
52 | project,
53 | projectInfo: projectInfo.result,
54 | locations: locations.result.items ?? []
55 | }
56 | }
57 |
58 | return {
59 | project,
60 | projectInfo: projectInfo.result,
61 | locations: locations.result.items ?? []
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/+layout.server.js:
--------------------------------------------------------------------------------
1 | export function load ({ locals }) {
2 | return {
3 | restoreProject: locals.project || ''
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | {@render children?.()}
15 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | const [
7 | usage,
8 | price
9 | ] = await Promise.all([
10 | api.invoke('project.usage', { project }, fetch),
11 | api.invoke('billing.project', { project }, fetch)
12 | ])
13 | return {
14 | menu: 'dashboard',
15 | usage: usage.result ?? {},
16 | price: price.result ?? {}
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/+page.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 | Dashboard
38 |
39 |
40 |
41 |
42 |
43 | Project Info
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | Billing
83 |
84 |
85 |
86 |
87 |
CPU
88 |
89 | {billing?.cpu}
90 | seconds
91 |
92 |
93 |
94 |
Memory
95 |
96 | {billing?.memory}
97 | GiB-s
98 |
99 |
100 |
101 |
Egress
102 |
103 | {billing?.egress}
104 | GiB
105 |
106 |
107 |
108 |
Disk
109 |
110 | {billing?.disk}
111 | GiB-s
112 |
113 |
114 |
115 |
Replica
116 |
117 | {billing?.replica}
118 |
119 |
120 |
121 |
Price
122 |
123 | {billing?.price}
124 | THB
125 |
126 |
127 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/+layout.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 | import { redirect, error } from '@sveltejs/kit'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const location = url.searchParams.get('location')
7 | const name = url.searchParams.get('name')
8 | /** @type {Api.Response} */
9 | const deployment = await api.invoke('deployment.get', { project, location, name }, fetch)
10 | if (!deployment.ok) {
11 | if (deployment.error?.notFound) {
12 | redirect(302, `/deployment?project=${project}`)
13 | }
14 | error(500, deployment.error?.message)
15 | }
16 |
17 | return {
18 | location,
19 | name,
20 | deployment: deployment.result
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
23 |
26 |
27 |
{deployment.name}
28 |
29 |
30 |
31 |
32 |
33 |
34 | api.invalidate('deployment.get')} />
35 |
36 | {@render children?.()}
37 |
38 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/detail/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ parent, fetch }) {
5 | const {
6 | project,
7 | location,
8 | deployment
9 | } = await parent()
10 |
11 | /** @type {Api.Response} */
12 | const locationInfo = await api.invoke('location.get', { project, id: location }, fetch)
13 | if (!locationInfo.ok) {
14 | error(500, `location: ${locationInfo.error?.message}`)
15 | }
16 | if (!locationInfo.result) redirect(302, `/deployment?project=${project}`)
17 |
18 | return {
19 | location: locationInfo.result,
20 | deployment
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/detail/+page.svelte:
--------------------------------------------------------------------------------
1 |
45 |
46 | Deployment details
47 |
48 |
49 |
50 | {#if deployment.type === 'WebService'}
51 | {#if !deployment.internal}
52 |
53 | URL |
54 |
55 |
56 | {`https://${deployment.url}`}
57 |
58 |
59 |
60 |
61 | |
62 |
63 | {/if}
64 |
65 | Internal URL |
66 |
67 | http://{deployment.internalUrl}
68 |
69 |
70 |
71 | |
72 |
73 | {/if}
74 | {#if hasExternalTCPAddress}
75 |
76 | Address |
77 | {deployment.address}:{deployment.nodePort} |
78 |
79 | {/if}
80 | {#if hasInternalTCPAddress}
81 |
82 | Internal Address |
83 |
84 | {deployment.internalAddress}:{deployment.port}
85 |
86 |
87 |
88 | |
89 |
90 | {/if}
91 |
92 | Type |
93 |
94 | {format.deploymentType(deployment.type)}
95 | {#if deployment.internal}
96 | (Internal)
97 | {/if}
98 | |
99 |
100 |
101 | Location |
102 |
103 | {deployment.location}
104 |
105 |
106 |
107 | |
108 |
109 |
110 | Image |
111 |
112 | {deployment.image}
113 |
114 |
115 |
116 | |
117 |
118 | {#if deployment.type === 'WebService'}
119 |
120 | Port |
121 | {deployment.port}{deployment.protocol ? `:${deployment.protocol}` : ''} |
122 |
123 | {:else if deployment.type === 'TCPService'}
124 |
125 | Port |
126 | {deployment.port}:{deployment.nodePort} |
127 |
128 | {/if}
129 | {#if location.features.disk}
130 |
131 | Disk |
132 |
133 | {#if deployment.disk}
134 | {deployment.disk.name}
135 | (mount: {deployment.disk.mountPath || '-'}, sub: {deployment.disk.subPath || '-'})
136 | {:else}
137 | -
138 | {/if}
139 | |
140 |
141 | {/if}
142 | {#if deployment.minReplicas > 0}
143 |
144 | Replicas |
145 |
146 | {#if deployment.minReplicas > 0}
147 | {#if deployment.minReplicas === deployment.maxReplicas}
148 | {deployment.minReplicas}
149 | {:else}
150 | {deployment.minReplicas} - {deployment.maxReplicas}
151 | {/if}
152 | {:else}
153 | -
154 | {/if}
155 | |
156 |
157 | {/if}
158 | {#if deployment.type === 'CronJob'}
159 |
160 | Schedule |
161 | {deployment.schedule} |
162 |
163 | {/if}
164 |
165 | Command |
166 | {deployment.command.join(' ') || '-'} |
167 |
168 |
169 | Args |
170 | {deployment.args.join(' ') || '-'} |
171 |
172 |
173 | Pull Secret |
174 | {deployment.pullSecret || '-'} |
175 |
176 | {#if location.features.workloadIdentity}
177 |
178 | Workload Identity |
179 | {deployment.workloadIdentity || '-'} |
180 |
181 | {/if}
182 |
183 |
184 |
185 |
186 |
187 | CPU limited |
188 | {format.cpuLimited(deployment.resources.limits.cpu)} |
189 |
190 |
191 | Memory allocated |
192 | {format.memory(deployment.resources.requests.memory)} |
193 |
194 |
195 | Sidecars |
196 | {deployment.sidecars?.length || 0} |
197 |
198 |
199 | Deployed At |
200 | {format.datetime(deployment.createdAt)} |
201 |
202 |
203 | Deployed By |
204 | {deployment.createdBy} |
205 |
206 |
207 | Allocated Price |
208 | {deployment.allocatedPrice.toLocaleString(undefined, { maximumFractionDigits: 2 })} THB/month/replica |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 | Environment variables
221 |
222 |
223 |
224 |
225 | Env |
226 | Value |
227 |
228 |
229 |
230 | {#each Object.entries(deployment.env || {}) as [k, v]}
231 |
232 | {k} |
233 |
234 |
235 | |
236 |
237 | {:else}
238 |
239 | {/each}
240 |
241 |
242 |
243 |
244 | Mount Data
245 |
246 |
247 |
248 |
249 | Path |
250 | Data |
251 |
252 |
253 |
254 | {#each Object.entries(deployment.mountData || {}) as [k, v]}
255 |
256 | {k} |
257 | {v} |
258 |
259 | {:else}
260 |
261 | {/each}
262 |
263 |
264 |
265 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/events/+page.js:
--------------------------------------------------------------------------------
1 | export async function load ({ parent }) {
2 | const {
3 | deployment
4 | } = await parent()
5 |
6 | return {
7 | deployment
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/events/+page.svelte:
--------------------------------------------------------------------------------
1 |
30 |
31 | Events
32 |
33 |
34 |
35 |
36 |
37 | Last Seen |
38 | Type |
39 | Reason |
40 | Message |
41 |
42 |
43 |
44 | {#each events as it}
45 |
46 | {format.datetime(it.lastSeen)} |
47 | {it.type} |
48 | {it.reason} |
49 | {it.message} |
50 |
51 | {:else}
52 |
53 | {/each}
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/logs/+page.js:
--------------------------------------------------------------------------------
1 | export async function load ({ parent }) {
2 | const {
3 | deployment
4 | } = await parent()
5 |
6 | return {
7 | deployment
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/logs/+page.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 | Logs
38 | Stream Raw Logs
39 | {text}
40 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/metrics/+page.svelte:
--------------------------------------------------------------------------------
1 |
78 |
79 | Metric
80 |
81 |
82 |
83 |
97 |
98 |
99 |
100 |
101 |
102 | {#if deployment.type === 'WebService'}
103 |
104 | {/if}
105 |
106 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/revision/+page.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ parent, fetch }) {
5 | const {
6 | project,
7 | location,
8 | name,
9 | deployment
10 | } = await parent()
11 |
12 | const revisions = await api.invoke('deployment.revisions', { project, location, name }, fetch)
13 | if (!revisions.ok) error(500, revisions.error?.message)
14 |
15 | return {
16 | deployment,
17 | revisions: revisions.result.items ?? []
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/(detail)/revision/+page.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 | Revision
35 |
36 |
37 |
38 |
39 | Revision |
40 | Image |
41 | Deployed At |
42 | Deployed By |
43 | |
44 |
45 |
46 |
47 | {#each revisions as it, index}
48 |
49 | {it.revision} |
50 | {it.image} |
51 | {format.datetime(it.createdAt)} |
52 | {it.createdBy} |
53 |
54 | {#if index > 0}
55 |
56 | {/if}
57 | |
58 |
59 | {/each}
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'deployment',
4 | overrideRedirect: '/deployment'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('deployment.list', { project }, fetch)
8 | return {
9 | deployments: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | Deployments
15 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
29 | Name |
30 | Type |
31 |
32 | Memory |
33 | Replicas |
34 | Location |
35 | Last deployed |
36 |
37 |
38 |
39 |
40 | {#each deployments as it (`${it.name}-${it.location}`)}
41 |
42 |
43 |
44 |
45 | {it.name}
46 |
47 | |
48 | {format.deploymentType(it.type)} |
49 |
50 | {format.memory(it.resources.requests.memory)} |
51 |
52 | {#if it.minReplicas > 0}
53 | {#if it.minReplicas === it.maxReplicas}
54 | {it.minReplicas}
55 | {:else}
56 | {it.minReplicas} - {it.maxReplicas}
57 | {/if}
58 | {:else}
59 | -
60 | {/if}
61 | |
62 | {it.location} |
63 | {format.datetime(it.createdAt)} |
64 |
65 |
66 | {/each}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/_components/Header.svelte:
--------------------------------------------------------------------------------
1 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | {deployment.name}
68 |
{deployment.image}
69 |
70 |
71 |
72 |
74 | Deploy New Revision
75 |
76 | {#if canPause}
77 |
78 |
81 |
82 | {/if}
83 | {#if canResume}
84 |
85 |
88 |
89 | {/if}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
123 |
124 |
129 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/deployment/deploy/+page.js:
--------------------------------------------------------------------------------
1 | import { error, redirect } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const location = url.searchParams.get('location')
7 | const name = url.searchParams.get('name')
8 |
9 | /** @type {Api.Response | null} */
10 | let deployment = null
11 | if (location && name) {
12 | deployment = await api.invoke('deployment.get', { project, location, name }, fetch)
13 | if (!deployment.ok) {
14 | if (deployment.error?.notFound) redirect(302, `/deployment?project=${project}`)
15 | error(500, deployment.error?.message)
16 | }
17 | }
18 | return {
19 | deployment: deployment?.result
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/(detail)/+layout.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 | import { redirect, error } from '@sveltejs/kit'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const location = url.searchParams.get('location')
7 | const name = url.searchParams.get('name')
8 |
9 | const disk = await api.invoke('disk.get', { project, location, name }, fetch)
10 | if (!disk.ok) {
11 | if (disk.error?.notFound) redirect(302, `/disk?project=${project}`)
12 | error(500, disk.error?.message)
13 | }
14 | if (!disk.result) redirect(302, `/disk?project=${project}`)
15 |
16 | return {
17 | location,
18 | name,
19 | disk: disk.result
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/(detail)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
24 |
25 |
{disk.name}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | Disk: {disk.name}
36 |
37 |
38 |
39 |
40 |
41 |
53 |
54 | {@render children?.()}
55 |
56 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/(detail)/detail/+page.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {format.datetime(disk.createdAt)}
53 |
54 |
55 |
56 |
57 |
58 | {disk.createdBy}
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
Update
67 |
68 |
71 |
72 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/(detail)/metrics/+page.svelte:
--------------------------------------------------------------------------------
1 |
59 |
60 | Metric
61 |
62 |
63 |
64 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'disk',
4 | overrideRedirect: '/disk'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('disk.list', { project }, fetch)
8 | return {
9 | disks: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | Disks
15 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
29 | Disk name |
30 | Size |
31 | Location |
32 | Created at |
33 | |
34 |
35 |
36 |
37 | {#each disks as it (`${it.name}-${it.location}`)}
38 |
39 |
40 |
41 |
42 | {it.name}
43 |
44 | |
45 | {it.size} GiB |
46 | {it.location} |
47 | {format.datetime(it.createdAt)} |
48 |
49 |
50 |
51 |
52 |
53 |
54 | |
55 |
56 | {/each}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/create/+page.js:
--------------------------------------------------------------------------------
1 | import { error, redirect } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const location = url.searchParams.get('location')
7 | const name = url.searchParams.get('name')
8 |
9 | let disk
10 | if (location && name) {
11 | disk = await api.invoke('disk.get', { project, location, name }, fetch)
12 | if (!disk.ok) {
13 | if (disk.error?.notFound) redirect(302, `/disk?project=${project}`)
14 | error(500, disk.error?.message)
15 | }
16 | }
17 |
18 | return {
19 | location,
20 | name,
21 | disk: disk?.result
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/disk/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
55 |
56 |
57 |
60 | {#if disk}
61 |
64 | {/if}
65 |
66 |
{#if disk}Update{:else}Create{/if}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | {#if disk}
77 | Update Disk {disk.name}
78 | {:else}
79 | Create Disk
80 | {/if}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/domain/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'domain',
4 | overrideRedirect: '/domain'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/domain/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | const res = await api.invoke('domain.list', { project }, fetch)
7 | return {
8 | domains: res.result?.items ?? [],
9 | error: res.error
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/domain/+page.svelte:
--------------------------------------------------------------------------------
1 |
32 |
33 | Domains
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
48 | Domain |
49 | Wildcard |
50 | CDN |
51 | Location |
52 |
53 |
54 | |
55 |
56 |
57 |
58 | {#each domains as it (`${it.domain}-${it.location}`)}
59 |
60 |
61 |
62 | {it.domain}
63 | |
64 |
65 | {#if it.wildcard}
66 |
67 | {:else}
68 |
69 | {/if}
70 | |
71 |
72 | {#if it.cdn}
73 |
74 | {:else}
75 |
76 | {/if}
77 | |
78 | {it.location} |
79 |
80 |
81 |
82 |
85 | |
86 |
87 | {/each}
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/domain/cdn-downgrade/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const domainName = url.searchParams.get('domain')
7 |
8 | /** @type {Api.Response} */
9 | const domain = await api.invoke('domain.get', { project, domain: domainName }, fetch)
10 | if (!domain.ok) {
11 | if (domain.error?.notFound) redirect(302, `/domain?project=${project}`)
12 | error(500, domain.error?.message)
13 | }
14 | if (!domain.result) redirect(302, `/domain?project=${project}`)
15 | if (!domain.result.cdn) redirect(302, `/domain/detail?project=${project}&domain=${domainName}`)
16 |
17 | /** @type {Api.Response} */
18 | const location = await api.invoke('location.get', { id: domain.result.location }, fetch)
19 | if (!location.ok) {
20 | error(500, location.error?.message)
21 | }
22 | if (!location.result) redirect(302, `/domain?project=${project}`)
23 |
24 | return {
25 | domain: domain.result,
26 | location: location.result
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/domain/cdn-downgrade/+page.svelte:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
47 |
50 |
51 |
CDN Downgrade
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | CDN Downgrade
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | {#if location.endpoint}
87 |
88 |
89 | {#each [location.endpoint] as ip}
90 |
91 |
92 |
94 |
95 |
96 |
97 | {/each}
98 |
99 | {/if}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | {#if location.cname}
115 |
116 |
117 | {#each [location.cname] as cname}
118 |
119 |
120 |
122 |
123 |
124 |
125 | {/each}
126 |
127 | {/if}
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/domain/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
51 |
52 |
53 |
56 |
57 |
Create
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
Create domain
67 |
68 |
69 |
70 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/domain/detail/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const domainName = url.searchParams.get('domain')
7 |
8 | /** @type {Api.Response} */
9 | const domain = await api.invoke('domain.get', { project, domain: domainName }, fetch)
10 | if (!domain.ok) {
11 | if (domain.error?.notFound) redirect(302, `/domain?project=${project}`)
12 | error(500, domain.error?.message)
13 | }
14 | if (!domain.result) redirect(302, `/domain?project=${project}`)
15 |
16 | return {
17 | domain: domain.result
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/dropbox/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'dropbox',
4 | overrideRedirect: '/dropbox'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/dropbox/+page.svelte:
--------------------------------------------------------------------------------
1 |
44 |
45 | Dropbox (Alpha)
46 |
47 |
48 |
56 |
57 | {#if downloadUrl}
58 |
59 | Download URL
60 |
{downloadUrl}
61 |
62 | {/if}
63 |
64 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/email/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'email',
4 | overrideRedirect: '/email'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/email/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('email.list', { project }, fetch)
8 | return {
9 | domains: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/email/+page.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 | Emails
13 |
14 |
15 |
16 |
17 | Contact us to request access
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Domain |
26 | Quota |
27 | Created at |
28 |
29 |
30 |
31 | {#each domains as it (it.domain)}
32 |
33 | {it.domain} |
34 | - |
35 | {format.datetime(it.createdAt)} |
36 |
37 | {/each}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/pull-secret/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'pull-secret',
4 | overrideRedirect: '/pull-secret'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/pull-secret/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('pullSecret.list', { project }, fetch)
8 | return {
9 | pullSecrets: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/pull-secret/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | Pull Secrets
15 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
29 | Name |
30 | Location |
31 | Created at |
32 | Created by |
33 |
34 |
35 |
36 | {#each pullSecrets as it (`${it.name}-${it.location}`)}
37 |
38 |
39 |
40 |
41 | {it.name}
42 |
43 | |
44 | {it.location} |
45 | {format.datetime(it.createdAt)} |
46 | {it.createdBy} |
47 |
48 | {/each}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/pull-secret/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
60 |
61 |
62 |
65 |
66 |
Create
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
Create pull secret
75 |
76 |
77 |
78 |
79 |
80 |
121 |
122 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/pull-secret/detail/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const location = url.searchParams.get('location')
7 | const name = url.searchParams.get('name')
8 |
9 | const pullSecret = await api.invoke('pullSecret.get', { project, location, name }, fetch)
10 | if (!pullSecret.ok) {
11 | if (pullSecret.error?.notFound) redirect(302, `/pull-secret?project=${project}`)
12 | error(500, pullSecret.error?.message)
13 | }
14 | if (!pullSecret.result) redirect(302, `/pull-secret?project=${project}`)
15 |
16 | return {
17 | location,
18 | name,
19 | pullSecret: pullSecret.result
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/pull-secret/detail/+page.svelte:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
42 |
43 |
{pullSecret.name}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
Pull secret "{pullSecret.name}"
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/registry/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'registry',
4 | overrideRedirect: '/registry'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/registry/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('registry/list', { project }, fetch)
8 | return {
9 | repositories: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/registry/+page.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 | Registry (Alpha)
13 |
14 |
15 |
registry.deploys.app/{project}/{'{repository}'}:{'{tag}'}
16 |
17 |
18 |
19 |
20 | Repository |
21 |
22 |
23 |
24 | {#each repositories as repo (repo.name)}
25 |
26 |
27 |
29 | {repo.name}
30 |
31 | |
32 |
33 | {/each}
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/registry/detail/+page.js:
--------------------------------------------------------------------------------
1 | import { error, redirect } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const id = url.searchParams.get('repository')
7 |
8 | const repository = await api.invoke('registry/get', {
9 | project,
10 | repository: id
11 | }, fetch)
12 | if (!repository.ok) {
13 | if (repository.error?.message === 'repository not found') {
14 | redirect(302, `/registry?project=${project}`)
15 | }
16 | error(500, repository.error?.message)
17 | }
18 |
19 | /** @type {Api.Response} */
20 | const res = await api.invoke('registry/getTags', {
21 | project,
22 | repository: id
23 | }, fetch)
24 |
25 | return {
26 | id,
27 | repository: repository.result,
28 | tags: res.result?.items ?? [],
29 | error: res.error
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/registry/detail/+page.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
27 |
28 |
{repository.name}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {repository.name}
38 |
registry.deploys.app/{project}/{repository.name}
39 | {format.storage(repository.size)}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | Tag |
48 | Digest |
49 | Created At |
50 |
51 |
52 |
53 | {#each tags as tag}
54 |
55 |
56 | {tag.tag}
57 |
58 |
59 |
60 | |
61 |
62 | {format.shortDigest(tag.digest)}
63 |
64 |
65 |
66 | |
67 | {format.datetime(tag.createdAt)} |
68 |
69 | {/each}
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'role',
4 | overrideRedirect: '/role'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('role.list', { project }, fetch)
8 | return {
9 | roles: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/+page.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 | Roles
21 |
22 |
23 |
30 |
31 |
32 |
33 |
34 |
35 | Role |
36 | Name |
37 | Created At |
38 | Created By |
39 | |
40 |
41 |
42 |
43 | {#each roles as it (it.role)}
44 |
45 |
46 | {#if roleCanUpdate(it.role)}
47 |
48 | {it.role}
49 |
50 | {:else}
51 | {it.role}
52 | {/if}
53 | |
54 | {it.name} |
55 | {format.datetime(it.createdAt)} |
56 | {it.createdBy} |
57 |
58 | {#if roleCanUpdate(it.role)}
59 |
60 |
61 |
62 |
63 |
64 | {/if}
65 | |
66 |
67 | {/each}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/bind/+page.js:
--------------------------------------------------------------------------------
1 | import { error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const email = url.searchParams.get('email')
7 |
8 | const roles = await api.invoke('role.list', { project }, fetch)
9 | if (!roles.ok) error(500, roles.error?.message)
10 |
11 | let selected
12 | if (email) {
13 | const users = await api.invoke('role.users', { project }, fetch)
14 | if (!users.ok) error(500, users.error?.message)
15 |
16 | selected = users.result?.items?.find((x) => x.email === email)?.roles
17 | }
18 |
19 | return {
20 | menu: 'role.users',
21 | overrideRedirect: '/role/users',
22 | email,
23 | roles: roles.result.items ?? [],
24 | selected: selected ?? []
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/bind/+page.svelte:
--------------------------------------------------------------------------------
1 |
67 |
68 |
69 |
72 |
73 |
Add
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
Add member to "{project}" project
82 |
83 |
84 |
85 |
86 |
87 |
136 |
137 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/create/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const roleId = url.searchParams.get('role')
7 |
8 | /** @type {Api.Response} */
9 | const permissions = await api.invoke('role.permissions', {}, fetch)
10 | if (!permissions.ok) error(500, permissions.error?.message)
11 |
12 | let role = null
13 | if (roleId) {
14 | /** @type {Api.Response} */
15 | const roleInfo = await api.invoke('role.get', { project, role: roleId }, fetch)
16 | if (!roleInfo.ok) {
17 | if (roleInfo.error?.notFound) redirect(302, `/role?project=${project}`)
18 | error(500, roleInfo.error?.message)
19 | }
20 | if (!roleInfo.result) redirect(302, `/role?project=${project}`)
21 | role = roleInfo.result
22 | }
23 |
24 | return {
25 | menu: 'role',
26 | role,
27 | permissions: permissions.result ?? []
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
88 |
89 |
90 |
93 | {#if role}
94 |
95 |
{role.role}
96 |
97 |
98 |
Update
99 |
100 | {:else}
101 |
102 |
Create
103 |
104 | {/if}
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | {#if role}
113 | Update role "{form.role}"
114 | {:else}
115 | Create new role
116 | {/if}
117 |
118 |
119 |
120 |
121 |
122 |
193 |
194 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/users/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('role.users', { project }, fetch)
8 | return {
9 | menu: 'role.users',
10 | overrideRedirect: '/role/users',
11 | users: res.result?.items ?? [],
12 | error: res.error
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/role/users/+page.svelte:
--------------------------------------------------------------------------------
1 |
35 |
36 | Users
37 |
38 |
39 |
46 |
47 |
48 |
49 |
50 |
51 | Email |
52 | Roles |
53 | |
54 |
55 |
56 |
57 | {#each users as it (it.email)}
58 |
59 | {it.email} |
60 |
61 | {#each it.roles as r}
62 | {r}
63 | {/each}
64 | |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
74 | |
75 |
76 | {/each}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/route/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'route',
4 | overrideRedirect: '/route'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/route/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('route.list', { project }, fetch)
8 | return {
9 | routes: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/route/+page.svelte:
--------------------------------------------------------------------------------
1 |
36 |
37 | Routes
38 |
39 |
40 |
47 |
48 |
49 |
50 |
51 |
52 | Route |
53 | Target |
54 | Location |
55 | Config |
56 |
57 |
58 | |
59 |
60 |
61 |
62 | {#each routes as it (`${it.domain}${it.path}-${it.location}`)}
63 |
64 |
65 | https://{it.domain}{it.path}
68 | |
69 | {it.target} |
70 | {it.location} |
71 |
72 | {#if it.config.basicAuth}
73 |
74 | {/if}
75 | |
76 |
77 |
78 |
79 |
82 | |
83 |
84 | {/each}
85 |
86 |
87 |
88 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/route/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
123 |
124 |
125 |
128 |
129 |
Create
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
Create route
138 |
139 |
140 |
141 |
251 |
252 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/service-account/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'service-account',
4 | overrideRedirect: '/service-account'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/service-account/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('serviceAccount.list', { project }, fetch)
8 | return {
9 | serviceAccounts: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/service-account/+page.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 | Service Accounts
14 |
15 |
16 |
23 |
24 |
25 |
26 |
27 |
28 | Email |
29 | Name |
30 | Created At |
31 | |
32 |
33 |
34 |
35 | {#each serviceAccounts as it (it.email)}
36 |
37 |
38 |
39 | {it.email}
40 |
41 | |
42 | {it.name} |
43 | {format.datetime(it.createdAt)} |
44 |
45 |
46 |
47 |
48 |
49 |
50 | |
51 |
52 | {/each}
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/service-account/create/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const id = url.searchParams.get('id')
7 |
8 | let serviceAccount
9 | if (id) {
10 | serviceAccount = await api.invoke('serviceAccount.get', { project, id }, fetch)
11 | if (!serviceAccount.ok) {
12 | if (serviceAccount.error?.notFound) {
13 | redirect(302, `/service-account?project=${project}`)
14 | }
15 | error(500, serviceAccount.error?.message)
16 | }
17 | }
18 |
19 | return {
20 | id,
21 | serviceAccount: serviceAccount?.result
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/service-account/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 | {#if id}
49 |
52 | {:else}
53 |
56 | {/if}
57 |
58 |
59 | {#if id}
60 | Update
61 | {:else}
62 | Create
63 | {/if}
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | {#if id}
75 | Update service account "{serviceAccount.sid}"
76 | {:else}
77 | Create service account
78 | {/if}
79 |
80 |
81 |
82 |
83 |
84 |
85 |
118 |
119 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/service-account/detail/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const id = url.searchParams.get('id')
7 | const serviceAccount = await api.invoke('serviceAccount.get', { project, id }, fetch)
8 | if (!serviceAccount.ok) {
9 | if (serviceAccount.error?.message === 'api: service account not found') {
10 | redirect(302, `/service-account?project=${project}`)
11 | }
12 | error(500, serviceAccount.error?.message)
13 | }
14 | return {
15 | id,
16 | serviceAccount: serviceAccount.result
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/service-account/detail/+page.svelte:
--------------------------------------------------------------------------------
1 |
65 |
66 |
67 |
70 |
71 |
{serviceAccount.name}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | {serviceAccount.name}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
Keys
121 |
122 | {#each (serviceAccount.keys ?? []) as key}
123 |
124 |
125 |
128 |
129 | {/each}
130 |
131 |
132 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/workload-identity/+layout.js:
--------------------------------------------------------------------------------
1 | export function load () {
2 | return {
3 | menu: 'workload-identity',
4 | overrideRedirect: '/workload-identity'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/workload-identity/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ parent, fetch }) {
4 | const { project } = await parent()
5 |
6 | /** @type {Api.Response>} */
7 | const res = await api.invoke('workloadIdentity.list', { project }, fetch)
8 | return {
9 | workloadIdentities: res.result?.items ?? [],
10 | error: res.error
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/workload-identity/+page.svelte:
--------------------------------------------------------------------------------
1 |
13 |
14 | Workload Identities
15 |
16 |
17 |
24 |
25 |
26 |
27 |
28 |
29 | Name |
30 | Location |
31 | Created at |
32 |
33 |
34 |
35 | {#each workloadIdentities as it}
36 |
37 |
38 |
39 |
40 | {it.name}
41 |
42 | |
43 | {it.location} |
44 | {format.datetime(it.createdAt)} |
45 |
46 | {/each}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/workload-identity/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
48 |
49 |
50 |
53 |
54 |
Create
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
Create
63 |
64 |
65 |
66 |
95 |
96 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/workload-identity/detail/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, parent, fetch }) {
5 | const { project } = await parent()
6 | const location = url.searchParams.get('location')
7 | const name = url.searchParams.get('name')
8 |
9 | const [locationData, workloadIdentity] = await Promise.all([
10 | api.invoke('location.get', { project, id: location }, fetch),
11 | api.invoke('workloadIdentity.get', { project, location, name }, fetch)
12 | ])
13 |
14 | if (!locationData.ok || !workloadIdentity.ok) {
15 | if (locationData.error?.notFound || workloadIdentity.error?.notFound) {
16 | redirect(302, `/workload-identity?project=${project}`)
17 | }
18 | error(500, `location: ${locationData.error?.message}, workloadIdentity: ${workloadIdentity.error?.message}`)
19 | }
20 | return {
21 | // location: locationData.result,
22 | name,
23 | workloadIdentity: workloadIdentity.result
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/routes/(auth)/(project)/workload-identity/detail/+page.svelte:
--------------------------------------------------------------------------------
1 |
41 |
42 |
43 |
46 |
47 |
{workloadIdentity.name}
48 |
49 |
50 |
51 |
52 |
53 |
54 |
Workload Identity: {workloadIdentity.name}
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | {format.datetime(workloadIdentity.createdAt)}
74 |
75 |
76 |
77 |
78 |
79 | {workloadIdentity.createdBy}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | {#if projectInfo}
90 | {format.gsaBinding(projectInfo.id, workloadIdentity.name, workloadIdentity.gsa, 'acoshift-1362')}
91 | {/if}
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/src/routes/(auth)/+layout.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ fetch }) {
5 | /** @type {[Api.Response, Api.Response>]} */
6 | const [
7 | me,
8 | projects
9 | ] = await Promise.all([
10 | api.invoke('me.get', {}, fetch),
11 | api.invoke('project.list', {}, fetch)
12 | ])
13 | if (!me.ok) {
14 | if (me.error?.unauth) redirect(302, '/auth/signin')
15 | error(500, me.error?.message)
16 | }
17 | if (!projects.ok) error(500, projects.error?.message)
18 |
19 | return {
20 | profile: me.result,
21 | projects: projects.result?.items ?? []
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/routes/(auth)/+layout.svelte:
--------------------------------------------------------------------------------
1 |
34 |
35 |
37 |
38 | showSidebar = !showSidebar} />
39 |
40 |
41 |
46 |
47 |
48 | {@render children?.()}
49 |
50 |
51 |
52 |
53 |
54 |
125 |
--------------------------------------------------------------------------------
/src/routes/(auth)/ModalSelectProject.svelte:
--------------------------------------------------------------------------------
1 |
41 |
42 |
43 |
44 |
✕
45 |
Projects
46 |
47 |
48 |
49 |
50 |
51 | |
52 | Name |
53 | ID |
54 |
55 |
56 |
57 | {#each projects as it}
58 |
59 |
60 | {#if project === it.project}
61 |
62 | {/if}
63 | |
64 |
65 | setProject(it.project)} onkeypress={() => setProject(it.project)}
66 | tabindex="0" role="link"
67 | class="_tdcrt-udl _cs-pt _cl-primary:hover"
68 | style="font-weight: 500">
69 | {it.name}
70 |
71 | |
72 | {it.project} |
73 |
74 | {/each}
75 |
76 |
77 |
78 |
79 |
80 |
81 |
98 |
--------------------------------------------------------------------------------
/src/routes/(auth)/Navbar.svelte:
--------------------------------------------------------------------------------
1 |
46 |
47 |
91 |
92 |
175 |
--------------------------------------------------------------------------------
/src/routes/(auth)/Sidebar.svelte:
--------------------------------------------------------------------------------
1 |
98 |
99 |
186 |
187 |
264 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/+page.js:
--------------------------------------------------------------------------------
1 | import api from '$lib/api'
2 |
3 | export async function load ({ fetch }) {
4 | /** @type {Api.Response>} */
5 | const res = await api.invoke('billing.list', {}, fetch)
6 | return {
7 | billingAccounts: res.result?.items ?? [],
8 | error: res.error
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/+page.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 | Billing
12 |
13 |
14 |
21 |
22 |
23 |
24 |
25 |
26 | Billing name |
27 | Billing ID |
28 | Active |
29 |
30 |
31 |
32 | {#each billingAccounts as it (it.id)}
33 |
34 |
35 | {it.name}
36 | |
37 | {it.id} |
38 |
39 | {#if it.active}
40 |
41 | {:else}
42 |
43 | {/if}
44 | |
45 |
46 | {/each}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/create/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, fetch }) {
5 | const id = url.searchParams.get('id')
6 |
7 | let billingAccount = null
8 | if (id) {
9 | /** @type {Api.Response} */
10 | const res = await api.invoke('billing.get', { id }, fetch)
11 | if (!res.ok) {
12 | if (res.error?.notFound) redirect(302, '/billing')
13 | error(500, res.error?.message)
14 | }
15 | if (!res.result) redirect(302, '/billing')
16 |
17 | billingAccount = res.result
18 | }
19 |
20 | return {
21 | billingAccount
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
49 |
50 |
51 |
54 | {#if billingAccount}
55 |
58 |
59 |
Update
60 |
61 | {:else}
62 |
63 |
Create
64 |
65 | {/if}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
Account information
74 |
75 |
76 |
77 |
112 |
113 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/detail/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, fetch }) {
5 | const id = url.searchParams.get('id')
6 |
7 | /** @type {Api.Response} */
8 | const billingAccount = await api.invoke('billing.get', { id }, fetch)
9 | if (!billingAccount.ok) {
10 | if (billingAccount.error?.notFound) redirect(302, '/billing')
11 | error(500, billingAccount.error?.message)
12 | }
13 | if (!billingAccount.result) redirect(302, '/billing')
14 |
15 | return {
16 | billingAccount: billingAccount?.result
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/detail/+page.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
30 |
31 |
{billingAccount.name}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
"{billingAccount.name}" account information
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Account name |
50 | {billingAccount.name} |
51 |
52 |
53 | Tax ID |
54 | {billingAccount.taxId} |
55 |
56 |
57 | Name |
58 | {billingAccount.taxName} |
59 |
60 |
61 | Address |
62 | {billingAccount.taxAddress} |
63 |
64 |
65 |
66 |
67 |
68 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/report/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, fetch }) {
5 | const id = url.searchParams.get('id')
6 |
7 | const billingAccount = await api.invoke('billing.get', { id }, fetch)
8 | if (!billingAccount.ok) {
9 | if (billingAccount.error?.notFound) redirect(302, '/billing')
10 | error(500, billingAccount.error?.message)
11 | }
12 | if (!billingAccount.result) redirect(302, '/billing')
13 |
14 | return {
15 | billingAccount: billingAccount?.result
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/routes/(auth)/billing/report/+page.svelte:
--------------------------------------------------------------------------------
1 |
106 |
107 |
108 |
111 |
114 |
115 |
Report
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | {#each (report?.projectList ?? []) as it}
139 |
140 |
141 |
142 |
143 | {/each}
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
Billings
152 |
153 |
154 |
155 |
156 | Project |
157 | Name |
158 | Usage Value |
159 | Billing Value |
160 |
161 |
162 |
163 | {#each (report?.list ?? []) as it}
164 |
165 | {it.projectSid} |
166 | {it.name} |
167 | {it.usageValue} |
168 | {it.billingValue} |
169 |
170 | {:else}
171 |
172 | {/each}
173 |
174 |
175 |
176 |
177 |
--------------------------------------------------------------------------------
/src/routes/(auth)/project/+page.js:
--------------------------------------------------------------------------------
1 | export async function load ({ parent }) {
2 | const { projects } = await parent()
3 |
4 | return {
5 | projects
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/routes/(auth)/project/+page.svelte:
--------------------------------------------------------------------------------
1 |
45 |
46 | Projects
47 |
48 |
49 |
56 |
57 |
58 |
59 |
60 |
61 | Name |
62 | ID |
63 | Number |
64 | |
65 |
66 |
67 |
68 | {#each projects as it (it.project)}
69 |
70 |
71 |
72 | {it.name}
73 |
74 | |
75 | {it.project} |
76 | {it.id} |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
86 | |
87 |
88 | {:else}
89 |
90 | {/each}
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/src/routes/(auth)/project/create/+page.js:
--------------------------------------------------------------------------------
1 | import { redirect, error } from '@sveltejs/kit'
2 | import api from '$lib/api'
3 |
4 | export async function load ({ url, fetch }) {
5 | const project = url.searchParams.get('project')
6 |
7 | /** @type {Api.Response | null} */
8 | let projectInfo = null
9 | if (project) {
10 | projectInfo = await api.invoke('project.get', { project }, fetch)
11 | if (!projectInfo.ok) {
12 | if (projectInfo.error?.notFound) redirect(302, '/project')
13 | error(500, projectInfo.error?.message)
14 | }
15 | }
16 |
17 | /** @type {Api.Response>} */
18 | const billingAccounts = await api.invoke('billing.list', {}, fetch)
19 | if (!billingAccounts.ok) error(500, billingAccounts.error?.message)
20 |
21 | return {
22 | project: projectInfo?.result,
23 | billingAccounts: billingAccounts.result?.items ?? []
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/routes/(auth)/project/create/+page.svelte:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
58 | {#if project}
59 |
62 | {/if}
63 |
64 |
{#if project}Update{:else}Create{/if}
65 |
66 |
67 |
68 |
69 |
70 |
71 | {#if project}
72 |
Update Project: {project.name}
73 | {:else}
74 | Create Project
75 | {/if}
76 |
77 |
78 |
79 |
80 |
115 |
116 |
--------------------------------------------------------------------------------
/src/routes/+layout.js:
--------------------------------------------------------------------------------
1 | // export const ssr = false
2 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 | {@render children?.()}
23 |
--------------------------------------------------------------------------------
/src/routes/api/[fn]/+server.js:
--------------------------------------------------------------------------------
1 | import { env } from '$env/dynamic/private'
2 |
3 | const endpoint = env.API_ENDPOINT
4 |
5 | /** @type {import('@sveltejs/kit').RequestHandler} */
6 | export async function POST ({ locals, params, request }) {
7 | const token = locals.token
8 |
9 | // fast-path to reject unauthorized requests
10 | if (!token) {
11 | return new Response(JSON.stringify({
12 | ok: false,
13 | error: {
14 | message: 'api: unauthorized'
15 | }
16 | }), {
17 | headers: {
18 | 'content-type': 'application/json'
19 | }
20 | })
21 | }
22 |
23 | const resp = await fetch(`${endpoint}/${params.fn}`, {
24 | method: 'POST',
25 | body: request.body,
26 | // @ts-expect-error workaround for missing type
27 | duplex: 'half',
28 | headers: {
29 | accept: 'application/json',
30 | 'content-type': request.headers.get('content-type') ?? 'application/json',
31 | authorization: `bearer ${token}`
32 | }
33 | })
34 | return new Response(resp.body, {
35 | status: resp.status,
36 | headers: {
37 | 'content-type': resp.headers.get('content-type') ?? ''
38 | }
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/src/routes/api/dropbox/+server.js:
--------------------------------------------------------------------------------
1 | /** @type {import('./$types').RequestHandler} */
2 | export async function POST ({ locals, request, url }) {
3 | const token = locals.token
4 | const project = url.searchParams.get('project')
5 |
6 | if (!token || !project) {
7 | return new Response(JSON.stringify({
8 | ok: false,
9 | error: {
10 | message: 'api: unauthorized'
11 | }
12 | }), {
13 | headers: {
14 | 'content-type': 'application/json'
15 | }
16 | })
17 | }
18 |
19 | const resp = await fetch('https://dropbox.deploys.app/', {
20 | method: 'POST',
21 | body: request.body,
22 | // @ts-expect-error workaround for missing type
23 | duplex: 'half',
24 | headers: {
25 | accept: 'application/json',
26 | 'content-type': request.headers.get('content-type') ?? 'application/octet-stream',
27 | 'param-project': project,
28 | authorization: `bearer ${token}`
29 | }
30 | })
31 | return new Response(resp.body, {
32 | status: resp.status,
33 | headers: {
34 | 'content-type': resp.headers.get('content-type') ?? ''
35 | }
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/src/routes/api/registry/[fn]/+server.js:
--------------------------------------------------------------------------------
1 | const endpoint = 'https://registry.deploys.app/api'
2 |
3 | /** @type {import('./$types').RequestHandler} */
4 | export async function POST ({ locals, params, request }) {
5 | const token = locals.token
6 |
7 | // fast-path to reject unauthorized requests
8 | if (!token) {
9 | return new Response(JSON.stringify({
10 | ok: false,
11 | error: {
12 | message: 'api: unauthorized'
13 | }
14 | }), {
15 | headers: {
16 | 'content-type': 'application/json'
17 | }
18 | })
19 | }
20 |
21 | const resp = await fetch(`${endpoint}/${params.fn}`, {
22 | method: 'POST',
23 | body: request.body,
24 | // @ts-expect-error workaround for missing type
25 | duplex: 'half',
26 | headers: {
27 | accept: 'application/json',
28 | 'content-type': request.headers.get('content-type') ?? 'application/json',
29 | authorization: `bearer ${token}`
30 | }
31 | })
32 | return new Response(resp.body, {
33 | status: resp.status,
34 | headers: {
35 | 'content-type': resp.headers.get('content-type') ?? ''
36 | }
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/src/routes/auth/callback/+server.js:
--------------------------------------------------------------------------------
1 | import { env } from '$env/dynamic/private'
2 |
3 | /** @type {import('@sveltejs/kit').RequestHandler} */
4 | export async function GET ({ cookies, url }) {
5 | const state = url.searchParams.get('state') ?? ''
6 | const code = url.searchParams.get('code') ?? ''
7 |
8 | if (state !== cookies.get('state')) {
9 | return new Response('invalid state', { status: 400 })
10 | }
11 |
12 | // exchange token
13 | const resp = await fetch('https://auth.deploys.app/token', {
14 | method: 'POST',
15 | body: new URLSearchParams({
16 | grant_type: 'authorization_code',
17 | code,
18 | client_id: env.OAUTH2_CLIENT_ID,
19 | client_secret: env.OAUTH2_CLIENT_SECRET
20 | }),
21 | headers: {
22 | 'content-type': 'application/x-www-form-urlencoded'
23 | }
24 | })
25 | if (resp.status < 200 || resp.status > 299) {
26 | return resp
27 | }
28 | const respBody = await resp.json()
29 | const token = respBody.refresh_token
30 | if (!token) {
31 | return new Response('unknown error', { status: 500 })
32 | }
33 |
34 | cookies.set('token', token, {
35 | httpOnly: true,
36 | maxAge: 60 * 60 * 24 * 7,
37 | sameSite: 'lax',
38 | path: '/',
39 | secure: import.meta.env.PROD
40 | })
41 | cookies.delete('state', {
42 | httpOnly: true,
43 | sameSite: 'lax',
44 | path: '/',
45 | secure: import.meta.env.PROD
46 | })
47 |
48 | return new Response(undefined, {
49 | status: 302,
50 | headers: {
51 | location: '/'
52 | }
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/src/routes/auth/signin/+server.js:
--------------------------------------------------------------------------------
1 | import { env } from '$env/dynamic/private'
2 |
3 | /** @type {Crypto} */
4 | let webcrypto
5 |
6 | // workaround for dev
7 | if (typeof crypto === 'undefined') {
8 | import('node:crypto')
9 | .then((imp) => {
10 | // @ts-expect-error workaround to use Crypto while dev
11 | webcrypto = imp.webcrypto
12 | })
13 | .catch(() => {
14 | // don't throw error on compile time
15 | })
16 | } else {
17 | webcrypto = crypto
18 | }
19 |
20 | /**
21 | * randomState generates a random string for OAuth2 state
22 | * @returns {string}
23 | */
24 | function randomState () {
25 | const x = new Uint8Array(16)
26 | webcrypto.getRandomValues(x)
27 | return Array.from(x, (d) => d.toString(16).padStart(2, '0')).join('')
28 | }
29 |
30 | /** @type {import('@sveltejs/kit').RequestHandler} */
31 | export async function GET ({ cookies, url }) {
32 | const state = randomState()
33 |
34 | const callback = new URL(url.toString())
35 | callback.pathname = '/auth/callback'
36 |
37 | const q = new URLSearchParams()
38 | q.set('response_type', 'code')
39 | q.set('client_id', env.OAUTH2_CLIENT_ID)
40 | q.set('redirect_uri', callback.toString())
41 | q.set('state', state)
42 |
43 | cookies.set('state', state, {
44 | httpOnly: true,
45 | maxAge: 60 * 60,
46 | sameSite: 'lax',
47 | path: '/',
48 | secure: import.meta.env.PROD
49 | })
50 |
51 | return new Response(undefined, {
52 | status: 302,
53 | headers: {
54 | location: `https://auth.deploys.app/?${q.toString()}`
55 | }
56 | })
57 | }
58 |
--------------------------------------------------------------------------------
/src/routes/auth/signout/+server.js:
--------------------------------------------------------------------------------
1 | const landing = 'https://www.deploys.app/'
2 |
3 | /** @type {import('@sveltejs/kit').RequestHandler} */
4 | export async function POST ({ locals }) {
5 | const token = locals.token
6 |
7 | if (token) {
8 | const q = new URLSearchParams()
9 | q.set('token', token)
10 | q.set('callback', landing)
11 |
12 | return new Response(undefined, {
13 | status: 302,
14 | headers: {
15 | location: `https://auth.deploys.app/revoke?${q.toString()}`
16 | }
17 | })
18 | }
19 |
20 | return new Response(undefined, {
21 | status: 302,
22 | headers: {
23 | location: landing
24 | }
25 | })
26 | }
27 |
--------------------------------------------------------------------------------
/src/style/main.scss:
--------------------------------------------------------------------------------
1 | @use 'theme';
2 |
3 | @use '@nomimono/nomimono-css/reset.css';
4 | @use '@nomimono/nomimono-css/atomic.css';
5 | @use '@nomimono/nomimono-css/layout.css';
6 | @use '@nomimono/nomimono-css/component.css';
7 |
8 | html, body {
9 | background-color: hsl(var(--hsl-base-200));
10 | }
11 |
12 | body, h1, h2, h3, h4, h4, h5, h6 {
13 | line-height: 1.25;
14 | }
15 |
16 | [disabled] {
17 | cursor: not-allowed;
18 | }
19 |
20 | ul, ol {
21 | list-style: none;
22 | }
23 |
24 | .lo-grid-span-horizontal {
25 | display: grid;
26 | grid-auto-flow: column;
27 | grid-gap: 1rem;
28 | justify-content: start;
29 | }
30 |
31 | input:-webkit-autofill,
32 | input:-webkit-autofill:hover,
33 | input:-webkit-autofill:focus,
34 | textarea:-webkit-autofill,
35 | textarea:-webkit-autofill:hover,
36 | textarea:-webkit-autofill:focus,
37 | select:-webkit-autofill,
38 | select:-webkit-autofill:hover,
39 | select:-webkit-autofill:focus {
40 | -webkit-text-fill-color: hsl(var(--hsl-content));
41 | -webkit-box-shadow: 0 0 0 1000px hsl(var(--hsl-base-400)/0.2) inset;
42 | transition: background-color 5000s ease-in-out 0s;
43 | }
44 |
45 | .nm-panel {
46 | padding: 1.75rem 1.5rem;
47 |
48 | form, .content {
49 | max-width: 768px;
50 | }
51 | }
52 |
53 | h1,
54 | h2,
55 | h3,
56 | h4,
57 | h5,
58 | h6 {
59 | font-family: var(--ffml-secondary);
60 | font-weight: 400;
61 | line-height: 1.25;
62 | }
63 |
64 | h1 {
65 | font-size: var(--fs-10);
66 |
67 | @media (min-width: 768px) {
68 | font-size: var(--fs-10)
69 | }
70 |
71 | @media (min-width: 1024px) {
72 | font-size: var(--fs-11);
73 | }
74 |
75 | @media (min-width: 1280px) {
76 | font-size: var(--fs-11);
77 | }
78 | }
79 |
80 | h2 {
81 | font-size: var(--fs-9);
82 |
83 | @media (min-width: 768px) {
84 | font-size: var(--fs-9)
85 | }
86 |
87 | @media (min-width: 1024px) {
88 | font-size: var(--fs-10);
89 | }
90 |
91 | @media (min-width: 1280px) {
92 | font-size: var(--fs-10);
93 | }
94 | }
95 |
96 | h3 {
97 | font-size: var(--fs-8);
98 |
99 | @media (min-width: 768px) {
100 | font-size: var(--fs-8)
101 | }
102 |
103 | @media (min-width: 1024px) {
104 | font-size: var(--fs-9);
105 | }
106 |
107 | @media (min-width: 1280px) {
108 | font-size: var(--fs-9);
109 | }
110 | }
111 |
112 | h4 {
113 | font-size: var(--fs-7);
114 | }
115 |
116 | h5 {
117 | font-size: var(--fs-6);
118 | }
119 |
120 | h6 {
121 | font-size: var(--fs-5);
122 | }
123 |
124 | p {
125 | font-size: var(--fs-4);
126 | margin: 0;
127 | line-height: 1.65;
128 | }
129 |
130 | .nm-field > .helper {
131 | color: hsl(var(--hsl-content)/0.75);
132 | }
133 |
134 | *::placeholder,
135 | *::-moz-placeholder,
136 | *::webkit-input-placeholder {
137 | color: rgba(255, 255, 255, .3);
138 | }
139 |
140 | .nm-input.-has-icon-left,
141 | .nm-input.-has-icon-right {
142 | position: relative;
143 |
144 | > .icon {
145 | position: absolute;
146 | top: 0;
147 |
148 | display: flex;
149 | align-items: center;
150 | justify-content: center;
151 |
152 | width: 2.625rem;
153 | height: 100%;
154 | }
155 | }
156 |
157 | .icon {
158 | margin-left: var(--spc-5);
159 | border: 0;
160 | background-color: transparent;
161 | color: var(--color-gray-400);
162 | cursor: pointer;
163 | font-size: var(--fs-4);
164 | user-select: none;
165 |
166 | &.copy {
167 | font-size: var(--fs-6);
168 | }
169 |
170 | &:hover {
171 | color: white;
172 | }
173 | }
174 |
175 | .nm-input.-has-icon-left {
176 | > input {
177 | padding-left: 2.625rem;
178 | }
179 |
180 | > .icon:not(.-is-right) {
181 | left: 0;
182 | }
183 | }
184 |
185 | .nm-input.-has-icon-right {
186 | > input {
187 | padding-right: 2.625rem;
188 | }
189 |
190 | > .icon.-is-right {
191 | right: 0;
192 | }
193 | }
194 |
195 | input[type=number].-no-arrow {
196 | -moz-appearance: textfield;
197 |
198 | &::-webkit-outer-spin-button,
199 | &::-webkit-inner-spin-button {
200 | -webkit-appearance: none;
201 | margin: 0;
202 | }
203 | }
204 |
205 | .icon-button {
206 | display: inline-flex;
207 | align-items: center;
208 | justify-content: center;
209 |
210 | width: 2rem;
211 | height: 2rem;
212 |
213 | border: 0;
214 | background-color: transparent;
215 | color: hsl(var(--hsl-content));
216 |
217 | border-radius: 50%;
218 | font-size: 12px;
219 | cursor: pointer;
220 |
221 | transition: all var(--timing-faster) ease;
222 |
223 | &:hover {
224 | transform: scale(1.1);
225 | }
226 | }
227 |
228 | pre {
229 | position: relative;
230 | border: none;
231 | padding: 20px;
232 | border-radius: 3px;
233 | margin-bottom: 30px;
234 | white-space: pre-wrap;
235 | word-wrap: break-word;
236 | background-color: hsl(var(--hsl-base-200));
237 |
238 | .copy {
239 | position: absolute;
240 | top: 0;
241 | right: 0;
242 | background-color: inherit;
243 | padding: 6px 10px;
244 | font-size: 16px;
245 | font-weight: 600;
246 | cursor: pointer;
247 | user-select: none;
248 | border: 0;
249 | color: hsl(var(--hsl-content));
250 | outline: 0;
251 |
252 | &:hover {
253 | background-color: hsl(var(--hsl-base-100));
254 | }
255 |
256 | &:hover:active {
257 | background-color: hsl(var(--hsl-base-100));
258 | }
259 | }
260 |
261 | code {
262 | white-space: pre-wrap;
263 | display: block;
264 | }
265 |
266 | &.pre-scoll {
267 | min-height: 800px;
268 | max-height: 800px;
269 | overflow-y: auto;
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deploys-app/console/ad30df05d9462ff395f5683e0db85389dbcec82f/static/favicon.png
--------------------------------------------------------------------------------
/static/favicon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deploys-app/console/ad30df05d9462ff395f5683e0db85389dbcec82f/static/favicon.webp
--------------------------------------------------------------------------------
/static/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deploys-app/console/ad30df05d9462ff395f5683e0db85389dbcec82f/static/images/logo.png
--------------------------------------------------------------------------------
/static/images/logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/deploys-app/console/ad30df05d9462ff395f5683e0db85389dbcec82f/static/images/logo.webp
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapterCloudflare from '@sveltejs/adapter-cloudflare'
2 | import adapterNode from '@sveltejs/adapter-node'
3 | import preprocess from 'svelte-preprocess'
4 |
5 | /** @type {import('@sveltejs/kit').Config} */
6 | const config = {
7 | preprocess: preprocess({
8 | sass: true
9 | }),
10 | kit: {
11 | adapter: process.env.ADAPTER === 'node' ? adapterNode() : adapterCloudflare(),
12 | alias: {
13 | $style: './src/style',
14 | $types: './src/types'
15 | },
16 | version: {
17 | pollInterval: 60 * 1000 // 1 min
18 | }
19 | }
20 | }
21 |
22 | export default config
23 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { sveltekit } from '@sveltejs/kit/vite'
2 | import { defineConfig } from 'vite'
3 |
4 | export default defineConfig({
5 | plugins: [
6 | sveltekit()
7 | ]
8 | })
9 |
--------------------------------------------------------------------------------
/wrangler.toml:
--------------------------------------------------------------------------------
1 | name = "deploys-app--console"
2 | pages_build_output_dir = ".svelte-kit/cloudflare"
3 | compatibility_date = "2024-05-13"
4 |
5 | [vars]
6 | API_ENDPOINT = "https://api.deploys.app"
7 | OAUTH2_CLIENT_ID = "deploys-console-dev"
8 | PUBLIC_API_ENDPOINT = "https://api.deploys.app"
9 |
10 | [env.production.vars]
11 | API_ENDPOINT = "https://api.deploys.app"
12 | OAUTH2_CLIENT_ID = "deploys-console"
13 | PUBLIC_API_ENDPOINT = "https://api.deploys.app"
14 |
--------------------------------------------------------------------------------