fix(core): update
This commit is contained in:
commit
c24262f765
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@ -0,0 +1 @@
|
||||
node_modules/
|
71
.gitea/workflows/docker_nottags.yaml
Normal file
71
.gitea/workflows/docker_nottags.yaml
Normal file
@ -0,0 +1,71 @@
|
||||
name: Docker (tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags-ignore:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
|
||||
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci npm prepare
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci command npm run build
|
106
.gitea/workflows/docker_tags.yaml
Normal file
106
.gitea/workflows/docker_tags.yaml
Normal file
@ -0,0 +1,106 @@
|
||||
name: Docker (tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_LOGIN_DOCKER_GITEA: ${{ github.server_url }}|${{ gitea.repository_owner }}|${{ secrets.GITEA_TOKEN }}
|
||||
NPMCI_LOGIN_DOCKER_DOCKERREGISTRY: ${{ secrets.NPMCI_LOGIN_DOCKER_DOCKERREGISTRY }}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci command npm run build
|
||||
|
||||
release:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: registry.gitlab.com/hosttoday/ht-docker-dbase:npmci
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
npmci docker login
|
||||
npmci docker build
|
||||
npmci docker test
|
||||
# npmci docker push gitea.lossless.digital
|
||||
npmci docker push dockerregistry.lossless.digital
|
||||
|
||||
metadata:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Trigger
|
||||
run: npmci trigger
|
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "npm test",
|
||||
"name": "Run npm test",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"npmci": {
|
||||
"type": "object",
|
||||
"description": "settings for npmci"
|
||||
},
|
||||
"gitzone": {
|
||||
"type": "object",
|
||||
"description": "settings for gitzone",
|
||||
"properties": {
|
||||
"projectType": {
|
||||
"type": "string",
|
||||
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@ -0,0 +1,46 @@
|
||||
# gitzone dockerfile_service
|
||||
## STAGE 1 // BUILD
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node1
|
||||
COPY ./ /app
|
||||
WORKDIR /app
|
||||
ARG NPMCI_TOKEN_NPM2
|
||||
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
|
||||
RUN npmci npm prepare
|
||||
RUN pnpm config set store-dir .pnpm-store
|
||||
RUN rm -rf node_modules && pnpm install
|
||||
RUN pnpm run build
|
||||
|
||||
# gitzone dockerfile_service
|
||||
## STAGE 2 // install production
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:npmci as node2
|
||||
WORKDIR /app
|
||||
COPY --from=node1 /app /app
|
||||
RUN rm -rf .pnpm-store
|
||||
ARG NPMCI_TOKEN_NPM2
|
||||
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
|
||||
RUN npmci npm prepare
|
||||
RUN pnpm config set store-dir .pnpm-store
|
||||
RUN rm -rf node_modules/ && pnpm install --prod
|
||||
|
||||
|
||||
## STAGE 3 // rebuild dependencies for alpine
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpinenpmci as node3
|
||||
WORKDIR /app
|
||||
COPY --from=node2 /app /app
|
||||
ARG NPMCI_TOKEN_NPM2
|
||||
ENV NPMCI_TOKEN_NPM2 $NPMCI_TOKEN_NPM2
|
||||
RUN npmci npm prepare
|
||||
RUN pnpm config set store-dir .pnpm-store
|
||||
RUN pnpm rebuild -r
|
||||
|
||||
## STAGE 4 // the final production image with all dependencies in place
|
||||
FROM registry.gitlab.com/hosttoday/ht-docker-node:alpine as node4
|
||||
WORKDIR /app
|
||||
COPY --from=node3 /app /app
|
||||
|
||||
### Healthchecks
|
||||
RUN pnpm install -g @servezone/healthy
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=30s --retries=3 CMD [ "healthy" ]
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["npm", "start"]
|
0
Dockerfile_cloudron_##version##
Normal file
0
Dockerfile_cloudron_##version##
Normal file
4
cli.child.ts
Normal file
4
cli.child.ts
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
process.env.CLI_CALL = 'true';
|
||||
import * as cliTool from './ts/index.js';
|
||||
cliTool.runCli();
|
4
cli.js
Normal file
4
cli.js
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env node
|
||||
process.env.CLI_CALL = 'true';
|
||||
const cliTool = await import('./dist_ts/index.js');
|
||||
cliTool.runCli();
|
5
cli.ts.js
Normal file
5
cli.ts.js
Normal file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
process.env.CLI_CALL = 'true';
|
||||
|
||||
import * as tsrun from '@git.zone/tsrun';
|
||||
tsrun.runPath('./cli.child.js', import.meta.url);
|
124
html/index.html
Normal file
124
html/index.html
Normal file
@ -0,0 +1,124 @@
|
||||
<!--gitzone default-->
|
||||
<!-- made by Lossless GmbH -->
|
||||
<!-- checkout https://maintainedby.lossless.com for awesome OpenSource projects -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!--Lets set some basic meta tags-->
|
||||
<meta
|
||||
name="viewport"
|
||||
content="user-scalable=0, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height"
|
||||
/>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<!--Lets make sure we recognize this as an PWA-->
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="icon" type="image/png" href="/assetbroker/manifest/favicon.png" />
|
||||
|
||||
<!--Lets load standard fonts-->
|
||||
<link rel="preconnect" href="https://assetbroker.lossless.one/" crossorigin>
|
||||
<link rel="stylesheet" href="https://assetbroker.lossless.one/fonts/fonts.css">
|
||||
|
||||
|
||||
<!--Lets avoid a rescaling flicker due to default body margins-->
|
||||
<style>
|
||||
html {
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
body {
|
||||
position: relative;
|
||||
background: #000;
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
projectVersion = '';
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<style>
|
||||
body {
|
||||
background: #303f9f;
|
||||
font-family: Inter, Roboto, sans-serif;
|
||||
color: #ffffff;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin-top: 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 600px;
|
||||
margin: auto;
|
||||
margin-top: 20px;
|
||||
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
background: #4357d9;
|
||||
}
|
||||
.contentHeader {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 25px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.content {
|
||||
padding: 20px;
|
||||
}
|
||||
.footer {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<div class="logo">
|
||||
<img src="https://assetbroker.lossless.one/brandfiles/lossless/svg-minimal-bright.svg" />
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="contentHeader">We need JavaScript to run properly!</div>
|
||||
<div class="content">
|
||||
This site is being built using lit-element (made by Google). This technology works with
|
||||
JavaScript. Subsequently this website does not work as intended by Lossless GmbH without
|
||||
JavaScript.
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<a href="https://lossless.gmbh">Legal Info</a> |
|
||||
<a href="https://lossless.gmbh/privacy">Privacy Policy</a>
|
||||
</div>
|
||||
</noscript>
|
||||
<script type="text/javascript" async defer>
|
||||
window.revenueEnabled = true;
|
||||
const runRevenueCheck = async () => {
|
||||
var e = document.createElement('div');
|
||||
e.id = '476kjuhzgtr764';
|
||||
e.style.display = 'none';
|
||||
document.body.appendChild(e);
|
||||
if (document.getElementById('476kjuhzgtr764')) {
|
||||
window.revenueEnabled = true;
|
||||
} else {
|
||||
window.revenueEnabled = false;
|
||||
}
|
||||
console.log(`revenue enabled: ${window.revenueEnabled}`);
|
||||
};
|
||||
|
||||
runRevenueCheck();
|
||||
</script>
|
||||
</body>
|
||||
<script defer type="module" src="/bundle.js"></script>
|
||||
</html>
|
40
npmextra.json
Normal file
40
npmextra.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public",
|
||||
"npmRegistryUrl": "verdaccio.lossless.one",
|
||||
"dockerRegistryRepoMap": {
|
||||
"registry.gitlab.com": "losslessone/services/servezone/cloudly"
|
||||
},
|
||||
"dockerBuildargEnvMap": {
|
||||
"NPMCI_TOKEN_NPM2": "NPMCI_TOKEN_NPM2"
|
||||
}
|
||||
},
|
||||
"gitzone": {
|
||||
"projectType": "service",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "servezone/private",
|
||||
"gitrepo": "cloudly",
|
||||
"description": "A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.",
|
||||
"npmPackagename": "@serve.zone/cloudly",
|
||||
"license": "UNLICENSED",
|
||||
"keywords": [
|
||||
"cloud",
|
||||
"Docker Swarmkit",
|
||||
"DigitalOcean",
|
||||
"Hetzner Cloud",
|
||||
"Cloudflare",
|
||||
"container management",
|
||||
"configuration management",
|
||||
"LetsEncrypt SSL",
|
||||
"cloud infrastructure automation",
|
||||
"node.js",
|
||||
"TypeScript"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
}
|
||||
}
|
102
package.json
Normal file
102
package.json
Normal file
@ -0,0 +1,102 @@
|
||||
{
|
||||
"name": "@serve.zone/cloudly",
|
||||
"version": "1.0.213",
|
||||
"private": false,
|
||||
"description": "A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"author": "Task Venture Capital GmbH",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"test": "(tstest test/)",
|
||||
"build": "tsbuild --web --allowimplicitany && tsbundle website --production",
|
||||
"start": "node cli.js",
|
||||
"startTs": "node cli.ts.js",
|
||||
"watch": "tswatch website",
|
||||
"localPublish": "gitzone commit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.65",
|
||||
"@git.zone/tsbundle": "^2.0.15",
|
||||
"@git.zone/tstest": "^1.0.73",
|
||||
"@git.zone/tswatch": "^2.0.23",
|
||||
"@push.rocks/tapbundle": "^5.0.4",
|
||||
"@types/node": "^20.11.25"
|
||||
},
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest": "3.0.19",
|
||||
"@api.global/typedserver": "^3.0.27",
|
||||
"@api.global/typedsocket": "^3.0.0",
|
||||
"@apiclient.xyz/cloudflare": "^6.0.1",
|
||||
"@apiclient.xyz/digitalocean": "^1.0.5",
|
||||
"@apiclient.xyz/hetznercloud": "^1.0.18",
|
||||
"@apiclient.xyz/slack": "^3.0.9",
|
||||
"@design.estate/dees-catalog": "^1.0.289",
|
||||
"@design.estate/dees-domtools": "^2.0.57",
|
||||
"@design.estate/dees-element": "^2.0.34",
|
||||
"@git.zone/tsrun": "^1.2.37",
|
||||
"@push.rocks/early": "^4.0.3",
|
||||
"@push.rocks/npmextra": "^5.0.10",
|
||||
"@push.rocks/projectinfo": "^5.0.1",
|
||||
"@push.rocks/qenv": "^6.0.5",
|
||||
"@push.rocks/smartacme": "^4.0.8",
|
||||
"@push.rocks/smartbucket": "^2.0.4",
|
||||
"@push.rocks/smartcli": "^4.0.6",
|
||||
"@push.rocks/smartdata": "^5.0.8",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartexit": "^1.0.20",
|
||||
"@push.rocks/smartfile": "^11.0.4",
|
||||
"@push.rocks/smartguard": "^2.0.1",
|
||||
"@push.rocks/smartjson": "^5.0.14",
|
||||
"@push.rocks/smartjwt": "^2.0.4",
|
||||
"@push.rocks/smartlog": "^3.0.1",
|
||||
"@push.rocks/smartlog-destination-clickhouse": "^1.0.11",
|
||||
"@push.rocks/smartpath": "^5.0.5",
|
||||
"@push.rocks/smartpromise": "^4.0.3",
|
||||
"@push.rocks/smartrequest": "^2.0.11",
|
||||
"@push.rocks/smartssh": "^2.0.1",
|
||||
"@push.rocks/smartstring": "^4.0.15",
|
||||
"@push.rocks/smartunique": "^3.0.8",
|
||||
"@push.rocks/taskbuffer": "^3.0.2",
|
||||
"@push.rocks/webjwt": "^1.0.9",
|
||||
"@serve.zone/interfaces": "^1.0.47",
|
||||
"@tsclass/tsclass": "^4.0.52"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://gitlab.com/servezone/private/cloudly.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/servezone/private/cloudly/issues"
|
||||
},
|
||||
"homepage": "https://gitlab.com/servezone/private/cloudly#readme",
|
||||
"keywords": [
|
||||
"cloud",
|
||||
"Docker Swarmkit",
|
||||
"DigitalOcean",
|
||||
"Hetzner Cloud",
|
||||
"Cloudflare",
|
||||
"container management",
|
||||
"configuration management",
|
||||
"LetsEncrypt SSL",
|
||||
"cloud infrastructure automation",
|
||||
"node.js",
|
||||
"TypeScript"
|
||||
]
|
||||
}
|
9227
pnpm-lock.yaml
generated
Normal file
9227
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
12
qenv.yml
Normal file
12
qenv.yml
Normal file
@ -0,0 +1,12 @@
|
||||
required:
|
||||
- CF_TOKEN
|
||||
- DIGITALOCEAN_TOKEN
|
||||
- GITLAB_USER
|
||||
- GITLAB_TOKEN
|
||||
- LETSENCRYPT_EMAIL
|
||||
- MONGODB_URL
|
||||
- MONGODB_DATABASE
|
||||
- MONGODB_PASSWORD
|
||||
- SERVEZONE_URL
|
||||
- SERVEZONE_PORT
|
||||
- SERVEZONE_ENVIRONMENT
|
0
readme.hints.md
Normal file
0
readme.hints.md
Normal file
104
readme.md
Normal file
104
readme.md
Normal file
@ -0,0 +1,104 @@
|
||||
# @serve.zone/cloudly
|
||||
configure the cloud
|
||||
|
||||
## Install
|
||||
To install `@serve.zone/cloudly`, run the following command in your terminal:
|
||||
```bash
|
||||
npm install @serve.zone/cloudly --save
|
||||
```
|
||||
This will install the package and add it to your project's `package.json` dependencies.
|
||||
|
||||
## Usage
|
||||
`@serve.zone/cloudly` is designed to help you manage and configure cloud environments. This package provides a comprehensive TypeScript and ESM-based interface for interacting with various cloud services, including Docker Swarmkit cluster management, and integration with cloud providers such as DigitalOcean, Hetzner Cloud, and Cloudflare.
|
||||
|
||||
### Getting Started
|
||||
Before diving into the specifics, ensure your environment is properly set up. This includes having Node.js installed (preferably the latest LTS version), and if you are working in a TypeScript project, ensure TypeScript is configured.
|
||||
|
||||
#### Initializing Cloudly
|
||||
First, import `Cloudly` class from the package and initialize it as shown below:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
|
||||
const myCloudlyInstance = new Cloudly();
|
||||
```
|
||||
|
||||
The `Cloudly` class is the entry point to using the library features. It prepares the environment for configuring the cloud services.
|
||||
|
||||
#### Configuration
|
||||
Configuration plays a pivotal role in how `@serve.zone/cloudly` operates. The library expects certain configurations to be provided, which can include credentials for cloud services, database connections, etc.
|
||||
|
||||
For example, to configure a connection to MongoDB, specify your MongoDB details as shown:
|
||||
|
||||
```typescript
|
||||
const myCloudlyConfig = {
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
// Additional configuration values...
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
```
|
||||
|
||||
#### Managing Docker Swarmkit Cluster
|
||||
Cloudly allows managing Docker Swarmkit clusters through an abstracted interface, simplifying operations such as deployment and scaling.
|
||||
|
||||
```typescript
|
||||
// Assuming myCloudlyInstance is already configured and initialized
|
||||
|
||||
// Start the cloud instance
|
||||
await myCloudlyInstance.start();
|
||||
|
||||
// Now you can perform various operations on your Docker Swarmkit cluster
|
||||
```
|
||||
|
||||
### Integration with Cloud Providers
|
||||
`@serve.zone/cloudly` integrates seamlessly with cloud providers like DigitalOcean, Hetzner Cloud, etc., by leveraging the power of APIs provided by these platforms.
|
||||
|
||||
#### Managing DigitalOcean Resources
|
||||
To manage DigitalOcean resources, you'll need to configure your DigitalOcean token and then use the provided interfaces to interact with the resources, such as creating droplets, managing volumes, etc.
|
||||
|
||||
```typescript
|
||||
// Set your DigitalOcean API token
|
||||
const digitalOceanToken = "your_digital_ocean_api_token";
|
||||
|
||||
// Now you can use myCloudlyInstance to manage DigitalOcean resources
|
||||
```
|
||||
|
||||
#### Using the Cloudflare Integration
|
||||
Similarly, for managing DNS records and SSL certificates with Cloudflare, set up your Cloudflare API token:
|
||||
|
||||
```typescript
|
||||
const cloudflareToken = "your_cloudflare_api_token";
|
||||
|
||||
// Use myCloudlyInstance to interact with Cloudflare, such as setting DNS records
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
`@serve.zone/cloudly` offers more than just cloud resource management. It integrates various modules for error logging, security, working with JSON data, and much more. Explore the comprehensive documentation and typings to leverage the full potential of the package.
|
||||
|
||||
### Conclusion
|
||||
With `@serve.zone/cloudly`, configuring the cloud becomes a less tedious task. By abstracting away the complexities and providing a unified interface to manage cloud resources, development efficiency is significantly improved. The examples provided above merely scratch the surface of what's possible. Dive into the detailed documentation to explore all features and capabilities.
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
39
scripts/deployclusterconfigs.ts
Normal file
39
scripts/deployclusterconfigs.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
delete process.env.CLI_CALL;
|
||||
|
||||
import * as cloudly from '../ts/index.js';
|
||||
|
||||
process.env.TESTING_CLOUDLY = 'true';
|
||||
|
||||
let testCloudly: cloudly.Cloudly;
|
||||
|
||||
tap.test('first test', async () => {
|
||||
const cloudlyConfig: cloudly.ICloudlyConfig = {
|
||||
cfToken: await testQenv.getEnvVarOnDemand('CF_TOKEN'),
|
||||
environment: 'integration',
|
||||
letsEncryptEmail: await testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
|
||||
publicUrl: await testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
|
||||
publicPort: await testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
|
||||
mongoDescriptor: {
|
||||
mongoDbName: await testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
|
||||
mongoDbPass: await testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
|
||||
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGODB_URL')
|
||||
},
|
||||
digitalOceanToken: await testQenv.getEnvVarOnDemand('DIGITALOCEAN_TOKEN')
|
||||
};
|
||||
testCloudly = new cloudly.Cloudly(cloudlyConfig);
|
||||
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
|
||||
});
|
||||
|
||||
tap.test('should init servezone', async () => {
|
||||
await testCloudly.start();
|
||||
});
|
||||
|
||||
tap.test('should end the service', async () => {
|
||||
await testCloudly.stop();
|
||||
});
|
||||
|
||||
tap.start();
|
39
scripts/informofnewcontainer.ts
Normal file
39
scripts/informofnewcontainer.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
delete process.env.CLI_CALL;
|
||||
|
||||
import * as cloudly from '../ts/index';
|
||||
|
||||
process.env.TESTING_CLOUDLY = 'true';
|
||||
|
||||
let testCloudly: cloudly.Cloudly;
|
||||
|
||||
tap.test('first test', async () => {
|
||||
const cloudlyConfig: cloudly.ICloudlyConfig = {
|
||||
cfToken: testQenv.getEnvVarOnDemand('CF_TOKEN'),
|
||||
environment: 'integration',
|
||||
letsEncryptEmail: testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
|
||||
publicUrl: testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
|
||||
publicPort: testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
|
||||
mongoDescriptor: {
|
||||
mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
|
||||
mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
|
||||
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL')
|
||||
},
|
||||
digitalOceanToken: testQenv.getEnvVarOnDemand('DIGITALOCEAN_TOKEN')
|
||||
};
|
||||
testCloudly = new cloudly.Cloudly(cloudlyConfig);
|
||||
expect(testCloudly).to.be.instanceof(cloudly.Cloudly);
|
||||
});
|
||||
|
||||
tap.test('should init servezone', async () => {
|
||||
await testCloudly.start();
|
||||
});
|
||||
|
||||
tap.test('should end the service', async () => {
|
||||
await testCloudly.stop();
|
||||
});
|
||||
|
||||
tap.start();
|
39
test/test.ts
Normal file
39
test/test.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
process.env.TESTING_CLOUDLY = 'true';
|
||||
|
||||
delete process.env.CLI_CALL;
|
||||
|
||||
import * as cloudly from '../ts/index.js';
|
||||
|
||||
let testCloudly: cloudly.Cloudly;
|
||||
tap.test('first test', async () => {
|
||||
const cloudlyConfig: cloudly.ICloudlyConfig = {
|
||||
cfToken: testQenv.getEnvVarOnDemand('CF_TOKEN'),
|
||||
environment: 'integration',
|
||||
letsEncryptEmail: testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
|
||||
publicUrl: testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
|
||||
publicPort: testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
|
||||
mongoDescriptor: {
|
||||
mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
|
||||
mongoDbUser: testQenv.getEnvVarOnDemand('MONGODB_USER'),
|
||||
mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
|
||||
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL'),
|
||||
},
|
||||
digitalOceanToken: testQenv.getEnvVarOnDemand('DIGITALOCEAN_TOKEN'),
|
||||
};
|
||||
testCloudly = new cloudly.Cloudly(cloudlyConfig);
|
||||
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
|
||||
});
|
||||
|
||||
tap.test('should init cloudly', async () => {
|
||||
await testCloudly.start();
|
||||
});
|
||||
|
||||
tap.test('should end the service', async (tools) => {
|
||||
await testCloudly.stop();
|
||||
tools.delayFor(1000).then(() => process.exit())
|
||||
});
|
||||
|
||||
tap.start();
|
1
test/test_latest.sh
Normal file
1
test/test_latest.sh
Normal file
@ -0,0 +1 @@
|
||||
echo 'hi'
|
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/cloudly',
|
||||
version: '1.0.214',
|
||||
description: 'A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.'
|
||||
}
|
120
ts/cloudly.classes.cloudly.ts
Normal file
120
ts/cloudly.classes.cloudly.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import { CloudlyConfig } from './cloudly.classes.config.js';
|
||||
|
||||
// interfaces
|
||||
import {} from '@tsclass/tsclass';
|
||||
|
||||
// Cloudly mods
|
||||
import { CloudlyInfo } from './cloudly.classes.cloudlyinfo.js';
|
||||
import { CloudlyServer } from './cloudly.classes.server.js';
|
||||
|
||||
// connectors
|
||||
import { CloudflareConnector } from './connector.cloudflare/connector.js';
|
||||
import { LetsencryptConnector } from './connector.letsencrypt/connector.js';
|
||||
import { MongodbConnector } from './connector.mongodb/connector.js';
|
||||
|
||||
// processes
|
||||
import { CloudlyCoreflowManager } from './manager.coreflow/coreflowmanager.js';
|
||||
import { ClusterManager } from './manager.cluster/clustermanager.js';
|
||||
import { CloudlyTaskmanager } from './manager.task/taskmanager.js';
|
||||
import { CloudlyVersionManager } from './manager.version/versionmanager.js';
|
||||
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js'
|
||||
import { CloudlyServerManager } from './manager.server/servermanager.js';
|
||||
import { ExternalApiManager } from './manager.status/statusmanager.js';
|
||||
import { ImageManager } from './manager.image/classes.imagemanager.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
|
||||
/**
|
||||
* Cloudly class can be used to instantiate a cloudly server.
|
||||
* It is implemented as class in order to make it easier ro write node services that are more adjusted to invidual services
|
||||
*
|
||||
* ```ts
|
||||
* const mycloudly = new Cloudly ({...})
|
||||
* ```
|
||||
*/
|
||||
export class Cloudly {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public config: CloudlyConfig;
|
||||
public logger: plugins.smartlog.Smartlog;
|
||||
public ready: Promise<any>;
|
||||
|
||||
// mods
|
||||
public cloudlyInfo: CloudlyInfo;
|
||||
public server: CloudlyServer;
|
||||
|
||||
// connectors
|
||||
public cloudflareConnector: CloudflareConnector;
|
||||
public letsencryptConnector: LetsencryptConnector;
|
||||
public mongodbConnector: MongodbConnector;
|
||||
|
||||
// managers
|
||||
public secretManager: CloudlySecretManager;
|
||||
public clusterManager: ClusterManager;
|
||||
public coreflowManager: CloudlyCoreflowManager;
|
||||
public externalApiManager: ExternalApiManager;
|
||||
public imageManager: ImageManager;
|
||||
public taskManager: CloudlyTaskmanager;
|
||||
public versionManager: CloudlyVersionManager;
|
||||
public serverManager: CloudlyServerManager;
|
||||
|
||||
private readyDeferred = new plugins.smartpromise.Deferred();
|
||||
|
||||
constructor() {
|
||||
this.cloudlyInfo = new CloudlyInfo(this);
|
||||
this.config = new CloudlyConfig(this);
|
||||
|
||||
this.logger = logger;
|
||||
this.server = new CloudlyServer(this);
|
||||
this.ready = this.readyDeferred.promise;
|
||||
|
||||
// connectors
|
||||
this.mongodbConnector = new MongodbConnector(this); // database needs to go first
|
||||
this.cloudflareConnector = new CloudflareConnector(this);
|
||||
this.letsencryptConnector = new LetsencryptConnector(this);
|
||||
|
||||
// processes
|
||||
this.clusterManager = new ClusterManager(this);
|
||||
this.coreflowManager = new CloudlyCoreflowManager(this);
|
||||
this.externalApiManager = new ExternalApiManager(this);
|
||||
this.imageManager = new ImageManager(this);
|
||||
this.taskManager = new CloudlyTaskmanager(this);
|
||||
this.versionManager = new CloudlyVersionManager(this);
|
||||
this.secretManager = new CloudlySecretManager(this);
|
||||
this.serverManager = new CloudlyServerManager(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* starts the cloudly instance and
|
||||
* @param configArg
|
||||
*/
|
||||
public async start() {
|
||||
// config
|
||||
await this.config.init();
|
||||
|
||||
// manageers
|
||||
await this.secretManager.start();
|
||||
await this.serverManager.start();
|
||||
|
||||
// connectors
|
||||
await this.mongodbConnector.init();
|
||||
await this.cloudflareConnector.init();
|
||||
await this.letsencryptConnector.init();
|
||||
await this.clusterManager.init();
|
||||
await this.server.start();
|
||||
this.readyDeferred.resolve();
|
||||
|
||||
// start the managers
|
||||
this.imageManager.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* stop the reception instance
|
||||
*/
|
||||
public async stop() {
|
||||
await this.server.stop();
|
||||
await this.letsencryptConnector.stop();
|
||||
await this.mongodbConnector.stop();
|
||||
await this.secretManager.stop();
|
||||
}
|
||||
}
|
13
ts/cloudly.classes.cloudlyinfo.ts
Normal file
13
ts/cloudly.classes.cloudlyinfo.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { Cloudly } from './index.js';
|
||||
|
||||
export class CloudlyInfo {
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public projectInfo = new plugins.projectinfo.ProjectInfo(paths.packageDir);
|
||||
}
|
79
ts/cloudly.classes.config.ts
Normal file
79
ts/cloudly.classes.config.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
import type { Cloudly } from './cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* the main cloudly config
|
||||
*/
|
||||
export class CloudlyConfig {
|
||||
public cloudlyRef: Cloudly;
|
||||
public appData: plugins.npmextra.AppData<plugins.servezoneInterfaces.data.ICloudlyConfig>;
|
||||
public data: plugins.servezoneInterfaces.data.ICloudlyConfig
|
||||
|
||||
// authentication and settings
|
||||
public smartjwtInstance: plugins.smartjwt.SmartJwt;
|
||||
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.appData = await plugins.npmextra.AppData.createAndInit<plugins.servezoneInterfaces.data.ICloudlyConfig>({
|
||||
envMapping: {
|
||||
cfToken: 'CF_TOKEN',
|
||||
environment: 'SERVEZONE_ENVIRONMENT' as 'production' | 'integration',
|
||||
letsEncryptEmail: 'hard:domains@lossless.org',
|
||||
hetznerToken: 'HETZNER_API_TOKEN',
|
||||
letsEncryptPrivateKey: null,
|
||||
publicUrl: 'SERVEZONE_URL',
|
||||
publicPort: 'SERVEZONE_PORT',
|
||||
mongoDescriptor: {
|
||||
mongoDbUrl: 'MONGODB_URL',
|
||||
mongoDbName: 'MONGODB_DATABASE',
|
||||
mongoDbUser: 'MONGODB_USER',
|
||||
mongoDbPass: 'MONGODB_PASSWORD',
|
||||
},
|
||||
s3Descriptor: {
|
||||
endpoint: 'S3_ENDPOINT',
|
||||
accessKey: 'S3_ACCESSKEY',
|
||||
accessSecret: 'S3_SECRETKEY',
|
||||
port: 'S3_PORT', // Note: This will remain as a string. Ensure to parse it to an integer where it's used.
|
||||
useSsl: true,
|
||||
},
|
||||
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
|
||||
},
|
||||
requiredKeys: [
|
||||
'cfToken',
|
||||
'hetznerToken',
|
||||
'letsEncryptEmail',
|
||||
'publicUrl',
|
||||
'publicPort',
|
||||
'sslMode',
|
||||
'environment',
|
||||
'mongoDescriptor',
|
||||
],
|
||||
});
|
||||
|
||||
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
||||
const kvStore = await this.appData.getKvStore();
|
||||
|
||||
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys');
|
||||
|
||||
if (!existingJwtKeys) {
|
||||
await this.smartjwtInstance.createNewKeyPair();
|
||||
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
||||
await kvStore.writeKey('jwtKeys', newJwtKeys);
|
||||
} else {
|
||||
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
||||
}
|
||||
|
||||
this.data = await kvStore.readAll();
|
||||
const missingKeys = await this.appData.logMissingKeys();
|
||||
if (missingKeys.length > 0) {
|
||||
logger.log('error', `missing keys: ${missingKeys.join(', ')}`);
|
||||
throw new Error('missing keys');
|
||||
}
|
||||
}
|
||||
}
|
96
ts/cloudly.classes.server.ts
Normal file
96
ts/cloudly.classes.server.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { Cloudly } from './cloudly.classes.cloudly.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
|
||||
/**
|
||||
* handles incoming requests from CI to deploy new versions of apps
|
||||
*/
|
||||
export class CloudlyServer {
|
||||
/**
|
||||
* a reference to the cloudly instance
|
||||
*/
|
||||
private cloudlyRef: Cloudly;
|
||||
|
||||
/**
|
||||
* the smartexpress server handling the actual requests
|
||||
*/
|
||||
public typedServer: plugins.typedserver.TypedServer;
|
||||
public typedsocketServer: plugins.typedsocket.TypedSocket;
|
||||
|
||||
/**
|
||||
* typedrouter
|
||||
* @param cloudlyArg
|
||||
*/
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
|
||||
// =========
|
||||
// LIFECYCLE
|
||||
// =========
|
||||
|
||||
/**
|
||||
* init the reception instance
|
||||
*/
|
||||
public async start() {
|
||||
logger.log('info', `cloudly domain is ${this.cloudlyRef.config.data.publicUrl}`)
|
||||
let sslCert: plugins.smartacme.Cert;
|
||||
|
||||
if (this.cloudlyRef.config.data.sslMode === 'letsencrypt') {
|
||||
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`)
|
||||
logger.log('info', `This might take 10 minutes...`)
|
||||
sslCert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||
this.cloudlyRef.config.data.publicUrl
|
||||
);
|
||||
logger.log('success', `Successfully obtained certificate for cloudly domain ${this.cloudlyRef.config.data.publicUrl}`)
|
||||
} else if (this.cloudlyRef.config.data.sslMode === 'external') {
|
||||
logger.log('info', `Using external certificate for ssl mode, meaning cloudly is not in charge of ssl termination.`)
|
||||
}
|
||||
|
||||
interface IRequestGuardData {
|
||||
req: plugins.typedserver.Request;
|
||||
res: plugins.typedserver.Response;
|
||||
}
|
||||
|
||||
// guards
|
||||
const guardIp = new plugins.smartguard.Guard<IRequestGuardData>(async (dataArg) => {
|
||||
if (true) {
|
||||
return true;
|
||||
} else {
|
||||
dataArg.res.status(500);
|
||||
dataArg.res.send(`Not allowed to perform this operation!`);
|
||||
dataArg.res.end();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// server
|
||||
this.typedServer = new plugins.typedserver.TypedServer({
|
||||
cors: true,
|
||||
forceSsl: false,
|
||||
port: this.cloudlyRef.config.data.publicPort,
|
||||
...(sslCert ? {
|
||||
privateKey: sslCert.privateKey,
|
||||
publicKey: sslCert.publicKey,
|
||||
} : {}),
|
||||
injectReload: true,
|
||||
serveDir: paths.distServeDir,
|
||||
watch: true,
|
||||
enableCompression: true,
|
||||
preferredCompressionMethod: 'gzip',
|
||||
});
|
||||
this.typedServer.typedrouter.addTypedRouter(this.typedrouter);
|
||||
await this.typedServer.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* stop the reception instance
|
||||
*/
|
||||
public async stop() {
|
||||
await this.typedServer.stop();
|
||||
}
|
||||
}
|
16
ts/cloudly.logging.ts
Normal file
16
ts/cloudly.logging.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
|
||||
export const logger = new plugins.smartlog.Smartlog({
|
||||
logContext: {
|
||||
company: null,
|
||||
environment: null,
|
||||
runtime: null,
|
||||
zone: null,
|
||||
companyunit: null,
|
||||
containerName: null,
|
||||
}
|
||||
});
|
||||
logger.enableConsole({
|
||||
captureAll: false
|
||||
});
|
5
ts/cloudly.paths.ts
Normal file
5
ts/cloudly.paths.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
|
||||
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
|
||||
export const distServeDir = plugins.path.join(packageDir, './dist_serve');
|
77
ts/cloudly.plugins.ts
Normal file
77
ts/cloudly.plugins.ts
Normal file
@ -0,0 +1,77 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
|
||||
export { path };
|
||||
|
||||
// @apiglobal scope
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
import * as typedsocket from '@api.global/typedsocket';
|
||||
|
||||
export { typedrequest, typedsocket };
|
||||
|
||||
// @mojoio scope
|
||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||
import * as digitalocean from '@apiclient.xyz/digitalocean';
|
||||
import * as hetznercloud from '@apiclient.xyz/hetznercloud';
|
||||
import * as slack from '@apiclient.xyz/slack';
|
||||
|
||||
export { cloudflare, digitalocean, hetznercloud, slack };
|
||||
|
||||
// @tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export { tsclass };
|
||||
|
||||
// @push.rocks scope
|
||||
import * as npmextra from '@push.rocks/npmextra';
|
||||
import * as projectinfo from '@push.rocks/projectinfo';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import * as smartacme from '@push.rocks/smartacme';
|
||||
import * as smartbucket from '@push.rocks/smartbucket';
|
||||
import * as smartcli from '@push.rocks/smartcli';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
import * as smartexit from '@push.rocks/smartexit';
|
||||
import * as typedserver from '@api.global/typedserver';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartguard from '@push.rocks/smartguard';
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
import * as smartjwt from '@push.rocks/smartjwt';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
import * as smartssh from '@push.rocks/smartssh';
|
||||
import * as smartstring from '@push.rocks/smartstring';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||
|
||||
export {
|
||||
npmextra,
|
||||
projectinfo,
|
||||
qenv,
|
||||
smartacme,
|
||||
smartbucket,
|
||||
smartcli,
|
||||
smartdata,
|
||||
smartexit,
|
||||
typedserver,
|
||||
smartdelay,
|
||||
smartfile,
|
||||
smartguard,
|
||||
smartjson,
|
||||
smartjwt,
|
||||
smartlog,
|
||||
smartpath,
|
||||
smartpromise,
|
||||
smartrequest,
|
||||
smartssh,
|
||||
smartstring,
|
||||
smartunique,
|
||||
taskbuffer,
|
||||
};
|
||||
|
||||
// @servezone scope
|
||||
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
||||
|
||||
export { servezoneInterfaces };
|
19
ts/connector.cloudflare/connector.ts
Normal file
19
ts/connector.cloudflare/connector.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* the portion of Cloudflare responsible
|
||||
*/
|
||||
export class CloudflareConnector {
|
||||
private cloudlyRef: Cloudly;
|
||||
public cloudflare: plugins.cloudflare.CloudflareAccount;
|
||||
|
||||
constructor(cloudlyArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyArg;
|
||||
}
|
||||
|
||||
// init the instance
|
||||
public async init() {
|
||||
this.cloudflare = new plugins.cloudflare.CloudflareAccount(this.cloudlyRef.config.data.cfToken);
|
||||
}
|
||||
}
|
46
ts/connector.letsencrypt/connector.ts
Normal file
46
ts/connector.letsencrypt/connector.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export class LetsencryptConnector {
|
||||
private cloudlyRef: Cloudly;
|
||||
private smartacme: plugins.smartacme.SmartAcme;
|
||||
|
||||
constructor(cloudlyArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyArg;
|
||||
}
|
||||
|
||||
public async getCertificateForDomain(domainName: string) {
|
||||
const cert = await this.smartacme.getCertificateForDomain(domainName);
|
||||
return cert;
|
||||
}
|
||||
|
||||
/**
|
||||
* inits letsencrypt
|
||||
*/
|
||||
public async init() {
|
||||
this.smartacme = new plugins.smartacme.SmartAcme({
|
||||
accountEmail: this.cloudlyRef.config.data.letsEncryptEmail,
|
||||
accountPrivateKey: this.cloudlyRef.config.data.letsEncryptPrivateKey,
|
||||
environment: this.cloudlyRef.config.data.environment,
|
||||
setChallenge: async (dnsChallenge) => {
|
||||
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeSetDnsChallenge(
|
||||
dnsChallenge
|
||||
);
|
||||
},
|
||||
removeChallenge: async (dnsChallenge) => {
|
||||
await this.cloudlyRef.cloudflareConnector.cloudflare.convenience.acmeRemoveDnsChallenge(
|
||||
dnsChallenge
|
||||
);
|
||||
},
|
||||
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
|
||||
});
|
||||
await this.smartacme.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* stops the instance
|
||||
*/
|
||||
public async stop() {
|
||||
await this.smartacme.stop();
|
||||
}
|
||||
}
|
21
ts/connector.mongodb/connector.ts
Normal file
21
ts/connector.mongodb/connector.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export class MongodbConnector {
|
||||
// INSTANCE
|
||||
private cloudlyRef: Cloudly;
|
||||
public smartdataDb: plugins.smartdata.SmartdataDb;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.smartdataDb = new plugins.smartdata.SmartdataDb(this.cloudlyRef.config.data.mongoDescriptor);
|
||||
await this.smartdataDb.init();
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
await this.smartdataDb.close();
|
||||
}
|
||||
}
|
0
ts/demo/demo.data.clusters.ts
Normal file
0
ts/demo/demo.data.clusters.ts
Normal file
83
ts/demo/demo.data.secrets.ts
Normal file
83
ts/demo/demo.data.secrets.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
// Create an array to hold 10 ISecretGroup objects
|
||||
const demoSecretGroups: plugins.servezoneInterfaces.data.ISecretGroup[] = [];
|
||||
|
||||
// Generate 10 ISecretGroup objects
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
const secretGroup: plugins.servezoneInterfaces.data.ISecretGroup = {
|
||||
id: `${plugins.smartunique.shortId(8)}`,
|
||||
data: {
|
||||
name: `Demo Secret Group ${i}`,
|
||||
description: `This is a demo secret group for testing purposes ${i}`,
|
||||
key: `CI_RUNNER_TOKEN_${i}`,
|
||||
priority: i,
|
||||
tags: [
|
||||
{
|
||||
key: 'project',
|
||||
value: `my_project_${i}`,
|
||||
},
|
||||
{
|
||||
key: 'environment',
|
||||
value: i % 2 === 0 ? 'staging' : 'production',
|
||||
},
|
||||
],
|
||||
environments: {
|
||||
production: {
|
||||
value: `prod_secret_value_${i}`,
|
||||
lastUpdated: 1630522000 + i,
|
||||
history: [
|
||||
{
|
||||
timestamp: String(1630521000 + i),
|
||||
value: `old_prod_value_${i}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
staging: {
|
||||
value: `stag_secret_value_${i}`,
|
||||
updateToken: `updateToken${i}`,
|
||||
lastUpdated: 1630522500 + i,
|
||||
history: [
|
||||
{
|
||||
timestamp: String(1630521500 + i),
|
||||
value: `old_stag_value_${i}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Push each ISecretGroup object into the array
|
||||
demoSecretGroups.push(secretGroup);
|
||||
}
|
||||
|
||||
// Create an array to hold 10 IConfigBundle objects
|
||||
const demoConfigBundles: plugins.servezoneInterfaces.data.ISecretBundle[] = [];
|
||||
|
||||
// Generate 10 IConfigBundle objects that match demoSecretGroups
|
||||
for (let i = 0; i < demoSecretGroups.length; i++) {
|
||||
const secretGroup = demoSecretGroups[i];
|
||||
|
||||
const configBundle: plugins.servezoneInterfaces.data.ISecretBundle = {
|
||||
id: `configBundleId${i + 1}`,
|
||||
data: {
|
||||
name: `Demo Config Bundle ${i + 1}`,
|
||||
description: 'Demo Purpose',
|
||||
includedSecretGroupIds: [secretGroup.id],
|
||||
includedTags: secretGroup.data.tags,
|
||||
authorizations: Object.keys(secretGroup.data.environments).map((env) => {
|
||||
return {
|
||||
secretAccessKey: `mockSecretAccessKeyFor${env}`,
|
||||
environment: env,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// Push each IConfigBundle object into the array
|
||||
demoConfigBundles.push(configBundle);
|
||||
}
|
||||
|
||||
// Exporting the array of demo IConfigBundle objects
|
||||
export { demoSecretGroups, demoConfigBundles };
|
38
ts/demo/index.ts
Normal file
38
ts/demo/index.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export const installDemoData = async (cloudlyRef: Cloudly) => {
|
||||
|
||||
// ================================================================================
|
||||
// SECRETS
|
||||
|
||||
const demoDataSecrets = await import('./demo.data.secrets.js');
|
||||
|
||||
const secretGroups = await cloudlyRef.secretManager.CSecretGroup.getInstances({});
|
||||
for (const secretGroup of secretGroups) {
|
||||
await secretGroup.delete();
|
||||
}
|
||||
|
||||
const secretBundles = await cloudlyRef.secretManager.CSecretBundle.getInstances({});
|
||||
for (const secretBundle of secretBundles) {
|
||||
await secretBundle.delete();
|
||||
}
|
||||
|
||||
for (const secretData of demoDataSecrets.demoSecretGroups) {
|
||||
const secretGroup = new cloudlyRef.secretManager.CSecretGroup();
|
||||
Object.assign(secretGroup, secretData);
|
||||
await secretGroup.save();
|
||||
}
|
||||
for (const secretBundleData of demoDataSecrets.demoConfigBundles) {
|
||||
const secretBundle = new cloudlyRef.secretManager.CSecretBundle();
|
||||
Object.assign(secretBundle, secretBundleData);
|
||||
await secretBundle.save();
|
||||
}
|
||||
|
||||
// ================================================================================
|
||||
// CLUSTERS
|
||||
const clusters = await cloudlyRef.clusterManager.CCluster.getInstances({});
|
||||
for (const cluster of clusters) {
|
||||
await cluster.delete();
|
||||
}
|
||||
|
||||
}
|
28
ts/index.ts
Normal file
28
ts/index.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import * as early from '@push.rocks/early';
|
||||
early.start('cloudly');
|
||||
import * as plugins from './cloudly.plugins.js';
|
||||
import * as paths from './cloudly.paths.js';
|
||||
import { Cloudly } from './cloudly.classes.cloudly.js';
|
||||
import { logger } from './cloudly.logging.js';
|
||||
const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true);
|
||||
early.stop();
|
||||
|
||||
|
||||
/**
|
||||
* starts the cloudly instance
|
||||
*/
|
||||
const runCli = async () => {
|
||||
logger.log('info', process.env.SERVEZONE_ENVIRONMENT);
|
||||
const cloudlyInstance = new Cloudly();
|
||||
|
||||
logger.log(
|
||||
'info',
|
||||
`running in environment ${await cloudlyQenv.getEnvVarOnDemand('SERVEZONE_ENVIRONMENT')}`
|
||||
);
|
||||
|
||||
await cloudlyInstance.start();
|
||||
const demoMod = await import('./demo/index.js');
|
||||
demoMod.installDemoData(cloudlyInstance);
|
||||
};
|
||||
|
||||
export { runCli, Cloudly };
|
31
ts/manager.cluster/cluster.ts
Normal file
31
ts/manager.cluster/cluster.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
/*
|
||||
* cluster defines a swarmkit cluster
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class Cluster extends plugins.smartdata.SmartDataDbDoc<Cluster, plugins.servezoneInterfaces.data.ICluster> {
|
||||
// STATIC
|
||||
public static async fromConfigObject(
|
||||
configObjectArg: plugins.servezoneInterfaces.data.ICluster
|
||||
) {
|
||||
const newCluster = new Cluster();
|
||||
Object.assign(newCluster, configObjectArg);
|
||||
return newCluster;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.ICluster['data'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getServices(): Promise<plugins.servezoneInterfaces.data.IService[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
131
ts/manager.cluster/clustermanager.ts
Normal file
131
ts/manager.cluster/clustermanager.ts
Normal file
@ -0,0 +1,131 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import * as paths from '../cloudly.paths.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
|
||||
import { Cluster } from './cluster.js';
|
||||
|
||||
export class ClusterManager {
|
||||
public ready = plugins.smartpromise.defer();
|
||||
|
||||
public cloudlyRef: Cloudly;
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public CCluster = plugins.smartdata.setDefaultManagerForDoc(this, Cluster);
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>(
|
||||
new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => {
|
||||
const cluster = await this.storeCluster({
|
||||
id: plugins.smartunique.uniSimple('cluster'),
|
||||
data: {
|
||||
name: dataArg.clusterName,
|
||||
jumpCode: plugins.smartunique.uniSimple('cluster'),
|
||||
jumpCodeUsedAt: null,
|
||||
secretKey: plugins.smartunique.shortId(16),
|
||||
acmeInfo: null,
|
||||
cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`,
|
||||
servers: [],
|
||||
sshKeys: [],
|
||||
},
|
||||
});
|
||||
console.log(await cluster.createSavableObject());
|
||||
this.cloudlyRef.serverManager.ensureServerInfrastructure();
|
||||
return {
|
||||
clusterConfig: await cluster.createSavableObject(),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_GetAllClusters>(
|
||||
new plugins.typedrequest.TypedHandler('getAllClusters', async (dataArg) => {
|
||||
// TODO: do authentication here
|
||||
const clusters = await this.getAllClusters();
|
||||
return {
|
||||
clusters: await Promise.all(
|
||||
clusters.map((clusterArg) => clusterArg.createSavableObject())
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async init() {
|
||||
// lets read the config folder
|
||||
logger.log('info', 'config manager is now initializing');
|
||||
this.ready.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a cluster config by Name
|
||||
*/
|
||||
public async getClusterConfigBy_ServerIP() {
|
||||
await this.ready.promise;
|
||||
// TODO: implement getclusterConfigByServerIp
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_JumpCode(jumpCodeArg: string) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
jumpCode: jumpCodeArg,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
name: clusterIdentifier.clusterName,
|
||||
secretKey: clusterIdentifier.secretKey,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* get config by id
|
||||
*/
|
||||
public async getConfigBy_ConfigID(configId: string) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
id: configId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* gets all cluster configs
|
||||
*/
|
||||
public async getAllClusters() {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstances({});
|
||||
}
|
||||
|
||||
/**
|
||||
* allows storage of a config
|
||||
* @param configName
|
||||
* @param configObjectArg
|
||||
*/
|
||||
public async storeCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
|
||||
let clusterInstance = await Cluster.getInstance({ id: configObjectArg.id });
|
||||
if (!clusterInstance) {
|
||||
clusterInstance = await Cluster.fromConfigObject(configObjectArg);
|
||||
} else {
|
||||
Object.assign(clusterInstance, configObjectArg);
|
||||
}
|
||||
await clusterInstance.save();
|
||||
return clusterInstance;
|
||||
}
|
||||
}
|
73
ts/manager.coreflow/coreflowmanager.ts
Normal file
73
ts/manager.coreflow/coreflowmanager.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* in charge of talking to coreflow services on clusters
|
||||
* coreflow runs on a server when ServerManager is done.
|
||||
*/
|
||||
export class CloudlyCoreflowManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
|
||||
new plugins.typedrequest.TypedHandler('getIdentityByJumpCode', async (requestData) => {
|
||||
const clusterConfig =
|
||||
await this.cloudlyRef.clusterManager.getClusterConfigBy_JumpCode(
|
||||
requestData.jumpCode
|
||||
);
|
||||
|
||||
if (!clusterConfig) {
|
||||
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid.');
|
||||
}
|
||||
|
||||
return {
|
||||
clusterIdentifier: {
|
||||
clusterName: clusterConfig.data.name,
|
||||
secretKey: clusterConfig.data.secretKey,
|
||||
},
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// lets enable the getting of cluster configs
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
|
||||
'getClusterConfig',
|
||||
async (dataArg) => {
|
||||
const clusterIdentifier = dataArg.clusterIdentifier;
|
||||
console.log('trying to get clusterConfigSet');
|
||||
console.log(dataArg);
|
||||
const clusterConfigSet =
|
||||
await this.cloudlyRef.clusterManager.getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier
|
||||
);
|
||||
console.log('got cluster config and sending it back to coreflow');
|
||||
return {
|
||||
configData: await clusterConfigSet.createSavableObject()
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// lets enable getting of certificates
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
|
||||
'getSslCertificate',
|
||||
async (dataArg) => {
|
||||
console.log(`got request for certificate ${dataArg.requiredCertName}`);
|
||||
const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||
dataArg.requiredCertName
|
||||
);
|
||||
console.log(`got certificate ready for reponse ${dataArg.requiredCertName}`);
|
||||
return {
|
||||
certificate: await cert.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
21
ts/manager.image/classes.image.ts
Normal file
21
ts/manager.image/classes.image.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import type { ImageManager } from './classes.imagemanager.js';
|
||||
|
||||
@plugins.smartdata.Manager()
|
||||
export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.servezoneInterfaces.data.IImage, ImageManager> {
|
||||
public static async create(imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
|
||||
const image = new Image();
|
||||
image.id = plugins.smartunique.uni('image');
|
||||
Object.assign(image.data, imageDataArg);
|
||||
await image.save();
|
||||
return image;
|
||||
}
|
||||
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IImage['data'];
|
||||
|
||||
public async getVersions() {}
|
||||
}
|
82
ts/manager.image/classes.imagemanager.ts
Normal file
82
ts/manager.image/classes.imagemanager.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
import { Image } from './classes.image.js';
|
||||
|
||||
export class ImageManager {
|
||||
cloudlyRef: Cloudly;
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
|
||||
|
||||
smartbucketInstance: plugins.smartbucket.SmartBucket;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||
'getAllImages',
|
||||
async (requestArg) => {
|
||||
const images = await this.CImage.getInstances({});
|
||||
return {
|
||||
images: await Promise.all(
|
||||
images.map((image) => {
|
||||
return image.createSavableObject();
|
||||
})
|
||||
),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||
'createImage',
|
||||
async (reqArg) => {
|
||||
const image = await this.CImage.create({
|
||||
name: reqArg.name,
|
||||
});
|
||||
return {
|
||||
image: await image.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DownloadImage>(
|
||||
'pullImage',
|
||||
async (reqArg) => {
|
||||
const image = await this.CImage.getInstance({
|
||||
data: {
|
||||
name: reqArg.name,
|
||||
}
|
||||
});
|
||||
const imageVersion =
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
const s3Descriptor: plugins.tsclass.storage.IS3Descriptor =
|
||||
await this.cloudlyRef.config.appData.waitForAndGetKey('s3Descriptor');
|
||||
console.log(this.cloudlyRef.config.data.s3Descriptor);
|
||||
this.smartbucketInstance = new plugins.smartbucket.SmartBucket(
|
||||
this.cloudlyRef.config.data.s3Descriptor
|
||||
);
|
||||
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
|
||||
await bucket.fastStore('test/test.txt', 'hello');
|
||||
}
|
||||
|
||||
public async createImage(nameArg: string) {
|
||||
const newImage = await this.CImage.create({
|
||||
name: nameArg,
|
||||
});
|
||||
}
|
||||
}
|
15
ts/manager.log/logmanager.ts
Normal file
15
ts/manager.log/logmanager.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* takes care of receiving and providing logs
|
||||
*/
|
||||
export class LogManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
}
|
||||
}
|
62
ts/manager.secret/classes.secretbundle.ts
Normal file
62
ts/manager.secret/classes.secretbundle.ts
Normal file
@ -0,0 +1,62 @@
|
||||
// a secret bundle is a set of secrets ready to be used in a project.
|
||||
// it bundles secretgroups
|
||||
import { SecretGroup } from './classes.secretgroup.js';
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
@plugins.smartdata.Manager()
|
||||
export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
|
||||
SecretBundle,
|
||||
plugins.servezoneInterfaces.data.ISecretBundle
|
||||
> {
|
||||
// STATIC
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.ISecretBundle['data'];
|
||||
|
||||
public async getSecretGroups() {
|
||||
const secretGroups: SecretGroup[] = [];
|
||||
for (const secretGroupId of this.data.includedSecretGroupIds) {
|
||||
secretGroups.push(
|
||||
await SecretGroup.getInstance({
|
||||
id: secretGroupId,
|
||||
})
|
||||
);
|
||||
}
|
||||
return secretGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* searches the secretGroups for environments and returns them
|
||||
*/
|
||||
public async getEnvironments() {
|
||||
const environments = new Set();
|
||||
const secretGroups = await this.getSecretGroups();
|
||||
for (const secretGroup of secretGroups) {
|
||||
environments.add(secretGroup.data.environments);
|
||||
}
|
||||
return Array.from(environments);
|
||||
}
|
||||
|
||||
public async getAuthorizationFromAuthKey(authKeyArg: string) {
|
||||
const authorization = this.data.authorizations.find((authArg) => {
|
||||
return authArg.secretAccessKey === authKeyArg;
|
||||
});
|
||||
return authorization;
|
||||
}
|
||||
|
||||
public async getKeyValueObjectForEnvironment(environmentArg: string) {
|
||||
const secretGroups = await this.getSecretGroups();
|
||||
const returnObject = {};
|
||||
for (const secretGroup of secretGroups) {
|
||||
if (!secretGroup.data.environments[environmentArg]) {
|
||||
continue;
|
||||
}
|
||||
returnObject[secretGroup.data.key] = secretGroup.data.environments[environmentArg].value;
|
||||
}
|
||||
return returnObject;
|
||||
}
|
||||
}
|
21
ts/manager.secret/classes.secretgroup.ts
Normal file
21
ts/manager.secret/classes.secretgroup.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* a secretgroup is a set of secrets for different environments.
|
||||
*/
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
@plugins.smartdata.Manager()
|
||||
export class SecretGroup extends plugins.smartdata.SmartDataDbDoc<
|
||||
SecretGroup,
|
||||
plugins.servezoneInterfaces.data.ISecretGroup
|
||||
> {
|
||||
// INSTANCE
|
||||
|
||||
/**
|
||||
* the insatnce id. This should be a random id, except for default
|
||||
*/
|
||||
@plugins.smartdata.unI()
|
||||
id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
data: plugins.servezoneInterfaces.data.ISecretGroup['data'];
|
||||
}
|
156
ts/manager.secret/classes.secretmanager.ts
Normal file
156
ts/manager.secret/classes.secretmanager.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import * as paths from '../cloudly.paths.js';
|
||||
import { SecretBundle } from './classes.secretbundle.js';
|
||||
import { SecretGroup } from './classes.secretgroup.js';
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
/**
|
||||
* The `ConfigVault` class provides methods for reading and writing configuration data to a file.
|
||||
* It uses the `TypedServer` and `TypedRouter` classes from the `configvault.plugins.js` module to handle HTTP requests and route them to the appropriate handlers.
|
||||
*
|
||||
* @class
|
||||
*/
|
||||
export class CloudlySecretManager {
|
||||
// attached classes
|
||||
public CSecretBundle = plugins.smartdata.setDefaultManagerForDoc(this, SecretBundle);
|
||||
public CSecretGroup = plugins.smartdata.setDefaultManagerForDoc(this, SecretGroup);
|
||||
|
||||
// INSTANCE
|
||||
public cloudlyRef: Cloudly;
|
||||
public projectinfo = new plugins.projectinfo.ProjectinfoNpm(paths.packageDir);
|
||||
public serviceQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir);
|
||||
public typedrouter: plugins.typedrequest.TypedRouter;
|
||||
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets set up a typedrouter
|
||||
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
||||
'adminLoginWithUsernameAndPassword',
|
||||
async (dataArg) => {
|
||||
let jwt: string;
|
||||
// console.log(dataArg);
|
||||
if (dataArg.username !== 'admin' || dataArg.password !== 'password') {
|
||||
logger.log('warn', 'login failed');
|
||||
} else {
|
||||
jwt = await this.cloudlyRef.config.smartjwtInstance.createJWT({
|
||||
status: 'loggedIn',
|
||||
});
|
||||
logger.log('success', 'login successful');
|
||||
}
|
||||
return {
|
||||
jwt,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
||||
'adminGetConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
dataArg.jwt
|
||||
const secretBundles = await SecretBundle.getInstances({});
|
||||
const secretGroups = await SecretGroup.getInstances({});
|
||||
return {
|
||||
secretBundles: [
|
||||
...(await Promise.all(
|
||||
secretBundles.map((configBundle) => configBundle.createSavableObject())
|
||||
)),
|
||||
],
|
||||
secretGroups: [
|
||||
...(await Promise.all(
|
||||
secretGroups.map((secretGroup) => secretGroup.createSavableObject())
|
||||
)),
|
||||
],
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'adminCreateConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
for (const secretGroupObject of dataArg.secretGroups) {
|
||||
const secretGroup = new SecretGroup();
|
||||
secretGroup.id = plugins.smartunique.shortId(8);
|
||||
secretGroup.data = secretGroupObject.data;
|
||||
await secretGroup.save();
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
||||
'adminDeleteConfigBundlesAndSecretGroups',
|
||||
async (dataArg) => {
|
||||
for (const secretGroupId of dataArg.secretGroupIds) {
|
||||
const secretGroup = await SecretGroup.getInstance({
|
||||
id: secretGroupId,
|
||||
});
|
||||
await secretGroup.delete();
|
||||
}
|
||||
for (const secretBundleId of dataArg.secretBundleIds) {
|
||||
const configBundle = await SecretBundle.getInstance({
|
||||
id: secretBundleId,
|
||||
});
|
||||
await configBundle.delete();
|
||||
console.log(`deleted configbundle ${secretBundleId}`);
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// lets add typedrouter routes for accessing the configvailt from apps
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_GetEnvBundle>(
|
||||
'getEnvBundle',
|
||||
async (dataArg) => {
|
||||
const wantedBundle = await SecretBundle.getInstance({
|
||||
data: {
|
||||
authorizations: {
|
||||
// @ts-ignore
|
||||
$elemMatch: {
|
||||
secretAccessKey: dataArg.authorization,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const authorization = await wantedBundle.getAuthorizationFromAuthKey(
|
||||
dataArg.authorization
|
||||
);
|
||||
return {
|
||||
envBundle: {
|
||||
configKeyValueObject: await wantedBundle.getKeyValueObjectForEnvironment(
|
||||
authorization.environment
|
||||
),
|
||||
environment: authorization.environment,
|
||||
timeSensitive: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
}
|
39
ts/manager.server/server.ts
Normal file
39
ts/manager.server/server.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
/*
|
||||
* cluster defines a swarmkit cluster
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class Server extends plugins.smartdata.SmartDataDbDoc<Server, plugins.servezoneInterfaces.data.IServer> {
|
||||
// STATIC
|
||||
public static async createFromHetznerServer(
|
||||
hetznerServerArg: plugins.hetznercloud.HetznerServer
|
||||
) {
|
||||
const newServer = new Server();
|
||||
newServer.id = plugins.smartunique.shortId(8);
|
||||
const data: plugins.servezoneInterfaces.data.IServer['data'] = {
|
||||
assignedClusterId: hetznerServerArg.data.labels.clusterId,
|
||||
requiredDebianPackages: [],
|
||||
sshKeys: [],
|
||||
type: 'hetzner',
|
||||
}
|
||||
Object.assign(newServer, { data });
|
||||
await newServer.save();
|
||||
return newServer;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IServer['data'];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public async getServices(): Promise<plugins.servezoneInterfaces.data.IService[]> {
|
||||
return [];
|
||||
}
|
||||
}
|
103
ts/manager.server/servermanager.ts
Normal file
103
ts/manager.server/servermanager.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
import { Cluster } from '../manager.cluster/cluster.js';
|
||||
import { Server } from './server.js';
|
||||
|
||||
export class CloudlyServerManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
||||
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public CServer = plugins.smartdata.setDefaultManagerForDoc(this, Server);
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
|
||||
/**
|
||||
* is used be serverconfig module on the server to get the actual server config
|
||||
*/
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
|
||||
'getServerConfig',
|
||||
async (requestData) => {
|
||||
const serverId = requestData.serverId;
|
||||
const server = await this.CServer.getInstance({
|
||||
id: serverId,
|
||||
})
|
||||
return {
|
||||
configData: await server.createSavableObject(),
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
this.hetznerAccount = new plugins.hetznercloud.HetznerAccount(this.cloudlyRef.config.data.hetznerToken);
|
||||
}
|
||||
|
||||
public async stop() {}
|
||||
|
||||
/**
|
||||
* creates the server infrastructure on hetzner
|
||||
* ensures that there are exactly the reources that are needed
|
||||
* no more, no less
|
||||
*/
|
||||
public async ensureServerInfrastructure() {
|
||||
// get all clusters
|
||||
const allClusters = await this.cloudlyRef.clusterManager.getAllClusters();
|
||||
for (const cluster of allClusters) {
|
||||
// get existing servers
|
||||
const servers = await this.getServersByCluster(cluster);
|
||||
|
||||
// if there is no server, create one
|
||||
if (servers.length === 0) {
|
||||
const server = await this.hetznerAccount.createServer({
|
||||
name: plugins.smartunique.uniSimple('server'),
|
||||
location: 'nbg1',
|
||||
type: 'cpx41',
|
||||
labels: {
|
||||
clusterId: cluster.id,
|
||||
priority: '1',
|
||||
}
|
||||
});
|
||||
const newServer = await Server.createFromHetznerServer(server);
|
||||
console.log(`cluster created new server for cluster ${cluster.id}`);
|
||||
} else {
|
||||
console.log(`cluster ${cluster.id} already has servers. Making sure that they actually exist in the real world...`);
|
||||
// if there is a server, make sure that it exists
|
||||
for (const server of servers) {
|
||||
const hetznerServer = await this.hetznerAccount.getServersByLabel({
|
||||
'clusterId': cluster.id
|
||||
});
|
||||
if (!hetznerServer) {
|
||||
console.log(`server ${server.id} does not exist in the real world. Creating it now...`);
|
||||
const hetznerServer = await this.hetznerAccount.createServer({
|
||||
name: plugins.smartunique.uniSimple('server'),
|
||||
location: 'nbg1',
|
||||
type: 'cpx41',
|
||||
labels: {
|
||||
clusterId: cluster.id,
|
||||
priority: '1',
|
||||
}
|
||||
});
|
||||
const newServer = await Server.createFromHetznerServer(hetznerServer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getServersByCluster(clusterArg: Cluster) {
|
||||
const results = await this.CServer.getInstances({
|
||||
data: {
|
||||
assignedClusterId: clusterArg.id,
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
}
|
0
ts/manager.service/classes.service.ts
Normal file
0
ts/manager.service/classes.service.ts
Normal file
0
ts/manager.service/classes.servicemanager.ts
Normal file
0
ts/manager.service/classes.servicemanager.ts
Normal file
22
ts/manager.status/statusmanager.ts
Normal file
22
ts/manager.status/statusmanager.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../index.js';
|
||||
|
||||
/**
|
||||
* external api manager manages external api requests
|
||||
*/
|
||||
export class ExternalApiManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.network.IRequest_Any_Cloudly_GetNetworkNodes>(
|
||||
new plugins.typedrequest.TypedHandler('getNetworkNodes', async (requestData) => {
|
||||
const networkNodes = [];
|
||||
return {
|
||||
networkNodes,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
35
ts/manager.task/taskmanager.ts
Normal file
35
ts/manager.task/taskmanager.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
import { logger } from '../cloudly.logging.js';
|
||||
|
||||
export class CloudlyTaskmanager {
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
}
|
||||
|
||||
public everyMinuteTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {},
|
||||
});
|
||||
|
||||
public everyHourTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {
|
||||
logger.log('info', `Performing hourly maintenance check.`);
|
||||
const configs = await this.cloudlyRef.clusterManager.getAllClusters();
|
||||
logger.log('info', `Got ${configs.length} configs`);
|
||||
configs.map((configArg) => {
|
||||
console.log(configArg.name);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
public everyDayTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {},
|
||||
});
|
||||
|
||||
public everyWeekTask = new plugins.taskbuffer.Task({
|
||||
taskFunction: async () => {},
|
||||
});
|
||||
}
|
32
ts/manager.version/containerversion.ts
Normal file
32
ts/manager.version/containerversion.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
/*
|
||||
A container version is managed by the versionmanager
|
||||
*/
|
||||
@plugins.smartdata.Manager()
|
||||
export class ContainerVersion
|
||||
extends plugins.smartdata.SmartDataDbDoc<ContainerVersion, unknown>
|
||||
implements plugins.servezoneInterfaces.data.IContainerVersionData
|
||||
{
|
||||
public static async fromIVersionData(
|
||||
dataArg: plugins.servezoneInterfaces.data.IContainerVersionData
|
||||
) {
|
||||
const containerVersionInstance = new ContainerVersion();
|
||||
containerVersionInstance.id = plugins.smartunique.shortId();
|
||||
Object.assign(containerVersionInstance, dataArg);
|
||||
return containerVersionInstance;
|
||||
}
|
||||
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public dockerImageUrl: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public dockerImageVersion: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
}
|
149
ts/manager.version/versionmanager.ts
Normal file
149
ts/manager.version/versionmanager.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import * as plugins from '../cloudly.plugins.js';
|
||||
|
||||
import { ContainerVersion } from './containerversion.js';
|
||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
||||
|
||||
export class CloudlyVersionManager {
|
||||
// INSTANCE
|
||||
public cloudlyRef: Cloudly;
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
|
||||
// connected classes
|
||||
public CContainerVersion = plugins.smartdata.setDefaultManagerForDoc(this, ContainerVersion);
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
||||
// get version
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_GetLatestContainerVersion>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'getLatestContainerVersion',
|
||||
async (typedRequestData) => {
|
||||
const containerVersionGet: ContainerVersion =
|
||||
await ContainerVersion.getInstance<ContainerVersion>({
|
||||
dockerImageUrl: typedRequestData.dockerImageUrl,
|
||||
});
|
||||
return {
|
||||
dockerImageUrl: containerVersionGet.dockerImageUrl,
|
||||
dockerImageVersion: containerVersionGet.dockerImageVersion,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// update version
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
|
||||
new plugins.typedrequest.TypedHandler(
|
||||
'informCloudlyAboutNewContainerVersion',
|
||||
async (dataArg) => {
|
||||
console.log(`Got a container version announcement! "${dataArg.dockerImageUrl}"`);
|
||||
let containerVersion: ContainerVersion =
|
||||
await ContainerVersion.getInstance<ContainerVersion>({
|
||||
dockerImageUrl: dataArg.dockerImageUrl,
|
||||
});
|
||||
|
||||
if (containerVersion) {
|
||||
containerVersion.dockerImageVersion = dataArg.dockerImageVersion;
|
||||
await containerVersion.save();
|
||||
} else {
|
||||
containerVersion = await ContainerVersion.fromIVersionData(dataArg);
|
||||
await containerVersion.save();
|
||||
}
|
||||
|
||||
// lets push this info to the relevant clusters
|
||||
const clusters = await this.cloudlyRef.clusterManager.getAllClusters();
|
||||
let foundServices: plugins.servezoneInterfaces.data.IService;
|
||||
let relevantClusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier;
|
||||
for (const clusterArg of clusters) {
|
||||
console.log(clusterArg);
|
||||
for (const serviceArg of await clusterArg.getServices()) {
|
||||
if (serviceArg.image === containerVersion.dockerImageUrl) {
|
||||
foundServices = serviceArg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundServices) {
|
||||
relevantClusterIdentifier = {
|
||||
clusterName: clusterArg.data.name,
|
||||
secretKey: clusterArg.data.secretKey,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!relevantClusterIdentifier) {
|
||||
console.log('no cluster found that needs to update');
|
||||
return {};
|
||||
} else {
|
||||
console.log('found relevant cluster identifier:');
|
||||
console.log(relevantClusterIdentifier);
|
||||
}
|
||||
|
||||
const targetConnection =
|
||||
await this.cloudlyRef.server.typedsocketServer.findTargetConnection(
|
||||
async (connectionArg) => {
|
||||
const identityTag = await connectionArg.getTagById('identity');
|
||||
if (!identityTag) {
|
||||
return false;
|
||||
}
|
||||
const result =
|
||||
plugins.smartjson.stringify(identityTag.payload) ===
|
||||
plugins.smartjson.stringify(relevantClusterIdentifier);
|
||||
return result;
|
||||
}
|
||||
);
|
||||
|
||||
if (targetConnection) {
|
||||
console.log(`the relevant cluster is connected and is now being informed.`);
|
||||
const informCoreflowTR =
|
||||
this.cloudlyRef.server.typedsocketServer.createTypedRequest<plugins.servezoneInterfaces.requests.version.IRequest_Cloudly_Coreflow_VersionManager_InformCoreflowAboutNewContainerVersion>(
|
||||
'informCoreflowAboutNewContainerVersion',
|
||||
targetConnection
|
||||
);
|
||||
informCoreflowTR.fire({
|
||||
dockerImageUrl: containerVersion.dockerImageUrl,
|
||||
dockerImageVersion: containerVersion.dockerImageVersion,
|
||||
});
|
||||
} else {
|
||||
console.log('the relevant cluster is not connected at this time.');
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// lets support the servezone standard
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.IRequest_InformAboutNewContainerImage>(
|
||||
'servezonestandard_InformAboutNewContainerVersion',
|
||||
async (dataArg) => {
|
||||
const result =
|
||||
await this.typedRouter.routeAndAddResponse<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
|
||||
{
|
||||
method: 'informCloudlyAboutNewContainerVersion',
|
||||
request: {
|
||||
dockerImageUrl: dataArg.containerImageInfo.registryUrl,
|
||||
dockerImageVersion: dataArg.containerImageInfo.version,
|
||||
},
|
||||
response: null
|
||||
},
|
||||
true
|
||||
);
|
||||
return result.response;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets all versions
|
||||
*/
|
||||
public async getAllVersions() {
|
||||
const result = await ContainerVersion.getInstances<ContainerVersion>({});
|
||||
return result;
|
||||
}
|
||||
}
|
13
ts_apiclient/classes.cloudlyclient.ts
Normal file
13
ts_apiclient/classes.cloudlyclient.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class CloudlyClient {
|
||||
public clientToken: string;
|
||||
|
||||
constructor(clientToken: string) {
|
||||
this.clientToken = clientToken;
|
||||
}
|
||||
|
||||
public async getClusters() {
|
||||
|
||||
}
|
||||
}
|
5
ts_apiclient/classes.cluster.ts
Normal file
5
ts_apiclient/classes.cluster.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class Cluster {
|
||||
public getServers() {}
|
||||
}
|
5
ts_apiclient/classes.image.ts
Normal file
5
ts_apiclient/classes.image.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class Image {
|
||||
public getImages() {}
|
||||
}
|
0
ts_apiclient/classes.server.ts
Normal file
0
ts_apiclient/classes.server.ts
Normal file
5
ts_apiclient/classes.service.ts
Normal file
5
ts_apiclient/classes.service.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class Service {
|
||||
|
||||
}
|
1
ts_apiclient/index.ts
Normal file
1
ts_apiclient/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './classes.cloudlyclient.js';
|
5
ts_apiclient/plugins.ts
Normal file
5
ts_apiclient/plugins.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as typedrequest from '@api.global/typedrequest';
|
||||
|
||||
export {
|
||||
typedrequest
|
||||
}
|
0
ts_cliclient/index.ts
Normal file
0
ts_cliclient/index.ts
Normal file
8
ts_web/00_commitinfo_data.ts
Normal file
8
ts_web/00_commitinfo_data.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/cloudly',
|
||||
version: '1.0.214',
|
||||
description: 'A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.'
|
||||
}
|
192
ts_web/appstate.ts
Normal file
192
ts_web/appstate.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
const appstate = new plugins.deesDomtools.plugins.smartstate.Smartstate();
|
||||
export interface ILoginState {
|
||||
jwt: string;
|
||||
}
|
||||
export const loginStatePart = await appstate.getStatePart<ILoginState>(
|
||||
'login',
|
||||
{ jwt: null },
|
||||
'persistent'
|
||||
);
|
||||
|
||||
export const loginAction = loginStatePart.createAction<{ username: string; password: string }>(
|
||||
async (statePartArg, payloadArg) => {
|
||||
const currentState = statePartArg.getState();
|
||||
const trLogin =
|
||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
||||
'/typedrequest',
|
||||
'adminLoginWithUsernameAndPassword'
|
||||
);
|
||||
const response = await trLogin.fire({
|
||||
username: payloadArg.username,
|
||||
password: payloadArg.password,
|
||||
});
|
||||
return {
|
||||
...currentState,
|
||||
...(response.jwt ? { jwt: response.jwt } : {}),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
|
||||
const currentState = statePartArg.getState();
|
||||
return {
|
||||
...currentState,
|
||||
jwt: null,
|
||||
};
|
||||
});
|
||||
|
||||
export interface IDataState {
|
||||
secretGroups?: plugins.interfaces.data.ISecretGroup[];
|
||||
secretBundles?: plugins.interfaces.data.ISecretBundle[];
|
||||
clusters?: plugins.interfaces.data.ICluster[];
|
||||
images?: any[];
|
||||
services?: any[];
|
||||
deployments?: any[];
|
||||
dns?: any[];
|
||||
mails?: any[];
|
||||
logs?: any[];
|
||||
s3?: any[];
|
||||
dbs?: any[];
|
||||
backups?: any[];
|
||||
}
|
||||
export const dataState = await appstate.getStatePart<IDataState>(
|
||||
'data',
|
||||
{
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
clusters: [],
|
||||
images: [],
|
||||
services: [],
|
||||
deployments: [],
|
||||
dns: [],
|
||||
mails: [],
|
||||
logs: [],
|
||||
s3: [],
|
||||
dbs: [],
|
||||
backups: [],
|
||||
},
|
||||
'soft'
|
||||
);
|
||||
|
||||
// Getting data
|
||||
export const getDataAction = dataState.createAction(async (statePartArg) => {
|
||||
let currentState = statePartArg.getState();
|
||||
// Secrets
|
||||
const trGetSecrets =
|
||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
||||
'/typedrequest',
|
||||
'adminGetConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trGetSecrets.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
});
|
||||
currentState = {
|
||||
...currentState,
|
||||
...response,
|
||||
};
|
||||
|
||||
// Clusters
|
||||
const trGetClusters =
|
||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.cluster.IRequest_GetAllClusters>(
|
||||
'/typedrequest',
|
||||
'getAllClusters'
|
||||
);
|
||||
const responseClusters = await trGetClusters.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
});
|
||||
|
||||
currentState = {
|
||||
...currentState,
|
||||
...responseClusters,
|
||||
}
|
||||
|
||||
return currentState;
|
||||
});
|
||||
|
||||
// SecretGroup Actions
|
||||
export const createSecretGroupAction = dataState.createAction(
|
||||
async (statePartArg, payloadArg: plugins.interfaces.data.ISecretGroup) => {
|
||||
let currentState = statePartArg.getState();
|
||||
const trCreateSecretGroup =
|
||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_CreateConfigBundlesAndSecretGroups>(
|
||||
'/typedrequest',
|
||||
'adminCreateConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trCreateSecretGroup.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
secretBundles: [],
|
||||
secretGroups: [payloadArg],
|
||||
});
|
||||
currentState = await dataState.dispatchAction(getDataAction, null);
|
||||
return currentState;
|
||||
return currentState;
|
||||
}
|
||||
);
|
||||
|
||||
export const deleteSecretGroupAction = dataState.createAction(
|
||||
async (statePartArg, payloadArg: { secretGroupId: string }) => {
|
||||
let currentState = statePartArg.getState();
|
||||
const trDeleteSecretGroup =
|
||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
||||
'/typedrequest',
|
||||
'adminDeleteConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trDeleteSecretGroup.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
secretBundleIds: [],
|
||||
secretGroupIds: [payloadArg.secretGroupId],
|
||||
});
|
||||
currentState = await dataState.dispatchAction(getDataAction, null);
|
||||
return currentState;
|
||||
}
|
||||
);
|
||||
|
||||
// SecretBundle Actions
|
||||
export const deleteSecretBundleAction = dataState.createAction(
|
||||
async (statePartArg, payloadArg: { configBundleId: string }) => {
|
||||
let currentState = statePartArg.getState();
|
||||
const trDeleteConfigBundle =
|
||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.secret.IReq_Admin_DeleteConfigBundlesAndSecretGroups>(
|
||||
'/typedrequest',
|
||||
'adminDeleteConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trDeleteConfigBundle.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
secretBundleIds: [payloadArg.configBundleId],
|
||||
secretGroupIds: [],
|
||||
});
|
||||
currentState = await dataState.dispatchAction(getDataAction, null);
|
||||
return currentState;
|
||||
}
|
||||
);
|
||||
|
||||
// cluster
|
||||
export const addClusterAction = dataState.createAction(
|
||||
async (
|
||||
statePartArg,
|
||||
payloadArg: {
|
||||
clusterName: string;
|
||||
}
|
||||
) => {
|
||||
let currentState = statePartArg.getState();
|
||||
const trAddCluster =
|
||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.cluster.IRequest_CreateCluster>(
|
||||
'/typedrequest',
|
||||
'createCluster'
|
||||
);
|
||||
const response = await trAddCluster.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
...payloadArg,
|
||||
});
|
||||
currentState = {
|
||||
...currentState,
|
||||
...{
|
||||
clusters: [...currentState.clusters, response.clusterConfig],
|
||||
},
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
);
|
202
ts_web/elements/cloudly-dashboard.ts
Normal file
202
ts_web/elements/cloudly-dashboard.ts
Normal file
@ -0,0 +1,202 @@
|
||||
import { commitinfo } from '../00_commitinfo_data.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
css,
|
||||
cssManager,
|
||||
customElement,
|
||||
html,
|
||||
state
|
||||
} from '@design.estate/dees-element';
|
||||
import { CloudlyViewBackups } from './cloudly-view-backups.js';
|
||||
import { CloudlyViewClusters } from './cloudly-view-clusters.js';
|
||||
import { CloudlyViewDbs } from './cloudly-view-dbs.js';
|
||||
import { CloudlyViewDeployments } from './cloudly-view-deployments.js';
|
||||
import { CloudlyViewDns } from './cloudly-view-dns.js';
|
||||
import { CloudlyViewImages } from './cloudly-view-images.js';
|
||||
import { CloudlyViewLogs } from './cloudly-view-logs.js';
|
||||
import { CloudlyViewMails } from './cloudly-view-mails.js';
|
||||
import { CloudlyViewOverview } from './cloudly-view-overview.js';
|
||||
import { CloudlyViewS3 } from './cloudly-view-s3.js';
|
||||
import { CloudlyViewSecretBundles } from './cloudly-view-secretbundles.js';
|
||||
import { CloudlyViewSecretGroups } from './cloudly-view-secretgroups.js';
|
||||
import { CloudlyViewServices } from './cloudly-view-services.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'cvault-dashboard': CloudlyDashboard;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('cloudly-dashboard')
|
||||
export class CloudlyDashboard extends DeesElement {
|
||||
@state() private jwt: string;
|
||||
@state() private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
clusters: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subcription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subcription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
.maincontainer {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: 400;
|
||||
font-size: 24px;
|
||||
font-family: 'Cal Sans';
|
||||
}
|
||||
`,
|
||||
];
|
||||
public render() {
|
||||
return html`
|
||||
<div class="maincontainer">
|
||||
<dees-simple-login name="cloudly v${commitinfo.version}">
|
||||
<dees-simple-appdash name="cloudly v${commitinfo.version}"
|
||||
.viewTabs=${[
|
||||
{
|
||||
name: 'Overview',
|
||||
element: CloudlyViewOverview,
|
||||
},
|
||||
{
|
||||
name: 'SecretGroups',
|
||||
element: CloudlyViewSecretGroups,
|
||||
},
|
||||
{
|
||||
name: 'SecretBundles',
|
||||
element: CloudlyViewSecretBundles,
|
||||
},
|
||||
{
|
||||
name: 'Clusters',
|
||||
element: CloudlyViewClusters,
|
||||
},
|
||||
{
|
||||
name: 'Images',
|
||||
element: CloudlyViewImages,
|
||||
},
|
||||
{
|
||||
name: 'Services',
|
||||
element: CloudlyViewServices,
|
||||
},
|
||||
{
|
||||
name: 'Deployments',
|
||||
element: CloudlyViewDeployments,
|
||||
},
|
||||
{
|
||||
name: 'DNS',
|
||||
element: CloudlyViewDns,
|
||||
},
|
||||
{
|
||||
name: 'Mails',
|
||||
element: CloudlyViewMails,
|
||||
},
|
||||
{
|
||||
name: 'Logs',
|
||||
element: CloudlyViewLogs,
|
||||
},
|
||||
{
|
||||
name: 's3',
|
||||
element: CloudlyViewS3,
|
||||
},
|
||||
{
|
||||
name: 'DBs',
|
||||
element: CloudlyViewDbs,
|
||||
},
|
||||
{
|
||||
name: 'Backups',
|
||||
element: CloudlyViewBackups,
|
||||
},
|
||||
{
|
||||
name: 'Fleet',
|
||||
element: CloudlyViewBackups,
|
||||
}
|
||||
] as plugins.deesCatalog.IView[]}
|
||||
></dees-simple-appdash>
|
||||
</dees-simple-login>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
public async firstUpdated() {
|
||||
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
|
||||
simpleLogin.addEventListener('login', (e: CustomEvent) => {
|
||||
console.log(e.detail);
|
||||
this.login(e.detail.data.username, e.detail.data.password);
|
||||
});
|
||||
this.addEventListener('contextmenu', (eventArg) => {
|
||||
plugins.deesCatalog.DeesContextmenu.openContextMenuWithOptions(eventArg, [
|
||||
{
|
||||
name: 'About',
|
||||
iconName: 'mugHot',
|
||||
action: async () => {
|
||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'About',
|
||||
content: html`configvault ${commitinfo.version}`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'close',
|
||||
iconName: null,
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// lets deal with initial state
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const loginState = appstate.loginStatePart.getState();
|
||||
console.log(loginState);
|
||||
if (loginState.jwt) {
|
||||
this.jwt = loginState.jwt;
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async login(username: string, password: string) {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
console.log(`attempting to login...`);
|
||||
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
|
||||
const form = simpleLogin.shadowRoot.querySelector('dees-form');
|
||||
form.setStatus('pending', 'Logging in...');
|
||||
const state = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
if (state.jwt) {
|
||||
console.log('got jwt');
|
||||
this.jwt = state.jwt;
|
||||
form.setStatus('success', 'Logged in!');
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
||||
} else {
|
||||
form.setStatus('error', 'Login failed!');
|
||||
await domtools.convenience.smartdelay.delayFor(2000);
|
||||
form.reset();
|
||||
}
|
||||
}
|
||||
|
||||
private async logout() {}
|
||||
}
|
133
ts_web/elements/cloudly-view-backups.ts
Normal file
133
ts_web/elements/cloudly-view-backups.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-backups')
|
||||
export class CloudlyViewBackups extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Backups</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'Backups'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.backups}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
140
ts_web/elements/cloudly-view-clusters.ts
Normal file
140
ts_web/elements/cloudly-view-clusters.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-clusters')
|
||||
export class CloudlyViewClusters extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Clusters</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'Clusters'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.clusters}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
console.log(itemArg);
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add cluster',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add Cluster',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.key=${'clusterName'}
|
||||
.label=${'cluster name'}
|
||||
.description=${'a descriptive name for the cluster'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'create',
|
||||
action: async (modalArg) => {
|
||||
const data: {
|
||||
clusterName: string;
|
||||
} = (await modalArg.shadowRoot
|
||||
.querySelector('dees-form')
|
||||
.collectFormData()) as any;
|
||||
await appstate.dataState.dispatchAction(appstate.addClusterAction, data);
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
133
ts_web/elements/cloudly-view-dbs.ts
Normal file
133
ts_web/elements/cloudly-view-dbs.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-dbs')
|
||||
export class CloudlyViewDbs extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>DBs</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'DBs'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.dbs}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
133
ts_web/elements/cloudly-view-deployments.ts
Normal file
133
ts_web/elements/cloudly-view-deployments.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-deployments')
|
||||
export class CloudlyViewDeployments extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Deployments</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'Deployments'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.deployments}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
133
ts_web/elements/cloudly-view-dns.ts
Normal file
133
ts_web/elements/cloudly-view-dns.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-dns')
|
||||
export class CloudlyViewDns extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>DNS</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'DNS'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.deployments}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
367
ts_web/elements/cloudly-view-images.ts
Normal file
367
ts_web/elements/cloudly-view-images.ts
Normal file
@ -0,0 +1,367 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import { DeesElement, customElement, html, state, css, cssManager } from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-images')
|
||||
export class CloudlyViewImages extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Images</cloudly-sectionheading>
|
||||
<dees-table
|
||||
heading1="SecretGroups"
|
||||
heading2="decoded in client"
|
||||
.data=${this.data.images}
|
||||
.displayFunction=${(secretGroup: plugins.interfaces.data.ISecretGroup) => {
|
||||
return {
|
||||
name: secretGroup.data.name,
|
||||
priority: secretGroup.data.priority,
|
||||
tags: html`<dees-chips
|
||||
.selectionMode=${'none'}
|
||||
.selectableChips=${secretGroup.data.tags}
|
||||
></dees-chips>`,
|
||||
key: secretGroup.data.key,
|
||||
history: (() => {
|
||||
const allHistory = [];
|
||||
for (const environment in secretGroup.data.environments) {
|
||||
allHistory.push(...secretGroup.data.environments[environment].history);
|
||||
}
|
||||
return allHistory.length;
|
||||
})(),
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add SecretGroup',
|
||||
type: ['header', 'footer'],
|
||||
iconName: 'plus',
|
||||
actionFunc: async () => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'create new SecretGroup',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.label=${'name'}
|
||||
.key=${'data.name'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.label=${'description'}
|
||||
.key=${'data.description'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.label=${'Secret Key (data.key)'}
|
||||
.key=${'data.key'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-table
|
||||
.heading1=${'Environments'}
|
||||
.heading2=${'keys need to be unique'}
|
||||
key="environments"
|
||||
.data=${[
|
||||
{
|
||||
environment: 'production',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
environment: 'staging',
|
||||
value: '',
|
||||
},
|
||||
]}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add environment',
|
||||
iconName: 'plus',
|
||||
type: ['footer'],
|
||||
actionFunc: async (dataArg) => {
|
||||
dataArg.table.data.push({
|
||||
environment: 'new environment',
|
||||
value: '',
|
||||
});
|
||||
dataArg.table.requestUpdate('data');
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete environment',
|
||||
iconName: 'trash',
|
||||
type: ['inRow'],
|
||||
actionFunc: async (dataArg) => {
|
||||
dataArg.table.data.splice(dataArg.table.data.indexOf(dataArg.item), 1);
|
||||
dataArg.table.requestUpdate('data');
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
.editableFields=${['environment', 'value']}
|
||||
></dees-table>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'save',
|
||||
action: async (modalArg) => {
|
||||
const deesForm = modalArg.shadowRoot.querySelector('dees-form');
|
||||
const formData = await deesForm.collectFormData();
|
||||
console.log(`Prepare saving of data:`);
|
||||
console.log(formData);
|
||||
const environments: plugins.interfaces.data.ISecretGroup['data']['environments'] =
|
||||
{};
|
||||
for (const itemArg of formData['environments'] as any[]) {
|
||||
environments[itemArg.environment] = {
|
||||
value: itemArg.value,
|
||||
history: [],
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
}
|
||||
await appstate.dataState.dispatchAction(appstate.createSecretGroupAction, {
|
||||
id: null,
|
||||
data: {
|
||||
name: formData['data.name'] as string,
|
||||
description: formData['data.description'] as string,
|
||||
key: formData['data.key'] as string,
|
||||
environments,
|
||||
tags: [],
|
||||
},
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
type: ['contextmenu', 'inRow', 'doubleClick'],
|
||||
iconName: 'penToSquare',
|
||||
actionFunc: async (
|
||||
dataArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.ISecretGroup>
|
||||
) => {
|
||||
const environmentsArray: Array<
|
||||
plugins.interfaces.data.ISecretGroup['data']['environments'][any] & {
|
||||
environment: string;
|
||||
}
|
||||
> = [];
|
||||
for (const environmentName of Object.keys(dataArg.item.data.environments)) {
|
||||
environmentsArray.push({
|
||||
environment: environmentName,
|
||||
...dataArg.item.data.environments[environmentName],
|
||||
});
|
||||
}
|
||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Edit Secret',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.key=${'id'}
|
||||
.disabled=${true}
|
||||
.label=${'ID'}
|
||||
.value=${dataArg.item.id}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.name'}
|
||||
.disabled=${false}
|
||||
.label=${'name'}
|
||||
.value=${dataArg.item.data.name}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.description'}
|
||||
.disabled=${false}
|
||||
.label=${'description'}
|
||||
.value=${dataArg.item.data.description}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.key'}
|
||||
.disabled=${false}
|
||||
.label=${'key'}
|
||||
.value=${dataArg.item.data.key}
|
||||
></dees-input-text>
|
||||
<dees-table
|
||||
.key=${'environments'}
|
||||
.heading1=${'Environments'}
|
||||
.heading2=${'double-click to edit values'}
|
||||
.data=${environmentsArray.map((itemArg) => {
|
||||
return {
|
||||
environment: itemArg.environment,
|
||||
value: itemArg.value,
|
||||
};
|
||||
})}
|
||||
.editableFields=${['environment', 'value']}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
actionDataArg.table.data.splice(
|
||||
actionDataArg.table.data.indexOf(actionDataArg.item),
|
||||
1
|
||||
);
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Cancel',
|
||||
iconName: null,
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Save',
|
||||
iconName: null,
|
||||
action: async (modalArg) => {
|
||||
const data = await modalArg.shadowRoot
|
||||
.querySelector('dees-form')
|
||||
.collectFormData();
|
||||
console.log(data);
|
||||
const updatedSecretGroup: plugins.interfaces.data.ISecretGroup = {
|
||||
id: dataArg.item.id,
|
||||
data: {
|
||||
name: data['data.name'] as string,
|
||||
description: data['data.description'] as string,
|
||||
key: data['data.key'] as string,
|
||||
environments: {},
|
||||
tags: dataArg.item.data.tags,
|
||||
},
|
||||
};
|
||||
const environments: plugins.interfaces.data.ISecretGroup['data']['environments'] =
|
||||
{};
|
||||
for (const itemArg of data['environments'] as any[]) {
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'history',
|
||||
iconName: 'clockRotateLeft',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (
|
||||
dataArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.ISecretGroup>
|
||||
) => {
|
||||
const historyArray: Array<{
|
||||
environment: string;
|
||||
value: string;
|
||||
}> = [];
|
||||
for (const environment of Object.keys(dataArg.item.data.environments)) {
|
||||
for (const historyItem of dataArg.item.data.environments[environment].history) {
|
||||
historyArray.push({
|
||||
environment,
|
||||
value: historyItem.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `history for ${dataArg.item.data.key}`,
|
||||
content: html`
|
||||
<dees-table
|
||||
.data=${historyArray}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (
|
||||
itemArg: plugins.deesCatalog.ITableActionDataArg<(typeof historyArray)[0]>
|
||||
) => {
|
||||
console.log('delete', itemArg);
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'close',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (
|
||||
itemArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.ISecretGroup>
|
||||
) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ${itemArg.item.data.key}`,
|
||||
content: html`
|
||||
<div style="text-align:center">Do you really want to delete the secret?</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${itemArg.item.data.key}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
console.log(`Delete ${itemArg.item.id}`);
|
||||
await appstate.dataState.dispatchAction(appstate.deleteSecretGroupAction, {
|
||||
secretGroupId: itemArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
133
ts_web/elements/cloudly-view-logs.ts
Normal file
133
ts_web/elements/cloudly-view-logs.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-logs')
|
||||
export class CloudlyViewLogs extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Logs</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'Logs'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.deployments}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
133
ts_web/elements/cloudly-view-mails.ts
Normal file
133
ts_web/elements/cloudly-view-mails.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-mails')
|
||||
export class CloudlyViewMails extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Mails</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'Mails'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.deployments}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
71
ts_web/elements/cloudly-view-overview.ts
Normal file
71
ts_web/elements/cloudly-view-overview.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-overview')
|
||||
export class CloudlyViewOverview extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
.clusterGrid {
|
||||
display: grid;
|
||||
grid-template-columns: ${cssManager.cssGridColumns(3, 8)};
|
||||
grid-gap: 16px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Overview</cloudly-sectionheading>
|
||||
${this.data.clusters.length === 0 ? html`
|
||||
You need to create at least one cluster to see an overview.
|
||||
`: html``}
|
||||
${this.data.clusters.map(
|
||||
(clusterArg) => html`
|
||||
<dees-label .label=${'cluster: ' + clusterArg.data.name}></dees-label>
|
||||
<div class="clusterGrid">
|
||||
<dees-chart-area .label=${'System Usage'}></dees-chart-area>
|
||||
<dees-chart-area .label=${'Internet Traffic'}></dees-chart-area>
|
||||
<dees-chart-area .label=${'Requests'}></dees-chart-area>
|
||||
<dees-chart-area .label=${'WebSocket Connections'}></dees-chart-area>
|
||||
<dees-chart-log class="services" .label=${'Deployed Services'}></dees-chart-log>
|
||||
<dees-chart-log class="eventLog" .label=${'Event Log'}></dees-chart-log>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
`;
|
||||
}
|
||||
}
|
133
ts_web/elements/cloudly-view-s3.ts
Normal file
133
ts_web/elements/cloudly-view-s3.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-s3')
|
||||
export class CloudlyViewS3 extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>S3</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'S3'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.s3}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
173
ts_web/elements/cloudly-view-secretbundles.ts
Normal file
173
ts_web/elements/cloudly-view-secretbundles.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-secretbundles')
|
||||
export class CloudlyViewSecretBundles extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>SecretBundles</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'SecretBundles'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.secretBundles}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ISecretBundle) => {
|
||||
return {
|
||||
name: itemArg.data.name,
|
||||
secretGroups: (() => {
|
||||
const secretGroupIds = itemArg.data.includedSecretGroupIds;
|
||||
let secretGroupNames: string[] = [];
|
||||
for (const secretGroupId of secretGroupIds) {
|
||||
const secretGroup = this.data.secretGroups.find(
|
||||
(secretGroupArg) => secretGroupArg.id === secretGroupId
|
||||
);
|
||||
if (secretGroup) {
|
||||
secretGroupNames.push(secretGroup.data.name);
|
||||
}
|
||||
}
|
||||
return secretGroupNames.join(', ');
|
||||
})(),
|
||||
tags: html`<dees-chips
|
||||
.selectionMode=${'none'}
|
||||
.selectableChips=${itemArg.data.includedTags}
|
||||
></dees-chips>`,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add SecretBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add SecretBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
iconName: 'edit',
|
||||
type: ['doubleClick', 'contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Edit SecretBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .label=${'purpose'}></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'save', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
367
ts_web/elements/cloudly-view-secretgroups.ts
Normal file
367
ts_web/elements/cloudly-view-secretgroups.ts
Normal file
@ -0,0 +1,367 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import { DeesElement, customElement, html, state, css, cssManager } from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-secretsgroups')
|
||||
export class CloudlyViewSecretGroups extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>SecretGroups</cloudly-sectionheading>
|
||||
<dees-table
|
||||
heading1="SecretGroups"
|
||||
heading2="decoded in client"
|
||||
.data=${this.data.secretGroups}
|
||||
.displayFunction=${(secretGroup: plugins.interfaces.data.ISecretGroup) => {
|
||||
return {
|
||||
name: secretGroup.data.name,
|
||||
priority: secretGroup.data.priority,
|
||||
tags: html`<dees-chips
|
||||
.selectionMode=${'none'}
|
||||
.selectableChips=${secretGroup.data.tags}
|
||||
></dees-chips>`,
|
||||
key: secretGroup.data.key,
|
||||
history: (() => {
|
||||
const allHistory = [];
|
||||
for (const environment in secretGroup.data.environments) {
|
||||
allHistory.push(...secretGroup.data.environments[environment].history);
|
||||
}
|
||||
return allHistory.length;
|
||||
})(),
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add SecretGroup',
|
||||
type: ['header', 'footer'],
|
||||
iconName: 'plus',
|
||||
actionFunc: async () => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'create new SecretGroup',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.label=${'name'}
|
||||
.key=${'data.name'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.label=${'description'}
|
||||
.key=${'data.description'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.label=${'Secret Key (data.key)'}
|
||||
.key=${'data.key'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-table
|
||||
.heading1=${'Environments'}
|
||||
.heading2=${'keys need to be unique'}
|
||||
key="environments"
|
||||
.data=${[
|
||||
{
|
||||
environment: 'production',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
environment: 'staging',
|
||||
value: '',
|
||||
},
|
||||
]}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add environment',
|
||||
iconName: 'plus',
|
||||
type: ['footer'],
|
||||
actionFunc: async (dataArg) => {
|
||||
dataArg.table.data.push({
|
||||
environment: 'new environment',
|
||||
value: '',
|
||||
});
|
||||
dataArg.table.requestUpdate('data');
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete environment',
|
||||
iconName: 'trash',
|
||||
type: ['inRow'],
|
||||
actionFunc: async (dataArg) => {
|
||||
dataArg.table.data.splice(dataArg.table.data.indexOf(dataArg.item), 1);
|
||||
dataArg.table.requestUpdate('data');
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
.editableFields=${['environment', 'value']}
|
||||
></dees-table>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'save',
|
||||
action: async (modalArg) => {
|
||||
const deesForm = modalArg.shadowRoot.querySelector('dees-form');
|
||||
const formData = await deesForm.collectFormData();
|
||||
console.log(`Prepare saving of data:`);
|
||||
console.log(formData);
|
||||
const environments: plugins.interfaces.data.ISecretGroup['data']['environments'] =
|
||||
{};
|
||||
for (const itemArg of formData['environments'] as any[]) {
|
||||
environments[itemArg.environment] = {
|
||||
value: itemArg.value,
|
||||
history: [],
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
}
|
||||
await appstate.dataState.dispatchAction(appstate.createSecretGroupAction, {
|
||||
id: null,
|
||||
data: {
|
||||
name: formData['data.name'] as string,
|
||||
description: formData['data.description'] as string,
|
||||
key: formData['data.key'] as string,
|
||||
environments,
|
||||
tags: [],
|
||||
},
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'edit',
|
||||
type: ['contextmenu', 'inRow', 'doubleClick'],
|
||||
iconName: 'penToSquare',
|
||||
actionFunc: async (
|
||||
dataArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.ISecretGroup>
|
||||
) => {
|
||||
const environmentsArray: Array<
|
||||
plugins.interfaces.data.ISecretGroup['data']['environments'][any] & {
|
||||
environment: string;
|
||||
}
|
||||
> = [];
|
||||
for (const environmentName of Object.keys(dataArg.item.data.environments)) {
|
||||
environmentsArray.push({
|
||||
environment: environmentName,
|
||||
...dataArg.item.data.environments[environmentName],
|
||||
});
|
||||
}
|
||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Edit Secret',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text
|
||||
.key=${'id'}
|
||||
.disabled=${true}
|
||||
.label=${'ID'}
|
||||
.value=${dataArg.item.id}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.name'}
|
||||
.disabled=${false}
|
||||
.label=${'name'}
|
||||
.value=${dataArg.item.data.name}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.description'}
|
||||
.disabled=${false}
|
||||
.label=${'description'}
|
||||
.value=${dataArg.item.data.description}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.key'}
|
||||
.disabled=${false}
|
||||
.label=${'key'}
|
||||
.value=${dataArg.item.data.key}
|
||||
></dees-input-text>
|
||||
<dees-table
|
||||
.key=${'environments'}
|
||||
.heading1=${'Environments'}
|
||||
.heading2=${'double-click to edit values'}
|
||||
.data=${environmentsArray.map((itemArg) => {
|
||||
return {
|
||||
environment: itemArg.environment,
|
||||
value: itemArg.value,
|
||||
};
|
||||
})}
|
||||
.editableFields=${['environment', 'value']}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
actionDataArg.table.data.splice(
|
||||
actionDataArg.table.data.indexOf(actionDataArg.item),
|
||||
1
|
||||
);
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'Cancel',
|
||||
iconName: null,
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Save',
|
||||
iconName: null,
|
||||
action: async (modalArg) => {
|
||||
const data = await modalArg.shadowRoot
|
||||
.querySelector('dees-form')
|
||||
.collectFormData();
|
||||
console.log(data);
|
||||
const updatedSecretGroup: plugins.interfaces.data.ISecretGroup = {
|
||||
id: dataArg.item.id,
|
||||
data: {
|
||||
name: data['data.name'] as string,
|
||||
description: data['data.description'] as string,
|
||||
key: data['data.key'] as string,
|
||||
environments: {},
|
||||
tags: dataArg.item.data.tags,
|
||||
},
|
||||
};
|
||||
const environments: plugins.interfaces.data.ISecretGroup['data']['environments'] =
|
||||
{};
|
||||
for (const itemArg of data['environments'] as any[]) {
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'history',
|
||||
iconName: 'clockRotateLeft',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (
|
||||
dataArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.ISecretGroup>
|
||||
) => {
|
||||
const historyArray: Array<{
|
||||
environment: string;
|
||||
value: string;
|
||||
}> = [];
|
||||
for (const environment of Object.keys(dataArg.item.data.environments)) {
|
||||
for (const historyItem of dataArg.item.data.environments[environment].history) {
|
||||
historyArray.push({
|
||||
environment,
|
||||
value: historyItem.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `history for ${dataArg.item.data.key}`,
|
||||
content: html`
|
||||
<dees-table
|
||||
.data=${historyArray}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (
|
||||
itemArg: plugins.deesCatalog.ITableActionDataArg<(typeof historyArray)[0]>
|
||||
) => {
|
||||
console.log('delete', itemArg);
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'close',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (
|
||||
itemArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.ISecretGroup>
|
||||
) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ${itemArg.item.data.key}`,
|
||||
content: html`
|
||||
<div style="text-align:center">Do you really want to delete the secret?</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${itemArg.item.data.key}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
console.log(`Delete ${itemArg.item.id}`);
|
||||
await appstate.dataState.dispatchAction(appstate.deleteSecretGroupAction, {
|
||||
secretGroupId: itemArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
133
ts_web/elements/cloudly-view-services.ts
Normal file
133
ts_web/elements/cloudly-view-services.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
customElement,
|
||||
html,
|
||||
state,
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as appstate from '../appstate.js';
|
||||
|
||||
@customElement('cloudly-view-services')
|
||||
export class CloudlyViewServices extends DeesElement {
|
||||
@state()
|
||||
private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const subecription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
this.data = dataArg;
|
||||
});
|
||||
this.rxSubscriptions.push(subecription);
|
||||
}
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<cloudly-sectionheading>Services</cloudly-sectionheading>
|
||||
<dees-table
|
||||
.heading1=${'Services'}
|
||||
.heading2=${'decoded in client'}
|
||||
.data=${this.data.services}
|
||||
.displayFunction=${(itemArg: plugins.interfaces.data.ICluster) => {
|
||||
return {
|
||||
id: itemArg.id,
|
||||
serverAmount: itemArg.data.servers.length,
|
||||
};
|
||||
}}
|
||||
.dataActions=${[
|
||||
{
|
||||
name: 'add configBundle',
|
||||
iconName: 'plus',
|
||||
type: ['header', 'footer'],
|
||||
actionFunc: async (dataActionArg) => {
|
||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'Add ConfigBundle',
|
||||
content: html`
|
||||
<dees-form>
|
||||
<dees-input-text .key=${'id'} .label=${'ID'} .value=${''}></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.secretGroupIds'}
|
||||
.label=${'secretGroupIds'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
<dees-input-text
|
||||
.key=${'data.includedTags'}
|
||||
.label=${'includedTags'}
|
||||
.value=${''}
|
||||
></dees-input-text>
|
||||
</dees-form>
|
||||
`,
|
||||
menuOptions: [
|
||||
{ name: 'create', action: async (modalArg) => {} },
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
iconName: 'trash',
|
||||
type: ['contextmenu', 'inRow'],
|
||||
actionFunc: async (actionDataArg) => {
|
||||
plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: `Delete ConfigBundle ${actionDataArg.item.id}`,
|
||||
content: html`
|
||||
<div style="text-align:center">
|
||||
Do you really want to delete the ConfigBundle?
|
||||
</div>
|
||||
<div
|
||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||
>
|
||||
${actionDataArg.item.id}
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'cancel',
|
||||
action: async (modalArg) => {
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
action: async (modalArg) => {
|
||||
appstate.dataState.dispatchAction(appstate.deleteSecretBundleAction, {
|
||||
configBundleId: actionDataArg.item.id,
|
||||
});
|
||||
await modalArg.destroy();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
},
|
||||
] as plugins.deesCatalog.ITableAction[]}
|
||||
></dees-table>
|
||||
`;
|
||||
}
|
||||
}
|
4
ts_web/elements/index.ts
Normal file
4
ts_web/elements/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './shared/index.js';
|
||||
export * from './cloudly-dashboard.js';
|
||||
export * from './cloudly-view-secretgroups.js';
|
||||
export * from './cloudly-view-secretbundles.js';
|
33
ts_web/elements/shared/cloudly-sectionheading.ts
Normal file
33
ts_web/elements/shared/cloudly-sectionheading.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import * as plugins from '../../plugins.js';
|
||||
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
DeesElement,
|
||||
property,
|
||||
type TemplateResult,
|
||||
cssManager,
|
||||
css,
|
||||
unsafeCSS,
|
||||
type CSSResult,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
@customElement('cloudly-sectionheading')
|
||||
export class CloudlySectionheading extends DeesElement {
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
h1 {
|
||||
font-family: 'Cal Sans';
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
`,
|
||||
]
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<h1><slot></slot></h1>
|
||||
`;
|
||||
}
|
||||
}
|
1
ts_web/elements/shared/index.ts
Normal file
1
ts_web/elements/shared/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './cloudly-sectionheading.js';
|
9
ts_web/index.ts
Normal file
9
ts_web/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
import { html } from '@design.estate/dees-element';
|
||||
|
||||
import './elements/index.js';
|
||||
|
||||
plugins.deesElement.render(html`
|
||||
<cloudly-dashboard></cloudly-dashboard>
|
||||
`, document.body);
|
19
ts_web/plugins.ts
Normal file
19
ts_web/plugins.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as interfaces from '@serve.zone/interfaces';
|
||||
|
||||
export {
|
||||
interfaces
|
||||
}
|
||||
|
||||
// @design.estate scope
|
||||
import * as deesDomtools from '@design.estate/dees-domtools';
|
||||
import * as deesElement from '@design.estate/dees-element';
|
||||
import * as deesCatalog from '@design.estate/dees-catalog';
|
||||
|
||||
export { deesDomtools, deesElement, deesCatalog };
|
||||
|
||||
// @push.rocks scope
|
||||
import * as webjwt from '@push.rocks/webjwt';
|
||||
|
||||
export {
|
||||
webjwt,
|
||||
}
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user