Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
4eb62200e8 | |||
c105596455 | |||
5aa136b8d9 | |||
240516520a | |||
4e1f9464fe | |||
41f9f93d1c | |||
c502d410b1 | |||
53f96095c7 | |||
d212dfb9f9 | |||
0ec665516d | |||
acc642adf9 | |||
a6521708f7 | |||
206fe445bc | |||
a7ee92cde9 | |||
cdbab26008 | |||
1983c64b77 | |||
a6e3a7f5fe | |||
6dd687012f | |||
55b2872ffc | |||
2e6e7f6ca8 | |||
f453ce3126 |
88
changelog.md
Normal file
88
changelog.md
Normal file
@ -0,0 +1,88 @@
|
||||
# Changelog
|
||||
|
||||
## 2024-10-23 - 1.2.1 - fix(core)
|
||||
Fixed startup issue for the Cloudly instance
|
||||
|
||||
|
||||
## 2024-10-21 - 1.2.0 - feat(cli)
|
||||
Add tspublish.json for CLI client and interfaces
|
||||
|
||||
- Added registration details for publishing CLI client (@serve.zone/cli)
|
||||
- Specified npm and custom registries in the tspublish.json for better publish control
|
||||
- Updated dependency version for @git.zone/tspublish in package.json
|
||||
|
||||
## 2024-10-21 - 1.1.9 - fix(build)
|
||||
Update Node types and other dependencies, add tspublish.json for api client
|
||||
|
||||
- Updated Node types devDependency to version ^22.7.7 from ^22.7.5.
|
||||
- Updated smartbucket dependency to version ^3.0.23 from ^3.0.22.
|
||||
- Added tspublish configuration file to the api client.
|
||||
- Fixed the npm publish script in package.json.
|
||||
|
||||
## 2024-10-16 - 1.1.8 - fix(big fix upgrade)
|
||||
fix: update dependency versions and address type errors
|
||||
|
||||
- Updated all listed dependencies in the package.json to their specified ranges.
|
||||
- Fixed type mismatches and added missing imports in various TypeScript files.
|
||||
- Refined existing tests and added a new helper to manage Docker image streams.
|
||||
|
||||
## 2024-08-25 - 1.1.7 - fix(deps)
|
||||
Update dependencies to latest versions
|
||||
|
||||
- Updated @git.zone/tsbuild from ^2.1.80 to ^2.1.84
|
||||
- Updated @push.rocks/tapbundle from ^5.0.23 to ^5.0.24
|
||||
- Updated @types/node from ^20.14.6 to ^22.5.0
|
||||
- Updated @apiclient.xyz/docker from ^1.2.2 to ^1.2.3
|
||||
- Updated @design.estate/dees-catalog from ^1.0.289 to ^1.1.6
|
||||
- Updated @design.estate/dees-element from ^2.0.34 to ^2.0.36
|
||||
- Updated @git.zone/tsrun from ^1.2.37 to ^1.2.49
|
||||
- Updated @push.rocks/smartbucket from ^3.0.20 to ^3.0.22
|
||||
- Updated @push.rocks/smartpromise from ^4.0.3 to ^4.0.4
|
||||
- Updated @serve.zone/interfaces from ^1.0.74 to ^1.0.78
|
||||
- Updated @tsclass/tsclass from ^4.0.60 to ^4.1.2
|
||||
|
||||
## 2024-06-20 - 1.1.6 - Updates
|
||||
Routine updates and fixes.
|
||||
|
||||
- (fix) core: update
|
||||
|
||||
## 2024-06-13 - 1.1.4 - Service Management Preparation
|
||||
Incorporated updates and service management preparations.
|
||||
|
||||
- (fix) core: update
|
||||
- (feat) prepare service management
|
||||
|
||||
## 2024-06-05 - 1.1.3 - CI Integration Improvement
|
||||
Structural improvements and better CI integration preparation.
|
||||
|
||||
- (fix) structure: improve structure, prepare better CI integration
|
||||
|
||||
## 2024-06-02 - 1.1.2 - Image Manager Update
|
||||
Prepared proper storage and retrieval of container images.
|
||||
|
||||
- (fix) imagemanager: prepare proper storage and retrieval of container images
|
||||
|
||||
## 2024-06-01 - 1.1.0 - Image Registry Work
|
||||
Initiated work on image registry.
|
||||
|
||||
- (fix) image registry: start work on image registry
|
||||
|
||||
## 2024-05-30 - 1.0.216 - Enhanced Smartguards
|
||||
Enhanced smartguards to verify action authorization.
|
||||
|
||||
- (feat) guards: use better smartguards to verify action authorization
|
||||
|
||||
## 2024-05-28 - 1.0.215 - Unified Package Update
|
||||
Updated package unification for cloudly + API + CLI.
|
||||
|
||||
- (fix) switch to unified package for cloudly + API + CLI: update
|
||||
|
||||
## 2024-05-05 - 1.0.214 - Core Updates
|
||||
Routine core updates.
|
||||
|
||||
- (fix) core: update
|
||||
|
||||
## 2024-04-20 - 1.0.213 - Core Update
|
||||
Routine core updates.
|
||||
|
||||
- (fix) core: update
|
@ -16,26 +16,37 @@
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "servezone/private",
|
||||
"gitrepo": "cloudly",
|
||||
"description": "A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.",
|
||||
"description": "A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.",
|
||||
"npmPackagename": "@serve.zone/cloudly",
|
||||
"license": "UNLICENSED",
|
||||
"keywords": [
|
||||
"cloud management",
|
||||
"multi-cloud management",
|
||||
"Docker Swarmkit",
|
||||
"multi-cloud",
|
||||
"container orchestration",
|
||||
"cloud services",
|
||||
"DigitalOcean",
|
||||
"Hetzner Cloud",
|
||||
"Cloudflare",
|
||||
"container orchestration",
|
||||
"configuration management",
|
||||
"SSL management",
|
||||
"API integration",
|
||||
"TypeScript",
|
||||
"node.js",
|
||||
"infrastructure automation",
|
||||
"Cloudron",
|
||||
"configuration management",
|
||||
"SSL management",
|
||||
"APIs",
|
||||
"devOps",
|
||||
"cloud integration"
|
||||
"cloud API client",
|
||||
"system logging",
|
||||
"secret management",
|
||||
"CI/CD integration",
|
||||
"task scheduling",
|
||||
"frontend",
|
||||
"backend",
|
||||
"CLI",
|
||||
"web interface",
|
||||
"cloud providers",
|
||||
"security",
|
||||
"logging"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
82
package.json
82
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "@serve.zone/cloudly",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.1",
|
||||
"private": false,
|
||||
"description": "A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.",
|
||||
"description": "A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
@ -18,55 +18,60 @@
|
||||
"start": "node cli.js",
|
||||
"startTs": "node cli.ts.js",
|
||||
"watch": "tswatch website",
|
||||
"localPublish": "gitzone commit"
|
||||
"publish": "tspublish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.1.80",
|
||||
"@git.zone/tsbuild": "^2.1.84",
|
||||
"@git.zone/tsbundle": "^2.0.15",
|
||||
"@git.zone/tspublish": "^1.3.0",
|
||||
"@git.zone/tstest": "^1.0.90",
|
||||
"@git.zone/tswatch": "^2.0.23",
|
||||
"@push.rocks/tapbundle": "^5.0.23",
|
||||
"@types/node": "^20.12.14"
|
||||
"@push.rocks/tapbundle": "^5.3.0",
|
||||
"@types/node": "^22.7.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@api.global/typedrequest": "3.0.29",
|
||||
"@api.global/typedserver": "^3.0.50",
|
||||
"@api.global/typedrequest": "3.1.10",
|
||||
"@api.global/typedserver": "^3.0.51",
|
||||
"@api.global/typedsocket": "^3.0.1",
|
||||
"@apiclient.xyz/cloudflare": "^6.0.1",
|
||||
"@apiclient.xyz/digitalocean": "^1.0.5",
|
||||
"@apiclient.xyz/hetznercloud": "^1.0.18",
|
||||
"@apiclient.xyz/docker": "^1.2.7",
|
||||
"@apiclient.xyz/hetznercloud": "^1.2.0",
|
||||
"@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",
|
||||
"@design.estate/dees-catalog": "^1.2.0",
|
||||
"@design.estate/dees-domtools": "^2.0.64",
|
||||
"@design.estate/dees-element": "^2.0.39",
|
||||
"@git.zone/tsrun": "^1.2.49",
|
||||
"@push.rocks/early": "^4.0.3",
|
||||
"@push.rocks/npmextra": "^5.0.13",
|
||||
"@push.rocks/npmextra": "^5.0.23",
|
||||
"@push.rocks/projectinfo": "^5.0.1",
|
||||
"@push.rocks/qenv": "^6.0.5",
|
||||
"@push.rocks/smartacme": "^4.0.8",
|
||||
"@push.rocks/smartbucket": "^3.0.9",
|
||||
"@push.rocks/smartacme": "^5.0.0",
|
||||
"@push.rocks/smartbucket": "^3.0.23",
|
||||
"@push.rocks/smartcli": "^4.0.11",
|
||||
"@push.rocks/smartdata": "^5.2.4",
|
||||
"@push.rocks/smartclickhouse": "^2.0.17",
|
||||
"@push.rocks/smartdata": "^5.2.10",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartexit": "^1.0.23",
|
||||
"@push.rocks/smartfile": "^11.0.15",
|
||||
"@push.rocks/smartguard": "^3.0.2",
|
||||
"@push.rocks/smartexpect": "^1.2.1",
|
||||
"@push.rocks/smartfile": "^11.0.21",
|
||||
"@push.rocks/smartguard": "^3.1.0",
|
||||
"@push.rocks/smartjson": "^5.0.19",
|
||||
"@push.rocks/smartjwt": "^2.0.4",
|
||||
"@push.rocks/smartlog": "^3.0.6",
|
||||
"@push.rocks/smartlog-destination-clickhouse": "^1.0.11",
|
||||
"@push.rocks/smartjwt": "^2.2.1",
|
||||
"@push.rocks/smartlog": "^3.0.7",
|
||||
"@push.rocks/smartlog-destination-clickhouse": "^1.0.13",
|
||||
"@push.rocks/smartpath": "^5.0.18",
|
||||
"@push.rocks/smartpromise": "^4.0.3",
|
||||
"@push.rocks/smartpromise": "^4.0.4",
|
||||
"@push.rocks/smartrequest": "^2.0.22",
|
||||
"@push.rocks/smartrx": "^3.0.7",
|
||||
"@push.rocks/smartssh": "^2.0.1",
|
||||
"@push.rocks/smartstate": "^2.0.19",
|
||||
"@push.rocks/smartstream": "^3.2.4",
|
||||
"@push.rocks/smartstring": "^4.0.15",
|
||||
"@push.rocks/smartunique": "^3.0.9",
|
||||
"@push.rocks/taskbuffer": "^3.0.2",
|
||||
"@push.rocks/webjwt": "^1.0.9",
|
||||
"@serve.zone/interfaces": "^1.0.61",
|
||||
"@tsclass/tsclass": "^4.0.54"
|
||||
"@serve.zone/interfaces": "^1.1.2",
|
||||
"@tsclass/tsclass": "^4.1.2"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@ -93,20 +98,31 @@
|
||||
"homepage": "https://gitlab.com/servezone/private/cloudly#readme",
|
||||
"keywords": [
|
||||
"cloud management",
|
||||
"multi-cloud management",
|
||||
"Docker Swarmkit",
|
||||
"multi-cloud",
|
||||
"container orchestration",
|
||||
"cloud services",
|
||||
"DigitalOcean",
|
||||
"Hetzner Cloud",
|
||||
"Cloudflare",
|
||||
"container orchestration",
|
||||
"configuration management",
|
||||
"SSL management",
|
||||
"API integration",
|
||||
"TypeScript",
|
||||
"node.js",
|
||||
"infrastructure automation",
|
||||
"Cloudron",
|
||||
"configuration management",
|
||||
"SSL management",
|
||||
"APIs",
|
||||
"devOps",
|
||||
"cloud integration"
|
||||
"cloud API client",
|
||||
"system logging",
|
||||
"secret management",
|
||||
"CI/CD integration",
|
||||
"task scheduling",
|
||||
"frontend",
|
||||
"backend",
|
||||
"CLI",
|
||||
"web interface",
|
||||
"cloud providers",
|
||||
"security",
|
||||
"logging"
|
||||
]
|
||||
}
|
||||
|
12196
pnpm-lock.yaml
generated
12196
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
- This repository contains 4 projects around serve.zone
|
||||
- the cloudly backend under ts/*
|
||||
- the cloudly frontend under ts_web/*
|
||||
- the api client under ts_apiclient
|
||||
- the cli client under ts_cliclient
|
||||
|
||||
- the easiest method to spawn up a cloudly instance is to use the docker image:
|
||||
`code.foss.global/serve.zone/cloudly:latest`
|
||||
|
||||
- Note: the exports are defined in the package.json.
|
||||
- For now, cloud wise only the setup with cloudron and hetzner cloud is supported.
|
60
readme.md
60
readme.md
@ -1,21 +1,28 @@
|
||||
# @serve.zone/cloudly
|
||||
A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.
|
||||
|
||||
A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and integrate robust configuration and API management capabilities.
|
||||
|
||||
## 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:
|
||||
|
||||
First, import the `Cloudly` class from the package and initialize it as shown below:
|
||||
|
||||
```typescript
|
||||
import { Cloudly } from '@serve.zone/cloudly';
|
||||
@ -26,6 +33,7 @@ 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:
|
||||
@ -38,16 +46,22 @@ const myCloudlyConfig = {
|
||||
mongoDbUser: 'myUser',
|
||||
mongoDbPass: 'myPassword',
|
||||
},
|
||||
// Additional configuration values...
|
||||
cfToken: 'your_cloudflare_api_token',
|
||||
environment: 'development',
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
```
|
||||
|
||||
#### Managing Docker Swarmkit Cluster
|
||||
### Managing Docker Swarmkit Clusters
|
||||
|
||||
Cloudly allows managing Docker Swarmkit clusters through an abstracted interface, simplifying operations such as deployment and scaling. Below are examples to demonstrate these capabilities.
|
||||
|
||||
### Example: Start a Cloudly Instance and Add a Cluster
|
||||
#### Example: Initializing a Cloudly Instance and Adding a Cluster
|
||||
|
||||
```typescript
|
||||
import { Cloudly, ClusterManager } from '@serve.zone/cloudly';
|
||||
@ -65,7 +79,7 @@ async function main() {
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token'
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
@ -88,10 +102,15 @@ async function main() {
|
||||
|
||||
console.log('Cluster added:', newCluster);
|
||||
}
|
||||
|
||||
main();
|
||||
```
|
||||
|
||||
### Example: Manage Cloudflare DNS Records
|
||||
### Additional Use Cases
|
||||
|
||||
#### Managing Cloudflare DNS Records
|
||||
|
||||
You can manage Cloudflare DNS records using the `CloudflareConnector` provided by Cloudly.
|
||||
|
||||
```typescript
|
||||
import { Cloudly, CloudflareConnector } from '@serve.zone/cloudly';
|
||||
@ -109,7 +128,7 @@ async function manageDNSRecords() {
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token'
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
@ -135,7 +154,9 @@ async function manageDNSRecords() {
|
||||
manageDNSRecords();
|
||||
```
|
||||
|
||||
### Example: Integrate with DigitalOcean
|
||||
#### Integrating with DigitalOcean
|
||||
|
||||
Integrate with DigitalOcean to manage droplets and other resources.
|
||||
|
||||
```typescript
|
||||
import { Cloudly, DigitalOceanConnector } from '@serve.zone/cloudly';
|
||||
@ -153,7 +174,7 @@ async function manageDroplet() {
|
||||
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||
publicUrl: 'example.com',
|
||||
publicPort: 8443,
|
||||
hetznerToken: 'your_hetzner_api_token'
|
||||
hetznerToken: 'your_hetzner_api_token',
|
||||
};
|
||||
|
||||
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||
@ -180,8 +201,11 @@ manageDroplet();
|
||||
```
|
||||
|
||||
### Using Cloudly Web Interface
|
||||
|
||||
If your project includes a web interface to manage various sections like DNS, deployments, clusters, etc., you can use the provided elements and state management. Below is an example of setting up a dashboard using the components defined:
|
||||
|
||||
#### Web Dashboard Example
|
||||
|
||||
```typescript
|
||||
import { commitinfo } from '../00_commitinfo_data.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
@ -212,7 +236,7 @@ import { CloudlyViewServices } from './cloudly-view-services.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'cvault-dashboard': CloudlyDashboard;
|
||||
'cloudly-dashboard': CloudlyDashboard;
|
||||
}
|
||||
}
|
||||
|
||||
@ -227,6 +251,7 @@ export class CloudlyDashboard extends DeesElement {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
document.title = `cloudly v${commitinfo.version}`;
|
||||
const subcription = appstate.dataState
|
||||
.select((stateArg) => stateArg)
|
||||
.subscribe((dataArg) => {
|
||||
@ -309,6 +334,10 @@ export class CloudlyDashboard extends DeesElement {
|
||||
name: 'Backups',
|
||||
element: CloudlyViewBackups,
|
||||
},
|
||||
{
|
||||
name: 'Fleet',
|
||||
element: CloudlyViewBackups,
|
||||
}
|
||||
] as plugins.deesCatalog.IView[]}
|
||||
></dees-simple-appdash>
|
||||
</dees-simple-login>
|
||||
@ -329,7 +358,7 @@ export class CloudlyDashboard extends DeesElement {
|
||||
action: async () => {
|
||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||
heading: 'About',
|
||||
content: html`configvault ${commitinfo.version}`,
|
||||
content: html`cloudly ${commitinfo.version}`,
|
||||
menuOptions: [
|
||||
{
|
||||
name: 'close',
|
||||
@ -352,11 +381,12 @@ export class CloudlyDashboard extends DeesElement {
|
||||
if (loginState.jwt) {
|
||||
this.jwt = loginState.jwt;
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
||||
await appstate.dataState.dispatchAction(appstate.getAllDataAction, 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');
|
||||
@ -370,7 +400,7 @@ export class CloudlyDashboard extends DeesElement {
|
||||
this.jwt = state.jwt;
|
||||
form.setStatus('success', 'Logged in!');
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
||||
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
|
||||
} else {
|
||||
form.setStatus('error', 'Login failed!');
|
||||
await domtools.convenience.smartdelay.delayFor(2000);
|
||||
@ -382,8 +412,6 @@ export class CloudlyDashboard extends DeesElement {
|
||||
}
|
||||
```
|
||||
|
||||
This script sets up a cloud management dashboard for interacting with various cloud services seamlessly. It covers creating clusters, managing DNS records, handling cloud-provider-specific resources, and much more.
|
||||
|
||||
With the examples provided above, you should now have a good understanding of how to use `@serve.zone/cloudly` to manage your cloud infrastructure programmatically. For deeper insights and additional features, refer to the documentation relevant to specific modules and methods used in your application.
|
||||
|
||||
## License and Legal Information
|
||||
|
27
test/helpers/cloudlyfactory.ts
Normal file
27
test/helpers/cloudlyfactory.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { Qenv } from '@push.rocks/qenv';
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
|
||||
import * as cloudly from '../../ts/index.js';
|
||||
|
||||
export const createCloudly = 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'),
|
||||
mongoDbUser: await testQenv.getEnvVarOnDemand('MONGODB_USER'),
|
||||
mongoDbPass: await testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
|
||||
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGODB_URL'),
|
||||
},
|
||||
};
|
||||
const cloudlyInstance = new cloudly.Cloudly();
|
||||
return cloudlyInstance;
|
||||
}
|
||||
|
||||
export const getEnvVarOnDemand = async (envVarName: string) => {
|
||||
return testQenv.getEnvVarOnDemand(envVarName);
|
||||
}
|
||||
|
9
test/helpers/docker.ts
Normal file
9
test/helpers/docker.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import * as smartstream from '@push.rocks/smartstream';
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
|
||||
export const getAlpineImageReadableStream = async () => {
|
||||
const currentDir = smartpath.get.dirnameFromImportMetaUrl(import.meta.url);
|
||||
const imagePath = smartpath.join(currentDir, '../../.nogit/testfiles/alpine.tar');
|
||||
const readableStream = smartstream.nodewebhelpers.createWebReadableStreamFromFile(imagePath);
|
||||
return readableStream;
|
||||
}
|
2
test/helpers/index.ts
Normal file
2
test/helpers/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './cloudlyfactory.js';
|
||||
export * from './docker.js';
|
80
test/test.apiclient.ts
Normal file
80
test/test.apiclient.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as helpers from './helpers/index.js';
|
||||
|
||||
import * as cloudly from '../ts/index.js';
|
||||
import * as cloudlyApiClient from '../ts_apiclient/index.js';
|
||||
import { Image } from '../ts_apiclient/classes.image.js';
|
||||
|
||||
let testCloudly: cloudly.Cloudly;
|
||||
let testClient: cloudlyApiClient.CloudlyApiClient;
|
||||
|
||||
tap.preTask('should start cloudly', async () => {
|
||||
testCloudly = await helpers.createCloudly();
|
||||
await testCloudly.start();
|
||||
});
|
||||
|
||||
tap.preTask('should create a new machine user for testing', async () => {
|
||||
const machineUser = new testCloudly.authManager.CUser();
|
||||
machineUser.id = await testCloudly.authManager.CUser.getNewId();
|
||||
machineUser.data = {
|
||||
type: 'machine',
|
||||
username: 'test',
|
||||
password: 'test',
|
||||
tokens: [{
|
||||
token: 'test',
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['admin'],
|
||||
}],
|
||||
role: 'admin',
|
||||
};
|
||||
await machineUser.save();
|
||||
});
|
||||
|
||||
tap.test('should create a new cloudlyApiClient', async () => {
|
||||
testClient = new cloudlyApiClient.CloudlyApiClient({
|
||||
registerAs: 'api',
|
||||
cloudlyUrl: `http://localhost:${await helpers.getEnvVarOnDemand('SERVEZONE_PORT')}`,
|
||||
});
|
||||
await testClient.start();
|
||||
expect(testClient).toBeTruthy();
|
||||
});
|
||||
|
||||
tap.test('create a new machine user', async () => {
|
||||
const machineUser = await testCloudly.authManager.CUser.createMachineUser('test', 'api');
|
||||
machineUser.data.tokens.push({
|
||||
token: 'test',
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['api'],
|
||||
})
|
||||
await machineUser.save();
|
||||
})
|
||||
|
||||
tap.test('should get an identity', async () => {
|
||||
const identity = await testClient.getIdentityByToken('test');
|
||||
expect(identity).toBeTruthy();
|
||||
console.log(identity);
|
||||
});
|
||||
|
||||
let image: Image;
|
||||
tap.test('should create and upload an image', async () => {
|
||||
image = await testClient.images.createImage({
|
||||
name: 'test',
|
||||
description: 'test'
|
||||
});
|
||||
console.log('created image: ', image);
|
||||
expect(image).toBeInstanceOf(Image);
|
||||
})
|
||||
|
||||
tap.test('should upload an image version', async () => {
|
||||
const imageStream = await helpers.getAlpineImageReadableStream();
|
||||
|
||||
await image.pushImageVersion('v1.0.0', imageStream);
|
||||
});
|
||||
|
||||
tap.test('should stop the apiclient', async (toolsArg) => {
|
||||
await toolsArg.delayFor(10000);
|
||||
await testClient.stop();
|
||||
await testCloudly.stop();
|
||||
})
|
||||
|
||||
export default tap.start();
|
22
test/test.ts
22
test/test.ts
@ -1,29 +1,11 @@
|
||||
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 helpers from './helpers/index.js';
|
||||
|
||||
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);
|
||||
testCloudly = await helpers.createCloudly();
|
||||
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/cloudly',
|
||||
version: '1.1.1',
|
||||
description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.'
|
||||
version: '1.2.1',
|
||||
description: 'A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.'
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ for (let i = 0; i < demoSecretGroups.length; i++) {
|
||||
id: `configBundleId${i + 1}`,
|
||||
data: {
|
||||
name: `Demo Config Bundle ${i + 1}`,
|
||||
includedImages: [],
|
||||
type: 'external',
|
||||
description: 'Demo Purpose',
|
||||
includedSecretGroupIds: [secretGroup.id],
|
||||
includedTags: secretGroup.data.tags,
|
20
ts/00demo/demo.data.users.ts
Normal file
20
ts/00demo/demo.data.users.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
|
||||
export const getUsers = async (cloudlyRef: Cloudly) => {
|
||||
const users: plugins.servezoneInterfaces.data.IUser[] = [];
|
||||
const envAdminUser = await cloudlyRef.config.appData.waitForAndGetKey('servezoneAdminaccount');
|
||||
if (envAdminUser) {
|
||||
users.push({
|
||||
id: 'envadmin',
|
||||
data: {
|
||||
type: 'human',
|
||||
username: envAdminUser.split(':')[0],
|
||||
password: envAdminUser.split(':')[1],
|
||||
role: 'admin',
|
||||
},
|
||||
});
|
||||
}
|
||||
return users;
|
||||
};
|
@ -43,7 +43,7 @@ export const installDemoData = async (cloudlyRef: Cloudly) => {
|
||||
}
|
||||
|
||||
const demoDataUsers = await import('./demo.data.users.js');
|
||||
for (const user of demoDataUsers.users) {
|
||||
for (const user of await demoDataUsers.getUsers(cloudlyRef)) {
|
||||
const userInstance = new cloudlyRef.authManager.CUser();
|
||||
Object.assign(userInstance, user);
|
||||
await userInstance.save();
|
@ -15,10 +15,10 @@ import { MongodbConnector } from './connector.mongodb/connector.js';
|
||||
|
||||
// processes
|
||||
import { CloudlyCoreflowManager } from './manager.coreflow/coreflowmanager.js';
|
||||
import { ClusterManager } from './manager.cluster/clustermanager.js';
|
||||
import { ClusterManager } from './manager.cluster/classes.clustermanager.js';
|
||||
import { CloudlyTaskmanager } from './manager.task/taskmanager.js';
|
||||
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.js'
|
||||
import { CloudlyServerManager } from './manager.server/servermanager.js';
|
||||
import { CloudlyServerManager } from './manager.server/classes.servermanager.js';
|
||||
import { ExternalApiManager } from './manager.status/statusmanager.js';
|
||||
import { ImageManager } from './manager.image/classes.imagemanager.js';
|
||||
import { logger } from './logger.js';
|
||||
|
@ -40,6 +40,7 @@ export class CloudlyConfig {
|
||||
useSsl: true,
|
||||
},
|
||||
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
|
||||
servezoneAdminaccount: 'SERVEZONE_ADMINACCOUNT',
|
||||
},
|
||||
requiredKeys: [
|
||||
'cfToken',
|
||||
|
@ -10,7 +10,8 @@ export class CloudlyServer {
|
||||
/**
|
||||
* a reference to the cloudly instance
|
||||
*/
|
||||
private cloudlyRef: Cloudly;
|
||||
public cloudlyRef: Cloudly;
|
||||
public additionalHandlers: plugins.typedserver.servertools.Handler[] = [];
|
||||
|
||||
/**
|
||||
* the smartexpress server handling the actual requests
|
||||
@ -37,18 +38,24 @@ export class CloudlyServer {
|
||||
* init the reception instance
|
||||
*/
|
||||
public async start() {
|
||||
logger.log('info', `cloudly domain is ${this.cloudlyRef.config.data.publicUrl}`)
|
||||
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...`)
|
||||
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}`)
|
||||
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.`)
|
||||
logger.log(
|
||||
'info',
|
||||
`Using external certificate for ssl mode, meaning cloudly is not in charge of ssl termination.`
|
||||
);
|
||||
}
|
||||
|
||||
interface IRequestGuardData {
|
||||
@ -72,11 +79,13 @@ export class CloudlyServer {
|
||||
this.typedServer = new plugins.typedserver.TypedServer({
|
||||
cors: true,
|
||||
forceSsl: false,
|
||||
port: this.cloudlyRef.config.data.publicPort,
|
||||
...(sslCert ? {
|
||||
port: this.cloudlyRef.config.data.publicPort,
|
||||
...(sslCert
|
||||
? {
|
||||
privateKey: sslCert.privateKey,
|
||||
publicKey: sslCert.publicKey,
|
||||
} : {}),
|
||||
}
|
||||
: {}),
|
||||
injectReload: true,
|
||||
serveDir: paths.distServeDir,
|
||||
watch: true,
|
||||
@ -84,6 +93,10 @@ export class CloudlyServer {
|
||||
preferredCompressionMethod: 'gzip',
|
||||
});
|
||||
this.typedServer.typedrouter.addTypedRouter(this.typedrouter);
|
||||
this.typedServer.server.addRoute(
|
||||
'/curlfresh/:scriptname',
|
||||
this.cloudlyRef.serverManager.curlfreshInstance.handler
|
||||
);
|
||||
await this.typedServer.start();
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ export class LetsencryptConnector {
|
||||
},
|
||||
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
|
||||
});
|
||||
await this.smartacme.init().catch(err => {
|
||||
await this.smartacme.start().catch(err => {
|
||||
console.error('error in init', err);
|
||||
console.log(`trying again in a few minutes`)
|
||||
});
|
||||
|
@ -1,12 +0,0 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export const users: plugins.servezoneInterfaces.data.IUser[] = [
|
||||
{
|
||||
id: 'user1',
|
||||
data: {
|
||||
username: 'admin',
|
||||
password: 'password',
|
||||
role: 'admin',
|
||||
}
|
||||
}
|
||||
]
|
@ -21,8 +21,10 @@ const runCli = async () => {
|
||||
);
|
||||
|
||||
await cloudlyInstance.start();
|
||||
const demoMod = await import('./demo/index.js');
|
||||
const demoMod = await import('./00demo/index.js');
|
||||
demoMod.installDemoData(cloudlyInstance);
|
||||
};
|
||||
|
||||
export { runCli, Cloudly };
|
||||
type ICloudlyConfig = plugins.servezoneInterfaces.data.ICloudlyConfig;
|
||||
export { type ICloudlyConfig }
|
||||
|
@ -9,6 +9,7 @@ import { User } from './classes.user.js';
|
||||
export interface IJwtData {
|
||||
userId: string;
|
||||
status: 'loggedIn' | 'loggedOut';
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export class CloudlyAuthManager {
|
||||
@ -27,18 +28,22 @@ export class CloudlyAuthManager {
|
||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||
}
|
||||
|
||||
public async createNewSecureToken() {
|
||||
return plugins.smartunique.uniSimple('secureToken', 64);
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets setup the smartjwtInstance
|
||||
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
||||
await this.smartjwtInstance.init();
|
||||
const kvStore = await this.cloudlyRef.config.appData.getKvStore();
|
||||
|
||||
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys');
|
||||
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = (await kvStore.readKey('jwtKeypair')) as plugins.tsclass.network.IJwtKeypair;
|
||||
|
||||
if (!existingJwtKeys) {
|
||||
await this.smartjwtInstance.createNewKeyPair();
|
||||
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
||||
await kvStore.writeKey('jwtKeys', newJwtKeys);
|
||||
await kvStore.writeKey('jwtKeypair', newJwtKeys);
|
||||
} else {
|
||||
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
||||
}
|
||||
@ -48,18 +53,28 @@ export class CloudlyAuthManager {
|
||||
'adminLoginWithUsernameAndPassword',
|
||||
async (dataArg) => {
|
||||
let jwt: string;
|
||||
let expiresAtTimestamp: number = Date.now() + 3600 * 1000 * 24 * 7;
|
||||
const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password);
|
||||
if (!user) {
|
||||
logger.log('warn', 'login failed');
|
||||
throw new plugins.typedrequest.TypedResponseError('login failed');
|
||||
} else {
|
||||
jwt = await this.smartjwtInstance.createJWT({
|
||||
userId: user.id,
|
||||
status: 'loggedIn',
|
||||
expiresAt: expiresAtTimestamp,
|
||||
});
|
||||
logger.log('success', 'login successful');
|
||||
}
|
||||
return {
|
||||
identity: {
|
||||
jwt,
|
||||
userId: user.id,
|
||||
name: user.data.username,
|
||||
expiresAt: expiresAtTimestamp,
|
||||
role: user.data.role,
|
||||
type: user.data.type,
|
||||
},
|
||||
};
|
||||
}
|
||||
)
|
||||
@ -68,14 +83,33 @@ export class CloudlyAuthManager {
|
||||
|
||||
public async stop () {}
|
||||
|
||||
public adminJwtGuard = new plugins.smartguard.Guard<{jwt: string}>(async (dataArg) => {
|
||||
const jwt = dataArg.jwt;
|
||||
public validIdentityGuard = new plugins.smartguard.Guard<{identity: plugins.servezoneInterfaces.data.IIdentity}>(async (dataArg) => {
|
||||
const jwt = dataArg.identity.jwt;
|
||||
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
|
||||
const expired = jwtData.expiresAt < Date.now();
|
||||
plugins.smartexpect.expect(jwtData.status).setFailMessage('user not logged in').toEqual('loggedIn');
|
||||
plugins.smartexpect.expect(expired).setFailMessage(`jwt expired`).toBeFalse();
|
||||
plugins.smartexpect.expect(dataArg.identity.expiresAt).setFailMessage(`expiresAt >>identity valid until:${dataArg.identity.expiresAt}, but jwt says: ${jwtData.expiresAt}<< has been tampered with`).toEqual(jwtData.expiresAt);
|
||||
plugins.smartexpect.expect(dataArg.identity.userId).setFailMessage('userId has been tampered with').toEqual(jwtData.userId);
|
||||
if (expired) {
|
||||
throw new Error('identity is expired');
|
||||
}
|
||||
return true;
|
||||
}, {
|
||||
failedHint: 'identity is not valid.',
|
||||
name: 'validIdentityGuard',
|
||||
});
|
||||
|
||||
public adminIdentityGuard = new plugins.smartguard.Guard<{identity: plugins.servezoneInterfaces.data.IIdentity}>(async (dataArg) => {
|
||||
await plugins.smartguard.passGuardsOrReject(dataArg, [this.validIdentityGuard]);
|
||||
const jwt = dataArg.identity.jwt;
|
||||
const jwtData: IJwtData = await this.smartjwtInstance.verifyJWTAndGetData(jwt);
|
||||
const user = await this.CUser.getInstance({id: jwtData.userId});
|
||||
const isAdminBool = user.data.role === 'admin';
|
||||
console.log(`user is admin: ${isAdminBool}`);
|
||||
return isAdminBool;
|
||||
}, {
|
||||
failedHint: 'user is not admin.'
|
||||
failedHint: 'user is not admin.',
|
||||
name: 'adminIdentityGuard',
|
||||
})
|
||||
}
|
@ -5,6 +5,26 @@ export class User extends plugins.smartdata.SmartDataDbDoc<
|
||||
User,
|
||||
plugins.servezoneInterfaces.data.IUser
|
||||
> {
|
||||
/**
|
||||
* creates a machine user
|
||||
*/
|
||||
public static async createMachineUser(userNameArg: string, roleArg: 'api' | 'cluster') {
|
||||
const user = new User();
|
||||
user.id = await User.getNewId();
|
||||
user.data = {
|
||||
type: 'machine',
|
||||
username: userNameArg,
|
||||
tokens: [{
|
||||
token: 'machineUser',
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['admin'],
|
||||
}],
|
||||
role: 'api',
|
||||
};
|
||||
await user.save();
|
||||
return user;
|
||||
}
|
||||
|
||||
public static async findUserByUsernameAndPassword(usernameArg: string, passwordArg: string) {
|
||||
return await User.getInstance({
|
||||
data: {
|
||||
@ -14,14 +34,17 @@ export class User extends plugins.smartdata.SmartDataDbDoc<
|
||||
});
|
||||
}
|
||||
|
||||
constructor(optionsArg?: plugins.servezoneInterfaces.data.IUser) {
|
||||
super();
|
||||
if (optionsArg) {
|
||||
Object.assign(this, optionsArg);
|
||||
}
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
@plugins.smartdata.unI()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: {
|
||||
role: 'admin' | 'user';
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
public data: plugins.servezoneInterfaces.data.IUser['data'];
|
||||
}
|
||||
|
5
ts/manager.cert/cert.ts
Normal file
5
ts/manager.cert/cert.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export class Cert extends plugins.smartdata.SmartDataDbDoc<Cert, Cert> {
|
||||
|
||||
}
|
14
ts/manager.cert/certmanager.ts
Normal file
14
ts/manager.cert/certmanager.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
|
||||
export class CertManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
public get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
|
||||
constructor(cloudly: Cloudly) {
|
||||
this.cloudlyRef = cloudly;
|
||||
}
|
||||
}
|
@ -3,7 +3,8 @@ import * as paths from '../paths.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
import { Cluster } from './cluster.js';
|
||||
import { Cluster } from './classes.cluster.js';
|
||||
import { data } from '@serve.zone/interfaces';
|
||||
|
||||
export class ClusterManager {
|
||||
public ready = plugins.smartpromise.defer();
|
||||
@ -22,13 +23,12 @@ export class ClusterManager {
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_CreateCluster>(
|
||||
new plugins.typedrequest.TypedHandler('createCluster', async (dataArg) => {
|
||||
const cluster = await this.storeCluster({
|
||||
// TODO: guards
|
||||
const cluster = await this.createCluster({
|
||||
id: plugins.smartunique.uniSimple('cluster'),
|
||||
data: {
|
||||
userId: null, // this is created by the createCluster method
|
||||
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: [],
|
||||
@ -54,6 +54,17 @@ export class ClusterManager {
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// delete cluster
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.cluster.IRequest_DeleteCluster>(
|
||||
new plugins.typedrequest.TypedHandler('deleteCluster', async (reqDataArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqDataArg);
|
||||
await this.deleteCluster(reqDataArg.clusterId);
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async init() {
|
||||
@ -70,25 +81,22 @@ export class ClusterManager {
|
||||
// TODO: implement getclusterConfigByServerIp
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_JumpCode(jumpCodeArg: string) {
|
||||
public async getClusterBy_UserId(userIdArg: string) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
jumpCode: jumpCodeArg,
|
||||
userId: userIdArg,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public async getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
) {
|
||||
public async getClusterBy_Identity(clusterIdentity: plugins.servezoneInterfaces.data.IIdentity) {
|
||||
await this.ready.promise;
|
||||
|
||||
return await Cluster.getInstance({
|
||||
data: {
|
||||
name: clusterIdentifier.clusterName,
|
||||
secretKey: clusterIdentifier.secretKey,
|
||||
userId: clusterIdentity.userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
@ -114,18 +122,39 @@ export class ClusterManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* allows storage of a config
|
||||
* creates a cluster (and a new user for it) and saves it
|
||||
* @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);
|
||||
}
|
||||
public async createCluster(configObjectArg: plugins.servezoneInterfaces.data.ICluster) {
|
||||
// TODO: guards
|
||||
// lets create the cluster user
|
||||
const clusterUser = new this.cloudlyRef.authManager.CUser({
|
||||
id: await this.cloudlyRef.authManager.CUser.getNewId(),
|
||||
data: {
|
||||
role: 'cluster',
|
||||
type: 'machine',
|
||||
tokens: [
|
||||
{
|
||||
expiresAt: Date.now() + 3600 * 1000 * 24 * 365,
|
||||
assignedRoles: ['cluster'],
|
||||
token: await this.cloudlyRef.authManager.createNewSecureToken(),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
await clusterUser.save();
|
||||
Object.assign(configObjectArg, {
|
||||
userId: clusterUser.id,
|
||||
});
|
||||
const clusterInstance = await Cluster.fromConfigObject(configObjectArg);
|
||||
await clusterInstance.save();
|
||||
return clusterInstance;
|
||||
}
|
||||
|
||||
public async deleteCluster(clusterId: string) {
|
||||
await this.ready.promise;
|
||||
const clusterInstance = await Cluster.getInstance({ id: clusterId });
|
||||
await clusterInstance.delete();
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
import type { Cluster } from '../manager.cluster/classes.cluster.js';
|
||||
|
||||
/**
|
||||
* in charge of talking to coreflow services on clusters
|
||||
@ -13,24 +14,42 @@ export class CloudlyCoreflowManager {
|
||||
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
|
||||
);
|
||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
|
||||
new plugins.typedrequest.TypedHandler('getIdentityByToken', async (requestData) => {
|
||||
const user = await this.cloudlyRef.authManager.CUser.getInstance({
|
||||
data: {
|
||||
tokens: [{
|
||||
token: requestData.token,
|
||||
}] // find the proper user here.
|
||||
} as any
|
||||
});
|
||||
|
||||
if (!clusterConfig) {
|
||||
throw new plugins.typedrequest.TypedResponseError('The supplied jumpCode is not valid.');
|
||||
if (!user) {
|
||||
throw new plugins.typedrequest.TypedResponseError('The supplied token is not valid. No matching user found.');
|
||||
}
|
||||
|
||||
if (user.data.type !== 'machine') {
|
||||
throw new plugins.typedrequest.TypedResponseError('The supplied token is not valid. The user is not a machine.');
|
||||
}
|
||||
let cluster: Cluster;
|
||||
if (user.data.role === 'cluster') {
|
||||
cluster = await this.cloudlyRef.clusterManager.getClusterBy_UserId(user.id);
|
||||
}
|
||||
const expiryTimestamp = Date.now() + 3600 * 1000 * 24 * 365;
|
||||
return {
|
||||
clusterIdentifier: {
|
||||
clusterId: clusterConfig.id,
|
||||
clusterName: clusterConfig.data.name,
|
||||
identity: {
|
||||
name: user.data.username,
|
||||
role: user.data.role,
|
||||
type: 'machine', // if someone authenticates by token, they are a machine, no matter what.
|
||||
userId: user.id,
|
||||
expiresAt: expiryTimestamp,
|
||||
...(cluster ? {
|
||||
clusterId: cluster.id,
|
||||
clusterName: cluster.data.name,
|
||||
} : {}),
|
||||
jwt: await this.cloudlyRef.authManager.smartjwtInstance.createJWT({
|
||||
status: 'loggedIn',
|
||||
userId: 'cluster:' + clusterConfig.id, // TODO: create real users for clusters
|
||||
userId: user.id,
|
||||
expiresAt: expiryTimestamp,
|
||||
})
|
||||
},
|
||||
};
|
||||
@ -42,16 +61,16 @@ export class CloudlyCoreflowManager {
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
|
||||
'getClusterConfig',
|
||||
async (dataArg) => {
|
||||
const clusterIdentifier = dataArg.clusterIdentifier;
|
||||
const identity = dataArg.identity;
|
||||
console.log('trying to get clusterConfigSet');
|
||||
console.log(dataArg);
|
||||
const clusterConfigSet =
|
||||
await this.cloudlyRef.clusterManager.getClusterConfigBy_ClusterIdentifier(
|
||||
clusterIdentifier
|
||||
const cluster =
|
||||
await this.cloudlyRef.clusterManager.getClusterBy_Identity(
|
||||
identity
|
||||
);
|
||||
console.log('got cluster config and sending it back to coreflow');
|
||||
return {
|
||||
configData: await clusterConfigSet.createSavableObject(),
|
||||
configData: await cluster.createSavableObject(),
|
||||
deploymentDirectives: [],
|
||||
};
|
||||
}
|
||||
@ -60,14 +79,14 @@ export class CloudlyCoreflowManager {
|
||||
|
||||
// lets enable getting of certificates
|
||||
this.typedRouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
|
||||
'getSslCertificate',
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
|
||||
'getCertificateForDomain',
|
||||
async (dataArg) => {
|
||||
console.log(`got request for certificate ${dataArg.requiredCertName}`);
|
||||
console.log(`incoming API request for certificate ${dataArg.domainName}`);
|
||||
const cert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||
dataArg.requiredCertName
|
||||
dataArg.domainName
|
||||
);
|
||||
console.log(`got certificate ready for reponse ${dataArg.requiredCertName}`);
|
||||
console.log(`got certificate ready for reponse ${dataArg.domainName}`);
|
||||
return {
|
||||
certificate: await cert.createSavableObject(),
|
||||
};
|
||||
|
@ -26,4 +26,20 @@ export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.serve
|
||||
public data: plugins.servezoneInterfaces.data.IImage['data'];
|
||||
|
||||
public async getVersions() {}
|
||||
|
||||
/**
|
||||
* returns a storage path
|
||||
* note: this is relative to the storage method defined by the imageManager
|
||||
*/
|
||||
public async getStoragePath(versionStringArg: string) {
|
||||
return `${this.data.name}:${versionStringArg}`.replace('/', '__')
|
||||
}
|
||||
|
||||
public async getWriteStream() {
|
||||
|
||||
}
|
||||
|
||||
public async getReadStream() {
|
||||
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as paths from '../paths.js';
|
||||
|
||||
import { Image } from './classes.image.js';
|
||||
|
||||
@ -7,6 +8,8 @@ export class ImageManager {
|
||||
cloudlyRef: Cloudly;
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public smartbucketInstance: plugins.smartbucket.SmartBucket;
|
||||
public imageDir: plugins.smartbucket.Directory;
|
||||
public dockerImageStore: plugins.docker.DockerImageStore;
|
||||
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
@ -14,7 +17,6 @@ export class ImageManager {
|
||||
|
||||
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
|
||||
|
||||
|
||||
constructor(cloudlyRefArg: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRefArg;
|
||||
|
||||
@ -24,7 +26,7 @@ export class ImageManager {
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||
'createImage',
|
||||
async (reqArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], reqArg);
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
|
||||
const image = await this.CImage.create({
|
||||
name: reqArg.name,
|
||||
description: reqArg.description,
|
||||
@ -35,13 +37,25 @@ export class ImageManager {
|
||||
};
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
|
||||
new plugins.typedrequest.TypedHandler('getImage', async (reqArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
|
||||
const image = await this.CImage.getInstance({
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
return {
|
||||
image: await image.createSavableObject(),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_DeleteImage>(
|
||||
'deleteImage',
|
||||
async (reqArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], reqArg);
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], reqArg);
|
||||
const image = await this.CImage.getInstance({
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
@ -55,7 +69,7 @@ export class ImageManager {
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||
'getAllImages',
|
||||
async (requestArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], requestArg);
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], requestArg);
|
||||
const images = await this.CImage.getInstances({});
|
||||
return {
|
||||
images: await Promise.all(
|
||||
@ -71,9 +85,37 @@ export class ImageManager {
|
||||
this.typedrouter.addTypedHandler(
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
|
||||
'pushImageVersion',
|
||||
async (reqArg) => {
|
||||
const pushStream = reqArg.imageStream;
|
||||
return {};
|
||||
async (reqArg, toolsArg) => {
|
||||
await plugins.smartguard.passGuardsOrReject(reqArg, [
|
||||
this.cloudlyRef.authManager.validIdentityGuard,
|
||||
]);
|
||||
const refImage = await this.CImage.getInstance({
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
if (!refImage) {
|
||||
throw new plugins.typedrequest.TypedResponseError('Image not found');
|
||||
}
|
||||
const imageVersion = reqArg.versionString;
|
||||
console.log(
|
||||
`got request to push image version ${imageVersion} for image ${refImage.data.name}`
|
||||
);
|
||||
const imagePushStream = reqArg.imageStream;
|
||||
(async () => {
|
||||
const smartWebDuplex = new plugins.smartstream.webstream.WebDuplexStream<
|
||||
Uint8Array,
|
||||
Uint8Array
|
||||
>({
|
||||
writeFunction: async (chunkArg, toolsArg) => {
|
||||
console.log(chunkArg);
|
||||
return chunkArg;
|
||||
},
|
||||
});
|
||||
imagePushStream.writeToWebstream(smartWebDuplex.writable);
|
||||
await this.dockerImageStore.storeImage(refImage.id, plugins.smartstream.SmartDuplex.fromWebReadableStream(smartWebDuplex.readable));
|
||||
})();
|
||||
return {
|
||||
allowed: true,
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
@ -83,11 +125,17 @@ export class ImageManager {
|
||||
'pullImageVersion',
|
||||
async (reqArg) => {
|
||||
const image = await this.CImage.getInstance({
|
||||
data: {
|
||||
name: reqArg.name,
|
||||
},
|
||||
id: reqArg.imageId,
|
||||
});
|
||||
const imageVersion = null;
|
||||
const imageVersion = image.data.versions.find(
|
||||
(version) => version.versionString === reqArg.versionString
|
||||
);
|
||||
const readable = this.imageDir.fastGetStream(
|
||||
{
|
||||
path: await image.getStoragePath(reqArg.versionString),
|
||||
},
|
||||
'webstream'
|
||||
);
|
||||
const imageVirtualStream = new plugins.typedrequest.VirtualStream();
|
||||
return {
|
||||
imageStream: imageVirtualStream,
|
||||
@ -98,6 +146,7 @@ export class ImageManager {
|
||||
}
|
||||
|
||||
public async start() {
|
||||
// lets setup s3
|
||||
const s3Descriptor: plugins.tsclass.storage.IS3Descriptor =
|
||||
await this.cloudlyRef.config.appData.waitForAndGetKey('s3Descriptor');
|
||||
console.log(this.cloudlyRef.config.data.s3Descriptor);
|
||||
@ -105,12 +154,17 @@ export class ImageManager {
|
||||
this.cloudlyRef.config.data.s3Descriptor
|
||||
);
|
||||
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
|
||||
await bucket.fastPut({ path: 'test/test.txt', contents: 'hello' });
|
||||
}
|
||||
await bucket.fastPut({ path: 'images/00init', contents: 'init' });
|
||||
|
||||
public async createImage(nameArg: string) {
|
||||
const newImage = await this.CImage.create({
|
||||
name: nameArg,
|
||||
this.imageDir = await bucket.getDirectoryFromPath({
|
||||
path: '/images',
|
||||
});
|
||||
|
||||
// lets setup dockerstore
|
||||
await plugins.smartfile.fs.ensureDir(paths.dockerImageStoreDir);
|
||||
this.dockerImageStore = new plugins.docker.DockerImageStore({
|
||||
localDirPath: paths.dockerImageStoreDir,
|
||||
bucketDir: this.imageDir,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ export class CloudlySecretManager {
|
||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
||||
'adminGetConfigBundlesAndSecretGroups',
|
||||
async (dataArg, toolsArg) => {
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], dataArg);
|
||||
dataArg.jwt
|
||||
await toolsArg.passGuards([this.cloudlyRef.authManager.adminIdentityGuard], dataArg);
|
||||
dataArg.identity.jwt
|
||||
const secretBundles = await SecretBundle.getInstances({});
|
||||
const secretGroups = await SecretGroup.getInstances({});
|
||||
return {
|
||||
|
87
ts/manager.server/classes.curlfresh.ts
Normal file
87
ts/manager.server/classes.curlfresh.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { logger } from '../logger.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import type { CloudlyServerManager } from './classes.servermanager.js';
|
||||
|
||||
export class CurlFresh {
|
||||
public optionsArg = {
|
||||
npmRegistry: 'https://registry.npmjs.org',
|
||||
}
|
||||
public scripts = {
|
||||
'setup.sh': `#!/bin/bash
|
||||
|
||||
# lets update the system and install curl
|
||||
# might be installed already, but entrypoint could have been wget
|
||||
apt-get update
|
||||
apt-get install -y --force-yes curl
|
||||
|
||||
# Basic updating of the software lists
|
||||
echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
apt-get update
|
||||
apt-get upgrade -y --force-yes
|
||||
apt-get install -y --force-yes fail2ban curl git
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | bash
|
||||
|
||||
# Install docker
|
||||
curl -sSL https://get.docker.com/ | sh
|
||||
|
||||
# Install default nodejs to run nodejs tools
|
||||
apt-get install -y nodejs zsh
|
||||
zsh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
|
||||
npm config set unsafe-perm true
|
||||
|
||||
# lets install pnpm
|
||||
curl -fsSL https://get.pnpm.io/install.sh | sh -
|
||||
|
||||
# lets make sure we use the correct npm registry
|
||||
bash -c "npm config set registry ${this.optionsArg.npmRegistry}"
|
||||
|
||||
# lets install spark
|
||||
bash -c "pnpm install -g @serve.zone/spark"
|
||||
|
||||
# lets install the spark daemon
|
||||
bash -c "spark installdaemon"
|
||||
|
||||
# TODO: start spark with jump code
|
||||
`,
|
||||
};
|
||||
|
||||
public serverManagerRef: CloudlyServerManager;
|
||||
public curlFreshRoute: plugins.typedserver.servertools.Route;
|
||||
public handler = new plugins.typedserver.servertools.Handler('ALL', async (req, res) => {
|
||||
logger.log('info', 'curlfresh handler called. a server might be coming online soon :)');
|
||||
const scriptname = req.params.scriptname;
|
||||
switch(scriptname) {
|
||||
case 'setup.sh':
|
||||
logger.log('info', 'sending setup.sh');
|
||||
res.type('application/x-sh');
|
||||
res.send(this.scripts['setup.sh']);
|
||||
break;
|
||||
default:
|
||||
res.send('no script found');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
constructor(serverManagerRefArg: CloudlyServerManager) {
|
||||
this.serverManagerRef = serverManagerRefArg;
|
||||
}
|
||||
public async getServerUserData(): Promise<string> {
|
||||
const sslMode = await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('sslMode');
|
||||
let protocol: 'http' | 'https';
|
||||
if (sslMode === 'none') {
|
||||
protocol = 'http';
|
||||
} else {
|
||||
protocol = 'https';
|
||||
}
|
||||
|
||||
const domain = await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('publicUrl');
|
||||
const port = await this.serverManagerRef.cloudlyRef.config.appData.waitForAndGetKey('publicPort');
|
||||
|
||||
const serverUserData = `#cloud-config
|
||||
runcmd:
|
||||
- curl -o- ${protocol}://${domain}:${port}/curlfresh/setup.sh | sh
|
||||
`
|
||||
console.log(serverUserData);
|
||||
return serverUserData;
|
||||
};
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Cloudly } from '../classes.cloudly.js';
|
||||
import { Cluster } from '../manager.cluster/cluster.js';
|
||||
import { Server } from './server.js';
|
||||
import { Cluster } from '../manager.cluster/classes.cluster.js';
|
||||
import { Server } from './classes.server.js';
|
||||
import { CurlFresh } from './classes.curlfresh.js';
|
||||
|
||||
export class CloudlyServerManager {
|
||||
public cloudlyRef: Cloudly;
|
||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||
public curlfreshInstance = new CurlFresh(this);
|
||||
|
||||
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
||||
|
||||
@ -63,7 +65,8 @@ export class CloudlyServerManager {
|
||||
labels: {
|
||||
clusterId: cluster.id,
|
||||
priority: '1',
|
||||
}
|
||||
},
|
||||
userData: await this.curlfreshInstance.getServerUserData()
|
||||
});
|
||||
const newServer = await Server.createFromHetznerServer(server);
|
||||
console.log(`cluster created new server for cluster ${cluster.id}`);
|
@ -0,0 +1,13 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import { ServiceManager } from './classes.servicemanager.js';
|
||||
|
||||
export class Service extends plugins.smartdata.SmartDataDbDoc<Service, plugins.servezoneInterfaces.data.IService, ServiceManager> {
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public id: string;
|
||||
|
||||
@plugins.smartdata.svDb()
|
||||
public data: plugins.servezoneInterfaces.data.IService['data'];
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import type { Cloudly } from '../classes.cloudly.js';
|
||||
import * as plugins from '../plugins.js';
|
||||
import { Service } from './classes.service.js';
|
||||
|
||||
export class ServiceManager {
|
||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||
public cloudlyRef: Cloudly;
|
||||
|
||||
get db() {
|
||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||
}
|
||||
|
||||
public CService = plugins.smartdata.setDefaultManagerForDoc(this, Service);
|
||||
|
||||
constructor(cloudlyRef: Cloudly) {
|
||||
this.cloudlyRef = cloudlyRef;
|
||||
}
|
||||
}
|
@ -2,4 +2,5 @@ import * as plugins from './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 dockerImageStoreDir = plugins.path.join(nogitDir, './dockerimagestore/');
|
||||
export const distServeDir = plugins.path.join(packageDir, './dist_serve');
|
||||
|
@ -9,13 +9,13 @@ import * as typedsocket from '@api.global/typedsocket';
|
||||
|
||||
export { typedrequest, typedsocket };
|
||||
|
||||
// @mojoio scope
|
||||
// @apiclient.xyz scope
|
||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
||||
import * as digitalocean from '@apiclient.xyz/digitalocean';
|
||||
import * as docker from '@apiclient.xyz/docker';
|
||||
import * as hetznercloud from '@apiclient.xyz/hetznercloud';
|
||||
import * as slack from '@apiclient.xyz/slack';
|
||||
|
||||
export { cloudflare, digitalocean, hetznercloud, slack };
|
||||
export { cloudflare, docker, hetznercloud, slack };
|
||||
|
||||
// @tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
@ -29,9 +29,11 @@ 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 smartclickhouse from '@push.rocks/smartclickhouse';
|
||||
import * as smartdata from '@push.rocks/smartdata';
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
import * as smartexit from '@push.rocks/smartexit';
|
||||
import * as smartexpect from '@push.rocks/smartexpect';
|
||||
import * as smartfile from '@push.rocks/smartfile';
|
||||
import * as smartguard from '@push.rocks/smartguard';
|
||||
import * as smartjson from '@push.rocks/smartjson';
|
||||
@ -41,6 +43,7 @@ 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 smartstream from '@push.rocks/smartstream';
|
||||
import * as smartstring from '@push.rocks/smartstring';
|
||||
import * as smartunique from '@push.rocks/smartunique';
|
||||
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||
@ -53,8 +56,10 @@ export {
|
||||
smartacme,
|
||||
smartbucket,
|
||||
smartcli,
|
||||
smartclickhouse,
|
||||
smartdata,
|
||||
smartexit,
|
||||
smartexpect,
|
||||
smartdelay,
|
||||
smartfile,
|
||||
smartguard,
|
||||
@ -65,6 +70,7 @@ export {
|
||||
smartpromise,
|
||||
smartrequest,
|
||||
smartssh,
|
||||
smartstream,
|
||||
smartstring,
|
||||
smartunique,
|
||||
taskbuffer,
|
||||
|
@ -1,8 +1,10 @@
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export type TClientType = 'coreflow' | 'cli' | 'serverconfig';
|
||||
export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig';
|
||||
|
||||
export class CloudlyClient {
|
||||
import { Image } from './classes.image.js';
|
||||
|
||||
export class CloudlyApiClient {
|
||||
private cloudlyUrl: string;
|
||||
private registerAs: string;
|
||||
|
||||
@ -18,9 +20,13 @@ export class CloudlyClient {
|
||||
plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
|
||||
>();
|
||||
|
||||
constructor(registerAsArg: TClientType) {
|
||||
this.cloudlyUrl = process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443';
|
||||
this.registerAs = registerAsArg;
|
||||
constructor(optionsArg?: {
|
||||
registerAs: TClientType;
|
||||
cloudlyUrl?: string;
|
||||
}) {
|
||||
this.registerAs = optionsArg.registerAs;
|
||||
this.cloudlyUrl =
|
||||
optionsArg?.cloudlyUrl || process.env.CLOUDLY_URL || 'https://cloudly.layer.io:443';
|
||||
|
||||
console.log(
|
||||
`creating LoleCloudlyClient: registering as ${this.registerAs} and target url ${this.cloudlyUrl}`
|
||||
@ -33,13 +39,6 @@ export class CloudlyClient {
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig>(
|
||||
new plugins.typedrequest.TypedHandler('pushClusterConfig', async (dataArg) => {
|
||||
this.configUpdateSubject.next(dataArg);
|
||||
return {};
|
||||
})
|
||||
);
|
||||
|
||||
this.typedrouter.addTypedHandler<plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction>(
|
||||
new plugins.typedrequest.TypedHandler('triggerServerAction', async (dataArg) => {
|
||||
this.serverActionSubject.next(dataArg);
|
||||
@ -55,76 +54,114 @@ export class CloudlyClient {
|
||||
this.typedrouter,
|
||||
this.cloudlyUrl
|
||||
);
|
||||
console.log(
|
||||
`CloudlyClient connected to cloudly at ${this.cloudlyUrl}. Remember to get an identity.`
|
||||
);
|
||||
}
|
||||
|
||||
public async stop() {
|
||||
await this.typedsocketClient.stop();
|
||||
}
|
||||
|
||||
public async getIdentityByJumpCode(
|
||||
jumpCodeArg: string,
|
||||
tagConnection = false
|
||||
): Promise<plugins.servezoneInterfaces.data.IClusterIdentifier> {
|
||||
public identity: plugins.servezoneInterfaces.data.IIdentity;
|
||||
public async getIdentityByToken(
|
||||
token: string,
|
||||
optionsArg?: {
|
||||
tagConnection?: boolean;
|
||||
statefullIdentity?: boolean;
|
||||
}
|
||||
): Promise<plugins.servezoneInterfaces.data.IIdentity> {
|
||||
optionsArg = Object.assign({}, {
|
||||
tagConnection: false,
|
||||
statefullIdentity: true,
|
||||
}, optionsArg);
|
||||
|
||||
const identityRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
|
||||
'getIdentityByJumpCode'
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByToken>(
|
||||
'getIdentityByToken'
|
||||
);
|
||||
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${jumpCodeArg}`);
|
||||
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${token}`);
|
||||
const response = await identityRequest.fire({
|
||||
jumpCode: jumpCodeArg,
|
||||
token: token,
|
||||
});
|
||||
console.log('got identity response');
|
||||
const identity = response.clusterIdentifier;
|
||||
const identity = response.identity;
|
||||
|
||||
if (tagConnection) {
|
||||
if (optionsArg.tagConnection) {
|
||||
this.typedsocketClient.addTag('identity', identity);
|
||||
}
|
||||
|
||||
if (optionsArg.statefullIdentity) {
|
||||
this.identity = identity;
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* will use statefull identity by default
|
||||
*/
|
||||
public async getClusterConfigFromCloudlyByIdentity(
|
||||
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
|
||||
): Promise<plugins.servezoneInterfaces.data.ICluster> {
|
||||
const clusterConfigRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetClusterConfig>(
|
||||
'getClusterConfig'
|
||||
);
|
||||
const response = await clusterConfigRequest.fire({
|
||||
jwt: '',
|
||||
clusterIdentifier: identityArg,
|
||||
identity: identityArg,
|
||||
});
|
||||
return response.configData;
|
||||
}
|
||||
|
||||
/**
|
||||
* will use statefull identity by default
|
||||
*/
|
||||
public async getServerConfigFromCloudlyByIdentity(
|
||||
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||
identityArg: plugins.servezoneInterfaces.data.IIdentity = this.identity
|
||||
): Promise<plugins.servezoneInterfaces.data.IServer> {
|
||||
const serverConfigRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.config.IRequest_Any_Cloudly_GetServerConfig>(
|
||||
'getServerConfig'
|
||||
);
|
||||
const response = await serverConfigRequest.fire({
|
||||
jwt: '', // TODO: do proper auth here
|
||||
serverId: '' // TODO: get server id here
|
||||
identity: identityArg,
|
||||
serverId: '', // TODO: get server id here
|
||||
});
|
||||
return response.configData;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a certificate for a domain used by a service
|
||||
* @param serviceNameArg
|
||||
* @param domainNameArg
|
||||
*/
|
||||
public async getCertificateForDomainOverHttps(domainNameArg: string): Promise<plugins.tsclass.network.ICert> {
|
||||
public async getCertificateForDomain(optionsArg: {
|
||||
domainName: string;
|
||||
type: plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain['request']['type'];
|
||||
identity?: plugins.servezoneInterfaces.data.IIdentity;
|
||||
}): Promise<plugins.tsclass.network.ICert> {
|
||||
optionsArg.identity = optionsArg.identity || this.identity;
|
||||
if (!optionsArg.identity) {
|
||||
throw new Error('identity is required. Either provide one or login first.');
|
||||
}
|
||||
const typedCertificateRequest =
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
|
||||
'getSslCertificate'
|
||||
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetCertificateForDomain>(
|
||||
'getCertificateForDomain'
|
||||
);
|
||||
const typedResponse = await typedCertificateRequest.fire({
|
||||
authToken: '', // do proper auth here
|
||||
requiredCertName: domainNameArg,
|
||||
identity: this.identity, // do proper auth here
|
||||
domainName: optionsArg.domainName,
|
||||
type: optionsArg.type,
|
||||
});
|
||||
return typedResponse.certificate;
|
||||
}
|
||||
|
||||
public images = {
|
||||
// Images
|
||||
getImages: async () => {
|
||||
return Image.getImages(this);
|
||||
},
|
||||
createImage: async (optionsArg: Parameters<typeof Image.createImage>[1]) => {
|
||||
return Image.createImage(this, optionsArg);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,100 @@
|
||||
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||
import * as plugins from './plugins.js';
|
||||
|
||||
export class Image {
|
||||
public getImages() {
|
||||
export class Image implements plugins.servezoneInterfaces.data.IImage {
|
||||
public static async getImages(cloudlyClientRef: CloudlyApiClient) {
|
||||
const getAllImagesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||
'getAllImages'
|
||||
);
|
||||
const response = await getAllImagesTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
});
|
||||
const resultImages: Image[] = [];
|
||||
for (const image of response.images) {
|
||||
const newImage = new Image(cloudlyClientRef);
|
||||
Object.assign(newImage, image);
|
||||
resultImages.push(newImage);
|
||||
}
|
||||
return resultImages;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new image
|
||||
*/
|
||||
public static async createImage(cloudlyClientRef: CloudlyApiClient, imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
|
||||
const createImageTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||
'createImage'
|
||||
);
|
||||
const response = await createImageTR.fire({
|
||||
identity: cloudlyClientRef.identity,
|
||||
name: imageDataArg.name,
|
||||
description: imageDataArg.description,
|
||||
});
|
||||
const newImage = new Image(cloudlyClientRef);
|
||||
Object.assign(newImage, response.image);
|
||||
return newImage;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
cloudlyClientRef: CloudlyApiClient;
|
||||
|
||||
id: plugins.servezoneInterfaces.data.IImage['id'];
|
||||
data: plugins.servezoneInterfaces.data.IImage['data'];
|
||||
|
||||
constructor(cloudlyClientRef: CloudlyApiClient) {
|
||||
this.cloudlyClientRef = cloudlyClientRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* updates the image data
|
||||
*/
|
||||
public async update() {
|
||||
const getVersionsTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetImage>(
|
||||
'getImage'
|
||||
);
|
||||
const response = await getVersionsTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
imageId: this.id,
|
||||
});
|
||||
Object.assign(this, response.image);
|
||||
}
|
||||
|
||||
/**
|
||||
* pushes a new version of the image
|
||||
* @param imageVersion
|
||||
* @param imageReadableArg
|
||||
*/
|
||||
public async pushImageVersion(imageVersion: string, imageReadableArg: ReadableStream<Uint8Array>): Promise<void> {
|
||||
const done = plugins.smartpromise.defer();
|
||||
const pushImageTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
|
||||
'pushImageVersion'
|
||||
);
|
||||
const virtualStream = new plugins.typedrequest.VirtualStream();
|
||||
const response = await pushImageTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
imageId: this.id,
|
||||
versionString: '',
|
||||
imageStream: virtualStream,
|
||||
});
|
||||
await virtualStream.readFromWebstream(imageReadableArg);
|
||||
await this.update();
|
||||
};
|
||||
|
||||
/**
|
||||
* pulls a version of the image
|
||||
*/
|
||||
public async pullImageVersion(versionStringArg: string): Promise<ReadableStream<Uint8Array>> {
|
||||
const pullImageTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
|
||||
'pullImageVersion'
|
||||
);
|
||||
const response = await pullImageTR.fire({
|
||||
identity: this.cloudlyClientRef.identity,
|
||||
imageId: this.id,
|
||||
versionString: versionStringArg,
|
||||
});
|
||||
const imageStream = response.imageStream;
|
||||
const webduplexStream = new plugins.webstream.WebDuplexStream({});
|
||||
imageStream.writeToWebstream(webduplexStream.writable);
|
||||
return webduplexStream.readable;
|
||||
};
|
||||
}
|
@ -1 +1 @@
|
||||
export * from './classes.cloudlyclient.js';
|
||||
export * from './classes.cloudlyapiclient.js';
|
@ -6,10 +6,14 @@ export {
|
||||
}
|
||||
|
||||
// @push.rocks scope
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrx from '@push.rocks/smartrx';
|
||||
import * as webstream from '@push.rocks/smartstream/web';
|
||||
|
||||
export {
|
||||
smartpromise,
|
||||
smartrx,
|
||||
webstream,
|
||||
}
|
||||
|
||||
// @api.global scope
|
||||
|
16
ts_apiclient/tspublish.json
Normal file
16
ts_apiclient/tspublish.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "@serve.zone/api",
|
||||
"dependencies": [
|
||||
"@serve.zone/interfaces",
|
||||
"@push.rocks/smartpromise",
|
||||
"@push.rocks/smartrx",
|
||||
"@push.rocks/smartstream",
|
||||
"@api.global/typedrequest",
|
||||
"@api.global/typedsocket",
|
||||
"@tsclass/tsclass"
|
||||
],
|
||||
"registries": [
|
||||
"registry.npmjs.org:public",
|
||||
"verdaccio.lossless.digital:public"
|
||||
]
|
||||
}
|
3
ts_cliclient/tspublish.json
Normal file
3
ts_cliclient/tspublish.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"name": "@serve.zone/cli"
|
||||
}
|
10
ts_interfaces/tspublish.json
Normal file
10
ts_interfaces/tspublish.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@serve.zone/interfaces",
|
||||
"dependencies": [
|
||||
"@tsclass/tsclass"
|
||||
],
|
||||
"registries": [
|
||||
"registry.npmjs.org:public",
|
||||
"verdaccio.lossless.digital:public"
|
||||
]
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @pushrocks/commitinfo
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@serve.zone/cloudly',
|
||||
version: '1.1.1',
|
||||
description: 'A cloud manager leveraging Docker Swarmkit for multi-cloud operations including DigitalOcean, Hetzner Cloud, and Cloudflare, with integration support and robust configuration management system.'
|
||||
version: '1.2.1',
|
||||
description: 'A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and provide robust configuration and API integration.'
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
const appstate = new plugins.deesDomtools.plugins.smartstate.Smartstate();
|
||||
export interface ILoginState {
|
||||
jwt: string;
|
||||
identity: plugins.interfaces.data.IIdentity;
|
||||
}
|
||||
export const loginStatePart = await appstate.getStatePart<ILoginState>(
|
||||
export const loginStatePart: plugins.smartstate.StatePart<unknown, ILoginState> = await appstate.getStatePart<ILoginState>(
|
||||
'login',
|
||||
{ jwt: null },
|
||||
{ identity: null },
|
||||
'persistent'
|
||||
);
|
||||
|
||||
@ -22,10 +22,15 @@ export const loginAction = loginStatePart.createAction<{ username: string; passw
|
||||
const response = await trLogin.fire({
|
||||
username: payloadArg.username,
|
||||
password: payloadArg.password,
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
return {
|
||||
...statePartArg.getState(),
|
||||
}
|
||||
});
|
||||
return {
|
||||
...currentState,
|
||||
...(response.jwt ? { jwt: response.jwt } : {}),
|
||||
...(response.identity ? { identity: response.identity } : {}),
|
||||
};
|
||||
}
|
||||
);
|
||||
@ -34,7 +39,7 @@ export const logoutAction = loginStatePart.createAction(async (statePartArg) =>
|
||||
const currentState = statePartArg.getState();
|
||||
return {
|
||||
...currentState,
|
||||
jwt: null,
|
||||
identity: null,
|
||||
};
|
||||
});
|
||||
|
||||
@ -81,7 +86,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
|
||||
'adminGetConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trGetSecrets.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
});
|
||||
currentState = {
|
||||
...currentState,
|
||||
@ -95,7 +100,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
|
||||
'getAllImages'
|
||||
);
|
||||
const responseImages = await trGetImages.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
});
|
||||
currentState = {
|
||||
...currentState,
|
||||
@ -109,7 +114,7 @@ export const getAllDataAction = dataState.createAction(async (statePartArg, part
|
||||
'getAllClusters'
|
||||
);
|
||||
const responseClusters = await trGetClusters.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
});
|
||||
|
||||
currentState = {
|
||||
@ -130,7 +135,7 @@ export const createSecretGroupAction = dataState.createAction(
|
||||
'adminCreateConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trCreateSecretGroup.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
secretBundles: [],
|
||||
secretGroups: [payloadArg],
|
||||
});
|
||||
@ -149,7 +154,7 @@ export const deleteSecretGroupAction = dataState.createAction(
|
||||
'adminDeleteConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trDeleteSecretGroup.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
secretBundleIds: [],
|
||||
secretGroupIds: [payloadArg.secretGroupId],
|
||||
});
|
||||
@ -168,7 +173,7 @@ export const deleteSecretBundleAction = dataState.createAction(
|
||||
'adminDeleteConfigBundlesAndSecretGroups'
|
||||
);
|
||||
const response = await trDeleteConfigBundle.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
secretBundleIds: [payloadArg.configBundleId],
|
||||
secretGroupIds: [],
|
||||
});
|
||||
@ -187,7 +192,7 @@ export const createImageAction = dataState.createAction(
|
||||
'createImage'
|
||||
);
|
||||
const response = await trCreateImage.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
name: payloadArg.imageName,
|
||||
description: payloadArg.description,
|
||||
});
|
||||
@ -210,7 +215,7 @@ export const deleteImageAction = dataState.createAction(
|
||||
'deleteImage'
|
||||
);
|
||||
const response = await trDeleteImage.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
imageId: payloadArg.imageId,
|
||||
});
|
||||
currentState = {
|
||||
@ -238,7 +243,7 @@ export const addClusterAction = dataState.createAction(
|
||||
'createCluster'
|
||||
);
|
||||
const response = await trAddCluster.fire({
|
||||
jwt: loginStatePart.getState().jwt,
|
||||
identity: loginStatePart.getState().identity,
|
||||
...payloadArg,
|
||||
});
|
||||
currentState = {
|
||||
|
@ -33,7 +33,7 @@ declare global {
|
||||
|
||||
@customElement('cloudly-dashboard')
|
||||
export class CloudlyDashboard extends DeesElement {
|
||||
@state() private jwt: string;
|
||||
@state() private identity: plugins.interfaces.data.IIdentity;
|
||||
@state() private data: appstate.IDataState = {
|
||||
secretGroups: [],
|
||||
secretBundles: [],
|
||||
@ -97,6 +97,10 @@ export class CloudlyDashboard extends DeesElement {
|
||||
name: 'Services',
|
||||
element: CloudlyViewServices,
|
||||
},
|
||||
{
|
||||
name: 'Testing & Building',
|
||||
element: CloudlyViewServices,
|
||||
},
|
||||
{
|
||||
name: 'Deployments',
|
||||
element: CloudlyViewDeployments,
|
||||
@ -169,8 +173,8 @@ export class CloudlyDashboard extends DeesElement {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const loginState = appstate.loginStatePart.getState();
|
||||
console.log(loginState);
|
||||
if (loginState.jwt) {
|
||||
this.jwt = loginState.jwt;
|
||||
if (loginState.identity) {
|
||||
this.identity = loginState.identity;
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
|
||||
}
|
||||
@ -186,9 +190,9 @@ export class CloudlyDashboard extends DeesElement {
|
||||
username,
|
||||
password,
|
||||
});
|
||||
if (state.jwt) {
|
||||
if (state.identity) {
|
||||
console.log('got jwt');
|
||||
this.jwt = state.jwt;
|
||||
this.identity = state.identity;
|
||||
form.setStatus('success', 'Logged in!');
|
||||
await simpleLogin.switchToSlottedContent();
|
||||
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewBackups extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewClusters extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewDbs extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewDeployments extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,8 @@ export class CloudlyViewDns extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import { DeesElement, customElement, html, state, css, cssManager } from '@design.estate/dees-element';
|
||||
|
||||
@ -23,13 +24,9 @@ export class CloudlyViewImages extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewLogs extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,14 +32,8 @@ export class CloudlyViewMails extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`,
|
||||
shared.viewHostCss,
|
||||
css``
|
||||
];
|
||||
|
||||
public render() {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,8 @@ export class CloudlyViewOverview extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
.clusterGrid {
|
||||
display: grid;
|
||||
grid-template-columns: ${cssManager.cssGridColumns(3, 8)};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewS3 extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewSecretBundles extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import { DeesElement, customElement, html, state, css, cssManager } from '@design.estate/dees-element';
|
||||
|
||||
@ -23,13 +24,9 @@ export class CloudlyViewSecretGroups extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import * as plugins from '../plugins.js';
|
||||
import * as shared from '../elements/shared/index.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@ -31,13 +32,9 @@ export class CloudlyViewServices extends DeesElement {
|
||||
|
||||
public static styles = [
|
||||
cssManager.defaultStyles,
|
||||
shared.viewHostCss,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
|
@ -21,6 +21,8 @@ export class CloudlySectionheading extends DeesElement {
|
||||
h1 {
|
||||
font-family: 'Cal Sans';
|
||||
letter-spacing: 0.025em;
|
||||
margin: 0px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`,
|
||||
]
|
||||
|
10
ts_web/elements/shared/css.ts
Normal file
10
ts_web/elements/shared/css.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { css } from '@design.estate/dees-element';
|
||||
|
||||
export const viewHostCss = css`
|
||||
:host {
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 1280px;
|
||||
padding: 16px 16px;
|
||||
}
|
||||
`;
|
@ -1 +1,2 @@
|
||||
export * from './cloudly-sectionheading.js';
|
||||
export * from './css.js';
|
@ -13,7 +13,9 @@ export { deesDomtools, deesElement, deesCatalog };
|
||||
|
||||
// @push.rocks scope
|
||||
import * as webjwt from '@push.rocks/webjwt';
|
||||
import * as smartstate from '@push.rocks/smartstate';
|
||||
|
||||
export {
|
||||
webjwt,
|
||||
smartstate,
|
||||
}
|
Reference in New Issue
Block a user