@ossy/platform 0.0.1-alpha.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +22 -24
- package/src/http.js +85 -0
- package/src/proxy-internal.js +112 -0
- package/src/server.js +142 -0
- package/src/worker-entry.js +10 -0
- package/src/worker-runtime.js +115 -0
- package/CHANGELOG.md +0 -18
- package/README.md +0 -30
- package/src/Definition.js +0 -13
- package/src/PlatformHomeBody.jsx +0 -88
- package/src/ResourceList.jsx +0 -138
- package/src/constants.js +0 -2
- package/src/index.js +0 -6
- package/src/moduleStatus.js +0 -34
- package/src/platformHomeMetadata.js +0 -11
- package/src/resourceTemplates.js +0 -84
- package/src/utils/format-bytes.js +0 -14
package/package.json
CHANGED
|
@@ -1,39 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/platform",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.13.0",
|
|
4
|
+
"description": "Ossy application server runtime",
|
|
4
5
|
"repository": {
|
|
5
6
|
"type": "git",
|
|
6
|
-
"url": "git+https://github.com/ossy-se/
|
|
7
|
-
"directory": "packages/platform"
|
|
7
|
+
"url": "git+https://github.com/ossy-se/packages.git"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
-
"
|
|
10
|
+
"main": "./src/server.js",
|
|
11
11
|
"exports": {
|
|
12
|
-
".": "./src/
|
|
13
|
-
"./
|
|
12
|
+
".": "./src/server.js",
|
|
13
|
+
"./server": "./src/server.js",
|
|
14
|
+
"./proxy-internal": "./src/proxy-internal.js",
|
|
15
|
+
"./worker": "./src/worker-entry.js",
|
|
16
|
+
"./worker-runtime": "./src/worker-runtime.js"
|
|
14
17
|
},
|
|
15
|
-
"main": "./src/index.js",
|
|
16
|
-
"sideEffects": false,
|
|
17
18
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
-
"test": ""
|
|
19
|
+
"start": "PORT=3003 node ./src/server.js"
|
|
20
20
|
},
|
|
21
|
+
"keywords": [],
|
|
21
22
|
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
22
23
|
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@ossy/router": "^1.14.0",
|
|
26
|
+
"@ossy/sdk": "^1.14.0",
|
|
27
|
+
"cookie-parser": "^1.4.7",
|
|
28
|
+
"express": ">=5.0.0 <6.0.0",
|
|
29
|
+
"morgan": ">=1.10.1 <2.0.0",
|
|
30
|
+
"react": ">=19.0.0 <20.0.0",
|
|
31
|
+
"react-dom": ">=19.0.0 <20.0.0"
|
|
32
|
+
},
|
|
23
33
|
"files": [
|
|
24
|
-
"src"
|
|
25
|
-
"README.md",
|
|
26
|
-
"CHANGELOG.md"
|
|
34
|
+
"src"
|
|
27
35
|
],
|
|
28
|
-
"
|
|
29
|
-
"access": "public",
|
|
30
|
-
"registry": "https://registry.npmjs.org"
|
|
31
|
-
},
|
|
32
|
-
"peerDependencies": {
|
|
33
|
-
"@ossy/design-system": ">=0.14.0",
|
|
34
|
-
"@ossy/router-react": ">=0.14.0",
|
|
35
|
-
"@ossy/sdk-react": ">=0.14.0",
|
|
36
|
-
"react": ">=19.0.0"
|
|
37
|
-
},
|
|
38
|
-
"gitHead": "1da35d94f324f917d5ea790f838ce8e50214730e"
|
|
36
|
+
"gitHead": "c496b1f449dba609d65efac16aa100a02a5f0595"
|
|
39
37
|
}
|
package/src/http.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import url from 'url'
|
|
3
|
+
import express from 'express'
|
|
4
|
+
import morgan from 'morgan'
|
|
5
|
+
import { Router as OssyRouter } from '@ossy/router'
|
|
6
|
+
import cookieParser from 'cookie-parser'
|
|
7
|
+
|
|
8
|
+
const app = express();
|
|
9
|
+
|
|
10
|
+
const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
11
|
+
|
|
12
|
+
function parsePortFromArgv(argv) {
|
|
13
|
+
// Supports: --port 4000, --port=4000, -p 4000
|
|
14
|
+
const idx = argv.findIndex(a => a === '--port' || a === '-p')
|
|
15
|
+
if (idx !== -1 && argv[idx + 1]) return argv[idx + 1]
|
|
16
|
+
|
|
17
|
+
const eq = argv.find(a => a.startsWith('--port='))
|
|
18
|
+
if (eq) return eq.split('=')[1]
|
|
19
|
+
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizePort(value, fallback) {
|
|
24
|
+
if (value === undefined || value === null || value === '') return fallback
|
|
25
|
+
const n = Number.parseInt(String(value), 10)
|
|
26
|
+
if (!Number.isFinite(n) || n <= 0) return fallback
|
|
27
|
+
return n
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_PORT = 3000
|
|
31
|
+
const port = normalizePort(parsePortFromArgv(process.argv) ?? process.env.PORT, DEFAULT_PORT)
|
|
32
|
+
|
|
33
|
+
const middleware = [
|
|
34
|
+
morgan('tiny'),
|
|
35
|
+
express.json({ strict: false }),
|
|
36
|
+
cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
app.use(middleware)
|
|
40
|
+
|
|
41
|
+
// const PageRegistry = OssyRouter.of({ pages: [] })
|
|
42
|
+
// const ApiRegistry = OssyRouter.of({ api: [] })
|
|
43
|
+
// const TaskRegistry = OssyRouter.of({ tasks: [] })
|
|
44
|
+
// const StaticRegistry = OssyRouter.of({ static: [] })
|
|
45
|
+
|
|
46
|
+
const Registry = OssyRouter.of({ pages: [
|
|
47
|
+
{
|
|
48
|
+
id: 'home',
|
|
49
|
+
type: '@ossy/platform/page',
|
|
50
|
+
path: '/',
|
|
51
|
+
content: '<h1>Hello World from page route</h1>'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'home',
|
|
55
|
+
type: '@ossy/platform/api',
|
|
56
|
+
path: '/api',
|
|
57
|
+
handle: (req, res) => {
|
|
58
|
+
res.send({ message: 'Hello World from api route' })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
]})
|
|
62
|
+
|
|
63
|
+
app.all('*all', (req, res) => {
|
|
64
|
+
const pathname = req.originalUrl
|
|
65
|
+
|
|
66
|
+
// Will be replaced with fetching the resource from ossy storage
|
|
67
|
+
const resource = Registry.getPageByUrl(pathname)
|
|
68
|
+
|
|
69
|
+
if (resource.type === '@ossy/platform/page') {
|
|
70
|
+
res.send(resource.content)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
else if (resource.type === '@ossy/platform/api') {
|
|
74
|
+
resource.handle(req, res)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
else {
|
|
78
|
+
res.status(404).send('Not found')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
app.listen(port, () => {
|
|
84
|
+
console.log(`[@ossy/app][server] Running on http://localhost:${port}`);
|
|
85
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Node/Express can join duplicate workspace headers as `id, id` — API expects a single id. */
|
|
2
|
+
function normalizeWorkspaceIdHeader (value) {
|
|
3
|
+
if (!value || value === 'undefined') return undefined
|
|
4
|
+
const first = String(value).split(',')[0]?.trim()
|
|
5
|
+
return first || undefined
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ProxyInternal () {
|
|
9
|
+
return (req, res, next) => {
|
|
10
|
+
if (!req.originalUrl.startsWith('/@ossy')) {
|
|
11
|
+
return next()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (req.originalUrl.startsWith('/@ossy/users/me/app-settings') && req.method === 'PATCH') {
|
|
15
|
+
if (!req.body) {
|
|
16
|
+
res.status(400)
|
|
17
|
+
res.json('Invalid request body')
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const requestedSettings = req.body
|
|
22
|
+
const expiresMaxAge = 2147483647
|
|
23
|
+
const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
|
|
24
|
+
|
|
25
|
+
const updatedSettings = {
|
|
26
|
+
...userSettings,
|
|
27
|
+
...requestedSettings,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
res.cookie('x-ossy-user-settings', JSON.stringify(updatedSettings), {
|
|
31
|
+
httpOnly: true,
|
|
32
|
+
signed: true,
|
|
33
|
+
expires: new Date(Date.now() + expiresMaxAge),
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
res.status(201)
|
|
37
|
+
res.json('')
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (req.originalUrl.startsWith('/@ossy/users/me/app-settings') && req.method === 'GET') {
|
|
42
|
+
console.log('[@ossy/platform][proxy] GET /@ossy/users/me/app-settings')
|
|
43
|
+
const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
|
|
44
|
+
res.status(200)
|
|
45
|
+
res.json(userSettings)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(`[@ossy/platform][proxy] ${req.method} ${req.originalUrl}`)
|
|
50
|
+
|
|
51
|
+
const domain = process.env.OSSY_API_URL || 'https://api.ossy.se'
|
|
52
|
+
const url = `${domain}${req.originalUrl?.replace('/@ossy', '/api/v0')}`
|
|
53
|
+
const forwardedHeaders = JSON.parse(JSON.stringify(req.headers))
|
|
54
|
+
const workspaceId = normalizeWorkspaceIdHeader(req.get('workspaceId'))
|
|
55
|
+
|
|
56
|
+
if (workspaceId) {
|
|
57
|
+
forwardedHeaders.workspaceid = workspaceId
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const request = {
|
|
61
|
+
method: req.method,
|
|
62
|
+
headers: forwardedHeaders,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!['GET', 'HEAD'].includes(req.method)) {
|
|
66
|
+
request.body = JSON.stringify(req.body)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fetch(url, request)
|
|
70
|
+
.then((response) => {
|
|
71
|
+
response.headers.forEach((value, name) => {
|
|
72
|
+
res.setHeader(name, value)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
if (response.headers.get('content-type')?.includes('application/json')) {
|
|
76
|
+
return response.text().then((text) => {
|
|
77
|
+
const trimmed = text?.trim() ?? ''
|
|
78
|
+
let data
|
|
79
|
+
try {
|
|
80
|
+
data = trimmed === '' ? null : JSON.parse(trimmed)
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.log('[@ossy/platform][proxy][error]', error)
|
|
83
|
+
res.removeHeader('content-length')
|
|
84
|
+
const st = response.status
|
|
85
|
+
if (st === 401 || st === 403 || st === 404) {
|
|
86
|
+
res.status(st)
|
|
87
|
+
res.json(null)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
res.status(502)
|
|
91
|
+
res.json({ message: 'Upstream returned invalid JSON' })
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
res.removeHeader('content-length')
|
|
95
|
+
res.status(response.status)
|
|
96
|
+
res.json(data)
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return response.arrayBuffer().then((buffer) => {
|
|
101
|
+
res.status(response.status)
|
|
102
|
+
res.send(Buffer.from(buffer))
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
.catch((error) => {
|
|
106
|
+
console.log('[@ossy/platform][proxy][error]', error)
|
|
107
|
+
const status = error.status
|
|
108
|
+
res.status(status || 500)
|
|
109
|
+
res.json({ message: error.message || 'Internal Server Error' })
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/server.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import url from 'url'
|
|
4
|
+
import { pathToFileURL } from 'node:url'
|
|
5
|
+
import express from 'express'
|
|
6
|
+
import morgan from 'morgan'
|
|
7
|
+
import { Router as OssyRouter } from '@ossy/router'
|
|
8
|
+
import cookieParser from 'cookie-parser'
|
|
9
|
+
import { ProxyInternal } from './proxy-internal.js'
|
|
10
|
+
|
|
11
|
+
import buildTimeConfig from './.ossy/server-config.runtime.mjs'
|
|
12
|
+
import { BuildPage, buildPrerenderAppConfig } from './.ossy/render-page.task.js'
|
|
13
|
+
import Middleware from './.ossy/middleware.runtime.js'
|
|
14
|
+
|
|
15
|
+
const __ossyDir = path.dirname(url.fileURLToPath(import.meta.url)) + '/.ossy'
|
|
16
|
+
|
|
17
|
+
function readOssyJson (name) {
|
|
18
|
+
return JSON.parse(fs.readFileSync(path.join(__ossyDir, name), 'utf8'))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const apiRouteList = readOssyJson('api.generated.json') ?? []
|
|
22
|
+
const sitePageList = readOssyJson('pages.generated.json') ?? []
|
|
23
|
+
|
|
24
|
+
function pageRouterLanguageOptions (config, pages) {
|
|
25
|
+
let supported = config?.supportedLanguages
|
|
26
|
+
let defaultLanguage = config?.defaultLanguage
|
|
27
|
+
if ((!supported || supported.length <= 1) && pages.length > 0) {
|
|
28
|
+
const p0 = pages[0]
|
|
29
|
+
if (p0 && typeof p0.path === 'object' && p0.path != null) {
|
|
30
|
+
supported = Object.keys(p0.path)
|
|
31
|
+
defaultLanguage = defaultLanguage || supported[0]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
supportedLanguages: Array.isArray(supported) ? supported : [],
|
|
36
|
+
defaultLanguage,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const app = express()
|
|
41
|
+
|
|
42
|
+
const currentDir = path.dirname(url.fileURLToPath(import.meta.url))
|
|
43
|
+
const ROOT_PATH = path.resolve(currentDir, 'public')
|
|
44
|
+
|
|
45
|
+
function parsePortFromArgv (argv) {
|
|
46
|
+
const idx = argv.findIndex((a) => a === '--port' || a === '-p')
|
|
47
|
+
if (idx !== -1 && argv[idx + 1]) return argv[idx + 1]
|
|
48
|
+
|
|
49
|
+
const eq = argv.find((a) => a.startsWith('--port='))
|
|
50
|
+
if (eq) return eq.split('=')[1]
|
|
51
|
+
|
|
52
|
+
return undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizePort (value, fallback) {
|
|
56
|
+
if (value === undefined || value === null || value === '') return fallback
|
|
57
|
+
const n = Number.parseInt(String(value), 10)
|
|
58
|
+
if (!Number.isFinite(n) || n <= 0) return fallback
|
|
59
|
+
return n
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const DEFAULT_PORT = 3000
|
|
63
|
+
const port = normalizePort(parsePortFromArgv(process.argv) ?? process.env.PORT, DEFAULT_PORT)
|
|
64
|
+
|
|
65
|
+
if (Middleware !== undefined) {
|
|
66
|
+
console.log(`[@ossy/platform][server] ${Middleware?.length || 0} custom middleware loaded`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const middleware = [
|
|
70
|
+
morgan('tiny'),
|
|
71
|
+
express.json({ strict: false }),
|
|
72
|
+
cookieParser(process.env.OSSY_COOKIE_SECRET || 'default_secret'),
|
|
73
|
+
(req, _res, next) => {
|
|
74
|
+
const userSettings = JSON.parse(req.signedCookies?.['x-ossy-user-settings'] || '{}')
|
|
75
|
+
req.userAppSettings = userSettings
|
|
76
|
+
if (userSettings.workspaceId && !req.get('workspaceId')) {
|
|
77
|
+
req.headers.workspaceid = userSettings.workspaceId
|
|
78
|
+
}
|
|
79
|
+
const cookieHeader = req.headers.cookie
|
|
80
|
+
req.isAuthenticated = cookieHeader ? cookieHeader.includes('auth=') : false
|
|
81
|
+
next()
|
|
82
|
+
},
|
|
83
|
+
...(Middleware || []),
|
|
84
|
+
express.static(ROOT_PATH),
|
|
85
|
+
ProxyInternal(),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
app.use(middleware)
|
|
89
|
+
|
|
90
|
+
const apiRouter = OssyRouter.of({
|
|
91
|
+
pages: apiRouteList,
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
const { supportedLanguages, defaultLanguage } = pageRouterLanguageOptions(
|
|
95
|
+
buildTimeConfig,
|
|
96
|
+
sitePageList
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
const pageRouter = OssyRouter.of({
|
|
100
|
+
pages: sitePageList,
|
|
101
|
+
defaultLanguage,
|
|
102
|
+
supportedLanguages,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
app.all('*all', async (req, res) => {
|
|
106
|
+
const requestUrl = req.originalUrl || '/'
|
|
107
|
+
try {
|
|
108
|
+
const apiRoute = apiRouter.getPageByUrl(requestUrl)
|
|
109
|
+
if (apiRoute?.module) {
|
|
110
|
+
const mod = await import(pathToFileURL(path.resolve(__ossyDir, apiRoute.module)).href)
|
|
111
|
+
await mod.default(req, res)
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
115
|
+
res.status(404).send('Not found')
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
const pageRoute = pageRouter.getPageByUrl(requestUrl)
|
|
119
|
+
if (pageRoute) {
|
|
120
|
+
const appConfig = buildPrerenderAppConfig({
|
|
121
|
+
buildTimeConfig,
|
|
122
|
+
pageList: sitePageList,
|
|
123
|
+
activeRouteId: pageRoute.id,
|
|
124
|
+
urlPath: requestUrl,
|
|
125
|
+
isAuthenticated: !!req.isAuthenticated,
|
|
126
|
+
})
|
|
127
|
+
const html = await BuildPage.handle({ route: pageRoute, appConfig })
|
|
128
|
+
res.status(200).type('html').send(html)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
res.status(404).send('Not found')
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.error('[@ossy/platform][server] Request handling failed:', err)
|
|
134
|
+
if (!res.headersSent) {
|
|
135
|
+
res.status(500).type('text').send('Internal Server Error')
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
app.listen(port, () => {
|
|
141
|
+
console.log(`[@ossy/platform][server] Running on http://localhost:${port}`)
|
|
142
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import 'dotenv/config'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { runWorkerScheduler } from './worker-runtime.js'
|
|
6
|
+
|
|
7
|
+
const __ossyDir = path.dirname(fileURLToPath(import.meta.url)) + '/.ossy'
|
|
8
|
+
const tasks = JSON.parse(fs.readFileSync(path.join(__ossyDir, 'tasks.generated.json'), 'utf8')) ?? []
|
|
9
|
+
|
|
10
|
+
runWorkerScheduler(tasks, __ossyDir)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { pathToFileURL } from 'node:url'
|
|
3
|
+
import { SDK } from '@ossy/sdk'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {Array<{ type: string, module: string }>} tasks
|
|
7
|
+
* @param {string} ossyDir - absolute path to the .ossy directory
|
|
8
|
+
*/
|
|
9
|
+
export function runWorkerScheduler (tasks, ossyDir) {
|
|
10
|
+
const sdk = SDK.of({
|
|
11
|
+
workspaceId: process.env.OSSY_WORKSPACE_ID,
|
|
12
|
+
apiUrl: process.env.OSSY_API_URL,
|
|
13
|
+
authorization: process.env.OSSY_API_TOKEN,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
const jobsClient = /** @type {{ getUnprocessed: () => Promise<unknown[]> }} */ (sdk.jobs)
|
|
17
|
+
let status = 'running'
|
|
18
|
+
|
|
19
|
+
console.log('Starting scheduler')
|
|
20
|
+
main()
|
|
21
|
+
|
|
22
|
+
setInterval(() => {
|
|
23
|
+
if (status === 'running') return
|
|
24
|
+
status = 'running'
|
|
25
|
+
try {
|
|
26
|
+
main()
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.log('Error running main')
|
|
29
|
+
console.error(error)
|
|
30
|
+
status = 'idle'
|
|
31
|
+
}
|
|
32
|
+
}, 3000)
|
|
33
|
+
|
|
34
|
+
function main () {
|
|
35
|
+
console.log('Looking for jobs')
|
|
36
|
+
jobsClient
|
|
37
|
+
.getUnprocessed()
|
|
38
|
+
.then(async (jobs) => {
|
|
39
|
+
if (!jobs || !jobs.length) {
|
|
40
|
+
console.log('No jobs found, going idle')
|
|
41
|
+
status = 'idle'
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const jobsGroupedByResourceId = groupJobsByResourceId(jobs)
|
|
46
|
+
console.log(`Found ${jobs.length} jobs between ${jobsGroupedByResourceId.length} resources`)
|
|
47
|
+
|
|
48
|
+
const processedGroups = jobsGroupedByResourceId.map(([resourceId, groupJobs]) => {
|
|
49
|
+
console.log(`Processing group for resourceId ${resourceId}`)
|
|
50
|
+
return processJobsSequentially(groupJobs)
|
|
51
|
+
.then(() => console.log(`Completed group for resourceId ${resourceId}`))
|
|
52
|
+
.catch((err) => {
|
|
53
|
+
console.log(`Failed to process group for resourceId ${resourceId}`)
|
|
54
|
+
console.error(err)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await Promise.allSettled(processedGroups)
|
|
60
|
+
console.log('Finished processing of groups...')
|
|
61
|
+
console.log('Going idle')
|
|
62
|
+
status = 'idle'
|
|
63
|
+
console.log('----------------------------------')
|
|
64
|
+
console.groupEnd()
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.log('Error processing groups')
|
|
67
|
+
console.error(error)
|
|
68
|
+
status = 'idle'
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.catch((error) => {
|
|
72
|
+
console.log('Error getting jobs')
|
|
73
|
+
console.error(error)
|
|
74
|
+
status = 'idle'
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function groupJobsByResourceId (jobList) {
|
|
79
|
+
return Object.entries(
|
|
80
|
+
jobList.reduce((acc, job) => {
|
|
81
|
+
const content = /** @type {{ resourceId?: string }} */ (job.content || {})
|
|
82
|
+
const rid = content.resourceId
|
|
83
|
+
return {
|
|
84
|
+
...acc,
|
|
85
|
+
[rid]: [...(acc[rid] || []), job],
|
|
86
|
+
}
|
|
87
|
+
}, /** @type {Record<string, unknown[]>} */ ({}))
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function processJobsSequentially (jobList) {
|
|
92
|
+
console.log(`Processing ${jobList.length} jobs`)
|
|
93
|
+
for (const job of jobList) {
|
|
94
|
+
console.log(`Processing job ${job.id}`)
|
|
95
|
+
const task = tasks.find((t) => t.type === job.type)
|
|
96
|
+
if (!task) {
|
|
97
|
+
console.log('No handler found for job', job.id)
|
|
98
|
+
continue
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`Handler found for ${task.type}`)
|
|
102
|
+
try {
|
|
103
|
+
const mod = await import(pathToFileURL(path.resolve(ossyDir, task.module)).href)
|
|
104
|
+
const jobSdk = SDK.of({
|
|
105
|
+
workspaceId: job.belongsTo,
|
|
106
|
+
authorization: process.env.OSSY_API_TOKEN,
|
|
107
|
+
})
|
|
108
|
+
await mod.default({ sdk: jobSdk, job }).catch(() => {})
|
|
109
|
+
} catch (error) {
|
|
110
|
+
console.error(error)
|
|
111
|
+
console.log('Failed to processing job')
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Change Log
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
-
|
|
6
|
-
## 0.0.1-alpha.1 (2026-03-29)
|
|
7
|
-
|
|
8
|
-
**Note:** Version bump only for package @ossy/platform
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
# Changelog
|
|
15
|
-
|
|
16
|
-
## 0.0.1-alpha.0
|
|
17
|
-
|
|
18
|
-
- Initial extract from `website-ossy`: `Definition`, `PlatformResourceTemplates`, `PlatformHomeBody`, `platformHomeMetadata`.
|
package/README.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# @ossy/platform
|
|
2
|
-
|
|
3
|
-
Ossy **Platform** module: resource template `@ossy/platform/config` (mirrors deployment-tools `platform-config.json`) and the platform home UI (`PlatformHomeBody`, metadata for routing).
|
|
4
|
-
|
|
5
|
-
## Install
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install @ossy/platform
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Peer dependencies (your app should already include them):
|
|
12
|
-
|
|
13
|
-
- `react`
|
|
14
|
-
- `@ossy/design-system`
|
|
15
|
-
- `@ossy/router-react`
|
|
16
|
-
- `@ossy/sdk-react`
|
|
17
|
-
|
|
18
|
-
## Use in a website
|
|
19
|
-
|
|
20
|
-
1. Spread **`PlatformResourceTemplates`** into your app `resourceTemplates` (and sync to the API via publish / cms upload).
|
|
21
|
-
2. Add a page that wraps **`PlatformHomeBody`** in your shell layout and export **`platformHomeMetadata`**.
|
|
22
|
-
3. Register **`Definition`** in your navigation / module registry.
|
|
23
|
-
|
|
24
|
-
## Publishing
|
|
25
|
-
|
|
26
|
-
Released on its **own semver** from this monorepo (`lerna publish` with independent versions). Bump **`@ossy/platform`** when only this module changes.
|
|
27
|
-
|
|
28
|
-
## Direction
|
|
29
|
-
|
|
30
|
-
Long term, **which modules a workspace may enable** should be driven by the **CMS** and tied to **billing / entitlements**. Packages stay the **distributable implementation** of each module; access control lives in the product, not in npm scope alone.
|
package/src/Definition.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export const Definition = {
|
|
2
|
-
id: 'platform',
|
|
3
|
-
title: 'Platform',
|
|
4
|
-
description: 'Deployment platform configuration aligned with deployment-tools S3 platform-config.json.',
|
|
5
|
-
module: {
|
|
6
|
-
id: 'platform',
|
|
7
|
-
enabled: true,
|
|
8
|
-
},
|
|
9
|
-
statuses: ['beta'],
|
|
10
|
-
actions: ['resources.create', 'resources.update-content', 'resources.rename', 'resources.remove'],
|
|
11
|
-
views: ['home.page', 'resources.search', 'resources.get'],
|
|
12
|
-
tasks: [],
|
|
13
|
-
}
|
package/src/PlatformHomeBody.jsx
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { useRouter } from '@ossy/router-react'
|
|
3
|
-
import { useResources, AsyncStatus } from '@ossy/sdk-react'
|
|
4
|
-
import { Title, Text, View, Tags, Button, Switch } from '@ossy/design-system'
|
|
5
|
-
import { Definition } from './Definition.js'
|
|
6
|
-
import { moduleStatusTags } from './moduleStatus.js'
|
|
7
|
-
import { ResourceList } from './ResourceList.jsx'
|
|
8
|
-
import { PLATFORM_CONFIG_TEMPLATE_ID, PLATFORM_RESOURCE_LOCATION } from './constants.js'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Platform module home content (no site chrome). Wrap with your app {@code Layout} in a *.page.jsx file.
|
|
12
|
-
*/
|
|
13
|
-
export function PlatformHomeBody () {
|
|
14
|
-
const router = useRouter()
|
|
15
|
-
const { status, resources } = useResources(PLATFORM_RESOURCE_LOCATION)
|
|
16
|
-
const platformResources = (resources || []).filter(
|
|
17
|
-
(resource) => resource?.type === PLATFORM_CONFIG_TEMPLATE_ID
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
let listStatus = status
|
|
21
|
-
|
|
22
|
-
if (status === AsyncStatus.Success && platformResources.length === 0) {
|
|
23
|
-
listStatus = 'Empty'
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<View
|
|
28
|
-
gap="m"
|
|
29
|
-
surface="primary"
|
|
30
|
-
style={{ padding: 'var(--space-m) var(--space-l)', height: '100%', overflowY: 'auto' }}
|
|
31
|
-
>
|
|
32
|
-
<View inset="s" gap="s">
|
|
33
|
-
<View layout="row" justifyContent="space-between" alignItems="center">
|
|
34
|
-
<View layout="row" gap="s" alignItems="center">
|
|
35
|
-
<Title>{Definition.title}</Title>
|
|
36
|
-
{moduleStatusTags(Definition).length > 0 && (
|
|
37
|
-
<Tags tags={moduleStatusTags(Definition)} size="s" />
|
|
38
|
-
)}
|
|
39
|
-
</View>
|
|
40
|
-
<Button
|
|
41
|
-
variant="cta"
|
|
42
|
-
prefix="add"
|
|
43
|
-
href={router.getHref(
|
|
44
|
-
`@create-document?location=${encodeURIComponent(PLATFORM_RESOURCE_LOCATION)}&templateId=${encodeURIComponent(PLATFORM_CONFIG_TEMPLATE_ID)}`
|
|
45
|
-
)}
|
|
46
|
-
>
|
|
47
|
-
New platform config
|
|
48
|
-
</Button>
|
|
49
|
-
</View>
|
|
50
|
-
|
|
51
|
-
<Text style={{ maxWidth: '640px' }}>
|
|
52
|
-
{Definition.description} Field names match deployment-tools{' '}
|
|
53
|
-
<strong>platform-config.json</strong> on the S3 bucket (excluding <strong>awsRoleToAssume</strong>).
|
|
54
|
-
</Text>
|
|
55
|
-
</View>
|
|
56
|
-
|
|
57
|
-
<View gap="m" inset="s" style={{ overflowY: 'auto' }}>
|
|
58
|
-
<Switch on={listStatus}>
|
|
59
|
-
<Switch.Case match={[AsyncStatus.NotInitialized, AsyncStatus.Loading]}>
|
|
60
|
-
<Text>Loading…</Text>
|
|
61
|
-
</Switch.Case>
|
|
62
|
-
|
|
63
|
-
<Switch.Case match={[AsyncStatus.Error, AsyncStatus.AuthenticationError]}>
|
|
64
|
-
<Text>Could not load platform configs.</Text>
|
|
65
|
-
</Switch.Case>
|
|
66
|
-
|
|
67
|
-
<Switch.Case match={['Empty']}>
|
|
68
|
-
<Text>No platform configs yet. Create one to mirror a deployment-tools platform-config.json.</Text>
|
|
69
|
-
</Switch.Case>
|
|
70
|
-
|
|
71
|
-
<Switch.Case match={[AsyncStatus.Success]}>
|
|
72
|
-
<ResourceList
|
|
73
|
-
resources={platformResources.map((resource) => ({
|
|
74
|
-
...resource,
|
|
75
|
-
name:
|
|
76
|
-
resource?.content?.platformName ||
|
|
77
|
-
resource?.content?.platformId ||
|
|
78
|
-
resource?.name ||
|
|
79
|
-
'Unnamed platform',
|
|
80
|
-
href: router.getHref({ id: '@resource', params: { resourceId: resource?.id } }),
|
|
81
|
-
}))}
|
|
82
|
-
/>
|
|
83
|
-
</Switch.Case>
|
|
84
|
-
</Switch>
|
|
85
|
-
</View>
|
|
86
|
-
</View>
|
|
87
|
-
)
|
|
88
|
-
}
|
package/src/ResourceList.jsx
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { useWorkspace } from '@ossy/sdk-react'
|
|
4
|
-
import {
|
|
5
|
-
Dropdown,
|
|
6
|
-
DropZone,
|
|
7
|
-
Icon2,
|
|
8
|
-
Image,
|
|
9
|
-
View,
|
|
10
|
-
Text,
|
|
11
|
-
Button,
|
|
12
|
-
ContextMenu,
|
|
13
|
-
} from '@ossy/design-system'
|
|
14
|
-
import { formatBytes } from './utils/format-bytes.js'
|
|
15
|
-
|
|
16
|
-
export const ResourceList = ({
|
|
17
|
-
resources = [],
|
|
18
|
-
onClick = () => {},
|
|
19
|
-
}) => {
|
|
20
|
-
return resources
|
|
21
|
-
.map((resource) => (
|
|
22
|
-
<ResourceListItem
|
|
23
|
-
{...resource}
|
|
24
|
-
key={resource.id}
|
|
25
|
-
onClick={() => {
|
|
26
|
-
onClick(resource)
|
|
27
|
-
resource?.onClick?.(resource)
|
|
28
|
-
}}
|
|
29
|
-
/>
|
|
30
|
-
))
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function ResourceListItem ({
|
|
34
|
-
onClick,
|
|
35
|
-
href,
|
|
36
|
-
onDrop,
|
|
37
|
-
dragData,
|
|
38
|
-
...resource
|
|
39
|
-
}) {
|
|
40
|
-
let Container = ({ children }) => <>{children}</>
|
|
41
|
-
|
|
42
|
-
if (onDrop) {
|
|
43
|
-
Container = DropZone
|
|
44
|
-
} else if (dragData) {
|
|
45
|
-
Container = DropZone.Dragable
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Container onDrop={onDrop} dragData={dragData}>
|
|
50
|
-
<View
|
|
51
|
-
layout="row"
|
|
52
|
-
selectable
|
|
53
|
-
gap="m"
|
|
54
|
-
style={{
|
|
55
|
-
height: '56px',
|
|
56
|
-
flexShrink: 0,
|
|
57
|
-
borderBottom: '1px solid var(--separator-primary)',
|
|
58
|
-
}}
|
|
59
|
-
>
|
|
60
|
-
<View
|
|
61
|
-
as={href ? 'a' : undefined}
|
|
62
|
-
gap="m"
|
|
63
|
-
layout="row"
|
|
64
|
-
alignItems="center"
|
|
65
|
-
style={{ flexGrow: 1, padding: '12px 0 12px 20px' }}
|
|
66
|
-
href={href}
|
|
67
|
-
onClick={onClick}
|
|
68
|
-
>
|
|
69
|
-
<ResourceIcon {...resource} />
|
|
70
|
-
<Text>{typeof resource.name === 'string' && resource.name}</Text>
|
|
71
|
-
<View style={{ flexGrow: 1 }} />
|
|
72
|
-
<Size {...resource} />
|
|
73
|
-
</View>
|
|
74
|
-
|
|
75
|
-
<View style={{ padding: '12px 8px 12px 0' }}>
|
|
76
|
-
<RowActions {...resource} />
|
|
77
|
-
</View>
|
|
78
|
-
</View>
|
|
79
|
-
</Container>
|
|
80
|
-
)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function Size (resource) {
|
|
84
|
-
if (resource.content?.ContentLength) {
|
|
85
|
-
return (
|
|
86
|
-
<Text variant="small">
|
|
87
|
-
{formatBytes(resource.content.ContentLength)}
|
|
88
|
-
</Text>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return <></>
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function ResourceIcon ({ ...resource }) {
|
|
96
|
-
const { workspace } = useWorkspace()
|
|
97
|
-
|
|
98
|
-
const resourceTemplates = (workspace.resourceTemplates || [])
|
|
99
|
-
.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {})
|
|
100
|
-
|
|
101
|
-
if (resource?.type === 'directory') {
|
|
102
|
-
return (
|
|
103
|
-
<Icon2 size="s" name="folder" style={{ fill: 'hsl(0, 0%, 60%)' }} />
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (resource.type.startsWith('image')) {
|
|
108
|
-
return (
|
|
109
|
-
<Image
|
|
110
|
-
src={resource?.content?.sizes?.thumbnailSmall || resource?.content?.src}
|
|
111
|
-
placeholderSrc={resource?.content?.sizes?.['loader-square-blurred-after'] || resource?.content?.src}
|
|
112
|
-
style={{ width: '24px', height: '24px', borderRadius: '25%' }}
|
|
113
|
-
/>
|
|
114
|
-
)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<Icon2
|
|
119
|
-
size="s"
|
|
120
|
-
name={resourceTemplates[resource.type]?.icon || 'file'}
|
|
121
|
-
style={{ fill: 'hsl(0, 0%, 80%)' }}
|
|
122
|
-
/>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function RowActions ({ actions }) {
|
|
127
|
-
if (!actions) return <></>
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<Dropdown trigger={<Button prefix="more-vertical-alt" variant="command" />}>
|
|
131
|
-
<View inset="xs" surface="primary" roundness="s">
|
|
132
|
-
<ContextMenu roundness="s" surface="primary">
|
|
133
|
-
{actions}
|
|
134
|
-
</ContextMenu>
|
|
135
|
-
</View>
|
|
136
|
-
</Dropdown>
|
|
137
|
-
)
|
|
138
|
-
}
|
package/src/constants.js
DELETED
package/src/index.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { Definition } from './Definition.js'
|
|
2
|
-
export { PlatformResourceTemplates } from './resourceTemplates.js'
|
|
3
|
-
export { PLATFORM_RESOURCE_LOCATION, PLATFORM_CONFIG_TEMPLATE_ID } from './constants.js'
|
|
4
|
-
export { platformHomeMetadata } from './platformHomeMetadata.js'
|
|
5
|
-
export { PlatformHomeBody } from './PlatformHomeBody.jsx'
|
|
6
|
-
export { moduleStatusTags, moduleStatuses, MODULE_STATUS } from './moduleStatus.js'
|
package/src/moduleStatus.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal subset of website module status helpers for {@link Definition} tags.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export const MODULE_STATUS = {
|
|
6
|
-
beta: 'beta',
|
|
7
|
-
comingSoon: 'coming-soon',
|
|
8
|
-
stable: 'stable',
|
|
9
|
-
hidden: 'hidden',
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const STATUS_TO_TAG_LABEL = {
|
|
13
|
-
[MODULE_STATUS.beta]: 'Beta',
|
|
14
|
-
[MODULE_STATUS.comingSoon]: 'Coming Soon',
|
|
15
|
-
[MODULE_STATUS.stable]: null,
|
|
16
|
-
[MODULE_STATUS.hidden]: null,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function moduleStatuses (definition) {
|
|
20
|
-
const fromArray = Array.isArray(definition?.statuses) ? definition.statuses : []
|
|
21
|
-
const fromLegacy = definition?.status && typeof definition.status === 'string' ? [definition.status] : []
|
|
22
|
-
if (fromArray.length) return [...new Set(fromArray)]
|
|
23
|
-
if (fromLegacy.length) return [...new Set(fromLegacy)]
|
|
24
|
-
return []
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function moduleStatusTags (definition) {
|
|
28
|
-
const labels = []
|
|
29
|
-
for (const s of moduleStatuses(definition)) {
|
|
30
|
-
const label = STATUS_TO_TAG_LABEL[s]
|
|
31
|
-
if (label) labels.push(label)
|
|
32
|
-
}
|
|
33
|
-
return [...new Set(labels)]
|
|
34
|
-
}
|
package/src/resourceTemplates.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Matches the shape written to S3 as `platform-config.json` by deployment-tools
|
|
3
|
-
* (`BucketDeployment` → `Source.jsonData('platform-config.json', { ...config, awsRoleToAssume: undefined })`).
|
|
4
|
-
*
|
|
5
|
-
* @see ossy/packages/deployment-tools/src/config/platform-config.js (PlatformConfig typedef)
|
|
6
|
-
* @see ossy/packages/deployment-tools/src/infrastructure/container-deployment-target/container-deployment-target.js
|
|
7
|
-
*
|
|
8
|
-
* Note: `awsRoleToAssume` is intentionally omitted from the uploaded file (secret); do not store it here.
|
|
9
|
-
* Optional `sesDomains` / `dnsRecords` are JSON text fields for arrays/objects.
|
|
10
|
-
*/
|
|
11
|
-
export const PlatformResourceTemplates = [
|
|
12
|
-
{
|
|
13
|
-
name: 'Platform config',
|
|
14
|
-
id: '@ossy/platform/config',
|
|
15
|
-
icon: 'controller',
|
|
16
|
-
fields: [
|
|
17
|
-
{
|
|
18
|
-
name: 'platformId',
|
|
19
|
-
label: 'Platform ID (Ossy reference)',
|
|
20
|
-
type: 'text',
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: 'platformName',
|
|
24
|
-
label: 'platformName',
|
|
25
|
-
type: 'text',
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: 'awsAccountId',
|
|
29
|
-
label: 'awsAccountId',
|
|
30
|
-
type: 'text',
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: 'awsRegion',
|
|
34
|
-
label: 'awsRegion',
|
|
35
|
-
type: 'text',
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: 'awsKeyPairName',
|
|
39
|
-
label: 'awsKeyPairName',
|
|
40
|
-
type: 'text',
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: 'awsStaticBucketName',
|
|
44
|
-
label: 'awsStaticBucketName',
|
|
45
|
-
type: 'text',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: 'awsDeploymentSqsName',
|
|
49
|
-
label: 'awsDeploymentSqsName',
|
|
50
|
-
type: 'text',
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: 'awsDeploymentSqsArn',
|
|
54
|
-
label: 'awsDeploymentSqsArn',
|
|
55
|
-
type: 'text',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: 'ciGithubActionsRepo',
|
|
59
|
-
label: 'ciGithubActionsRepo (org/repo)',
|
|
60
|
-
type: 'text',
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: 'ciDockerNetworkName',
|
|
64
|
-
label: 'ciDockerNetworkName',
|
|
65
|
-
type: 'text',
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'sesDomains',
|
|
69
|
-
label: 'sesDomains (JSON array of strings, optional)',
|
|
70
|
-
type: 'textarea',
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'dnsRecords',
|
|
74
|
-
label: 'dnsRecords (JSON object, optional)',
|
|
75
|
-
type: 'textarea',
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: 'Notes',
|
|
79
|
-
label: 'Notes',
|
|
80
|
-
type: 'textarea',
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
},
|
|
84
|
-
]
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Converts bytes to a human-readable format.
|
|
3
|
-
* @param {number} bytes - The number of bytes.
|
|
4
|
-
* @param {number} decimals - The number of decimal places to include (default is 2).
|
|
5
|
-
* @returns {string} - The human-readable format.
|
|
6
|
-
*/
|
|
7
|
-
export function formatBytes (bytes, decimals = 2) {
|
|
8
|
-
if (!bytes) return
|
|
9
|
-
const k = 1024
|
|
10
|
-
const dm = decimals < 0 ? 0 : decimals
|
|
11
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
12
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
13
|
-
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`
|
|
14
|
-
}
|