Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
206fe445bc | |||
a7ee92cde9 | |||
cdbab26008 | |||
1983c64b77 | |||
a6e3a7f5fe | |||
6dd687012f | |||
55b2872ffc | |||
2e6e7f6ca8 | |||
f453ce3126 | |||
b8dd84b8a6 | |||
338ed5ed75 | |||
482a6a101c | |||
929f250006 | |||
6b3fd2ce31 | |||
21961fa2d7 |
@ -16,21 +16,37 @@
|
|||||||
"githost": "gitlab.com",
|
"githost": "gitlab.com",
|
||||||
"gitscope": "servezone/private",
|
"gitscope": "servezone/private",
|
||||||
"gitrepo": "cloudly",
|
"gitrepo": "cloudly",
|
||||||
"description": "A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.",
|
"description": "A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and integrate robust configuration and API management capabilities.",
|
||||||
"npmPackagename": "@serve.zone/cloudly",
|
"npmPackagename": "@serve.zone/cloudly",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cloud",
|
"cloud management",
|
||||||
|
"multi-cloud",
|
||||||
"Docker Swarmkit",
|
"Docker Swarmkit",
|
||||||
"DigitalOcean",
|
"DigitalOcean",
|
||||||
"Hetzner Cloud",
|
"Hetzner Cloud",
|
||||||
"Cloudflare",
|
"Cloudflare",
|
||||||
"container management",
|
"container orchestration",
|
||||||
"configuration management",
|
"configuration management",
|
||||||
"LetsEncrypt SSL",
|
"SSL management",
|
||||||
"cloud infrastructure automation",
|
"API integration",
|
||||||
|
"TypeScript",
|
||||||
"node.js",
|
"node.js",
|
||||||
"TypeScript"
|
"cloud integration",
|
||||||
|
"infrastructure automation",
|
||||||
|
"devOps",
|
||||||
|
"cloud API client",
|
||||||
|
"system logging",
|
||||||
|
"secret management",
|
||||||
|
"CI/CD integration",
|
||||||
|
"task scheduling",
|
||||||
|
"frontend",
|
||||||
|
"backend",
|
||||||
|
"cli",
|
||||||
|
"web interface",
|
||||||
|
"cloud providers",
|
||||||
|
"security",
|
||||||
|
"logging"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
80
package.json
80
package.json
@ -1,67 +1,75 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/cloudly",
|
"name": "@serve.zone/cloudly",
|
||||||
"version": "1.0.215",
|
"version": "1.1.5",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.",
|
"description": "A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and integrate robust configuration and API management capabilities.",
|
||||||
"main": "dist_ts/index.js",
|
|
||||||
"typings": "dist_ts/index.d.ts",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./dist/index.js",
|
||||||
|
"./apiclient": "./dist_apiclient/index.js",
|
||||||
|
"./cliclient": "./dist_cliclient/index.js",
|
||||||
|
"./web": "./dist_web/index.js"
|
||||||
|
},
|
||||||
"author": "Task Venture Capital GmbH",
|
"author": "Task Venture Capital GmbH",
|
||||||
"license": "UNLICENSED",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/)",
|
"test": "(tstest test/)",
|
||||||
"build": "tsbuild --web --allowimplicitany && tsbundle website --production",
|
"build": "tsbuild tsfolders --web --allowimplicitany && tsbundle website --production",
|
||||||
"start": "node cli.js",
|
"start": "node cli.js",
|
||||||
"startTs": "node cli.ts.js",
|
"startTs": "node cli.ts.js",
|
||||||
"watch": "tswatch website",
|
"watch": "tswatch website",
|
||||||
"localPublish": "gitzone commit"
|
"localPublish": "gitzone commit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.65",
|
"@git.zone/tsbuild": "^2.1.80",
|
||||||
"@git.zone/tsbundle": "^2.0.15",
|
"@git.zone/tsbundle": "^2.0.15",
|
||||||
"@git.zone/tstest": "^1.0.90",
|
"@git.zone/tstest": "^1.0.90",
|
||||||
"@git.zone/tswatch": "^2.0.23",
|
"@git.zone/tswatch": "^2.0.23",
|
||||||
"@push.rocks/tapbundle": "^5.0.23",
|
"@push.rocks/tapbundle": "^5.0.23",
|
||||||
"@types/node": "^20.12.8"
|
"@types/node": "^20.14.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@api.global/typedrequest": "3.0.23",
|
"@api.global/typedrequest": "3.0.30",
|
||||||
"@api.global/typedserver": "^3.0.29",
|
"@api.global/typedserver": "^3.0.50",
|
||||||
"@api.global/typedsocket": "^3.0.1",
|
"@api.global/typedsocket": "^3.0.1",
|
||||||
"@apiclient.xyz/cloudflare": "^6.0.1",
|
"@apiclient.xyz/cloudflare": "^6.0.1",
|
||||||
"@apiclient.xyz/digitalocean": "^1.0.5",
|
"@apiclient.xyz/docker": "^1.2.2",
|
||||||
"@apiclient.xyz/hetznercloud": "^1.0.18",
|
"@apiclient.xyz/hetznercloud": "^1.2.0",
|
||||||
"@apiclient.xyz/slack": "^3.0.9",
|
"@apiclient.xyz/slack": "^3.0.9",
|
||||||
"@design.estate/dees-catalog": "^1.0.289",
|
"@design.estate/dees-catalog": "^1.0.289",
|
||||||
"@design.estate/dees-domtools": "^2.0.57",
|
"@design.estate/dees-domtools": "^2.0.57",
|
||||||
"@design.estate/dees-element": "^2.0.34",
|
"@design.estate/dees-element": "^2.0.34",
|
||||||
"@git.zone/tsrun": "^1.2.37",
|
"@git.zone/tsrun": "^1.2.37",
|
||||||
"@push.rocks/early": "^4.0.3",
|
"@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/projectinfo": "^5.0.1",
|
||||||
"@push.rocks/qenv": "^6.0.5",
|
"@push.rocks/qenv": "^6.0.5",
|
||||||
"@push.rocks/smartacme": "^4.0.8",
|
"@push.rocks/smartacme": "^5.0.0",
|
||||||
"@push.rocks/smartbucket": "^2.0.4",
|
"@push.rocks/smartbucket": "^3.0.20",
|
||||||
"@push.rocks/smartcli": "^4.0.10",
|
"@push.rocks/smartcli": "^4.0.11",
|
||||||
"@push.rocks/smartdata": "^5.2.1",
|
"@push.rocks/smartclickhouse": "^2.0.17",
|
||||||
|
"@push.rocks/smartdata": "^5.2.6",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@push.rocks/smartexit": "^1.0.23",
|
"@push.rocks/smartexit": "^1.0.23",
|
||||||
"@push.rocks/smartfile": "^11.0.14",
|
"@push.rocks/smartfile": "^11.0.20",
|
||||||
"@push.rocks/smartguard": "^2.0.1",
|
"@push.rocks/smartguard": "^3.0.2",
|
||||||
"@push.rocks/smartjson": "^5.0.19",
|
"@push.rocks/smartjson": "^5.0.19",
|
||||||
"@push.rocks/smartjwt": "^2.0.4",
|
"@push.rocks/smartjwt": "^2.0.4",
|
||||||
"@push.rocks/smartlog": "^3.0.1",
|
"@push.rocks/smartlog": "^3.0.7",
|
||||||
"@push.rocks/smartlog-destination-clickhouse": "^1.0.11",
|
"@push.rocks/smartlog-destination-clickhouse": "^1.0.13",
|
||||||
"@push.rocks/smartpath": "^5.0.18",
|
"@push.rocks/smartpath": "^5.0.18",
|
||||||
"@push.rocks/smartpromise": "^4.0.3",
|
"@push.rocks/smartpromise": "^4.0.3",
|
||||||
"@push.rocks/smartrequest": "^2.0.22",
|
"@push.rocks/smartrequest": "^2.0.22",
|
||||||
|
"@push.rocks/smartrx": "^3.0.7",
|
||||||
"@push.rocks/smartssh": "^2.0.1",
|
"@push.rocks/smartssh": "^2.0.1",
|
||||||
|
"@push.rocks/smartstate": "^2.0.17",
|
||||||
|
"@push.rocks/smartstream": "^3.0.44",
|
||||||
"@push.rocks/smartstring": "^4.0.15",
|
"@push.rocks/smartstring": "^4.0.15",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@push.rocks/taskbuffer": "^3.0.2",
|
"@push.rocks/taskbuffer": "^3.0.2",
|
||||||
"@push.rocks/webjwt": "^1.0.9",
|
"@push.rocks/webjwt": "^1.0.9",
|
||||||
"@serve.zone/interfaces": "^1.0.51",
|
"@serve.zone/interfaces": "^1.0.74",
|
||||||
"@tsclass/tsclass": "^4.0.54"
|
"@tsclass/tsclass": "^4.0.60"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
@ -87,16 +95,32 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/servezone/private/cloudly#readme",
|
"homepage": "https://gitlab.com/servezone/private/cloudly#readme",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cloud",
|
"cloud management",
|
||||||
|
"multi-cloud",
|
||||||
"Docker Swarmkit",
|
"Docker Swarmkit",
|
||||||
"DigitalOcean",
|
"DigitalOcean",
|
||||||
"Hetzner Cloud",
|
"Hetzner Cloud",
|
||||||
"Cloudflare",
|
"Cloudflare",
|
||||||
"container management",
|
"container orchestration",
|
||||||
"configuration management",
|
"configuration management",
|
||||||
"LetsEncrypt SSL",
|
"SSL management",
|
||||||
"cloud infrastructure automation",
|
"API integration",
|
||||||
|
"TypeScript",
|
||||||
"node.js",
|
"node.js",
|
||||||
"TypeScript"
|
"cloud integration",
|
||||||
|
"infrastructure automation",
|
||||||
|
"devOps",
|
||||||
|
"cloud API client",
|
||||||
|
"system logging",
|
||||||
|
"secret management",
|
||||||
|
"CI/CD integration",
|
||||||
|
"task scheduling",
|
||||||
|
"frontend",
|
||||||
|
"backend",
|
||||||
|
"cli",
|
||||||
|
"web interface",
|
||||||
|
"cloud providers",
|
||||||
|
"security",
|
||||||
|
"logging"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
11119
pnpm-lock.yaml
generated
11119
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,10 @@
|
|||||||
|
- 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.
|
378
readme.md
378
readme.md
@ -1,21 +1,28 @@
|
|||||||
# @serve.zone/cloudly
|
# @serve.zone/cloudly
|
||||||
configure the cloud
|
|
||||||
|
A comprehensive multi-cloud manager leveraging Docker Swarmkit for orchestrating containerized applications and integrating various cloud services like DigitalOcean, Hetzner Cloud, and Cloudflare, with robust configuration and API management capabilities.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
To install `@serve.zone/cloudly`, run the following command in your terminal:
|
To install `@serve.zone/cloudly`, run the following command in your terminal:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @serve.zone/cloudly --save
|
npm install @serve.zone/cloudly --save
|
||||||
```
|
```
|
||||||
|
|
||||||
This will install the package and add it to your project's `package.json` dependencies.
|
This will install the package and add it to your project's `package.json` dependencies.
|
||||||
|
|
||||||
## Usage
|
## 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.
|
`@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
|
### 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.
|
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
|
#### 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
|
```typescript
|
||||||
import { Cloudly } from '@serve.zone/cloudly';
|
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.
|
The `Cloudly` class is the entry point to using the library features. It prepares the environment for configuring the cloud services.
|
||||||
|
|
||||||
#### Configuration
|
#### 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.
|
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:
|
For example, to configure a connection to MongoDB, specify your MongoDB details as shown:
|
||||||
@ -38,51 +46,373 @@ const myCloudlyConfig = {
|
|||||||
mongoDbUser: 'myUser',
|
mongoDbUser: 'myUser',
|
||||||
mongoDbPass: 'myPassword',
|
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);
|
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.
|
|
||||||
|
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: Initializing a Cloudly Instance and Adding a Cluster
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Assuming myCloudlyInstance is already configured and initialized
|
import { Cloudly, ClusterManager } from '@serve.zone/cloudly';
|
||||||
|
|
||||||
// Start the cloud instance
|
async function main() {
|
||||||
await myCloudlyInstance.start();
|
const myCloudlyConfig = {
|
||||||
|
mongoDescriptor: {
|
||||||
|
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||||
|
mongoDbName: 'myDatabase',
|
||||||
|
mongoDbUser: 'myUser',
|
||||||
|
mongoDbPass: 'myPassword',
|
||||||
|
},
|
||||||
|
cfToken: 'your_cloudflare_api_token',
|
||||||
|
environment: 'development',
|
||||||
|
letsEncryptEmail: 'lets_encrypt_email@example.com',
|
||||||
|
publicUrl: 'example.com',
|
||||||
|
publicPort: 8443,
|
||||||
|
hetznerToken: 'your_hetzner_api_token',
|
||||||
|
};
|
||||||
|
|
||||||
// Now you can perform various operations on your Docker Swarmkit cluster
|
const myCloudlyInstance = new Cloudly(myCloudlyConfig);
|
||||||
|
await myCloudlyInstance.start();
|
||||||
|
|
||||||
|
const clusterManager = myCloudlyInstance.clusterManager;
|
||||||
|
const newCluster = await clusterManager.storeCluster({
|
||||||
|
id: 'example_cluster_id',
|
||||||
|
data: {
|
||||||
|
name: 'example_cluster',
|
||||||
|
jumpCode: 'random_jump_code',
|
||||||
|
jumpCodeUsedAt: null,
|
||||||
|
secretKey: 'example_secret_key',
|
||||||
|
acmeInfo: null,
|
||||||
|
cloudlyUrl: 'https://example.com:8443',
|
||||||
|
servers: [],
|
||||||
|
sshKeys: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Cluster added:', newCluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Integration with Cloud Providers
|
### Additional Use Cases
|
||||||
`@serve.zone/cloudly` integrates seamlessly with cloud providers like DigitalOcean, Hetzner Cloud, etc., by leveraging the power of APIs provided by these platforms.
|
|
||||||
|
|
||||||
#### Managing DigitalOcean Resources
|
#### Managing Cloudflare DNS Records
|
||||||
To manage DigitalOcean resources, you'll need to configure your DigitalOcean token and then use the provided interfaces to interact with the resources, such as creating droplets, managing volumes, etc.
|
|
||||||
|
You can manage Cloudflare DNS records using the `CloudflareConnector` provided by Cloudly.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Set your DigitalOcean API token
|
import { Cloudly, CloudflareConnector } from '@serve.zone/cloudly';
|
||||||
const digitalOceanToken = "your_digital_ocean_api_token";
|
|
||||||
|
|
||||||
// Now you can use myCloudlyInstance to manage DigitalOcean resources
|
async function manageDNSRecords() {
|
||||||
|
const myCloudlyConfig = {
|
||||||
|
mongoDescriptor: {
|
||||||
|
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||||
|
mongoDbName: 'myDatabase',
|
||||||
|
mongoDbUser: 'myUser',
|
||||||
|
mongoDbPass: 'myPassword',
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
await myCloudlyInstance.start();
|
||||||
|
|
||||||
|
const dnsInfo = {
|
||||||
|
zoneName: 'example.com',
|
||||||
|
recordName: 'sub.example.com',
|
||||||
|
recordType: 'A',
|
||||||
|
recordContent: '127.0.0.1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const cfConnector = myCloudlyInstance.cloudflareConnector.cloudflare;
|
||||||
|
const newRecord = await cfConnector.createDNSRecord(
|
||||||
|
dnsInfo.zoneName,
|
||||||
|
dnsInfo.recordName,
|
||||||
|
dnsInfo.recordType,
|
||||||
|
dnsInfo.recordContent
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('DNS Record created:', newRecord);
|
||||||
|
}
|
||||||
|
manageDNSRecords();
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Using the Cloudflare Integration
|
#### Integrating with DigitalOcean
|
||||||
Similarly, for managing DNS records and SSL certificates with Cloudflare, set up your Cloudflare API token:
|
|
||||||
|
Integrate with DigitalOcean to manage droplets and other resources.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const cloudflareToken = "your_cloudflare_api_token";
|
import { Cloudly, DigitalOceanConnector } from '@serve.zone/cloudly';
|
||||||
|
|
||||||
// Use myCloudlyInstance to interact with Cloudflare, such as setting DNS records
|
async function manageDroplet() {
|
||||||
|
const myCloudlyConfig = {
|
||||||
|
mongoDescriptor: {
|
||||||
|
mongoDbUrl: 'mongodb+srv://<username>:<password>@<cluster>.mongodb.net/myFirstDatabase',
|
||||||
|
mongoDbName: 'myDatabase',
|
||||||
|
mongoDbUser: 'myUser',
|
||||||
|
mongoDbPass: 'myPassword',
|
||||||
|
},
|
||||||
|
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);
|
||||||
|
await myCloudlyInstance.start();
|
||||||
|
|
||||||
|
const doConnector = myCloudlyInstance.digitaloceanConnector;
|
||||||
|
const dropletInfo = {
|
||||||
|
name: 'example-droplet',
|
||||||
|
region: 'nyc3',
|
||||||
|
size: 's-1vcpu-1gb',
|
||||||
|
image: 'ubuntu-20-04-x64',
|
||||||
|
};
|
||||||
|
|
||||||
|
const newDroplet = await doConnector.createDroplet(
|
||||||
|
dropletInfo.name,
|
||||||
|
dropletInfo.region,
|
||||||
|
dropletInfo.size,
|
||||||
|
dropletInfo.image
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Droplet created:', newDroplet);
|
||||||
|
}
|
||||||
|
manageDroplet();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Advanced Usage
|
### Using Cloudly Web Interface
|
||||||
`@serve.zone/cloudly` offers more than just cloud resource management. It integrates various modules for error logging, security, working with JSON data, and much more. Explore the comprehensive documentation and typings to leverage the full potential of the package.
|
|
||||||
|
|
||||||
### Conclusion
|
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:
|
||||||
With `@serve.zone/cloudly`, configuring the cloud becomes a less tedious task. By abstracting away the complexities and providing a unified interface to manage cloud resources, development efficiency is significantly improved. The examples provided above merely scratch the surface of what's possible. Dive into the detailed documentation to explore all features and capabilities.
|
|
||||||
|
#### Web Dashboard Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { commitinfo } from '../00_commitinfo_data.js';
|
||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
import * as appstate from '../appstate.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
css,
|
||||||
|
cssManager,
|
||||||
|
customElement,
|
||||||
|
html,
|
||||||
|
state
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
import { CloudlyViewBackups } from './cloudly-view-backups.js';
|
||||||
|
import { CloudlyViewClusters } from './cloudly-view-clusters.js';
|
||||||
|
import { CloudlyViewDbs } from './cloudly-view-dbs.js';
|
||||||
|
import { CloudlyViewDeployments } from './cloudly-view-deployments.js';
|
||||||
|
import { CloudlyViewDns } from './cloudly-view-dns.js';
|
||||||
|
import { CloudlyViewImages } from './cloudly-view-images.js';
|
||||||
|
import { CloudlyViewLogs } from './cloudly-view-logs.js';
|
||||||
|
import { CloudlyViewMails } from './cloudly-view-mails.js';
|
||||||
|
import { CloudlyViewOverview } from './cloudly-view-overview.js';
|
||||||
|
import { CloudlyViewS3 } from './cloudly-view-s3.js';
|
||||||
|
import { CloudlyViewSecretBundles } from './cloudly-view-secretbundles.js';
|
||||||
|
import { CloudlyViewSecretGroups } from './cloudly-view-secretgroups.js';
|
||||||
|
import { CloudlyViewServices } from './cloudly-view-services.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'cloudly-dashboard': CloudlyDashboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('cloudly-dashboard')
|
||||||
|
export class CloudlyDashboard extends DeesElement {
|
||||||
|
@state() private jwt: string;
|
||||||
|
@state() private data: appstate.IDataState = {
|
||||||
|
secretGroups: [],
|
||||||
|
secretBundles: [],
|
||||||
|
clusters: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
document.title = `cloudly v${commitinfo.version}`;
|
||||||
|
const subcription = appstate.dataState
|
||||||
|
.select((stateArg) => stateArg)
|
||||||
|
.subscribe((dataArg) => {
|
||||||
|
this.data = dataArg;
|
||||||
|
});
|
||||||
|
this.rxSubscriptions.push(subcription);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
cssManager.defaultStyles,
|
||||||
|
css`
|
||||||
|
.maincontainer {
|
||||||
|
position: relative;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: 'Cal Sans';
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="maincontainer">
|
||||||
|
<dees-simple-login name="cloudly v${commitinfo.version}">
|
||||||
|
<dees-simple-appdash name="cloudly v${commitinfo.version}"
|
||||||
|
.viewTabs=${[
|
||||||
|
{
|
||||||
|
name: 'Overview',
|
||||||
|
element: CloudlyViewOverview,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SecretGroups',
|
||||||
|
element: CloudlyViewSecretGroups,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SecretBundles',
|
||||||
|
element: CloudlyViewSecretBundles,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clusters',
|
||||||
|
element: CloudlyViewClusters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Images',
|
||||||
|
element: CloudlyViewImages,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Services',
|
||||||
|
element: CloudlyViewServices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Deployments',
|
||||||
|
element: CloudlyViewDeployments,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DNS',
|
||||||
|
element: CloudlyViewDns,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mails',
|
||||||
|
element: CloudlyViewMails,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Logs',
|
||||||
|
element: CloudlyViewLogs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 's3',
|
||||||
|
element: CloudlyViewS3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'DBs',
|
||||||
|
element: CloudlyViewDbs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Backups',
|
||||||
|
element: CloudlyViewBackups,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Fleet',
|
||||||
|
element: CloudlyViewBackups,
|
||||||
|
}
|
||||||
|
] as plugins.deesCatalog.IView[]}
|
||||||
|
></dees-simple-appdash>
|
||||||
|
</dees-simple-login>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
public async firstUpdated() {
|
||||||
|
const simpleLogin = this.shadowRoot.querySelector('dees-simple-login');
|
||||||
|
simpleLogin.addEventListener('login', (e: CustomEvent) => {
|
||||||
|
console.log(e.detail);
|
||||||
|
this.login(e.detail.data.username, e.detail.data.password);
|
||||||
|
});
|
||||||
|
this.addEventListener('contextmenu', (eventArg) => {
|
||||||
|
plugins.deesCatalog.DeesContextmenu.openContextMenuWithOptions(eventArg, [
|
||||||
|
{
|
||||||
|
name: 'About',
|
||||||
|
iconName: 'mugHot',
|
||||||
|
action: async () => {
|
||||||
|
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
|
heading: 'About',
|
||||||
|
content: html`cloudly ${commitinfo.version}`,
|
||||||
|
menuOptions: [
|
||||||
|
{
|
||||||
|
name: 'close',
|
||||||
|
iconName: null,
|
||||||
|
action: async (modalArg) => {
|
||||||
|
await modalArg.destroy();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// lets deal with initial state
|
||||||
|
const domtools = await this.domtoolsPromise;
|
||||||
|
const loginState = appstate.loginStatePart.getState();
|
||||||
|
console.log(loginState);
|
||||||
|
if (loginState.jwt) {
|
||||||
|
this.jwt = loginState.jwt;
|
||||||
|
await simpleLogin.switchToSlottedContent();
|
||||||
|
await appstate.dataState.dispatchAction(appstate.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');
|
||||||
|
form.setStatus('pending', 'Logging in...');
|
||||||
|
const state = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
if (state.jwt) {
|
||||||
|
console.log('got jwt');
|
||||||
|
this.jwt = state.jwt;
|
||||||
|
form.setStatus('success', 'Logged in!');
|
||||||
|
await simpleLogin.switchToSlottedContent();
|
||||||
|
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
|
||||||
|
} else {
|
||||||
|
form.setStatus('error', 'Login failed!');
|
||||||
|
await domtools.convenience.smartdelay.delayFor(2000);
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async logout() {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
## License and Legal Information
|
||||||
|
|
||||||
|
24
test/test.apiclient.ts
Normal file
24
test/test.apiclient.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
|
import * as cloudlyApiClient from '../ts_apiclient/index.js';
|
||||||
|
|
||||||
|
let testClient: cloudlyApiClient.CloudlyApiClient;
|
||||||
|
|
||||||
|
tap.test('should create a new cloudlyApiClient', async () => {
|
||||||
|
testClient = new cloudlyApiClient.CloudlyApiClient({
|
||||||
|
registerAs: 'api',
|
||||||
|
cloudlyUrl: 'http://localhost:3000',
|
||||||
|
});
|
||||||
|
await testClient.start();
|
||||||
|
expect(testClient).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should trigger a server action', async () => {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
tap.test('should stop the apiclient', async () => {
|
||||||
|
await testClient.stop();
|
||||||
|
})
|
||||||
|
|
||||||
|
export default tap.start();
|
19
test/test.ts
19
test/test.ts
@ -10,20 +10,19 @@ import * as cloudly from '../ts/index.js';
|
|||||||
let testCloudly: cloudly.Cloudly;
|
let testCloudly: cloudly.Cloudly;
|
||||||
tap.test('first test', async () => {
|
tap.test('first test', async () => {
|
||||||
const cloudlyConfig: cloudly.ICloudlyConfig = {
|
const cloudlyConfig: cloudly.ICloudlyConfig = {
|
||||||
cfToken: testQenv.getEnvVarOnDemand('CF_TOKEN'),
|
cfToken: await testQenv.getEnvVarOnDemand('CF_TOKEN'),
|
||||||
environment: 'integration',
|
environment: 'integration',
|
||||||
letsEncryptEmail: testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
|
letsEncryptEmail: await testQenv.getEnvVarOnDemand('LETSENCRYPT_EMAIL'),
|
||||||
publicUrl: testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
|
publicUrl: await testQenv.getEnvVarOnDemand('SERVEZONE_URL'),
|
||||||
publicPort: testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
|
publicPort: await testQenv.getEnvVarOnDemand('SERVEZONE_PORT'),
|
||||||
mongoDescriptor: {
|
mongoDescriptor: {
|
||||||
mongoDbName: testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
|
mongoDbName: await testQenv.getEnvVarOnDemand('MONGODB_DATABASE'),
|
||||||
mongoDbUser: testQenv.getEnvVarOnDemand('MONGODB_USER'),
|
mongoDbUser: await testQenv.getEnvVarOnDemand('MONGODB_USER'),
|
||||||
mongoDbPass: testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
|
mongoDbPass: await testQenv.getEnvVarOnDemand('MONGODB_PASSWORD'),
|
||||||
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGODB_URL'),
|
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGODB_URL'),
|
||||||
},
|
},
|
||||||
digitalOceanToken: testQenv.getEnvVarOnDemand('DIGITALOCEAN_TOKEN'),
|
|
||||||
};
|
};
|
||||||
testCloudly = new cloudly.Cloudly(cloudlyConfig);
|
testCloudly = new cloudly.Cloudly();
|
||||||
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
|
expect(testCloudly).toBeInstanceOf(cloudly.Cloudly);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
name: '@serve.zone/cloudly',
|
||||||
version: '1.0.215',
|
version: '1.1.5',
|
||||||
description: 'A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.'
|
description: 'A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and integrate robust configuration and API management capabilities.'
|
||||||
}
|
}
|
||||||
|
13
ts/00demo/demo.data.images.ts
Normal file
13
ts/00demo/demo.data.images.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
export const demoImages: plugins.servezoneInterfaces.data.IImage[] = [
|
||||||
|
{
|
||||||
|
id: 'DemoImage1',
|
||||||
|
data: {
|
||||||
|
name: 'DemoImage1',
|
||||||
|
description: 'DemoImage1',
|
||||||
|
versions: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
// Create an array to hold 10 ISecretGroup objects
|
// Create an array to hold 10 ISecretGroup objects
|
||||||
const demoSecretGroups: plugins.servezoneInterfaces.data.ISecretGroup[] = [];
|
const demoSecretGroups: plugins.servezoneInterfaces.data.ISecretGroup[] = [];
|
||||||
@ -63,6 +63,8 @@ for (let i = 0; i < demoSecretGroups.length; i++) {
|
|||||||
id: `configBundleId${i + 1}`,
|
id: `configBundleId${i + 1}`,
|
||||||
data: {
|
data: {
|
||||||
name: `Demo Config Bundle ${i + 1}`,
|
name: `Demo Config Bundle ${i + 1}`,
|
||||||
|
includedImages: [],
|
||||||
|
type: 'external',
|
||||||
description: 'Demo Purpose',
|
description: 'Demo Purpose',
|
||||||
includedSecretGroupIds: [secretGroup.id],
|
includedSecretGroupIds: [secretGroup.id],
|
||||||
includedTags: secretGroup.data.tags,
|
includedTags: secretGroup.data.tags,
|
19
ts/00demo/demo.data.users.ts
Normal file
19
ts/00demo/demo.data.users.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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: {
|
||||||
|
username: envAdminUser.split(':')[0],
|
||||||
|
password: envAdminUser.split(':')[1],
|
||||||
|
role: 'admin',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
};
|
@ -1,4 +1,4 @@
|
|||||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
import type { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
export const installDemoData = async (cloudlyRef: Cloudly) => {
|
export const installDemoData = async (cloudlyRef: Cloudly) => {
|
||||||
|
|
||||||
@ -35,4 +35,31 @@ export const installDemoData = async (cloudlyRef: Cloudly) => {
|
|||||||
await cluster.delete();
|
await cluster.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ================================================================================
|
||||||
|
// USERS
|
||||||
|
const users = await cloudlyRef.authManager.CUser.getInstances({});
|
||||||
|
for (const user of users) {
|
||||||
|
await user.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoDataUsers = await import('./demo.data.users.js');
|
||||||
|
for (const user of await demoDataUsers.getUsers(cloudlyRef)) {
|
||||||
|
const userInstance = new cloudlyRef.authManager.CUser();
|
||||||
|
Object.assign(userInstance, user);
|
||||||
|
await userInstance.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================================
|
||||||
|
// IMAGES
|
||||||
|
const images = await cloudlyRef.imageManager.CImage.getInstances({});
|
||||||
|
for (const image of images) {
|
||||||
|
await image.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
const demoDataImages = await import('./demo.data.images.js');
|
||||||
|
for (const image of demoDataImages.demoImages) {
|
||||||
|
const imageInstance = new cloudlyRef.imageManager.CImage();
|
||||||
|
Object.assign(imageInstance, image);
|
||||||
|
await imageInstance.save();
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import * as plugins from './cloudly.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import { CloudlyConfig } from './cloudly.classes.config.js';
|
import { CloudlyConfig } from './classes.config.js';
|
||||||
|
|
||||||
// interfaces
|
// interfaces
|
||||||
import {} from '@tsclass/tsclass';
|
import {} from '@tsclass/tsclass';
|
||||||
|
|
||||||
// Cloudly mods
|
// Cloudly mods
|
||||||
import { CloudlyInfo } from './cloudly.classes.cloudlyinfo.js';
|
import { CloudlyInfo } from './classes.cloudlyinfo.js';
|
||||||
import { CloudlyServer } from './cloudly.classes.server.js';
|
import { CloudlyServer } from './classes.server.js';
|
||||||
|
|
||||||
// connectors
|
// connectors
|
||||||
import { CloudflareConnector } from './connector.cloudflare/connector.js';
|
import { CloudflareConnector } from './connector.cloudflare/connector.js';
|
||||||
@ -15,14 +15,14 @@ import { MongodbConnector } from './connector.mongodb/connector.js';
|
|||||||
|
|
||||||
// processes
|
// processes
|
||||||
import { CloudlyCoreflowManager } from './manager.coreflow/coreflowmanager.js';
|
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 { CloudlyTaskmanager } from './manager.task/taskmanager.js';
|
||||||
import { CloudlyVersionManager } from './manager.version/versionmanager.js';
|
|
||||||
import { CloudlySecretManager } from './manager.secret/classes.secretmanager.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 { ExternalApiManager } from './manager.status/statusmanager.js';
|
||||||
import { ImageManager } from './manager.image/classes.imagemanager.js';
|
import { ImageManager } from './manager.image/classes.imagemanager.js';
|
||||||
import { logger } from './cloudly.logging.js';
|
import { logger } from './logger.js';
|
||||||
|
import { CloudlyAuthManager } from './manager.auth/classes.authmanager.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cloudly class can be used to instantiate a cloudly server.
|
* Cloudly class can be used to instantiate a cloudly server.
|
||||||
@ -49,13 +49,13 @@ export class Cloudly {
|
|||||||
public mongodbConnector: MongodbConnector;
|
public mongodbConnector: MongodbConnector;
|
||||||
|
|
||||||
// managers
|
// managers
|
||||||
|
public authManager: CloudlyAuthManager;
|
||||||
public secretManager: CloudlySecretManager;
|
public secretManager: CloudlySecretManager;
|
||||||
public clusterManager: ClusterManager;
|
public clusterManager: ClusterManager;
|
||||||
public coreflowManager: CloudlyCoreflowManager;
|
public coreflowManager: CloudlyCoreflowManager;
|
||||||
public externalApiManager: ExternalApiManager;
|
public externalApiManager: ExternalApiManager;
|
||||||
public imageManager: ImageManager;
|
public imageManager: ImageManager;
|
||||||
public taskManager: CloudlyTaskmanager;
|
public taskManager: CloudlyTaskmanager;
|
||||||
public versionManager: CloudlyVersionManager;
|
|
||||||
public serverManager: CloudlyServerManager;
|
public serverManager: CloudlyServerManager;
|
||||||
|
|
||||||
private readyDeferred = new plugins.smartpromise.Deferred();
|
private readyDeferred = new plugins.smartpromise.Deferred();
|
||||||
@ -73,13 +73,13 @@ export class Cloudly {
|
|||||||
this.cloudflareConnector = new CloudflareConnector(this);
|
this.cloudflareConnector = new CloudflareConnector(this);
|
||||||
this.letsencryptConnector = new LetsencryptConnector(this);
|
this.letsencryptConnector = new LetsencryptConnector(this);
|
||||||
|
|
||||||
// processes
|
// managers
|
||||||
|
this.authManager = new CloudlyAuthManager(this);
|
||||||
this.clusterManager = new ClusterManager(this);
|
this.clusterManager = new ClusterManager(this);
|
||||||
this.coreflowManager = new CloudlyCoreflowManager(this);
|
this.coreflowManager = new CloudlyCoreflowManager(this);
|
||||||
this.externalApiManager = new ExternalApiManager(this);
|
this.externalApiManager = new ExternalApiManager(this);
|
||||||
this.imageManager = new ImageManager(this);
|
this.imageManager = new ImageManager(this);
|
||||||
this.taskManager = new CloudlyTaskmanager(this);
|
this.taskManager = new CloudlyTaskmanager(this);
|
||||||
this.versionManager = new CloudlyVersionManager(this);
|
|
||||||
this.secretManager = new CloudlySecretManager(this);
|
this.secretManager = new CloudlySecretManager(this);
|
||||||
this.serverManager = new CloudlyServerManager(this);
|
this.serverManager = new CloudlyServerManager(this);
|
||||||
}
|
}
|
||||||
@ -93,6 +93,7 @@ export class Cloudly {
|
|||||||
await this.config.init();
|
await this.config.init();
|
||||||
|
|
||||||
// manageers
|
// manageers
|
||||||
|
await this.authManager.start();
|
||||||
await this.secretManager.start();
|
await this.secretManager.start();
|
||||||
await this.serverManager.start();
|
await this.serverManager.start();
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from './cloudly.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './cloudly.paths.js';
|
import * as paths from './paths.js';
|
||||||
import { Cloudly } from './index.js';
|
import { Cloudly } from './index.js';
|
||||||
|
|
||||||
export class CloudlyInfo {
|
export class CloudlyInfo {
|
@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './cloudly.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './cloudly.paths.js';
|
import * as paths from './paths.js';
|
||||||
import { logger } from './cloudly.logging.js';
|
import { logger } from './logger.js';
|
||||||
import type { Cloudly } from './cloudly.classes.cloudly.js';
|
import type { Cloudly } from './classes.cloudly.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the main cloudly config
|
* the main cloudly config
|
||||||
@ -11,9 +11,6 @@ export class CloudlyConfig {
|
|||||||
public appData: plugins.npmextra.AppData<plugins.servezoneInterfaces.data.ICloudlyConfig>;
|
public appData: plugins.npmextra.AppData<plugins.servezoneInterfaces.data.ICloudlyConfig>;
|
||||||
public data: plugins.servezoneInterfaces.data.ICloudlyConfig
|
public data: plugins.servezoneInterfaces.data.ICloudlyConfig
|
||||||
|
|
||||||
// authentication and settings
|
|
||||||
public smartjwtInstance: plugins.smartjwt.SmartJwt;
|
|
||||||
|
|
||||||
|
|
||||||
constructor(cloudlyRefArg: Cloudly) {
|
constructor(cloudlyRefArg: Cloudly) {
|
||||||
this.cloudlyRef = cloudlyRefArg;
|
this.cloudlyRef = cloudlyRefArg;
|
||||||
@ -43,6 +40,7 @@ export class CloudlyConfig {
|
|||||||
useSsl: true,
|
useSsl: true,
|
||||||
},
|
},
|
||||||
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
|
sslMode: 'SERVEZONE_SSLMODE' as plugins.servezoneInterfaces.data.ICloudlyConfig['sslMode'],
|
||||||
|
servezoneAdminaccount: 'SERVEZONE_ADMINACCOUNT',
|
||||||
},
|
},
|
||||||
requiredKeys: [
|
requiredKeys: [
|
||||||
'cfToken',
|
'cfToken',
|
||||||
@ -56,19 +54,8 @@ export class CloudlyConfig {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.smartjwtInstance = new plugins.smartjwt.SmartJwt();
|
|
||||||
const kvStore = await this.appData.getKvStore();
|
const kvStore = await this.appData.getKvStore();
|
||||||
|
|
||||||
const existingJwtKeys: plugins.tsclass.network.IJwtKeypair = await kvStore.readKey('jwtKeys');
|
|
||||||
|
|
||||||
if (!existingJwtKeys) {
|
|
||||||
await this.smartjwtInstance.createNewKeyPair();
|
|
||||||
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
|
||||||
await kvStore.writeKey('jwtKeys', newJwtKeys);
|
|
||||||
} else {
|
|
||||||
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data = await kvStore.readAll();
|
this.data = await kvStore.readAll();
|
||||||
const missingKeys = await this.appData.logMissingKeys();
|
const missingKeys = await this.appData.logMissingKeys();
|
||||||
if (missingKeys.length > 0) {
|
if (missingKeys.length > 0) {
|
@ -1,7 +1,7 @@
|
|||||||
import * as plugins from './cloudly.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './cloudly.paths.js';
|
import * as paths from './paths.js';
|
||||||
import { Cloudly } from './cloudly.classes.cloudly.js';
|
import { Cloudly } from './classes.cloudly.js';
|
||||||
import { logger } from './cloudly.logging.js';
|
import { logger } from './logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handles incoming requests from CI to deploy new versions of apps
|
* handles incoming requests from CI to deploy new versions of apps
|
||||||
@ -10,7 +10,8 @@ export class CloudlyServer {
|
|||||||
/**
|
/**
|
||||||
* a reference to the cloudly instance
|
* 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
|
* the smartexpress server handling the actual requests
|
||||||
@ -37,18 +38,24 @@ export class CloudlyServer {
|
|||||||
* init the reception instance
|
* init the reception instance
|
||||||
*/
|
*/
|
||||||
public async start() {
|
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;
|
let sslCert: plugins.smartacme.Cert;
|
||||||
|
|
||||||
if (this.cloudlyRef.config.data.sslMode === 'letsencrypt') {
|
if (this.cloudlyRef.config.data.sslMode === 'letsencrypt') {
|
||||||
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`)
|
logger.log('info', `Using letsencrypt for ssl mode. Trying to obtain a certificate...`);
|
||||||
logger.log('info', `This might take 10 minutes...`)
|
logger.log('info', `This might take 10 minutes...`);
|
||||||
sslCert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
sslCert = await this.cloudlyRef.letsencryptConnector.getCertificateForDomain(
|
||||||
this.cloudlyRef.config.data.publicUrl
|
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') {
|
} 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 {
|
interface IRequestGuardData {
|
||||||
@ -72,11 +79,13 @@ export class CloudlyServer {
|
|||||||
this.typedServer = new plugins.typedserver.TypedServer({
|
this.typedServer = new plugins.typedserver.TypedServer({
|
||||||
cors: true,
|
cors: true,
|
||||||
forceSsl: false,
|
forceSsl: false,
|
||||||
port: this.cloudlyRef.config.data.publicPort,
|
port: this.cloudlyRef.config.data.publicPort,
|
||||||
...(sslCert ? {
|
...(sslCert
|
||||||
|
? {
|
||||||
privateKey: sslCert.privateKey,
|
privateKey: sslCert.privateKey,
|
||||||
publicKey: sslCert.publicKey,
|
publicKey: sslCert.publicKey,
|
||||||
} : {}),
|
}
|
||||||
|
: {}),
|
||||||
injectReload: true,
|
injectReload: true,
|
||||||
serveDir: paths.distServeDir,
|
serveDir: paths.distServeDir,
|
||||||
watch: true,
|
watch: true,
|
||||||
@ -84,6 +93,10 @@ export class CloudlyServer {
|
|||||||
preferredCompressionMethod: 'gzip',
|
preferredCompressionMethod: 'gzip',
|
||||||
});
|
});
|
||||||
this.typedServer.typedrouter.addTypedRouter(this.typedrouter);
|
this.typedServer.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
this.typedServer.server.addRoute(
|
||||||
|
'/curlfresh/:scriptname',
|
||||||
|
this.cloudlyRef.serverManager.curlfreshInstance.handler
|
||||||
|
);
|
||||||
await this.typedServer.start();
|
await this.typedServer.start();
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the portion of Cloudflare responsible
|
* the portion of Cloudflare responsible
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
export class LetsencryptConnector {
|
export class LetsencryptConnector {
|
||||||
private cloudlyRef: Cloudly;
|
private cloudlyRef: Cloudly;
|
||||||
@ -34,7 +34,10 @@ export class LetsencryptConnector {
|
|||||||
},
|
},
|
||||||
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
|
mongoDescriptor: this.cloudlyRef.config.data.mongoDescriptor,
|
||||||
});
|
});
|
||||||
await this.smartacme.init();
|
await this.smartacme.init().catch(err => {
|
||||||
|
console.error('error in init', err);
|
||||||
|
console.log(`trying again in a few minutes`)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
export class MongodbConnector {
|
export class MongodbConnector {
|
||||||
// INSTANCE
|
// INSTANCE
|
||||||
|
12
ts/index.ts
12
ts/index.ts
@ -1,9 +1,9 @@
|
|||||||
import * as early from '@push.rocks/early';
|
import * as early from '@push.rocks/early';
|
||||||
early.start('cloudly');
|
early.start('cloudly');
|
||||||
import * as plugins from './cloudly.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './cloudly.paths.js';
|
import * as paths from './paths.js';
|
||||||
import { Cloudly } from './cloudly.classes.cloudly.js';
|
import { Cloudly } from './classes.cloudly.js';
|
||||||
import { logger } from './cloudly.logging.js';
|
import { logger } from './logger.js';
|
||||||
const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true);
|
const cloudlyQenv = new plugins.qenv.Qenv(paths.packageDir, paths.nogitDir, true);
|
||||||
early.stop();
|
early.stop();
|
||||||
|
|
||||||
@ -21,8 +21,10 @@ const runCli = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await cloudlyInstance.start();
|
await cloudlyInstance.start();
|
||||||
const demoMod = await import('./demo/index.js');
|
const demoMod = await import('./00demo/index.js');
|
||||||
demoMod.installDemoData(cloudlyInstance);
|
demoMod.installDemoData(cloudlyInstance);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { runCli, Cloudly };
|
export { runCli, Cloudly };
|
||||||
|
type ICloudlyConfig = plugins.servezoneInterfaces.data.ICloudlyConfig;
|
||||||
|
export { type ICloudlyConfig }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from './cloudly.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import * as paths from './cloudly.paths.js';
|
import * as paths from './paths.js';
|
||||||
|
|
||||||
export const logger = new plugins.smartlog.Smartlog({
|
export const logger = new plugins.smartlog.Smartlog({
|
||||||
logContext: {
|
logContext: {
|
81
ts/manager.auth/classes.authmanager.ts
Normal file
81
ts/manager.auth/classes.authmanager.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
import type { Cloudly } from '../classes.cloudly.js';
|
||||||
|
import { logger } from '../logger.js';
|
||||||
|
import { Authorization } from './classes.authorization.js';
|
||||||
|
import { User } from './classes.user.js';
|
||||||
|
|
||||||
|
|
||||||
|
export interface IJwtData {
|
||||||
|
userId: string;
|
||||||
|
status: 'loggedIn' | 'loggedOut';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CloudlyAuthManager {
|
||||||
|
cloudlyRef: Cloudly
|
||||||
|
public get db() {
|
||||||
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||||
|
}
|
||||||
|
public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User);
|
||||||
|
public CAuthorization = plugins.smartdata.setDefaultManagerForDoc(this, Authorization);
|
||||||
|
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;
|
||||||
|
|
||||||
|
constructor(cloudlyRef: Cloudly) {
|
||||||
|
this.cloudlyRef = cloudlyRef;
|
||||||
|
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
if (!existingJwtKeys) {
|
||||||
|
await this.smartjwtInstance.createNewKeyPair();
|
||||||
|
const newJwtKeys = this.smartjwtInstance.getKeyPairAsJson();
|
||||||
|
await kvStore.writeKey('jwtKeys', newJwtKeys);
|
||||||
|
} else {
|
||||||
|
this.smartjwtInstance.setKeyPairAsJson(existingJwtKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
||||||
|
'adminLoginWithUsernameAndPassword',
|
||||||
|
async (dataArg) => {
|
||||||
|
let jwt: string;
|
||||||
|
const user = await User.findUserByUsernameAndPassword(dataArg.username, dataArg.password);
|
||||||
|
if (!user) {
|
||||||
|
logger.log('warn', 'login failed');
|
||||||
|
} else {
|
||||||
|
jwt = await this.smartjwtInstance.createJWT({
|
||||||
|
userId: user.id,
|
||||||
|
status: 'loggedIn',
|
||||||
|
});
|
||||||
|
logger.log('success', 'login successful');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
jwt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop () {}
|
||||||
|
|
||||||
|
public adminJwtGuard = new plugins.smartguard.Guard<{jwt: string}>(async (dataArg) => {
|
||||||
|
const jwt = dataArg.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.'
|
||||||
|
})
|
||||||
|
}
|
6
ts/manager.auth/classes.authorization.ts
Normal file
6
ts/manager.auth/classes.authorization.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
@plugins.smartdata.managed()
|
||||||
|
export class Authorization extends plugins.smartdata.SmartDataDbDoc<Authorization, Authorization> {
|
||||||
|
|
||||||
|
}
|
27
ts/manager.auth/classes.user.ts
Normal file
27
ts/manager.auth/classes.user.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
@plugins.smartdata.managed()
|
||||||
|
export class User extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
User,
|
||||||
|
plugins.servezoneInterfaces.data.IUser
|
||||||
|
> {
|
||||||
|
public static async findUserByUsernameAndPassword(usernameArg: string, passwordArg: string) {
|
||||||
|
return await User.getInstance({
|
||||||
|
data: {
|
||||||
|
username: usernameArg,
|
||||||
|
password: passwordArg,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// INSTANCE
|
||||||
|
@plugins.smartdata.unI()
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
@plugins.smartdata.svDb()
|
||||||
|
public data: {
|
||||||
|
role: 'admin' | 'user';
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
}
|
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<> {
|
||||||
|
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* cluster defines a swarmkit cluster
|
* cluster defines a swarmkit cluster
|
||||||
*/
|
*/
|
||||||
@plugins.smartdata.Manager()
|
@plugins.smartdata.managed()
|
||||||
export class Cluster extends plugins.smartdata.SmartDataDbDoc<Cluster, plugins.servezoneInterfaces.data.ICluster> {
|
export class Cluster extends plugins.smartdata.SmartDataDbDoc<Cluster, plugins.servezoneInterfaces.data.ICluster> {
|
||||||
// STATIC
|
// STATIC
|
||||||
public static async fromConfigObject(
|
public static async fromConfigObject(
|
@ -1,9 +1,9 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from '../cloudly.paths.js';
|
import * as paths from '../paths.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
import { logger } from '../cloudly.logging.js';
|
import { logger } from '../logger.js';
|
||||||
|
|
||||||
import { Cluster } from './cluster.js';
|
import { Cluster } from './classes.cluster.js';
|
||||||
|
|
||||||
export class ClusterManager {
|
export class ClusterManager {
|
||||||
public ready = plugins.smartpromise.defer();
|
public ready = plugins.smartpromise.defer();
|
||||||
@ -28,7 +28,6 @@ export class ClusterManager {
|
|||||||
name: dataArg.clusterName,
|
name: dataArg.clusterName,
|
||||||
jumpCode: plugins.smartunique.uniSimple('cluster'),
|
jumpCode: plugins.smartunique.uniSimple('cluster'),
|
||||||
jumpCodeUsedAt: null,
|
jumpCodeUsedAt: null,
|
||||||
secretKey: plugins.smartunique.shortId(16),
|
|
||||||
acmeInfo: null,
|
acmeInfo: null,
|
||||||
cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`,
|
cloudlyUrl: `https://${this.cloudlyRef.config.data.publicUrl}:${this.cloudlyRef.config.data.publicPort}/`,
|
||||||
servers: [],
|
servers: [],
|
||||||
@ -54,6 +53,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.adminJwtGuard], reqDataArg);
|
||||||
|
await this.deleteCluster(reqDataArg.clusterId);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async init() {
|
public async init() {
|
||||||
@ -86,9 +96,9 @@ export class ClusterManager {
|
|||||||
await this.ready.promise;
|
await this.ready.promise;
|
||||||
|
|
||||||
return await Cluster.getInstance({
|
return await Cluster.getInstance({
|
||||||
|
id: clusterIdentifier.clusterId,
|
||||||
data: {
|
data: {
|
||||||
name: clusterIdentifier.clusterName,
|
name: clusterIdentifier.clusterName,
|
||||||
secretKey: clusterIdentifier.secretKey,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -128,4 +138,10 @@ export class ClusterManager {
|
|||||||
await clusterInstance.save();
|
await clusterInstance.save();
|
||||||
return clusterInstance;
|
return clusterInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deleteCluster(clusterId: string) {
|
||||||
|
await this.ready.promise;
|
||||||
|
const clusterInstance = await Cluster.getInstance({ id: clusterId });
|
||||||
|
await clusterInstance.delete();
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* in charge of talking to coreflow services on clusters
|
* in charge of talking to coreflow services on clusters
|
||||||
@ -26,8 +26,12 @@ export class CloudlyCoreflowManager {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
clusterIdentifier: {
|
clusterIdentifier: {
|
||||||
|
clusterId: clusterConfig.id,
|
||||||
clusterName: clusterConfig.data.name,
|
clusterName: clusterConfig.data.name,
|
||||||
secretKey: clusterConfig.data.secretKey,
|
jwt: await this.cloudlyRef.authManager.smartjwtInstance.createJWT({
|
||||||
|
status: 'loggedIn',
|
||||||
|
userId: 'cluster:' + clusterConfig.id, // TODO: create real users for clusters
|
||||||
|
})
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@ -47,7 +51,8 @@ export class CloudlyCoreflowManager {
|
|||||||
);
|
);
|
||||||
console.log('got cluster config and sending it back to coreflow');
|
console.log('got cluster config and sending it back to coreflow');
|
||||||
return {
|
return {
|
||||||
configData: await clusterConfigSet.createSavableObject()
|
configData: await clusterConfigSet.createSavableObject(),
|
||||||
|
deploymentDirectives: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import type { ImageManager } from './classes.imagemanager.js';
|
import type { ImageManager } from './classes.imagemanager.js';
|
||||||
|
|
||||||
@plugins.smartdata.Manager()
|
@plugins.smartdata.managed()
|
||||||
export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.servezoneInterfaces.data.IImage, ImageManager> {
|
export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.servezoneInterfaces.data.IImage, ImageManager> {
|
||||||
public static async create(imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
|
public static async create(imageDataArg: Partial<plugins.servezoneInterfaces.data.IImage['data']>) {
|
||||||
const image = new Image();
|
const image = new Image();
|
||||||
image.id = plugins.smartunique.uni('image');
|
image.id = await this.getNewId();
|
||||||
Object.assign(image.data, imageDataArg);
|
console.log(imageDataArg);
|
||||||
|
Object.assign(image, {
|
||||||
|
data: {
|
||||||
|
name: imageDataArg.name,
|
||||||
|
description: imageDataArg.description,
|
||||||
|
versions: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log((Image as any).saveableProperties)
|
||||||
await image.save();
|
await image.save();
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
@ -18,4 +26,12 @@ export class Image extends plugins.smartdata.SmartDataDbDoc<Image, plugins.serve
|
|||||||
public data: plugins.servezoneInterfaces.data.IImage['data'];
|
public data: plugins.servezoneInterfaces.data.IImage['data'];
|
||||||
|
|
||||||
public async getVersions() {}
|
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('/', '__')
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,27 +1,63 @@
|
|||||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
import type { Cloudly } from '../classes.cloudly.js';
|
||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
import { Image } from './classes.image.js';
|
import { Image } from './classes.image.js';
|
||||||
|
|
||||||
export class ImageManager {
|
export class ImageManager {
|
||||||
cloudlyRef: Cloudly;
|
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() {
|
get db() {
|
||||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
||||||
}
|
}
|
||||||
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
|
public CImage = plugins.smartdata.setDefaultManagerForDoc(this, Image);
|
||||||
|
|
||||||
smartbucketInstance: plugins.smartbucket.SmartBucket;
|
|
||||||
|
|
||||||
constructor(cloudlyRefArg: Cloudly) {
|
constructor(cloudlyRefArg: Cloudly) {
|
||||||
this.cloudlyRef = cloudlyRefArg;
|
this.cloudlyRef = cloudlyRefArg;
|
||||||
|
|
||||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
|
||||||
|
this.typedrouter.addTypedHandler(
|
||||||
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
||||||
|
'createImage',
|
||||||
|
async (reqArg, toolsArg) => {
|
||||||
|
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], reqArg);
|
||||||
|
const image = await this.CImage.create({
|
||||||
|
name: reqArg.name,
|
||||||
|
description: reqArg.description,
|
||||||
|
versions: [],
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
const image = await this.CImage.getInstance({
|
||||||
|
id: reqArg.imageId,
|
||||||
|
});
|
||||||
|
await image.delete();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||||
'getAllImages',
|
'getAllImages',
|
||||||
async (requestArg) => {
|
async (requestArg, toolsArg) => {
|
||||||
|
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], requestArg);
|
||||||
const images = await this.CImage.getInstances({});
|
const images = await this.CImage.getInstances({});
|
||||||
return {
|
return {
|
||||||
images: await Promise.all(
|
images: await Promise.all(
|
||||||
@ -35,40 +71,39 @@ export class ImageManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_CreateImage>(
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
|
||||||
'createImage',
|
'pushImageVersion',
|
||||||
async (reqArg) => {
|
async (reqArg, toolsArg) => {
|
||||||
const image = await this.CImage.create({
|
const image = await this.CImage.getInstance({
|
||||||
name: reqArg.name,
|
id: reqArg.imageId,
|
||||||
});
|
});
|
||||||
|
if (!image) {
|
||||||
|
throw new plugins.typedrequest.TypedResponseError('Image not found');
|
||||||
|
}
|
||||||
|
const imageVersion = reqArg.versionString;
|
||||||
|
const imagePushStream = reqArg.imageStream;
|
||||||
return {
|
return {
|
||||||
image: await image.createSavableObject(),
|
allowed: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PushImage>('pushImage', async (reqArg) => {
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImageVersion>(
|
||||||
const pushStream = reqArg.imageStream;
|
'pullImageVersion',
|
||||||
return {}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.image.IRequest_PullImage>(
|
|
||||||
'pullImage',
|
|
||||||
async (reqArg) => {
|
async (reqArg) => {
|
||||||
const image = await this.CImage.getInstance({
|
const image = await this.CImage.getInstance({
|
||||||
data: {
|
id: reqArg.imageId,
|
||||||
name: reqArg.name,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
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();
|
const imageVirtualStream = new plugins.typedrequest.VirtualStream();
|
||||||
return {
|
return {
|
||||||
imageStream: imageVirtualStream,
|
imageStream: imageVirtualStream,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -82,12 +117,10 @@ export class ImageManager {
|
|||||||
this.cloudlyRef.config.data.s3Descriptor
|
this.cloudlyRef.config.data.s3Descriptor
|
||||||
);
|
);
|
||||||
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
|
const bucket = await this.smartbucketInstance.getBucketByName('cloudly-test');
|
||||||
await bucket.fastStore('test/test.txt', 'hello');
|
await bucket.fastPut({ path: 'test/test.txt', contents: 'hello' });
|
||||||
}
|
|
||||||
|
|
||||||
public async createImage(nameArg: string) {
|
this.imageDir = await bucket.getDirectoryFromPath({
|
||||||
const newImage = await this.CImage.create({
|
path: 'images',
|
||||||
name: nameArg,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* takes care of receiving and providing logs
|
* takes care of receiving and providing logs
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// a secret bundle is a set of secrets ready to be used in a project.
|
// a secret bundle is a set of secrets ready to be used in a project.
|
||||||
// it bundles secretgroups
|
// it bundles secretgroups
|
||||||
import { SecretGroup } from './classes.secretgroup.js';
|
import { SecretGroup } from './classes.secretgroup.js';
|
||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
@plugins.smartdata.Manager()
|
@plugins.smartdata.Manager()
|
||||||
export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
|
export class SecretBundle extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* a secretgroup is a set of secrets for different environments.
|
* a secretgroup is a set of secrets for different environments.
|
||||||
*/
|
*/
|
||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
@plugins.smartdata.Manager()
|
@plugins.smartdata.Manager()
|
||||||
export class SecretGroup extends plugins.smartdata.SmartDataDbDoc<
|
export class SecretGroup extends plugins.smartdata.SmartDataDbDoc<
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import * as paths from '../cloudly.paths.js';
|
import * as paths from '../paths.js';
|
||||||
import { SecretBundle } from './classes.secretbundle.js';
|
import { SecretBundle } from './classes.secretbundle.js';
|
||||||
import { SecretGroup } from './classes.secretgroup.js';
|
import { SecretGroup } from './classes.secretgroup.js';
|
||||||
import { logger } from '../cloudly.logging.js';
|
import { logger } from '../logger.js';
|
||||||
import type { Cloudly } from '../cloudly.classes.cloudly.js';
|
import type { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `ConfigVault` class provides methods for reading and writing configuration data to a file.
|
* The `ConfigVault` class provides methods for reading and writing configuration data to a file.
|
||||||
@ -35,31 +35,11 @@ export class CloudlySecretManager {
|
|||||||
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
this.typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter);
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_LoginWithUsernameAndPassword>(
|
|
||||||
'adminLoginWithUsernameAndPassword',
|
|
||||||
async (dataArg) => {
|
|
||||||
let jwt: string;
|
|
||||||
// console.log(dataArg);
|
|
||||||
if (dataArg.username !== 'admin' || dataArg.password !== 'password') {
|
|
||||||
logger.log('warn', 'login failed');
|
|
||||||
} else {
|
|
||||||
jwt = await this.cloudlyRef.config.smartjwtInstance.createJWT({
|
|
||||||
status: 'loggedIn',
|
|
||||||
});
|
|
||||||
logger.log('success', 'login successful');
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
jwt,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.typedrouter.addTypedHandler(
|
this.typedrouter.addTypedHandler(
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.secret.IReq_Admin_GetConfigBundlesAndSecretGroups>(
|
||||||
'adminGetConfigBundlesAndSecretGroups',
|
'adminGetConfigBundlesAndSecretGroups',
|
||||||
async (dataArg) => {
|
async (dataArg, toolsArg) => {
|
||||||
|
await toolsArg.passGuards([this.cloudlyRef.authManager.adminJwtGuard], dataArg);
|
||||||
dataArg.jwt
|
dataArg.jwt
|
||||||
const secretBundles = await SecretBundle.getInstances({});
|
const secretBundles = await SecretBundle.getInstances({});
|
||||||
const secretGroups = await SecretGroup.getInstances({});
|
const secretGroups = await SecretGroup.getInstances({});
|
||||||
|
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,4 +1,4 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* cluster defines a swarmkit cluster
|
* cluster defines a swarmkit cluster
|
@ -1,11 +1,13 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
import { Cluster } from '../manager.cluster/cluster.js';
|
import { Cluster } from '../manager.cluster/classes.cluster.js';
|
||||||
import { Server } from './server.js';
|
import { Server } from './classes.server.js';
|
||||||
|
import { CurlFresh } from './classes.curlfresh.js';
|
||||||
|
|
||||||
export class CloudlyServerManager {
|
export class CloudlyServerManager {
|
||||||
public cloudlyRef: Cloudly;
|
public cloudlyRef: Cloudly;
|
||||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
public typedRouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
public curlfreshInstance = new CurlFresh(this);
|
||||||
|
|
||||||
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
public hetznerAccount: plugins.hetznercloud.HetznerAccount;
|
||||||
|
|
||||||
@ -63,7 +65,8 @@ export class CloudlyServerManager {
|
|||||||
labels: {
|
labels: {
|
||||||
clusterId: cluster.id,
|
clusterId: cluster.id,
|
||||||
priority: '1',
|
priority: '1',
|
||||||
}
|
},
|
||||||
|
userData: await this.curlfreshInstance.getServerUserData()
|
||||||
});
|
});
|
||||||
const newServer = await Server.createFromHetznerServer(server);
|
const newServer = await Server.createFromHetznerServer(server);
|
||||||
console.log(`cluster created new server for cluster ${cluster.id}`);
|
console.log(`cluster created new server for cluster ${cluster.id}`);
|
@ -0,0 +1,12 @@
|
|||||||
|
import * as plugins from '../plugins.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;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../index.js';
|
import { Cloudly } from '../index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
import { Cloudly } from '../classes.cloudly.js';
|
||||||
|
|
||||||
import { logger } from '../cloudly.logging.js';
|
import { logger } from '../logger.js';
|
||||||
|
|
||||||
export class CloudlyTaskmanager {
|
export class CloudlyTaskmanager {
|
||||||
public cloudlyRef: Cloudly;
|
public cloudlyRef: Cloudly;
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
|
||||||
|
|
||||||
/*
|
|
||||||
A container version is managed by the versionmanager
|
|
||||||
*/
|
|
||||||
@plugins.smartdata.Manager()
|
|
||||||
export class ContainerVersion
|
|
||||||
extends plugins.smartdata.SmartDataDbDoc<ContainerVersion, unknown>
|
|
||||||
implements plugins.servezoneInterfaces.data.IContainerVersionData
|
|
||||||
{
|
|
||||||
public static async fromIVersionData(
|
|
||||||
dataArg: plugins.servezoneInterfaces.data.IContainerVersionData
|
|
||||||
) {
|
|
||||||
const containerVersionInstance = new ContainerVersion();
|
|
||||||
containerVersionInstance.id = plugins.smartunique.shortId();
|
|
||||||
Object.assign(containerVersionInstance, dataArg);
|
|
||||||
return containerVersionInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@plugins.smartdata.unI()
|
|
||||||
public id: string;
|
|
||||||
|
|
||||||
@plugins.smartdata.svDb()
|
|
||||||
public dockerImageUrl: string;
|
|
||||||
|
|
||||||
@plugins.smartdata.svDb()
|
|
||||||
public dockerImageVersion: string;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
import * as plugins from '../cloudly.plugins.js';
|
|
||||||
|
|
||||||
import { ContainerVersion } from './containerversion.js';
|
|
||||||
import { Cloudly } from '../cloudly.classes.cloudly.js';
|
|
||||||
|
|
||||||
export class CloudlyVersionManager {
|
|
||||||
// INSTANCE
|
|
||||||
public cloudlyRef: Cloudly;
|
|
||||||
public get db() {
|
|
||||||
return this.cloudlyRef.mongodbConnector.smartdataDb;
|
|
||||||
}
|
|
||||||
public typedRouter = new plugins.typedrequest.TypedRouter();
|
|
||||||
|
|
||||||
// connected classes
|
|
||||||
public CContainerVersion = plugins.smartdata.setDefaultManagerForDoc(this, ContainerVersion);
|
|
||||||
|
|
||||||
constructor(cloudlyRefArg: Cloudly) {
|
|
||||||
this.cloudlyRef = cloudlyRefArg;
|
|
||||||
this.cloudlyRef.typedrouter.addTypedRouter(this.typedRouter);
|
|
||||||
// get version
|
|
||||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_GetLatestContainerVersion>(
|
|
||||||
new plugins.typedrequest.TypedHandler(
|
|
||||||
'getLatestContainerVersion',
|
|
||||||
async (typedRequestData) => {
|
|
||||||
const containerVersionGet: ContainerVersion =
|
|
||||||
await ContainerVersion.getInstance<ContainerVersion>({
|
|
||||||
dockerImageUrl: typedRequestData.dockerImageUrl,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
dockerImageUrl: containerVersionGet.dockerImageUrl,
|
|
||||||
dockerImageVersion: containerVersionGet.dockerImageVersion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// update version
|
|
||||||
this.typedRouter.addTypedHandler<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
|
|
||||||
new plugins.typedrequest.TypedHandler(
|
|
||||||
'informCloudlyAboutNewContainerVersion',
|
|
||||||
async (dataArg) => {
|
|
||||||
console.log(`Got a container version announcement! "${dataArg.dockerImageUrl}"`);
|
|
||||||
let containerVersion: ContainerVersion =
|
|
||||||
await ContainerVersion.getInstance<ContainerVersion>({
|
|
||||||
dockerImageUrl: dataArg.dockerImageUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (containerVersion) {
|
|
||||||
containerVersion.dockerImageVersion = dataArg.dockerImageVersion;
|
|
||||||
await containerVersion.save();
|
|
||||||
} else {
|
|
||||||
containerVersion = await ContainerVersion.fromIVersionData(dataArg);
|
|
||||||
await containerVersion.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
// lets push this info to the relevant clusters
|
|
||||||
const clusters = await this.cloudlyRef.clusterManager.getAllClusters();
|
|
||||||
let foundServices: plugins.servezoneInterfaces.data.IService;
|
|
||||||
let relevantClusterIdentifier: plugins.servezoneInterfaces.data.IClusterIdentifier;
|
|
||||||
for (const clusterArg of clusters) {
|
|
||||||
console.log(clusterArg);
|
|
||||||
for (const serviceArg of await clusterArg.getServices()) {
|
|
||||||
if (serviceArg.image === containerVersion.dockerImageUrl) {
|
|
||||||
foundServices = serviceArg;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (foundServices) {
|
|
||||||
relevantClusterIdentifier = {
|
|
||||||
clusterName: clusterArg.data.name,
|
|
||||||
secretKey: clusterArg.data.secretKey,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relevantClusterIdentifier) {
|
|
||||||
console.log('no cluster found that needs to update');
|
|
||||||
return {};
|
|
||||||
} else {
|
|
||||||
console.log('found relevant cluster identifier:');
|
|
||||||
console.log(relevantClusterIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
const targetConnection =
|
|
||||||
await this.cloudlyRef.server.typedsocketServer.findTargetConnection(
|
|
||||||
async (connectionArg) => {
|
|
||||||
const identityTag = await connectionArg.getTagById('identity');
|
|
||||||
if (!identityTag) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const result =
|
|
||||||
plugins.smartjson.stringify(identityTag.payload) ===
|
|
||||||
plugins.smartjson.stringify(relevantClusterIdentifier);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (targetConnection) {
|
|
||||||
console.log(`the relevant cluster is connected and is now being informed.`);
|
|
||||||
const informCoreflowTR =
|
|
||||||
this.cloudlyRef.server.typedsocketServer.createTypedRequest<plugins.servezoneInterfaces.requests.version.IRequest_Cloudly_Coreflow_VersionManager_InformCoreflowAboutNewContainerVersion>(
|
|
||||||
'informCoreflowAboutNewContainerVersion',
|
|
||||||
targetConnection
|
|
||||||
);
|
|
||||||
informCoreflowTR.fire({
|
|
||||||
dockerImageUrl: containerVersion.dockerImageUrl,
|
|
||||||
dockerImageVersion: containerVersion.dockerImageVersion,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('the relevant cluster is not connected at this time.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// lets support the servezone standard
|
|
||||||
this.typedRouter.addTypedHandler(
|
|
||||||
new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.IRequest_InformAboutNewContainerImage>(
|
|
||||||
'servezonestandard_InformAboutNewContainerVersion',
|
|
||||||
async (dataArg) => {
|
|
||||||
const result =
|
|
||||||
await this.typedRouter.routeAndAddResponse<plugins.servezoneInterfaces.requests.version.IRequest_Any_Cloudly_VersionManager_InformCloudlyAboutNewContainerVersion>(
|
|
||||||
{
|
|
||||||
method: 'informCloudlyAboutNewContainerVersion',
|
|
||||||
request: {
|
|
||||||
dockerImageUrl: dataArg.containerImageInfo.registryUrl,
|
|
||||||
dockerImageVersion: dataArg.containerImageInfo.version,
|
|
||||||
},
|
|
||||||
response: null
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
return result.response;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gets all versions
|
|
||||||
*/
|
|
||||||
public async getAllVersions() {
|
|
||||||
const result = await ContainerVersion.getInstances<ContainerVersion>({});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './cloudly.plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
|
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
|
||||||
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
|
export const nogitDir = plugins.path.join(packageDir, '.nogit/');
|
@ -9,13 +9,13 @@ import * as typedsocket from '@api.global/typedsocket';
|
|||||||
|
|
||||||
export { typedrequest, typedsocket };
|
export { typedrequest, typedsocket };
|
||||||
|
|
||||||
// @mojoio scope
|
// @apiclient.xyz scope
|
||||||
import * as cloudflare from '@apiclient.xyz/cloudflare';
|
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 hetznercloud from '@apiclient.xyz/hetznercloud';
|
||||||
import * as slack from '@apiclient.xyz/slack';
|
import * as slack from '@apiclient.xyz/slack';
|
||||||
|
|
||||||
export { cloudflare, digitalocean, hetznercloud, slack };
|
export { cloudflare, docker, hetznercloud, slack };
|
||||||
|
|
||||||
// @tsclass scope
|
// @tsclass scope
|
||||||
import * as tsclass from '@tsclass/tsclass';
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
@ -29,10 +29,10 @@ import * as qenv from '@push.rocks/qenv';
|
|||||||
import * as smartacme from '@push.rocks/smartacme';
|
import * as smartacme from '@push.rocks/smartacme';
|
||||||
import * as smartbucket from '@push.rocks/smartbucket';
|
import * as smartbucket from '@push.rocks/smartbucket';
|
||||||
import * as smartcli from '@push.rocks/smartcli';
|
import * as smartcli from '@push.rocks/smartcli';
|
||||||
|
import * as smartclickhouse from '@push.rocks/smartclickhouse';
|
||||||
import * as smartdata from '@push.rocks/smartdata';
|
import * as smartdata from '@push.rocks/smartdata';
|
||||||
import * as smartdelay from '@push.rocks/smartdelay';
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
import * as smartexit from '@push.rocks/smartexit';
|
import * as smartexit from '@push.rocks/smartexit';
|
||||||
import * as typedserver from '@api.global/typedserver';
|
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
import * as smartguard from '@push.rocks/smartguard';
|
import * as smartguard from '@push.rocks/smartguard';
|
||||||
import * as smartjson from '@push.rocks/smartjson';
|
import * as smartjson from '@push.rocks/smartjson';
|
||||||
@ -45,6 +45,7 @@ import * as smartssh from '@push.rocks/smartssh';
|
|||||||
import * as smartstring from '@push.rocks/smartstring';
|
import * as smartstring from '@push.rocks/smartstring';
|
||||||
import * as smartunique from '@push.rocks/smartunique';
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
import * as taskbuffer from '@push.rocks/taskbuffer';
|
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||||
|
import * as typedserver from '@api.global/typedserver';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
npmextra,
|
npmextra,
|
||||||
@ -53,9 +54,9 @@ export {
|
|||||||
smartacme,
|
smartacme,
|
||||||
smartbucket,
|
smartbucket,
|
||||||
smartcli,
|
smartcli,
|
||||||
|
smartclickhouse,
|
||||||
smartdata,
|
smartdata,
|
||||||
smartexit,
|
smartexit,
|
||||||
typedserver,
|
|
||||||
smartdelay,
|
smartdelay,
|
||||||
smartfile,
|
smartfile,
|
||||||
smartguard,
|
smartguard,
|
||||||
@ -69,6 +70,7 @@ export {
|
|||||||
smartstring,
|
smartstring,
|
||||||
smartunique,
|
smartunique,
|
||||||
taskbuffer,
|
taskbuffer,
|
||||||
|
typedserver,
|
||||||
};
|
};
|
||||||
|
|
||||||
// @servezone scope
|
// @servezone scope
|
145
ts_apiclient/classes.cloudlyapiclient.ts
Normal file
145
ts_apiclient/classes.cloudlyapiclient.ts
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export type TClientType = 'api' | 'ci' | 'coreflow' | 'cli' | 'serverconfig';
|
||||||
|
|
||||||
|
import { Image } from './classes.image.js';
|
||||||
|
|
||||||
|
export class CloudlyApiClient {
|
||||||
|
private cloudlyUrl: string;
|
||||||
|
private registerAs: string;
|
||||||
|
|
||||||
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
||||||
|
public typedsocketClient: plugins.typedsocket.TypedSocket;
|
||||||
|
|
||||||
|
// Subjects
|
||||||
|
public configUpdateSubject = new plugins.smartrx.rxjs.Subject<
|
||||||
|
plugins.servezoneInterfaces.requests.config.IRequest_Cloudly_Coreflow_PushClusterConfig['request']
|
||||||
|
>();
|
||||||
|
|
||||||
|
public serverActionSubject = new plugins.smartrx.rxjs.Subject<
|
||||||
|
plugins.servezoneInterfaces.requests.server.IRequest_TriggerServerAction['request']
|
||||||
|
>();
|
||||||
|
|
||||||
|
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}`
|
||||||
|
);
|
||||||
|
|
||||||
|
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);
|
||||||
|
return {
|
||||||
|
actionConfirmed: true,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start() {
|
||||||
|
this.typedsocketClient = await plugins.typedsocket.TypedSocket.createClient(
|
||||||
|
this.typedrouter,
|
||||||
|
this.cloudlyUrl
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`CloudlyCluent connected to cloudly at ${this.cloudlyUrl}. Remember to get an identity.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop() {
|
||||||
|
await this.typedsocketClient.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public identity: plugins.servezoneInterfaces.data.IClusterIdentifier;
|
||||||
|
public async getIdentityByJumpCode(
|
||||||
|
jumpCodeArg: string,
|
||||||
|
tagConnection = false,
|
||||||
|
statefullIdentity = true
|
||||||
|
): Promise<plugins.servezoneInterfaces.data.IClusterIdentifier> {
|
||||||
|
const identityRequest =
|
||||||
|
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.identity.IRequest_Any_Cloudly_CoreflowManager_GetIdentityByJumpCode>(
|
||||||
|
'getIdentityByJumpCode'
|
||||||
|
);
|
||||||
|
console.log(`trying to get identity from cloudly with supplied jumpCodeArg: ${jumpCodeArg}`);
|
||||||
|
const response = await identityRequest.fire({
|
||||||
|
jumpCode: jumpCodeArg,
|
||||||
|
});
|
||||||
|
console.log('got identity response');
|
||||||
|
const identity = response.clusterIdentifier;
|
||||||
|
|
||||||
|
if (tagConnection) {
|
||||||
|
this.typedsocketClient.addTag('identity', identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statefullIdentity) {
|
||||||
|
this.identity = identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return identity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getClusterConfigFromCloudlyByIdentity(
|
||||||
|
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||||
|
): 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,
|
||||||
|
});
|
||||||
|
return response.configData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getServerConfigFromCloudlyByIdentity(
|
||||||
|
identityArg: plugins.servezoneInterfaces.data.IClusterIdentifier
|
||||||
|
): 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
|
||||||
|
});
|
||||||
|
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> {
|
||||||
|
const typedCertificateRequest =
|
||||||
|
this.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.certificate.IRequest_Any_Cloudly_GetSslCertificate>(
|
||||||
|
'getSslCertificate'
|
||||||
|
);
|
||||||
|
const typedResponse = await typedCertificateRequest.fire({
|
||||||
|
authToken: '', // do proper auth here
|
||||||
|
requiredCertName: domainNameArg,
|
||||||
|
});
|
||||||
|
return typedResponse.certificate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images
|
||||||
|
public async getImages() {
|
||||||
|
return Image.getImages(this);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
import * as plugins from './plugins.js';
|
|
||||||
|
|
||||||
export class CloudlyClient {
|
|
||||||
public clientToken: string;
|
|
||||||
|
|
||||||
constructor(clientToken: string) {
|
|
||||||
this.clientToken = clientToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getClusters() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,4 +2,5 @@ import * as plugins from './plugins.js';
|
|||||||
|
|
||||||
export class Cluster {
|
export class Cluster {
|
||||||
public getServers() {}
|
public getServers() {}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,84 @@
|
|||||||
|
import type { CloudlyApiClient } from './classes.cloudlyapiclient.js';
|
||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
export class Image {
|
export class Image implements plugins.servezoneInterfaces.data.IImage {
|
||||||
public getImages() {}
|
public static async getImages(cloudlyClientRef: CloudlyApiClient) {
|
||||||
|
const getAllImagesTR = cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_GetAllImages>(
|
||||||
|
'getAllImages'
|
||||||
|
);
|
||||||
|
const response = await getAllImagesTR.fire({
|
||||||
|
jwt: cloudlyClientRef.identity.jwt,
|
||||||
|
});
|
||||||
|
const resultImages: Image[] = [];
|
||||||
|
for (const image of response.images) {
|
||||||
|
const newImage = new Image(cloudlyClientRef);
|
||||||
|
Object.assign(newImage, image);
|
||||||
|
resultImages.push(newImage);
|
||||||
|
}
|
||||||
|
return resultImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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_GetImageMetadata>(
|
||||||
|
'getImageMetadata'
|
||||||
|
);
|
||||||
|
const response = await getVersionsTR.fire({
|
||||||
|
jwt: this.cloudlyClientRef.identity.jwt,
|
||||||
|
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 pullImageTR = this.cloudlyClientRef.typedsocketClient.createTypedRequest<plugins.servezoneInterfaces.requests.image.IRequest_PushImageVersion>(
|
||||||
|
'pushImageVersion'
|
||||||
|
);
|
||||||
|
const virtualStream = new plugins.typedrequest.VirtualStream();
|
||||||
|
const response = await pullImageTR.fire({
|
||||||
|
jwt: this.cloudlyClientRef.identity.jwt,
|
||||||
|
imageId: this.id,
|
||||||
|
versionString: '',
|
||||||
|
imageStream: virtualStream,
|
||||||
|
});
|
||||||
|
await virtualStream.readFromWebstream(imageReadableArg);
|
||||||
|
await done.promise;
|
||||||
|
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({
|
||||||
|
jwt: this.cloudlyClientRef.identity.jwt,
|
||||||
|
imageId: this.id,
|
||||||
|
versionString: versionStringArg,
|
||||||
|
});
|
||||||
|
const imageStream = response.imageStream;
|
||||||
|
const webduplexStream = new plugins.webstream.WebDuplexStream({});
|
||||||
|
imageStream.writeToWebstream(webduplexStream.writable);
|
||||||
|
return webduplexStream.readable;
|
||||||
|
};
|
||||||
}
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export class Server {
|
||||||
|
public static getServers() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
export * from './classes.cloudlyclient.js';
|
export * from './classes.cloudlyapiclient.js';
|
@ -1,5 +1,33 @@
|
|||||||
import * as typedrequest from '@api.global/typedrequest';
|
// @serve.zone scope
|
||||||
|
import * as servezoneInterfaces from '@serve.zone/interfaces';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
typedrequest
|
servezoneInterfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
// @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
|
||||||
|
import * as typedrequest from '@api.global/typedrequest';
|
||||||
|
import * as typedsocket from '@api.global/typedsocket';
|
||||||
|
|
||||||
|
export {
|
||||||
|
typedrequest,
|
||||||
|
typedsocket
|
||||||
|
}
|
||||||
|
|
||||||
|
// @tsclass scope
|
||||||
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
|
||||||
|
export {
|
||||||
|
tsclass,
|
||||||
}
|
}
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/cloudly',
|
name: '@serve.zone/cloudly',
|
||||||
version: '1.0.215',
|
version: '1.1.5',
|
||||||
description: 'A cloud manager utilizing Docker Swarmkit, designed for operations on Cloudron, and supports various cloud platforms like DigitalOcean, Hetzner Cloud, and Cloudflare.'
|
description: 'A comprehensive multi-cloud manager leveraging Docker Swarmkit to orchestrate containerized applications across various cloud services and integrate robust configuration and API management capabilities.'
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ const appstate = new plugins.deesDomtools.plugins.smartstate.Smartstate();
|
|||||||
export interface ILoginState {
|
export interface ILoginState {
|
||||||
jwt: string;
|
jwt: string;
|
||||||
}
|
}
|
||||||
export const loginStatePart = await appstate.getStatePart<ILoginState>(
|
export const loginStatePart: plugins.smartstate.StatePart<unknown, ILoginState> = await appstate.getStatePart<ILoginState>(
|
||||||
'login',
|
'login',
|
||||||
{ jwt: null },
|
{ jwt: null },
|
||||||
'persistent'
|
'persistent'
|
||||||
@ -72,7 +72,7 @@ export const dataState = await appstate.getStatePart<IDataState>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Getting data
|
// Getting data
|
||||||
export const getDataAction = dataState.createAction(async (statePartArg) => {
|
export const getAllDataAction = dataState.createAction(async (statePartArg, partialArg?: 'secrets' | 'images') => {
|
||||||
let currentState = statePartArg.getState();
|
let currentState = statePartArg.getState();
|
||||||
// Secrets
|
// Secrets
|
||||||
const trGetSecrets =
|
const trGetSecrets =
|
||||||
@ -88,6 +88,20 @@ export const getDataAction = dataState.createAction(async (statePartArg) => {
|
|||||||
...response,
|
...response,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// images
|
||||||
|
const trGetImages =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.image.IRequest_GetAllImages>(
|
||||||
|
'/typedrequest',
|
||||||
|
'getAllImages'
|
||||||
|
);
|
||||||
|
const responseImages = await trGetImages.fire({
|
||||||
|
jwt: loginStatePart.getState().jwt,
|
||||||
|
});
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...responseImages,
|
||||||
|
};
|
||||||
|
|
||||||
// Clusters
|
// Clusters
|
||||||
const trGetClusters =
|
const trGetClusters =
|
||||||
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.cluster.IRequest_GetAllClusters>(
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.cluster.IRequest_GetAllClusters>(
|
||||||
@ -120,7 +134,7 @@ export const createSecretGroupAction = dataState.createAction(
|
|||||||
secretBundles: [],
|
secretBundles: [],
|
||||||
secretGroups: [payloadArg],
|
secretGroups: [payloadArg],
|
||||||
});
|
});
|
||||||
currentState = await dataState.dispatchAction(getDataAction, null);
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
return currentState;
|
return currentState;
|
||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
@ -139,7 +153,7 @@ export const deleteSecretGroupAction = dataState.createAction(
|
|||||||
secretBundleIds: [],
|
secretBundleIds: [],
|
||||||
secretGroupIds: [payloadArg.secretGroupId],
|
secretGroupIds: [payloadArg.secretGroupId],
|
||||||
});
|
});
|
||||||
currentState = await dataState.dispatchAction(getDataAction, null);
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -158,7 +172,53 @@ export const deleteSecretBundleAction = dataState.createAction(
|
|||||||
secretBundleIds: [payloadArg.configBundleId],
|
secretBundleIds: [payloadArg.configBundleId],
|
||||||
secretGroupIds: [],
|
secretGroupIds: [],
|
||||||
});
|
});
|
||||||
currentState = await dataState.dispatchAction(getDataAction, null);
|
currentState = await dataState.dispatchAction(getAllDataAction, null);
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// image actions
|
||||||
|
export const createImageAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { imageName: string, description: string }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trCreateImage =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.image.IRequest_CreateImage>(
|
||||||
|
'/typedrequest',
|
||||||
|
'createImage'
|
||||||
|
);
|
||||||
|
const response = await trCreateImage.fire({
|
||||||
|
jwt: loginStatePart.getState().jwt,
|
||||||
|
name: payloadArg.imageName,
|
||||||
|
description: payloadArg.description,
|
||||||
|
});
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...{
|
||||||
|
images: [...currentState.images, response.image],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return currentState;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const deleteImageAction = dataState.createAction(
|
||||||
|
async (statePartArg, payloadArg: { imageId: string }) => {
|
||||||
|
let currentState = statePartArg.getState();
|
||||||
|
const trDeleteImage =
|
||||||
|
new domtools.plugins.typedrequest.TypedRequest<plugins.interfaces.requests.image.IRequest_DeleteImage>(
|
||||||
|
'/typedrequest',
|
||||||
|
'deleteImage'
|
||||||
|
);
|
||||||
|
const response = await trDeleteImage.fire({
|
||||||
|
jwt: loginStatePart.getState().jwt,
|
||||||
|
imageId: payloadArg.imageId,
|
||||||
|
});
|
||||||
|
currentState = {
|
||||||
|
...currentState,
|
||||||
|
...{
|
||||||
|
images: currentState.images.filter((image) => image.id !== payloadArg.imageId),
|
||||||
|
},
|
||||||
|
};
|
||||||
return currentState;
|
return currentState;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -42,6 +42,7 @@ export class CloudlyDashboard extends DeesElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
document.title = `cloudly v${commitinfo.version}`;
|
||||||
const subcription = appstate.dataState
|
const subcription = appstate.dataState
|
||||||
.select((stateArg) => stateArg)
|
.select((stateArg) => stateArg)
|
||||||
.subscribe((dataArg) => {
|
.subscribe((dataArg) => {
|
||||||
@ -148,7 +149,7 @@ export class CloudlyDashboard extends DeesElement {
|
|||||||
action: async () => {
|
action: async () => {
|
||||||
await plugins.deesCatalog.DeesModal.createAndShow({
|
await plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
heading: 'About',
|
heading: 'About',
|
||||||
content: html`configvault ${commitinfo.version}`,
|
content: html`cloudly ${commitinfo.version}`,
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
{
|
{
|
||||||
name: 'close',
|
name: 'close',
|
||||||
@ -171,7 +172,7 @@ export class CloudlyDashboard extends DeesElement {
|
|||||||
if (loginState.jwt) {
|
if (loginState.jwt) {
|
||||||
this.jwt = loginState.jwt;
|
this.jwt = loginState.jwt;
|
||||||
await simpleLogin.switchToSlottedContent();
|
await simpleLogin.switchToSlottedContent();
|
||||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +191,7 @@ export class CloudlyDashboard extends DeesElement {
|
|||||||
this.jwt = state.jwt;
|
this.jwt = state.jwt;
|
||||||
form.setStatus('success', 'Logged in!');
|
form.setStatus('success', 'Logged in!');
|
||||||
await simpleLogin.switchToSlottedContent();
|
await simpleLogin.switchToSlottedContent();
|
||||||
await appstate.dataState.dispatchAction(appstate.getDataAction, null);
|
await appstate.dataState.dispatchAction(appstate.getAllDataAction, null);
|
||||||
} else {
|
} else {
|
||||||
form.setStatus('error', 'Login failed!');
|
form.setStatus('error', 'Login failed!');
|
||||||
await domtools.convenience.smartdelay.delayFor(2000);
|
await domtools.convenience.smartdelay.delayFor(2000);
|
||||||
|
@ -37,35 +37,25 @@ export class CloudlyViewImages extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<cloudly-sectionheading>Images</cloudly-sectionheading>
|
<cloudly-sectionheading>Images</cloudly-sectionheading>
|
||||||
<dees-table
|
<dees-table
|
||||||
heading1="SecretGroups"
|
heading1="Images"
|
||||||
heading2="decoded in client"
|
heading2="an image is needed for running a service"
|
||||||
.data=${this.data.images}
|
.data=${this.data.images}
|
||||||
.displayFunction=${(secretGroup: plugins.interfaces.data.ISecretGroup) => {
|
.displayFunction=${(image: plugins.interfaces.data.IImage) => {
|
||||||
return {
|
return {
|
||||||
name: secretGroup.data.name,
|
id: image.id,
|
||||||
priority: secretGroup.data.priority,
|
name: image.data.name,
|
||||||
tags: html`<dees-chips
|
description: image.data.description,
|
||||||
.selectionMode=${'none'}
|
versions: image.data.versions.length,
|
||||||
.selectableChips=${secretGroup.data.tags}
|
|
||||||
></dees-chips>`,
|
|
||||||
key: secretGroup.data.key,
|
|
||||||
history: (() => {
|
|
||||||
const allHistory = [];
|
|
||||||
for (const environment in secretGroup.data.environments) {
|
|
||||||
allHistory.push(...secretGroup.data.environments[environment].history);
|
|
||||||
}
|
|
||||||
return allHistory.length;
|
|
||||||
})(),
|
|
||||||
};
|
};
|
||||||
}}
|
}}
|
||||||
.dataActions=${[
|
.dataActions=${[
|
||||||
{
|
{
|
||||||
name: 'add SecretGroup',
|
name: 'create Image',
|
||||||
type: ['header', 'footer'],
|
type: ['header', 'footer'],
|
||||||
iconName: 'plus',
|
iconName: 'plus',
|
||||||
actionFunc: async () => {
|
actionFunc: async () => {
|
||||||
plugins.deesCatalog.DeesModal.createAndShow({
|
plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
heading: 'create new SecretGroup',
|
heading: 'create new Image',
|
||||||
content: html`
|
content: html`
|
||||||
<dees-form>
|
<dees-form>
|
||||||
<dees-input-text
|
<dees-input-text
|
||||||
@ -78,50 +68,6 @@ export class CloudlyViewImages extends DeesElement {
|
|||||||
.key=${'data.description'}
|
.key=${'data.description'}
|
||||||
.value=${''}
|
.value=${''}
|
||||||
></dees-input-text>
|
></dees-input-text>
|
||||||
<dees-input-text
|
|
||||||
.label=${'Secret Key (data.key)'}
|
|
||||||
.key=${'data.key'}
|
|
||||||
.value=${''}
|
|
||||||
></dees-input-text>
|
|
||||||
<dees-table
|
|
||||||
.heading1=${'Environments'}
|
|
||||||
.heading2=${'keys need to be unique'}
|
|
||||||
key="environments"
|
|
||||||
.data=${[
|
|
||||||
{
|
|
||||||
environment: 'production',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
environment: 'staging',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
.dataActions=${[
|
|
||||||
{
|
|
||||||
name: 'add environment',
|
|
||||||
iconName: 'plus',
|
|
||||||
type: ['footer'],
|
|
||||||
actionFunc: async (dataArg) => {
|
|
||||||
dataArg.table.data.push({
|
|
||||||
environment: 'new environment',
|
|
||||||
value: '',
|
|
||||||
});
|
|
||||||
dataArg.table.requestUpdate('data');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'delete environment',
|
|
||||||
iconName: 'trash',
|
|
||||||
type: ['inRow'],
|
|
||||||
actionFunc: async (dataArg) => {
|
|
||||||
dataArg.table.data.splice(dataArg.table.data.indexOf(dataArg.item), 1);
|
|
||||||
dataArg.table.requestUpdate('data');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as plugins.deesCatalog.ITableAction[]}
|
|
||||||
.editableFields=${['environment', 'value']}
|
|
||||||
></dees-table>
|
|
||||||
</dees-form>
|
</dees-form>
|
||||||
`,
|
`,
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
@ -138,24 +84,9 @@ export class CloudlyViewImages extends DeesElement {
|
|||||||
const formData = await deesForm.collectFormData();
|
const formData = await deesForm.collectFormData();
|
||||||
console.log(`Prepare saving of data:`);
|
console.log(`Prepare saving of data:`);
|
||||||
console.log(formData);
|
console.log(formData);
|
||||||
const environments: plugins.interfaces.data.ISecretGroup['data']['environments'] =
|
await appstate.dataState.dispatchAction(appstate.createImageAction, {
|
||||||
{};
|
imageName: formData['data.name'] as string,
|
||||||
for (const itemArg of formData['environments'] as any[]) {
|
|
||||||
environments[itemArg.environment] = {
|
|
||||||
value: itemArg.value,
|
|
||||||
history: [],
|
|
||||||
lastUpdated: Date.now(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
await appstate.dataState.dispatchAction(appstate.createSecretGroupAction, {
|
|
||||||
id: null,
|
|
||||||
data: {
|
|
||||||
name: formData['data.name'] as string,
|
|
||||||
description: formData['data.description'] as string,
|
description: formData['data.description'] as string,
|
||||||
key: formData['data.key'] as string,
|
|
||||||
environments,
|
|
||||||
tags: [],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
await modalArg.destroy();
|
await modalArg.destroy();
|
||||||
},
|
},
|
||||||
@ -327,16 +258,16 @@ export class CloudlyViewImages extends DeesElement {
|
|||||||
iconName: 'trash',
|
iconName: 'trash',
|
||||||
type: ['contextmenu', 'inRow'],
|
type: ['contextmenu', 'inRow'],
|
||||||
actionFunc: async (
|
actionFunc: async (
|
||||||
itemArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.ISecretGroup>
|
itemArg: plugins.deesCatalog.ITableActionDataArg<plugins.interfaces.data.IImage>
|
||||||
) => {
|
) => {
|
||||||
plugins.deesCatalog.DeesModal.createAndShow({
|
plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
heading: `Delete ${itemArg.item.data.key}`,
|
heading: `Delete Image "${itemArg.item.data.name}"`,
|
||||||
content: html`
|
content: html`
|
||||||
<div style="text-align:center">Do you really want to delete the secret?</div>
|
<div style="text-align:center">Do you really want to delete the image?</div>
|
||||||
<div
|
<div
|
||||||
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
style="font-size: 0.8em; color: red; text-align:center; padding: 16px; margin-top: 24px; border: 1px solid #444; font-family: Intel One Mono; font-size: 16px;"
|
||||||
>
|
>
|
||||||
${itemArg.item.data.key}
|
${itemArg.item.id}
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
menuOptions: [
|
menuOptions: [
|
||||||
@ -350,8 +281,8 @@ export class CloudlyViewImages extends DeesElement {
|
|||||||
name: 'delete',
|
name: 'delete',
|
||||||
action: async (modalArg) => {
|
action: async (modalArg) => {
|
||||||
console.log(`Delete ${itemArg.item.id}`);
|
console.log(`Delete ${itemArg.item.id}`);
|
||||||
await appstate.dataState.dispatchAction(appstate.deleteSecretGroupAction, {
|
await appstate.dataState.dispatchAction(appstate.deleteImageAction, {
|
||||||
secretGroupId: itemArg.item.id,
|
imageId: itemArg.item.id,
|
||||||
});
|
});
|
||||||
await modalArg.destroy();
|
await modalArg.destroy();
|
||||||
},
|
},
|
||||||
|
@ -21,12 +21,12 @@ export class CloudlyViewSecretBundles extends DeesElement {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
const subecription = appstate.dataState
|
const subscription = appstate.dataState
|
||||||
.select((stateArg) => stateArg)
|
.select((stateArg) => stateArg)
|
||||||
.subscribe((dataArg) => {
|
.subscribe((dataArg) => {
|
||||||
this.data = dataArg;
|
this.data = dataArg;
|
||||||
});
|
});
|
||||||
this.rxSubscriptions.push(subecription);
|
this.rxSubscriptions.push(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
@ -144,7 +144,7 @@ export class CloudlyViewSecretBundles extends DeesElement {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'edit',
|
name: 'edit',
|
||||||
iconName: 'edit',
|
iconName: 'penToSquare',
|
||||||
type: ['doubleClick', 'contextmenu', 'inRow'],
|
type: ['doubleClick', 'contextmenu', 'inRow'],
|
||||||
actionFunc: async (actionDataArg) => {
|
actionFunc: async (actionDataArg) => {
|
||||||
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
const modal = await plugins.deesCatalog.DeesModal.createAndShow({
|
||||||
|
@ -13,7 +13,9 @@ export { deesDomtools, deesElement, deesCatalog };
|
|||||||
|
|
||||||
// @push.rocks scope
|
// @push.rocks scope
|
||||||
import * as webjwt from '@push.rocks/webjwt';
|
import * as webjwt from '@push.rocks/webjwt';
|
||||||
|
import * as smartstate from '@push.rocks/smartstate';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
webjwt,
|
webjwt,
|
||||||
|
smartstate,
|
||||||
}
|
}
|
Reference in New Issue
Block a user