Merge pull request #491 from coollabsio/next

v3.1.3
This commit is contained in:
Andras Bacsai 2022-07-18 10:11:19 +02:00 committed by GitHub
commit d624f95cb9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 2306 additions and 1979 deletions

View File

@ -14,7 +14,12 @@
// Set *default* container specific settings.json values on container create. // Set *default* container specific settings.json values on container create.
"settings": {}, "settings": {},
// Add the IDs of extensions you want installed when the container is created. // Add the IDs of extensions you want installed when the container is created.
"extensions": ["dbaeumer.vscode-eslint", "svelte.svelte-vscode"], "extensions": [
"dbaeumer.vscode-eslint",
"svelte.svelte-vscode",
"ardenivanov.svelte-intellisense",
"Prisma.prisma"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally. // Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [3000], "forwardPorts": [3000],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.

View File

@ -1,34 +1,28 @@
name: release-coolify name: production-release
on: on:
release: release:
types: published types: [published]
jobs: jobs:
make-it-coolifyed: making-something-cool:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- - name: Checkout
name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- - name: Set up QEMU
name: Set up QEMU
uses: docker/setup-qemu-action@v1 uses: docker/setup-qemu-action@v1
- - name: Set up Docker Buildx
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v1
- - name: Login to DockerHub
name: Login to DockerHub
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Get current package version
name: Get current package version
uses: martinbeentjes/npm-get-version-action@v1.2.3 uses: martinbeentjes/npm-get-version-action@v1.2.3
id: package-version id: package-version
- - name: Build and push
name: Build and push
uses: docker/build-push-action@v2 uses: docker/build-push-action@v2
with: with:
context: . context: .
@ -37,3 +31,7 @@ jobs:
tags: coollabsio/coolify:latest,coollabsio/coolify:${{steps.package-version.outputs.current-version}} tags: coollabsio/coolify:latest,coollabsio/coolify:${{steps.package-version.outputs.current-version}}
cache-from: type=registry,ref=coollabsio/coolify:buildcache cache-from: type=registry,ref=coollabsio/coolify:buildcache
cache-to: type=registry,ref=coollabsio/coolify:buildcache,mode=max cache-to: type=registry,ref=coollabsio/coolify:buildcache,mode=max
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_CHANNEL }}

35
.github/workflows/staging-release.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: staging-release
on:
push:
branches:
- next
jobs:
staging-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64
push: true
tags: coollabsio/coolify:next
cache-from: type=registry,ref=coollabsio/coolify:buildcache-next
cache-to: type=registry,ref=coollabsio/coolify:buildcache-next,mode=max
- uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_CHANNEL }}

View File

@ -2,6 +2,6 @@
"watch": ["src"], "watch": ["src"],
"ignore": ["src/**/*.test.ts"], "ignore": ["src/**/*.test.ts"],
"ext": "ts,mjs,json,graphql", "ext": "ts,mjs,json,graphql",
"exec": "rimraf build && esbuild `find src \\( -name '*.ts' \\) | grep -v client/` --platform=node --outdir=build --format=cjs && node build", "exec": "rimraf build && esbuild `find src \\( -name '*.ts' \\)` --minify=true --platform=node --outdir=build --format=cjs && node build",
"legacyWatch": true "legacyWatch": true
} }

View File

@ -15,11 +15,11 @@
}, },
"dependencies": { "dependencies": {
"@breejs/ts-worker": "2.0.0", "@breejs/ts-worker": "2.0.0",
"@fastify/autoload": "5.0.0", "@fastify/autoload": "5.1.0",
"@fastify/cookie": "7.0.0", "@fastify/cookie": "7.1.0",
"@fastify/cors": "8.0.0", "@fastify/cors": "8.0.0",
"@fastify/env": "4.0.0", "@fastify/env": "4.0.0",
"@fastify/jwt": "6.2.0", "@fastify/jwt": "6.3.1",
"@fastify/static": "6.4.0", "@fastify/static": "6.4.0",
"@iarna/toml": "2.2.5", "@iarna/toml": "2.2.5",
"@prisma/client": "3.15.2", "@prisma/client": "3.15.2",
@ -32,8 +32,8 @@
"dayjs": "1.11.3", "dayjs": "1.11.3",
"dockerode": "3.3.2", "dockerode": "3.3.2",
"dotenv-extended": "2.9.0", "dotenv-extended": "2.9.0",
"fastify": "4.2.0", "fastify": "4.2.1",
"fastify-plugin": "3.0.1", "fastify-plugin": "4.0.0",
"generate-password": "1.7.0", "generate-password": "1.7.0",
"get-port": "6.1.2", "get-port": "6.1.2",
"got": "12.1.0", "got": "12.1.0",
@ -47,11 +47,11 @@
"unique-names-generator": "4.7.1" "unique-names-generator": "4.7.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.0.3", "@types/node": "18.0.4",
"@types/node-os-utils": "1.3.0", "@types/node-os-utils": "1.3.0",
"@typescript-eslint/eslint-plugin": "5.30.5", "@typescript-eslint/eslint-plugin": "5.30.6",
"@typescript-eslint/parser": "5.30.5", "@typescript-eslint/parser": "5.30.6",
"esbuild": "0.14.48", "esbuild": "0.14.49",
"eslint": "8.19.0", "eslint": "8.19.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",

View File

@ -313,6 +313,7 @@ model Service {
umami Umami? umami Umami?
hasura Hasura? hasura Hasura?
fider Fider? fider Fider?
moodle Moodle?
} }
model PlausibleAnalytics { model PlausibleAnalytics {
@ -451,3 +452,20 @@ model Fider {
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }
model Moodle {
id String @id @default(cuid())
serviceId String @unique
defaultUsername String
defaultPassword String
defaultEmail String
mariadbUser String
mariadbPassword String
mariadbRootUser String
mariadbRootUserPassword String
mariadbDatabase String
mariadbPublicPort Int?
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@ -77,7 +77,7 @@ if (!isDev) {
root: path.join(__dirname, './public'), root: path.join(__dirname, './public'),
preCompressed: true preCompressed: true
}); });
fastify.setNotFoundHandler({}, function (request, reply) { fastify.setNotFoundHandler(async function (request, reply) {
if (request.raw.url && request.raw.url.startsWith('/api')) { if (request.raw.url && request.raw.url.startsWith('/api')) {
return reply.status(404).send({ return reply.status(404).send({
success: false success: false

View File

@ -189,7 +189,7 @@ import * as buildpacks from '../lib/buildPacks';
let imageFound = false; let imageFound = false;
try { try {
await image.inspect(); await image.inspect();
imageFound = false; imageFound = true;
} catch (error) { } catch (error) {
// //
} }
@ -235,7 +235,7 @@ import * as buildpacks from '../lib/buildPacks';
throw new Error(`Build pack ${buildPack} not found.`); throw new Error(`Build pack ${buildPack} not found.`);
} }
} else { } else {
await saveBuildLog({ line: 'Nothing changed.', buildId, applicationId }); await saveBuildLog({ line: 'Build image already available - no rebuild required.', buildId, applicationId });
} }
try { try {
await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${imageId}`); await asyncExecShell(`DOCKER_HOST=${host} docker stop -t 0 ${imageId}`);

View File

@ -14,9 +14,8 @@ import cuid from 'cuid';
import { checkContainer, getEngine, removeContainer } from './docker'; import { checkContainer, getEngine, removeContainer } from './docker';
import { day } from './dayjs'; import { day } from './dayjs';
import * as serviceFields from './serviceFields' import * as serviceFields from './serviceFields'
import axios from 'axios';
export const version = '3.1.2'; export const version = '3.1.3';
export const isDev = process.env.NODE_ENV === 'development'; export const isDev = process.env.NODE_ENV === 'development';
const algorithm = 'aes-256-ctr'; const algorithm = 'aes-256-ctr';
@ -69,7 +68,7 @@ export const include: any = {
meiliSearch: true, meiliSearch: true,
umami: true, umami: true,
hasura: true, hasura: true,
fider: true fider: true,
}; };
export const uniqueName = (): string => uniqueNamesGenerator(customConfig); export const uniqueName = (): string => uniqueNamesGenerator(customConfig);
@ -112,6 +111,168 @@ export const encrypt = (text: string) => {
}); });
} }
}; };
export const supportedServiceTypesAndVersions = [
{
name: 'plausibleanalytics',
fancyName: 'Plausible Analytics',
baseImage: 'plausible/analytics',
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
versions: ['latest', 'stable'],
recommendedVersion: 'stable',
ports: {
main: 8000
}
},
{
name: 'nocodb',
fancyName: 'NocoDB',
baseImage: 'nocodb/nocodb',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8080
}
},
{
name: 'minio',
fancyName: 'MinIO',
baseImage: 'minio/minio',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 9001
}
},
{
name: 'vscodeserver',
fancyName: 'VSCode Server',
baseImage: 'codercom/code-server',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8080
}
},
{
name: 'wordpress',
fancyName: 'Wordpress',
baseImage: 'wordpress',
images: ['bitnami/mysql:5.7'],
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
recommendedVersion: 'latest',
ports: {
main: 80
}
},
{
name: 'vaultwarden',
fancyName: 'Vaultwarden',
baseImage: 'vaultwarden/server',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 80
}
},
{
name: 'languagetool',
fancyName: 'LanguageTool',
baseImage: 'silviof/docker-languagetool',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8010
}
},
{
name: 'n8n',
fancyName: 'n8n',
baseImage: 'n8nio/n8n',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 5678
}
},
{
name: 'uptimekuma',
fancyName: 'Uptime Kuma',
baseImage: 'louislam/uptime-kuma',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 3001
}
},
{
name: 'ghost',
fancyName: 'Ghost',
baseImage: 'bitnami/ghost',
images: ['bitnami/mariadb'],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 2368
}
},
{
name: 'meilisearch',
fancyName: 'Meilisearch',
baseImage: 'getmeili/meilisearch',
images: [],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 7700
}
},
{
name: 'umami',
fancyName: 'Umami',
baseImage: 'ghcr.io/mikecao/umami',
images: ['postgres:12-alpine'],
versions: ['postgresql-latest'],
recommendedVersion: 'postgresql-latest',
ports: {
main: 3000
}
},
{
name: 'hasura',
fancyName: 'Hasura',
baseImage: 'hasura/graphql-engine',
images: ['postgres:12-alpine'],
versions: ['latest', 'v2.5.1'],
recommendedVersion: 'v2.5.1',
ports: {
main: 8080
}
},
{
name: 'fider',
fancyName: 'Fider',
baseImage: 'getfider/fider',
images: ['postgres:12-alpine'],
versions: ['stable'],
recommendedVersion: 'stable',
ports: {
main: 3000
}
},
// {
// name: 'moodle',
// fancyName: 'Moodle',
// baseImage: 'bitnami/moodle',
// images: [],
// versions: ['latest', 'v4.0.2'],
// recommendedVersion: 'latest',
// ports: {
// main: 8080
// }
// }
];
export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> { export async function checkDoubleBranch(branch: string, projectId: number): Promise<boolean> {
const applications = await prisma.application.findMany({ where: { branch, projectId } }); const applications = await prisma.application.findMany({ where: { branch, projectId } });
return applications.length > 1; return applications.length > 1;
@ -308,167 +469,6 @@ export const supportedDatabaseTypesAndVersions = [
}, },
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] } { name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
]; ];
export const supportedServiceTypesAndVersions = [
{
name: 'plausibleanalytics',
fancyName: 'Plausible Analytics',
baseImage: 'plausible/analytics',
images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
versions: ['latest', 'stable'],
recommendedVersion: 'stable',
ports: {
main: 8000
}
},
{
name: 'nocodb',
fancyName: 'NocoDB',
baseImage: 'nocodb/nocodb',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8080
}
},
{
name: 'minio',
fancyName: 'MinIO',
baseImage: 'minio/minio',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 9001
}
},
{
name: 'vscodeserver',
fancyName: 'VSCode Server',
baseImage: 'codercom/code-server',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8080
}
},
{
name: 'wordpress',
fancyName: 'Wordpress',
baseImage: 'wordpress',
images: ['bitnami/mysql:5.7'],
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
recommendedVersion: 'latest',
ports: {
main: 80
}
},
{
name: 'vaultwarden',
fancyName: 'Vaultwarden',
baseImage: 'vaultwarden/server',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 80
}
},
{
name: 'languagetool',
fancyName: 'LanguageTool',
baseImage: 'silviof/docker-languagetool',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 8010
}
},
{
name: 'n8n',
fancyName: 'n8n',
baseImage: 'n8nio/n8n',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 5678
}
},
{
name: 'uptimekuma',
fancyName: 'Uptime Kuma',
baseImage: 'louislam/uptime-kuma',
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 3001
}
},
{
name: 'ghost',
fancyName: 'Ghost',
baseImage: 'bitnami/ghost',
images: ['bitnami/mariadb'],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 2368
}
},
{
name: 'meilisearch',
fancyName: 'Meilisearch',
baseImage: 'getmeili/meilisearch',
images: [],
versions: ['latest'],
recommendedVersion: 'latest',
ports: {
main: 7700
}
},
{
name: 'umami',
fancyName: 'Umami',
baseImage: 'ghcr.io/mikecao/umami',
images: ['postgres:12-alpine'],
versions: ['postgresql-latest'],
recommendedVersion: 'postgresql-latest',
ports: {
main: 3000
}
},
{
name: 'hasura',
fancyName: 'Hasura',
baseImage: 'hasura/graphql-engine',
images: ['postgres:12-alpine'],
versions: ['latest', 'v2.8.3'],
recommendedVersion: 'v2.8.3',
ports: {
main: 8080
}
},
{
name: 'fider',
fancyName: 'Fider',
baseImage: 'getfider/fider',
images: ['postgres:12-alpine'],
versions: ['stable'],
recommendedVersion: 'stable',
ports: {
main: 3000
}
// },
// {
// name: 'appwrite',
// fancyName: 'AppWrite',
// baseImage: 'appwrite/appwrite',
// images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'],
// versions: ['latest', '0.13.0'],
// recommendedVersion: '0.13.0',
// ports: {
// main: 3000
// }
// }
}
];
export async function startTraefikProxy(engine: string): Promise<void> { export async function startTraefikProxy(engine: string): Promise<void> {
const host = getEngine(engine); const host = getEngine(engine);
@ -825,7 +825,7 @@ export type ComposeFileService = {
context: string; context: string;
dockerfile: string; dockerfile: string;
args?: Record<string, unknown>; args?: Record<string, unknown>;
}; } | string;
deploy?: { deploy?: {
restart_policy?: { restart_policy?: {
condition?: string; condition?: string;
@ -1347,6 +1347,40 @@ export async function configureServiceType({
} }
} }
}); });
} else if (type === 'moodle') {
const defaultUsername = cuid();
const defaultPassword = encrypt(generatePassword());
const defaultEmail = `${cuid()}@example.com`;
const mariadbUser = cuid();
const mariadbPassword = encrypt(generatePassword());
const mariadbDatabase = 'moodle_db';
const mariadbRootUser = cuid();
const mariadbRootUserPassword = encrypt(generatePassword());
await prisma.service.update({
where: { id },
data: {
type,
moodle: {
create: {
defaultUsername,
defaultPassword,
defaultEmail,
mariadbUser,
mariadbPassword,
mariadbDatabase,
mariadbRootUser,
mariadbRootUserPassword
}
}
}
});
} else {
await prisma.service.update({
where: { id },
data: {
type
}
});
} }
} }
@ -1502,10 +1536,10 @@ export function convertTolOldVolumeNames(type) {
return 'nc' return 'nc'
} }
} }
export async function getAvailableServices(): Promise<any> { // export async function getAvailableServices(): Promise<any> {
const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/291a957ee6ac01d480465623e183a30230ad921f/availableServices.json`) // const { data } = await axios.get(`https://gist.githubusercontent.com/andrasbacsai/4aac36d8d6214dbfc34fa78110554a50/raw/5b27e6c37d78aaeedc1148d797112c827a2f43cf/availableServices.json`)
return data // return data
} // }
export async function cleanupDockerStorage(host, lowDiskSpace, force) { export async function cleanupDockerStorage(host, lowDiskSpace, force) {
// Cleanup old coolify images // Cleanup old coolify images
try { try {

View File

@ -15,7 +15,7 @@ export const plausibleAnalytics = [{
isNumber: false, isNumber: false,
isBoolean: false, isBoolean: false,
isEncrypted: false isEncrypted: false
},{ }, {
name: 'username', name: 'username',
isEditable: true, isEditable: true,
isLowerCase: false, isLowerCase: false,
@ -86,7 +86,7 @@ export const minio = [{
isNumber: false, isNumber: false,
isBoolean: false, isBoolean: false,
isEncrypted: false isEncrypted: false
},{ }, {
name: 'rootUser', name: 'rootUser',
isEditable: false, isEditable: false,
isLowerCase: false, isLowerCase: false,
@ -325,7 +325,7 @@ export const fider = [{
isNumber: false, isNumber: false,
isBoolean: false, isBoolean: false,
isEncrypted: true isEncrypted: true
},{ }, {
name: 'postgreslUser', name: 'postgreslUser',
isEditable: false, isEditable: false,
isLowerCase: false, isLowerCase: false,
@ -413,3 +413,67 @@ export const fider = [{
isBoolean: false, isBoolean: false,
isEncrypted: false isEncrypted: false
}] }]
export const moodle = [{
name: 'defaultEmail',
isEditable: true,
isLowerCase: true,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'defaultUsername',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'defaultPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'mariadbUser',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'mariadbPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'mariadbRootUser',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
},
{
name: 'mariadbRootUserPassword',
isEditable: false,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: true
},
{
name: 'mariadbDatabase',
isEditable: true,
isLowerCase: false,
isNumber: false,
isBoolean: false,
isEncrypted: false
}]

View File

@ -2,18 +2,16 @@ import cuid from 'cuid';
import crypto from 'node:crypto' import crypto from 'node:crypto'
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import axios from 'axios'; import axios from 'axios';
import { day } from '../../../../lib/dayjs';
import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { day } from '../../../../lib/dayjs';
import { CheckDNS, DeleteApplication, DeployApplication, GetApplication, SaveApplication, SaveApplicationSettings } from '.';
import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common'; import { setDefaultBaseImage, setDefaultConfiguration } from '../../../../lib/buildPacks/common';
import { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common'; import { asyncExecShell, checkDomainsIsValidInDNS, checkDoubleBranch, decrypt, encrypt, errorHandler, generateSshKeyPair, getContainerUsage, getDomain, isDev, isDomainConfigured, prisma, stopBuild, uniqueName } from '../../../../lib/common';
import { checkContainer, dockerInstance, getEngine, isContainerExited, removeContainer } from '../../../../lib/docker'; import { checkContainer, dockerInstance, getEngine, isContainerExited, removeContainer } from '../../../../lib/docker';
import { scheduler } from '../../../../lib/scheduler'; import { scheduler } from '../../../../lib/scheduler';
import type { FastifyRequest } from 'fastify';
import type { GetImages, CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage, DeployApplication } from './types';
import { OnlyId } from '../../../../types';
export async function listApplications(request: FastifyRequest) { export async function listApplications(request: FastifyRequest) {
try { try {
@ -31,7 +29,7 @@ export async function listApplications(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getImages(request: FastifyRequest) { export async function getImages(request: FastifyRequest<GetImages>) {
try { try {
const { buildPack, deploymentType } = request.body const { buildPack, deploymentType } = request.body
let publishDirectory = undefined; let publishDirectory = undefined;
@ -65,14 +63,14 @@ export async function getImages(request: FastifyRequest) {
} }
} }
export async function getApplication(request: FastifyRequest<GetApplication>) { export async function getApplication(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
const appId = process.env['COOLIFY_APP_ID']; const appId = process.env['COOLIFY_APP_ID'];
let isRunning = false; let isRunning = false;
let isExited = false; let isExited = false;
const application = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId && application.destinationDocker?.engine) { if (application?.destinationDockerId && application.destinationDocker?.engine) {
isRunning = await checkContainer(application.destinationDocker.engine, id); isRunning = await checkContainer(application.destinationDocker.engine, id);
isExited = await isContainerExited(application.destinationDocker.engine, id); isExited = await isContainerExited(application.destinationDocker.engine, id);
@ -281,11 +279,11 @@ export async function saveApplicationSettings(request: FastifyRequest<SaveApplic
} }
} }
export async function stopApplication(request: FastifyRequest, reply: FastifyReply) { export async function stopApplication(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
const application = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId && application.destinationDocker?.engine) { if (application?.destinationDockerId && application.destinationDocker?.engine) {
const { engine } = application.destinationDocker; const { engine } = application.destinationDocker;
const found = await checkContainer(engine, id); const found = await checkContainer(engine, id);
@ -373,8 +371,9 @@ export async function getUsage(request) {
try { try {
const { id } = request.params const { id } = request.params
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
const application = await getApplicationFromDB(id, teamId);
let usage = {}; let usage = {};
const application: any = await getApplicationFromDB(id, teamId);
if (application.destinationDockerId) { if (application.destinationDockerId) {
[usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]); [usage] = await Promise.all([getContainerUsage(application.destinationDocker.engine, id)]);
} }
@ -389,7 +388,6 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
try { try {
const { id } = request.params const { id } = request.params
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
const { pullmergeRequestId = null, branch } = request.body const { pullmergeRequestId = null, branch } = request.body
const buildId = cuid(); const buildId = cuid();
const application = await getApplicationFromDB(id, teamId); const application = await getApplicationFromDB(id, teamId);
@ -450,7 +448,7 @@ export async function deployApplication(request: FastifyRequest<DeployApplicatio
} }
export async function saveApplicationSource(request: FastifyRequest, reply: FastifyReply) { export async function saveApplicationSource(request: FastifyRequest<SaveApplicationSource>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { gitSourceId } = request.body const { gitSourceId } = request.body
@ -464,11 +462,11 @@ export async function saveApplicationSource(request: FastifyRequest, reply: Fast
} }
} }
export async function getGitHubToken(request: FastifyRequest, reply: FastifyReply) { export async function getGitHubToken(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
const application = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
const payload = { const payload = {
iat: Math.round(new Date().getTime() / 1000), iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60), exp: Math.round(new Date().getTime() / 1000 + 60),
@ -490,7 +488,7 @@ export async function getGitHubToken(request: FastifyRequest, reply: FastifyRepl
} }
} }
export async function checkRepository(request: FastifyRequest) { export async function checkRepository(request: FastifyRequest<CheckRepository>) {
try { try {
const { id } = request.params const { id } = request.params
const { repository, branch } = request.query const { repository, branch } = request.query
@ -537,7 +535,7 @@ export async function saveRepository(request, reply) {
} }
} }
export async function saveDestination(request: FastifyRequest, reply: FastifyReply) { export async function saveDestination(request: FastifyRequest<SaveDestination>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { destinationId } = request.body const { destinationId } = request.body
@ -555,7 +553,7 @@ export async function getBuildPack(request) {
try { try {
const { id } = request.params const { id } = request.params
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
const application = await getApplicationFromDB(id, teamId); const application: any = await getApplicationFromDB(id, teamId);
return { return {
type: application.gitSource.type, type: application.gitSource.type,
projectId: application.projectId, projectId: application.projectId,
@ -579,7 +577,7 @@ export async function saveBuildPack(request, reply) {
} }
} }
export async function getSecrets(request: FastifyRequest) { export async function getSecrets(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
let secrets = await prisma.secret.findMany({ let secrets = await prisma.secret.findMany({
@ -601,7 +599,7 @@ export async function getSecrets(request: FastifyRequest) {
} }
} }
export async function saveSecret(request: FastifyRequest, reply: FastifyReply) { export async function saveSecret(request: FastifyRequest<SaveSecret>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
let { name, value, isBuildSecret, isPRMRSecret, isNew } = request.body let { name, value, isBuildSecret, isPRMRSecret, isNew } = request.body
@ -636,7 +634,7 @@ export async function saveSecret(request: FastifyRequest, reply: FastifyReply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function deleteSecret(request: FastifyRequest) { export async function deleteSecret(request: FastifyRequest<DeleteSecret>) {
try { try {
const { id } = request.params const { id } = request.params
const { name } = request.body const { name } = request.body
@ -647,7 +645,7 @@ export async function deleteSecret(request: FastifyRequest) {
} }
} }
export async function getStorages(request: FastifyRequest) { export async function getStorages(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const persistentStorages = await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } }); const persistentStorages = await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } });
@ -659,7 +657,7 @@ export async function getStorages(request: FastifyRequest) {
} }
} }
export async function saveStorage(request: FastifyRequest, reply: FastifyReply) { export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { path, newStorage, storageId } = request.body const { path, newStorage, storageId } = request.body
@ -680,7 +678,7 @@ export async function saveStorage(request: FastifyRequest, reply: FastifyReply)
} }
} }
export async function deleteStorage(request: FastifyRequest) { export async function deleteStorage(request: FastifyRequest<DeleteStorage>) {
try { try {
const { id } = request.params const { id } = request.params
const { path } = request.body const { path } = request.body
@ -691,7 +689,7 @@ export async function deleteStorage(request: FastifyRequest) {
} }
} }
export async function getPreviews(request: FastifyRequest) { export async function getPreviews(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
@ -739,7 +737,7 @@ export async function getPreviews(request: FastifyRequest) {
} }
} }
export async function getApplicationLogs(request: FastifyRequest) { export async function getApplicationLogs(request: FastifyRequest<GetApplicationLogs>) {
try { try {
const { id } = request.params const { id } = request.params
let { since = 0 } = request.query let { since = 0 } = request.query
@ -783,7 +781,7 @@ export async function getApplicationLogs(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getBuildLogs(request: FastifyRequest) { export async function getBuildLogs(request: FastifyRequest<GetBuildLogs>) {
try { try {
const { id } = request.params const { id } = request.params
let { buildId, skip = 0 } = request.query let { buildId, skip = 0 } = request.query
@ -820,9 +818,9 @@ export async function getBuildLogs(request: FastifyRequest) {
} }
} }
export async function getBuildIdLogs(request: FastifyRequest) { export async function getBuildIdLogs(request: FastifyRequest<GetBuildIdLogs>) {
try { try {
const { id, buildId } = request.params const { buildId } = request.params
let { sequence = 0 } = request.query let { sequence = 0 } = request.query
if (typeof sequence !== 'number') { if (typeof sequence !== 'number') {
sequence = Number(sequence) sequence = Number(sequence)
@ -841,7 +839,7 @@ export async function getBuildIdLogs(request: FastifyRequest) {
} }
} }
export async function getGitLabSSHKey(request: FastifyRequest) { export async function getGitLabSSHKey(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const application = await prisma.application.findUnique({ const application = await prisma.application.findUnique({
@ -854,7 +852,7 @@ export async function getGitLabSSHKey(request: FastifyRequest) {
} }
} }
export async function saveGitLabSSHKey(request: FastifyRequest, reply: FastifyReply) { export async function saveGitLabSSHKey(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const application = await prisma.application.findUnique({ const application = await prisma.application.findUnique({
@ -876,7 +874,7 @@ export async function saveGitLabSSHKey(request: FastifyRequest, reply: FastifyRe
} }
} }
export async function saveDeployKey(request: FastifyRequest, reply: FastifyReply) { export async function saveDeployKey(request: FastifyRequest<SaveDeployKey>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
let { deployKeyId } = request.body; let { deployKeyId } = request.body;
@ -896,9 +894,8 @@ export async function saveDeployKey(request: FastifyRequest, reply: FastifyReply
} }
} }
export async function cancelDeployment(request: FastifyRequest, reply: FastifyReply) { export async function cancelDeployment(request: FastifyRequest<CancelDeployment>, reply: FastifyReply) {
try { try {
const { id } = request.params
const { buildId, applicationId } = request.body; const { buildId, applicationId } = request.body;
if (!buildId) { if (!buildId) {
throw { status: 500, message: 'buildId is required' } throw { status: 500, message: 'buildId is required' }

View File

@ -1,90 +1,60 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { OnlyId } from '../../../../types';
import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers'; import { cancelDeployment, checkDNS, checkRepository, deleteApplication, deleteSecret, deleteStorage, deployApplication, getApplication, getApplicationLogs, getBuildIdLogs, getBuildLogs, getBuildPack, getGitHubToken, getGitLabSSHKey, getImages, getPreviews, getSecrets, getStorages, getUsage, listApplications, newApplication, saveApplication, saveApplicationSettings, saveApplicationSource, saveBuildPack, saveDeployKey, saveDestination, saveGitLabSSHKey, saveRepository, saveSecret, saveStorage, stopApplication } from './handlers';
export interface GetApplication { import type { CancelDeployment, CheckDNS, CheckRepository, DeleteApplication, DeleteSecret, DeleteStorage, DeployApplication, GetApplicationLogs, GetBuildIdLogs, GetBuildLogs, GetImages, SaveApplication, SaveApplicationSettings, SaveApplicationSource, SaveDeployKey, SaveDestination, SaveSecret, SaveStorage } from './types';
Params: { id: string; }
}
export interface SaveApplication { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
Params: { id: string; }, fastify.addHook('onRequest', async (request) => {
Body: any
}
export interface SaveApplicationSettings {
Params: { id: string; };
Querystring: { domain: string; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; };
}
export interface DeleteApplication {
Params: { id: string; };
Querystring: { domain: string; };
}
export interface CheckDNS {
Params: { id: string; };
Querystring: { domain: string; };
}
export interface DeployApplication {
Params: { id: string },
Querystring: { domain: string }
Body: { pullmergeRequestId: string | null, branch: string }
}
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
fastify.addHook('onRequest', async (request, reply) => {
return await request.jwtVerify() return await request.jwtVerify()
}) })
fastify.get('/', async (request) => await listApplications(request)); fastify.get('/', async (request) => await listApplications(request));
fastify.post('/images', async (request) => await getImages(request)); fastify.post<GetImages>('/images', async (request) => await getImages(request));
fastify.post('/new', async (request, reply) => await newApplication(request, reply)); fastify.post('/new', async (request, reply) => await newApplication(request, reply));
fastify.get<GetApplication>('/:id', async (request) => await getApplication(request)); fastify.get<OnlyId>('/:id', async (request) => await getApplication(request));
fastify.post<SaveApplication>('/:id', async (request, reply) => await saveApplication(request, reply)); fastify.post<SaveApplication>('/:id', async (request, reply) => await saveApplication(request, reply));
fastify.delete<DeleteApplication>('/:id', async (request, reply) => await deleteApplication(request, reply)); fastify.delete<DeleteApplication>('/:id', async (request, reply) => await deleteApplication(request, reply));
fastify.post('/:id/stop', async (request, reply) => await stopApplication(request, reply)); fastify.post<OnlyId>('/:id/stop', async (request, reply) => await stopApplication(request, reply));
fastify.post<SaveApplicationSettings>('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply)); fastify.post<SaveApplicationSettings>('/:id/settings', async (request, reply) => await saveApplicationSettings(request, reply));
fastify.post<SaveApplicationSettings>('/:id/check', async (request) => await checkDNS(request)); fastify.post<CheckDNS>('/:id/check', async (request) => await checkDNS(request));
fastify.get('/:id/secrets', async (request) => await getSecrets(request)); fastify.get<OnlyId>('/:id/secrets', async (request) => await getSecrets(request));
fastify.post('/:id/secrets', async (request, reply) => await saveSecret(request, reply)); fastify.post<SaveSecret>('/:id/secrets', async (request, reply) => await saveSecret(request, reply));
fastify.delete('/:id/secrets', async (request) => await deleteSecret(request)); fastify.delete<DeleteSecret>('/:id/secrets', async (request) => await deleteSecret(request));
fastify.get('/:id/storages', async (request) => await getStorages(request)); fastify.get<OnlyId>('/:id/storages', async (request) => await getStorages(request));
fastify.post('/:id/storages', async (request, reply) => await saveStorage(request, reply)); fastify.post<SaveStorage>('/:id/storages', async (request, reply) => await saveStorage(request, reply));
fastify.delete('/:id/storages', async (request) => await deleteStorage(request)); fastify.delete<DeleteStorage>('/:id/storages', async (request) => await deleteStorage(request));
fastify.get('/:id/previews', async (request) => await getPreviews(request)); fastify.get<OnlyId>('/:id/previews', async (request) => await getPreviews(request));
fastify.get('/:id/logs', async (request) => await getApplicationLogs(request)); fastify.get<GetApplicationLogs>('/:id/logs', async (request) => await getApplicationLogs(request));
fastify.get('/:id/logs/build', async (request) => await getBuildLogs(request)); fastify.get<GetBuildLogs>('/:id/logs/build', async (request) => await getBuildLogs(request));
fastify.get('/:id/logs/build/:buildId', async (request) => await getBuildIdLogs(request)); fastify.get<GetBuildIdLogs>('/:id/logs/build/:buildId', async (request) => await getBuildIdLogs(request));
fastify.get<DeployApplication>('/:id/usage', async (request) => await getUsage(request)) fastify.get('/:id/usage', async (request) => await getUsage(request))
fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request)) fastify.post<DeployApplication>('/:id/deploy', async (request) => await deployApplication(request))
fastify.post('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply)); fastify.post<CancelDeployment>('/:id/cancel', async (request, reply) => await cancelDeployment(request, reply));
fastify.post('/:id/configuration/source', async (request, reply) => await saveApplicationSource(request, reply)); fastify.post<SaveApplicationSource>('/:id/configuration/source', async (request, reply) => await saveApplicationSource(request, reply));
fastify.get('/:id/configuration/repository', async (request) => await checkRepository(request)); fastify.get<CheckRepository>('/:id/configuration/repository', async (request) => await checkRepository(request));
fastify.post('/:id/configuration/repository', async (request, reply) => await saveRepository(request, reply)); fastify.post('/:id/configuration/repository', async (request, reply) => await saveRepository(request, reply));
fastify.post('/:id/configuration/destination', async (request, reply) => await saveDestination(request, reply)); fastify.post<SaveDestination>('/:id/configuration/destination', async (request, reply) => await saveDestination(request, reply));
fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request)); fastify.get('/:id/configuration/buildpack', async (request) => await getBuildPack(request));
fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply)); fastify.post('/:id/configuration/buildpack', async (request, reply) => await saveBuildPack(request, reply));
fastify.get('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request)); fastify.get<OnlyId>('/:id/configuration/sshkey', async (request) => await getGitLabSSHKey(request));
fastify.post('/:id/configuration/sshkey', async (request, reply) => await saveGitLabSSHKey(request, reply)); fastify.post<OnlyId>('/:id/configuration/sshkey', async (request, reply) => await saveGitLabSSHKey(request, reply));
fastify.post('/:id/configuration/deploykey', async (request, reply) => await saveDeployKey(request, reply)); fastify.post<SaveDeployKey>('/:id/configuration/deploykey', async (request, reply) => await saveDeployKey(request, reply));
fastify.get<OnlyId>('/:id/configuration/githubToken', async (request, reply) => await getGitHubToken(request, reply));
fastify.get('/:id/configuration/githubToken', async (request, reply) => await getGitHubToken(request, reply));
}; };
export default root; export default root;

View File

@ -0,0 +1,117 @@
import type { OnlyId } from "../../../../types";
export interface SaveApplication extends OnlyId {
Body: {
name: string,
buildPack: string,
fqdn: string,
port: number,
exposePort: number,
installCommand: string,
buildCommand: string,
startCommand: string,
baseDirectory: string,
publishDirectory: string,
pythonWSGI: string,
pythonModule: string,
pythonVariable: string,
dockerFileLocation: string,
denoMainFile: string,
denoOptions: string,
baseImage: string,
baseBuildImage: string,
deploymentType: string
}
}
export interface SaveApplicationSettings extends OnlyId {
Querystring: { domain: string; };
Body: { debug: boolean; previews: boolean; dualCerts: boolean; autodeploy: boolean; branch: string; projectId: number; };
}
export interface DeleteApplication extends OnlyId {
Querystring: { domain: string; };
}
export interface CheckDNS extends OnlyId {
Querystring: { domain: string; };
Body: {
exposePort: number,
fqdn: string,
forceSave: boolean,
dualCerts: boolean
}
}
export interface DeployApplication {
Querystring: { domain: string }
Body: { pullmergeRequestId: string | null, branch: string }
}
export interface GetImages {
Body: { buildPack: string, deploymentType: string }
}
export interface SaveApplicationSource extends OnlyId {
Body: { gitSourceId: string }
}
export interface CheckRepository extends OnlyId {
Querystring: { repository: string, branch: string }
}
export interface SaveDestination extends OnlyId {
Body: { destinationId: string }
}
export interface SaveSecret extends OnlyId {
Body: {
name: string,
value: string,
isBuildSecret: boolean,
isPRMRSecret: boolean,
isNew: boolean
}
}
export interface DeleteSecret extends OnlyId {
Body: { name: string }
}
export interface SaveStorage extends OnlyId {
Body: {
path: string,
newStorage: boolean,
storageId: string
}
}
export interface DeleteStorage extends OnlyId {
Body: {
path: string,
}
}
export interface GetApplicationLogs extends OnlyId {
Querystring: {
since: number,
}
}
export interface GetBuildLogs extends OnlyId {
Querystring: {
buildId: string
skip: number,
}
}
export interface GetBuildIdLogs {
Params: {
buildId: string
},
Querystring: {
sequence: number
}
}
export interface SaveDeployKey extends OnlyId {
Body: {
deployKeyId: number
}
}
export interface CancelDeployment {
Body: {
buildId: string,
applicationId: string
}
}
export interface DeployApplication extends OnlyId {
Body: {
pullmergeRequestId: string | null,
branch: string
}
}

View File

@ -1,8 +1,8 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { errorHandler, version } from '../../../../lib/common'; import { errorHandler, version } from '../../../../lib/common';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/', async (request) => { fastify.get('/', async () => {
try { try {
return { return {
version, version,

View File

@ -6,10 +6,11 @@ import fs from 'fs/promises';
import { asyncExecShell, ComposeFile, createDirectories, decrypt, encrypt, errorHandler, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTcpProxy, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common'; import { asyncExecShell, ComposeFile, createDirectories, decrypt, encrypt, errorHandler, generateDatabaseConfiguration, generatePassword, getContainerUsage, getDatabaseImage, getDatabaseVersions, getFreePort, listSettings, makeLabelForStandaloneDatabase, prisma, startTcpProxy, startTraefikTCPProxy, stopDatabaseContainer, stopTcpHttpProxy, supportedDatabaseTypesAndVersions, uniqueName, updatePasswordInDb } from '../../../../lib/common';
import { dockerInstance, getEngine } from '../../../../lib/docker'; import { dockerInstance, getEngine } from '../../../../lib/docker';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
import { SaveDatabaseType } from './types';
export async function listDatabases(request: FastifyRequest) { export async function listDatabases(request: FastifyRequest) {
try { try {
const userId = request.user.userId;
const teamId = request.user.teamId; const teamId = request.user.teamId;
let databases = [] let databases = []
if (teamId === '0') { if (teamId === '0') {
@ -55,7 +56,7 @@ export async function newDatabase(request: FastifyRequest, reply: FastifyReply)
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getDatabase(request: FastifyRequest) { export async function getDatabase(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -107,7 +108,7 @@ export async function getDatabaseTypes(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveDatabaseType(request: FastifyRequest, reply: FastifyReply) { export async function saveDatabaseType(request: FastifyRequest<SaveDatabaseType>, reply: FastifyReply) {
try { try {
const { id } = request.params; const { id } = request.params;
const { type } = request.body; const { type } = request.body;
@ -120,7 +121,7 @@ export async function saveDatabaseType(request: FastifyRequest, reply: FastifyRe
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getVersions(request: FastifyRequest) { export async function getVersions(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -135,7 +136,7 @@ export async function getVersions(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveVersion(request: FastifyRequest, reply: FastifyReply) { export async function saveVersion(request: FastifyRequest<SaveVersion>, reply: FastifyReply) {
try { try {
const { id } = request.params; const { id } = request.params;
const { version } = request.body; const { version } = request.body;
@ -144,7 +145,6 @@ export async function saveVersion(request: FastifyRequest, reply: FastifyReply)
where: { id }, where: { id },
data: { data: {
version, version,
} }
}); });
return reply.code(201).send({}) return reply.code(201).send({})
@ -152,7 +152,7 @@ export async function saveVersion(request: FastifyRequest, reply: FastifyReply)
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveDatabaseDestination(request: FastifyRequest, reply: FastifyReply) { export async function saveDatabaseDestination(request: FastifyRequest<SaveDatabaseDestination>, reply: FastifyReply) {
try { try {
const { id } = request.params; const { id } = request.params;
const { destinationId } = request.body; const { destinationId } = request.body;
@ -181,7 +181,7 @@ export async function saveDatabaseDestination(request: FastifyRequest, reply: Fa
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getDatabaseUsage(request: FastifyRequest) { export async function getDatabaseUsage(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -203,7 +203,7 @@ export async function getDatabaseUsage(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function startDatabase(request: FastifyRequest) { export async function startDatabase(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -226,7 +226,6 @@ export async function startDatabase(request: FastifyRequest) {
const network = destinationDockerId && destinationDocker.network; const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine); const host = getEngine(destinationDocker.engine);
const engine = destinationDocker.engine;
const volumeName = volume.split(':')[0]; const volumeName = volume.split(':')[0];
const labels = await makeLabelForStandaloneDatabase({ id, image, volume }); const labels = await makeLabelForStandaloneDatabase({ id, image, volume });
@ -285,7 +284,7 @@ export async function startDatabase(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function stopDatabase(request: FastifyRequest) { export async function stopDatabase(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -310,7 +309,7 @@ export async function stopDatabase(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getDatabaseLogs(request: FastifyRequest) { export async function getDatabaseLogs(request: FastifyRequest<GetDatabaseLogs>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -361,7 +360,7 @@ export async function getDatabaseLogs(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function deleteDatabase(request: FastifyRequest) { export async function deleteDatabase(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -382,7 +381,7 @@ export async function deleteDatabase(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveDatabase(request: FastifyRequest, reply: FastifyReply) { export async function saveDatabase(request: FastifyRequest<SaveDatabase>, reply: FastifyReply) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -428,7 +427,7 @@ export async function saveDatabase(request: FastifyRequest, reply: FastifyReply)
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveDatabaseSettings(request: FastifyRequest) { export async function saveDatabaseSettings(request: FastifyRequest<SaveDatabaseSettings>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;

View File

@ -1,32 +1,35 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers'; import { deleteDatabase, getDatabase, getDatabaseLogs, getDatabaseTypes, getDatabaseUsage, getVersions, listDatabases, newDatabase, saveDatabase, saveDatabaseDestination, saveDatabaseSettings, saveDatabaseType, saveVersion, startDatabase, stopDatabase } from './handlers';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { import type { GetDatabaseLogs, OnlyId, SaveDatabase, SaveDatabaseDestination, SaveDatabaseSettings, SaveVersion } from '../../../../types';
fastify.addHook('onRequest', async (request, reply) => { import type { SaveDatabaseType } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify() return await request.jwtVerify()
}) })
fastify.get('/', async (request) => await listDatabases(request)); fastify.get('/', async (request) => await listDatabases(request));
fastify.post('/new', async (request, reply) => await newDatabase(request, reply)); fastify.post('/new', async (request, reply) => await newDatabase(request, reply));
fastify.get('/:id', async (request) => await getDatabase(request)); fastify.get<OnlyId>('/:id', async (request) => await getDatabase(request));
fastify.post('/:id', async (request, reply) => await saveDatabase(request, reply)); fastify.post<SaveDatabase>('/:id', async (request, reply) => await saveDatabase(request, reply));
fastify.delete('/:id', async (request) => await deleteDatabase(request)); fastify.delete<OnlyId>('/:id', async (request) => await deleteDatabase(request));
fastify.post('/:id/settings', async (request) => await saveDatabaseSettings(request)); fastify.post<SaveDatabaseSettings>('/:id/settings', async (request) => await saveDatabaseSettings(request));
fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request)); fastify.get('/:id/configuration/type', async (request) => await getDatabaseTypes(request));
fastify.post('/:id/configuration/type', async (request, reply) => await saveDatabaseType(request, reply)); fastify.post<SaveDatabaseType>('/:id/configuration/type', async (request, reply) => await saveDatabaseType(request, reply));
fastify.get('/:id/configuration/version', async (request) => await getVersions(request)); fastify.get<OnlyId>('/:id/configuration/version', async (request) => await getVersions(request));
fastify.post('/:id/configuration/version', async (request, reply) => await saveVersion(request, reply)); fastify.post<SaveVersion>('/:id/configuration/version', async (request, reply) => await saveVersion(request, reply));
fastify.post('/:id/configuration/destination', async (request, reply) => await saveDatabaseDestination(request, reply)); fastify.post<SaveDatabaseDestination>('/:id/configuration/destination', async (request, reply) => await saveDatabaseDestination(request, reply));
fastify.get('/:id/usage', async (request) => await getDatabaseUsage(request)); fastify.get<OnlyId>('/:id/usage', async (request) => await getDatabaseUsage(request));
fastify.get('/:id/logs', async (request) => await getDatabaseLogs(request)); fastify.get<GetDatabaseLogs>('/:id/logs', async (request) => await getDatabaseLogs(request));
fastify.post('/:id/start', async (request) => await startDatabase(request)); fastify.post<OnlyId>('/:id/start', async (request) => await startDatabase(request));
fastify.post('/:id/stop', async (request) => await stopDatabase(request)); fastify.post<OnlyId>('/:id/stop', async (request) => await stopDatabase(request));
}; };
export default root; export default root;

View File

@ -0,0 +1,5 @@
import type { OnlyId } from "../../../../types";
export interface SaveDatabaseType extends OnlyId {
Body: { type: string }
}

View File

@ -3,6 +3,9 @@ import { FastifyReply } from 'fastify';
import { asyncExecShell, errorHandler, listSettings, prisma, startCoolifyProxy, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; import { asyncExecShell, errorHandler, listSettings, prisma, startCoolifyProxy, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common';
import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker'; import { checkContainer, dockerInstance, getEngine } from '../../../../lib/docker';
import type { OnlyId } from '../../../../types';
import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
export async function listDestinations(request: FastifyRequest) { export async function listDestinations(request: FastifyRequest) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -22,7 +25,7 @@ export async function listDestinations(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function checkDestination(request: FastifyRequest) { export async function checkDestination(request: FastifyRequest<CheckDestination>) {
try { try {
const { network } = request.body; const { network } = request.body;
const found = await prisma.destinationDocker.findFirst({ where: { network } }); const found = await prisma.destinationDocker.findFirst({ where: { network } });
@ -36,7 +39,7 @@ export async function checkDestination(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getDestination(request: FastifyRequest) { export async function getDestination(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
@ -74,7 +77,7 @@ export async function getDestination(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function newDestination(request: FastifyRequest, reply: FastifyReply) { export async function newDestination(request: FastifyRequest<NewDestination>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
let { name, network, engine, isCoolifyProxyUsed } = request.body let { name, network, engine, isCoolifyProxyUsed } = request.body
@ -119,7 +122,7 @@ export async function newDestination(request: FastifyRequest, reply: FastifyRepl
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function deleteDestination(request: FastifyRequest) { export async function deleteDestination(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const destination = await prisma.destinationDocker.delete({ where: { id } }); const destination = await prisma.destinationDocker.delete({ where: { id } });
@ -143,7 +146,7 @@ export async function deleteDestination(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveDestinationSettings(request: FastifyRequest, reply: FastifyReply) { export async function saveDestinationSettings(request: FastifyRequest<SaveDestinationSettings>) {
try { try {
const { engine, isCoolifyProxyUsed } = request.body; const { engine, isCoolifyProxyUsed } = request.body;
await prisma.destinationDocker.updateMany({ await prisma.destinationDocker.updateMany({
@ -159,7 +162,7 @@ export async function saveDestinationSettings(request: FastifyRequest, reply: Fa
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function startProxy(request: FastifyRequest, reply: FastifyReply) { export async function startProxy(request: FastifyRequest<Proxy>) {
const { engine } = request.body; const { engine } = request.body;
try { try {
await startTraefikProxy(engine); await startTraefikProxy(engine);
@ -169,19 +172,16 @@ export async function startProxy(request: FastifyRequest, reply: FastifyReply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function stopProxy(request: FastifyRequest, reply: FastifyReply) { export async function stopProxy(request: FastifyRequest<Proxy>) {
const settings = await prisma.setting.findFirst({});
const { engine } = request.body; const { engine } = request.body;
try { try {
await stopTraefikProxy(engine); await stopTraefikProxy(engine);
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function restartProxy(request: FastifyRequest, reply: FastifyReply) { export async function restartProxy(request: FastifyRequest<Proxy>) {
const settings = await prisma.setting.findFirst({});
const { engine } = request.body; const { engine } = request.body;
try { try {
await stopTraefikProxy(engine); await stopTraefikProxy(engine);

View File

@ -1,24 +1,24 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy } from './handlers'; import { checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy } from './handlers';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { import type { OnlyId } from '../../../../types';
fastify.addHook('onRequest', async (request, reply) => { import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify() return await request.jwtVerify()
}) })
fastify.get('/', async (request) => await listDestinations(request)); fastify.get('/', async (request) => await listDestinations(request));
fastify.post('/check', async (request) => await checkDestination(request)); fastify.post<CheckDestination>('/check', async (request) => await checkDestination(request));
fastify.get('/:id', async (request) => await getDestination(request));
fastify.post('/:id', async (request, reply) => await newDestination(request, reply));
fastify.delete('/:id', async (request) => await deleteDestination(request));
fastify.post('/:id/settings', async (request, reply) => await saveDestinationSettings(request, reply));
fastify.post('/:id/start', async (request, reply) => await startProxy(request, reply));
fastify.post('/:id/stop', async (request, reply) => await stopProxy(request, reply));
fastify.post('/:id/restart', async (request, reply) => await restartProxy(request, reply));
fastify.get<OnlyId>('/:id', async (request) => await getDestination(request));
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
fastify.post<SaveDestinationSettings>('/:id/settings', async (request, reply) => await saveDestinationSettings(request));
fastify.post<Proxy>('/:id/start', async (request, reply) => await startProxy(request));
fastify.post<Proxy>('/:id/stop', async (request, reply) => await stopProxy(request));
fastify.post<Proxy>('/:id/restart', async (request, reply) => await restartProxy(request));
}; };
export default root; export default root;

View File

@ -0,0 +1,26 @@
import { OnlyId } from "../../../../types"
export interface CheckDestination {
Body: {
network: string
}
}
export interface NewDestination extends OnlyId {
Body: {
name: string
network: string
engine: string
isCoolifyProxyUsed: boolean
}
}
export interface SaveDestinationSettings extends OnlyId {
Body: {
engine: string
isCoolifyProxyUsed: boolean
}
}
export interface Proxy extends OnlyId {
Body: {
engine: string
}
}

View File

@ -8,7 +8,7 @@ import { asyncExecShell, asyncSleep, cleanupDockerStorage, errorHandler, isDev,
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import type { Login, Update } from '.'; import type { Login, Update } from '.';
import type { GetCurrentUser } from './types';
export async function hashPassword(password: string): Promise<string> { export async function hashPassword(password: string): Promise<string> {
const saltRounds = 15; const saltRounds = 15;
@ -25,17 +25,18 @@ export async function cleanupManually() {
} }
export async function checkUpdate(request: FastifyRequest) { export async function checkUpdate(request: FastifyRequest) {
try { try {
const isStaging = request.hostname === 'staging.coolify.io'
const currentVersion = version; const currentVersion = version;
const { data: versions } = await axios.get( const { data: versions } = await axios.get(
`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}` `https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}`
); );
const latestVersion = const latestVersion =
request.hostname === 'staging.coolify.io' isStaging
? versions['coolify'].next.version ? versions['coolify'].next.version
: versions['coolify'].main.version; : versions['coolify'].main.version;
const isUpdateAvailable = compare(latestVersion, currentVersion); const isUpdateAvailable = compare(latestVersion, currentVersion);
return { return {
isUpdateAvailable: isUpdateAvailable === 1, isUpdateAvailable: isStaging ? true : isUpdateAvailable === 1,
latestVersion latestVersion
}; };
} catch ({ status, message }) { } catch ({ status, message }) {
@ -253,9 +254,9 @@ export async function login(request: FastifyRequest<Login>, reply: FastifyReply)
} }
} }
export async function getCurrentUser(request: FastifyRequest, fastify) { export async function getCurrentUser(request: FastifyRequest<GetCurrentUser>, fastify) {
let token = null let token = null
const { teamId } = request.query
try { try {
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { id: request.user.userId } where: { id: request.user.userId }
@ -266,17 +267,17 @@ export async function getCurrentUser(request: FastifyRequest, fastify) {
} catch (error) { } catch (error) {
throw { status: 401, message: error }; throw { status: 401, message: error };
} }
if (request.query.teamId) { if (teamId) {
try { try {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { id: request.user.userId, teams: { some: { id: request.query.teamId } } }, where: { id: request.user.userId, teams: { some: { id: teamId } } },
include: { teams: true, permission: true } include: { teams: true, permission: true }
}) })
if (user) { if (user) {
const permission = user.permission.find(p => p.teamId === request.query.teamId).permission const permission = user.permission.find(p => p.teamId === teamId).permission
const payload = { const payload = {
...request.user, ...request.user,
teamId: request.query.teamId, teamId,
permission: permission || null, permission: permission || null,
isAdmin: permission === 'owner' || permission === 'admin' isAdmin: permission === 'owner' || permission === 'admin'

View File

@ -3,6 +3,10 @@ import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { decrypt, errorHandler, prisma, uniqueName } from '../../../../lib/common'; import { decrypt, errorHandler, prisma, uniqueName } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import type { OnlyId } from '../../../../types';
import type { BodyId, InviteToTeam, SaveTeam, SetPermission } from './types';
export async function listTeams(request: FastifyRequest) { export async function listTeams(request: FastifyRequest) {
try { try {
const userId = request.user.userId; const userId = request.user.userId;
@ -36,7 +40,7 @@ export async function listTeams(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function deleteTeam(request: FastifyRequest, reply: FastifyReply) { export async function deleteTeam(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try { try {
const userId = request.user.userId; const userId = request.user.userId;
const { id } = request.params; const { id } = request.params;
@ -136,7 +140,7 @@ export async function newTeam(request: FastifyRequest, reply: FastifyReply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getTeam(request: FastifyRequest, reply: FastifyReply) { export async function getTeam(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try { try {
const userId = request.user.userId; const userId = request.user.userId;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -163,7 +167,7 @@ export async function getTeam(request: FastifyRequest, reply: FastifyReply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveTeam(request: FastifyRequest, reply: FastifyReply) { export async function saveTeam(request: FastifyRequest<SaveTeam>, reply: FastifyReply) {
try { try {
const { id } = request.params; const { id } = request.params;
const { name } = request.body; const { name } = request.body;
@ -263,7 +267,7 @@ export async function saveTeam(request: FastifyRequest, reply: FastifyReply) {
// } // }
// } // }
export async function inviteToTeam(request: FastifyRequest, reply: FastifyReply) { export async function inviteToTeam(request: FastifyRequest<InviteToTeam>, reply: FastifyReply) {
try { try {
const userId = request.user.userId; const userId = request.user.userId;
const { email, permission, teamId, teamName } = request.body; const { email, permission, teamId, teamName } = request.body;
@ -306,7 +310,7 @@ export async function inviteToTeam(request: FastifyRequest, reply: FastifyReply)
} }
} }
export async function acceptInvitation(request: FastifyRequest) { export async function acceptInvitation(request: FastifyRequest<BodyId>) {
try { try {
const userId = request.user.userId; const userId = request.user.userId;
const { id } = request.body; const { id } = request.body;
@ -331,7 +335,7 @@ export async function acceptInvitation(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function revokeInvitation(request: FastifyRequest) { export async function revokeInvitation(request: FastifyRequest<BodyId>) {
try { try {
const { id } = request.body const { id } = request.body
await prisma.teamInvitation.delete({ where: { id } }); await prisma.teamInvitation.delete({ where: { id } });
@ -341,15 +345,15 @@ export async function revokeInvitation(request: FastifyRequest) {
} }
} }
export async function removeUser(request: FastifyRequest, reply: FastifyReply) { export async function removeUser(request: FastifyRequest<BodyId>, reply: FastifyReply) {
try { try {
const { uid } = request.body; const { id } = request.body;
const user = await prisma.user.findUnique({ where: { id: uid }, include: { teams: true, permission: true } }); const user = await prisma.user.findUnique({ where: { id }, include: { teams: true, permission: true } });
if (user) { if (user) {
const permissions = user.permission; const permissions = user.permission;
if (permissions.length > 0) { if (permissions.length > 0) {
for (const permission of permissions) { for (const permission of permissions) {
await prisma.permission.deleteMany({ where: { id: permission.id, userId: uid } }); await prisma.permission.deleteMany({ where: { id: permission.id, userId: id } });
} }
} }
const teams = user.teams; const teams = user.teams;
@ -357,7 +361,7 @@ export async function removeUser(request: FastifyRequest, reply: FastifyReply) {
for (const team of teams) { for (const team of teams) {
const newTeam = await prisma.team.update({ const newTeam = await prisma.team.update({
where: { id: team.id }, where: { id: team.id },
data: { users: { disconnect: { id: uid } } }, data: { users: { disconnect: { id } } },
include: { applications: true, database: true, gitHubApps: true, gitLabApps: true, gitSources: true, destinationDocker: true, service: true, users: true } include: { applications: true, database: true, gitHubApps: true, gitLabApps: true, gitSources: true, destinationDocker: true, service: true, users: true }
}); });
if (newTeam.users.length === 0) { if (newTeam.users.length === 0) {
@ -422,14 +426,14 @@ export async function removeUser(request: FastifyRequest, reply: FastifyReply) {
} }
} }
} }
await prisma.user.delete({ where: { id: uid } }); await prisma.user.delete({ where: { id } });
return reply.code(201).send() return reply.code(201).send()
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function setPermission(request: FastifyRequest, reply: FastifyReply) { export async function setPermission(request: FastifyRequest<SetPermission>, reply: FastifyReply) {
try { try {
const { userId, newPermission, permissionId } = request.body; const { userId, newPermission, permissionId } = request.body;
await prisma.permission.updateMany({ await prisma.permission.updateMany({
@ -442,7 +446,7 @@ export async function setPermission(request: FastifyRequest, reply: FastifyReply
} }
} }
export async function changePassword(request: FastifyRequest, reply: FastifyReply) { export async function changePassword(request: FastifyRequest<BodyId>, reply: FastifyReply) {
try { try {
const { id } = request.body; const { id } = request.body;
await prisma.user.update({ where: { id }, data: { password: 'RESETME' } }); await prisma.user.update({ where: { id }, data: { password: 'RESETME' } });

View File

@ -1,27 +1,28 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { acceptInvitation, changePassword, deleteTeam, getTeam, inviteToTeam, listTeams, newTeam, removeUser, revokeInvitation, saveTeam, setPermission } from './handlers'; import { acceptInvitation, changePassword, deleteTeam, getTeam, inviteToTeam, listTeams, newTeam, removeUser, revokeInvitation, saveTeam, setPermission } from './handlers';
import type { OnlyId } from '../../../../types';
import type { BodyId, InviteToTeam, SaveTeam, SetPermission } from './types';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request, reply) => { fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify() return await request.jwtVerify()
}) })
fastify.get('/', async (request) => await listTeams(request)); fastify.get('/', async (request) => await listTeams(request));
fastify.post('/new', async (request, reply) => await newTeam(request, reply)); fastify.post('/new', async (request, reply) => await newTeam(request, reply));
fastify.get('/team/:id', async (request, reply) => await getTeam(request, reply)); fastify.get<OnlyId>('/team/:id', async (request, reply) => await getTeam(request, reply));
fastify.post('/team/:id', async (request, reply) => await saveTeam(request, reply)); fastify.post<SaveTeam>('/team/:id', async (request, reply) => await saveTeam(request, reply));
fastify.delete('/team/:id', async (request, reply) => await deleteTeam(request, reply)); fastify.delete<OnlyId>('/team/:id', async (request, reply) => await deleteTeam(request, reply));
fastify.post('/team/:id/invitation/invite', async (request, reply) => await inviteToTeam(request, reply)) fastify.post<InviteToTeam>('/team/:id/invitation/invite', async (request, reply) => await inviteToTeam(request, reply))
fastify.post('/team/:id/invitation/accept', async (request) => await acceptInvitation(request)); fastify.post<BodyId>('/team/:id/invitation/accept', async (request) => await acceptInvitation(request));
fastify.post('/team/:id/invitation/revoke', async (request) => await revokeInvitation(request)); fastify.post<BodyId>('/team/:id/invitation/revoke', async (request) => await revokeInvitation(request));
fastify.post<SetPermission>('/team/:id/permission', async (request, reply) => await setPermission(request, reply));
fastify.post('/team/:id/permission', async (request, reply) => await setPermission(request, reply)); fastify.delete<BodyId>('/user/remove', async (request, reply) => await removeUser(request, reply));
fastify.post<BodyId>('/user/password', async (request, reply) => await changePassword(request, reply));
fastify.delete('/user/remove', async (request, reply) => await removeUser(request, reply));
fastify.post('/user/password', async (request, reply) => await changePassword(request, reply));
// fastify.delete('/user', async (request, reply) => await deleteUser(request, reply)); // fastify.delete('/user', async (request, reply) => await deleteUser(request, reply));
}; };

View File

@ -0,0 +1,27 @@
import { OnlyId } from "../../../../types"
export interface SaveTeam extends OnlyId {
Body: {
name: string
}
}
export interface InviteToTeam {
Body: {
email: string,
permission: string,
teamId: string,
teamName: string
}
}
export interface BodyId {
Body: {
id: string
}
}
export interface SetPermission {
Body: {
userId: string,
newPermission: string,
permissionId: string
}
}

View File

@ -1,6 +1,6 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { scheduler } from '../../../lib/scheduler';
import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually } from './handlers'; import { checkUpdate, login, showDashboard, update, showUsage, getCurrentUser, cleanupManually } from './handlers';
import { GetCurrentUser } from './types';
export interface Update { export interface Update {
Body: { latestVersion: string } Body: { latestVersion: string }
@ -9,7 +9,7 @@ export interface Login {
Body: { email: string, password: string, isLogin: boolean } Body: { email: string, password: string, isLogin: boolean }
} }
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/', async function (_request, reply) { fastify.get('/', async function (_request, reply) {
return reply.redirect(302, '/'); return reply.redirect(302, '/');
}); });
@ -19,7 +19,7 @@ const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
return { token, payload } return { token, payload }
}); });
fastify.get('/user', { fastify.get<GetCurrentUser>('/user', {
onRequest: [fastify.authenticate] onRequest: [fastify.authenticate]
}, async (request) => await getCurrentUser(request, fastify)); }, async (request) => await getCurrentUser(request, fastify));

View File

@ -2,78 +2,148 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
import fs from 'fs/promises'; import fs from 'fs/promises';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import bcrypt from 'bcryptjs'; import bcrypt from 'bcryptjs';
import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, supportedServiceTypesAndVersions, generatePassword, isDev, stopTcpHttpProxy, getAvailableServices } from '../../../../lib/common'; import { prisma, uniqueName, asyncExecShell, getServiceImage, getServiceImages, configureServiceType, getServiceFromDB, getContainerUsage, removeService, isDomainConfigured, saveUpdateableFields, fixType, decrypt, encrypt, getServiceMainPort, createDirectories, ComposeFile, makeLabelForServices, getFreePort, getDomain, errorHandler, generatePassword, isDev, stopTcpHttpProxy, supportedServiceTypesAndVersions } from '../../../../lib/common';
import { day } from '../../../../lib/dayjs'; import { day } from '../../../../lib/dayjs';
import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker'; import { checkContainer, dockerInstance, getEngine, removeContainer } from '../../../../lib/docker';
import cuid from 'cuid'; import cuid from 'cuid';
async function startServiceNew(request: FastifyRequest) { import type { OnlyId } from '../../../../types';
try { import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort(type);
const { workdir } = await createDirectories({ repository: type, buildId: id }); // async function startServiceNew(request: FastifyRequest<OnlyId>) {
const image = getServiceImage(type); // try {
const config = (await getAvailableServices()).find((name) => name.name === type).compose // const { id } = request.params;
const environmentVariables = {} // const teamId = request.user.teamId;
if (serviceSecret.length > 0) { // const service = await getServiceFromDB({ id, teamId });
serviceSecret.forEach((secret) => { // const { type, version, destinationDockerId, destinationDocker, serviceSecret, exposePort } =
environmentVariables[secret.name] = secret.value; // service;
}); // const network = destinationDockerId && destinationDocker.network;
} // const host = getEngine(destinationDocker.engine);
config.services[id] = JSON.parse(JSON.stringify(config.services[type])) // const port = getServiceMainPort(type);
config.services[id].container_name = id
config.services[id].image = `${image}:${version}`
config.services[id].ports = (exposePort ? [`${exposePort}:${port}`] : []),
config.services[id].restart = "always"
config.services[id].networks = [network]
config.services[id].labels = makeLabelForServices(type)
config.services[id].deploy = {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
}
config.networks = {
[network]: {
external: true
}
}
config.volumes = {}
config.services[id].volumes.forEach((volume, index) => {
let oldVolumeName = volume.split(':')[0]
const path = volume.split(':')[1]
oldVolumeName = convertTolOldVolumeNames(type)
const volumeName = `${id}-${oldVolumeName}`
config.volumes[volumeName] = {
name: volumeName
}
config.services[id].volumes[index] = `${volumeName}:${path}`
})
delete config.services[type]
config.services[id].environment = environmentVariables
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(config));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
// const { workdir } = await createDirectories({ repository: type, buildId: id });
// const image = getServiceImage(type);
// const config = (await getAvailableServices()).find((name) => name.name === type).compose
// const environmentVariables = {}
// if (serviceSecret.length > 0) {
// serviceSecret.forEach((secret) => {
// environmentVariables[secret.name] = secret.value;
// });
// }
// config.newVolumes = {}
// for (const service of Object.entries(config.services)) {
// const name = service[0]
// const details: any = service[1]
// config.services[`${id}-${name}`] = JSON.parse(JSON.stringify(details))
// config.services[`${id}-${name}`].container_name = `${id}-${name}`
// config.services[`${id}-${name}`].restart = "always"
// config.services[`${id}-${name}`].networks = [network]
// config.services[`${id}-${name}`].labels = makeLabelForServices(type)
// if (name === config.name) {
// config.services[`${id}-${name}`].image = `${details.image.split(':')[0]}:${version}`
// config.services[`${id}-${name}`].ports = (exposePort ? [`${exposePort}:${port}`] : [])
// config.services[`${id}-${name}`].environment = environmentVariables
// }
// config.services[`${id}-${name}`].deploy = {
// restart_policy: {
// condition: 'on-failure',
// delay: '5s',
// max_attempts: 3,
// window: '120s'
// }
// }
// if (config.services[`${id}-${name}`]?.volumes?.length > 0) {
// config.services[`${id}-${name}`].volumes.forEach((volume, index) => {
// let oldVolumeName = volume.split(':')[0]
// const path = volume.split(':')[1]
// // if (config?.volumes[oldVolumeName]) delete config?.volumes[oldVolumeName]
// const newName = convertTolOldVolumeNames(type)
// if (newName) oldVolumeName = newName
// const volumeName = `${id}-${oldVolumeName}`
// config.newVolumes[volumeName] = {
// name: volumeName
// }
// config.services[`${id}-${name}`].volumes[index] = `${volumeName}:${path}`
// })
// config.services[`${id}-${config.name}`] = {
// ...config.services[`${id}-${config.name}`],
// environment: environmentVariables
// }
// }
// config.networks = {
// [network]: {
// external: true
// }
// }
// config.volumes = config.newVolumes
// // config.services[`${id}-${name}`]?.volumes?.length > 0 && config.services[`${id}-${name}`].volumes.forEach((volume, index) => {
// // let oldVolumeName = volume.split(':')[0]
// // const path = volume.split(':')[1]
// // oldVolumeName = convertTolOldVolumeNames(type)
// // const volumeName = `${id}-${oldVolumeName}`
// // config.volumes[volumeName] = {
// // name: volumeName
// // }
// // config.services[`${id}-${name}`].volumes[index] = `${volumeName}:${path}`
// // })
// // config.services[`${id}-${config.name}`] = {
// // ...config.services[`${id}-${config.name}`],
// // environment: environmentVariables
// // }
// delete config.services[name]
// }
// console.log(config.services)
// console.log(config.volumes)
// // config.services[id] = JSON.parse(JSON.stringify(config.services[type]))
// // config.services[id].container_name = id
// // config.services[id].image = `${image}:${version}`
// // config.services[id].ports = (exposePort ? [`${exposePort}:${port}`] : []),
// // config.services[id].restart = "always"
// // config.services[id].networks = [network]
// // config.services[id].labels = makeLabelForServices(type)
// // config.services[id].deploy = {
// // restart_policy: {
// // condition: 'on-failure',
// // delay: '5s',
// // max_attempts: 3,
// // window: '120s'
// // }
// // }
// // config.networks = {
// // [network]: {
// // external: true
// // }
// // }
// // config.volumes = {}
// // config.services[id].volumes.forEach((volume, index) => {
// // let oldVolumeName = volume.split(':')[0]
// // const path = volume.split(':')[1]
// // oldVolumeName = convertTolOldVolumeNames(type)
// // const volumeName = `${id}-${oldVolumeName}`
// // config.volumes[volumeName] = {
// // name: volumeName
// // }
// // config.services[id].volumes[index] = `${volumeName}:${path}`
// // })
// // delete config.services[type]
// // config.services[id].environment = environmentVariables
// const composeFileDestination = `${workdir}/docker-compose.yaml`;
// // await fs.writeFile(composeFileDestination, yaml.dump(config));
// // await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
// // await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
// return {}
// } catch ({ status, message }) {
// return errorHandler({ status, message })
// }
// }
export async function listServices(request: FastifyRequest) { export async function listServices(request: FastifyRequest) {
try { try {
const userId = request.user.userId;
const teamId = request.user.teamId; const teamId = request.user.teamId;
let services = [] let services = []
if (teamId === '0') { if (teamId === '0') {
@ -102,7 +172,7 @@ export async function newService(request: FastifyRequest, reply: FastifyReply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getService(request: FastifyRequest) { export async function getService(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -155,9 +225,8 @@ export async function getServiceType(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveServiceType(request: FastifyRequest, reply: FastifyReply) { export async function saveServiceType(request: FastifyRequest<SaveServiceType>, reply: FastifyReply) {
try { try {
const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
const { type } = request.body; const { type } = request.body;
await configureServiceType({ id, type }); await configureServiceType({ id, type });
@ -166,7 +235,7 @@ export async function saveServiceType(request: FastifyRequest, reply: FastifyRep
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getServiceVersions(request: FastifyRequest) { export async function getServiceVersions(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -179,9 +248,8 @@ export async function getServiceVersions(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveServiceVersion(request: FastifyRequest, reply: FastifyReply) { export async function saveServiceVersion(request: FastifyRequest<SaveServiceVersion>, reply: FastifyReply) {
try { try {
const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
const { version } = request.body; const { version } = request.body;
await prisma.service.update({ await prisma.service.update({
@ -193,9 +261,8 @@ export async function saveServiceVersion(request: FastifyRequest, reply: Fastify
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveServiceDestination(request: FastifyRequest, reply: FastifyReply) { export async function saveServiceDestination(request: FastifyRequest<SaveServiceDestination>, reply: FastifyReply) {
try { try {
const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
const { destinationId } = request.body; const { destinationId } = request.body;
await prisma.service.update({ await prisma.service.update({
@ -207,7 +274,7 @@ export async function saveServiceDestination(request: FastifyRequest, reply: Fas
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getServiceUsage(request: FastifyRequest) { export async function getServiceUsage(request: FastifyRequest<OnlyId>) {
try { try {
const teamId = request.user.teamId; const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
@ -225,9 +292,8 @@ export async function getServiceUsage(request: FastifyRequest) {
} }
} }
export async function getServiceLogs(request: FastifyRequest) { export async function getServiceLogs(request: FastifyRequest<GetServiceLogs>) {
try { try {
const teamId = request.user.teamId;
const { id } = request.params; const { id } = request.params;
let { since = 0 } = request.query let { since = 0 } = request.query
if (since !== 0) { if (since !== 0) {
@ -276,7 +342,7 @@ export async function getServiceLogs(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function deleteService(request: FastifyRequest) { export async function deleteService(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params; const { id } = request.params;
await removeService({ id }); await removeService({ id });
@ -285,7 +351,7 @@ export async function deleteService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveServiceSettings(request: FastifyRequest, reply: FastifyReply) { export async function saveServiceSettings(request: FastifyRequest<SaveServiceSettings>, reply: FastifyReply) {
try { try {
const { id } = request.params; const { id } = request.params;
const { dualCerts } = request.body; const { dualCerts } = request.body;
@ -298,7 +364,7 @@ export async function saveServiceSettings(request: FastifyRequest, reply: Fastif
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function checkService(request: FastifyRequest) { export async function checkService(request: FastifyRequest<CheckService>) {
try { try {
const { id } = request.params; const { id } = request.params;
let { fqdn, exposePort, otherFqdns } = request.body; let { fqdn, exposePort, otherFqdns } = request.body;
@ -337,7 +403,7 @@ export async function checkService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveService(request: FastifyRequest, reply: FastifyReply) { export async function saveService(request: FastifyRequest<SaveService>, reply: FastifyReply) {
try { try {
const { id } = request.params; const { id } = request.params;
let { name, fqdn, exposePort, type } = request.body; let { name, fqdn, exposePort, type } = request.body;
@ -365,7 +431,7 @@ export async function saveService(request: FastifyRequest, reply: FastifyReply)
} }
} }
export async function getServiceSecrets(request: FastifyRequest) { export async function getServiceSecrets(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
let secrets = await prisma.serviceSecret.findMany({ let secrets = await prisma.serviceSecret.findMany({
@ -385,10 +451,10 @@ export async function getServiceSecrets(request: FastifyRequest) {
} }
} }
export async function saveServiceSecret(request: FastifyRequest, reply: FastifyReply) { export async function saveServiceSecret(request: FastifyRequest<SaveServiceSecret>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
let { name, value, isBuildSecret, isPRMRSecret, isNew } = request.body let { name, value, isNew } = request.body
if (isNew) { if (isNew) {
const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } }); const found = await prisma.serviceSecret.findFirst({ where: { name, serviceId: id } });
@ -420,7 +486,7 @@ export async function saveServiceSecret(request: FastifyRequest, reply: FastifyR
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function deleteServiceSecret(request: FastifyRequest) { export async function deleteServiceSecret(request: FastifyRequest<DeleteServiceSecret>) {
try { try {
const { id } = request.params const { id } = request.params
const { name } = request.body const { name } = request.body
@ -431,7 +497,7 @@ export async function deleteServiceSecret(request: FastifyRequest) {
} }
} }
export async function getServiceStorages(request: FastifyRequest) { export async function getServiceStorages(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const persistentStorages = await prisma.servicePersistentStorage.findMany({ const persistentStorages = await prisma.servicePersistentStorage.findMany({
@ -445,7 +511,7 @@ export async function getServiceStorages(request: FastifyRequest) {
} }
} }
export async function saveServiceStorage(request: FastifyRequest, reply: FastifyReply) { export async function saveServiceStorage(request: FastifyRequest<SaveServiceStorage>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { path, newStorage, storageId } = request.body const { path, newStorage, storageId } = request.body
@ -466,7 +532,7 @@ export async function saveServiceStorage(request: FastifyRequest, reply: Fastify
} }
} }
export async function deleteServiceStorage(request: FastifyRequest) { export async function deleteServiceStorage(request: FastifyRequest<DeleteServiceStorage>) {
try { try {
const { id } = request.params const { id } = request.params
const { path } = request.body const { path } = request.body
@ -477,7 +543,7 @@ export async function deleteServiceStorage(request: FastifyRequest) {
} }
} }
export async function startService(request: FastifyRequest,) { export async function startService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { type } = request.params const { type } = request.params
if (type === 'plausibleanalytics') { if (type === 'plausibleanalytics') {
@ -522,12 +588,15 @@ export async function startService(request: FastifyRequest,) {
if (type === 'fider') { if (type === 'fider') {
return await startFiderService(request) return await startFiderService(request)
} }
if (type === 'moodle') {
return await startMoodleService(request)
}
throw `Service type ${type} not supported.` throw `Service type ${type} not supported.`
} catch (error) { } catch (error) {
throw { status: 500, message: error?.message || error } throw { status: 500, message: error?.message || error }
} }
} }
export async function stopService(request: FastifyRequest) { export async function stopService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { type } = request.params const { type } = request.params
if (type === 'plausibleanalytics') { if (type === 'plausibleanalytics') {
@ -572,12 +641,15 @@ export async function stopService(request: FastifyRequest) {
if (type === 'fider') { if (type === 'fider') {
return await stopFiderService(request) return await stopFiderService(request)
} }
if (type === 'moodle') {
return await stopMoodleService(request)
}
throw `Service type ${type} not supported.` throw `Service type ${type} not supported.`
} catch (error) { } catch (error) {
throw { status: 500, message: error?.message || error } throw { status: 500, message: error?.message || error }
} }
} }
export async function setSettingsService(request: FastifyRequest, reply: FastifyReply) { export async function setSettingsService(request: FastifyRequest<ServiceStartStop & SetWordpressSettings>, reply: FastifyReply) {
try { try {
const { type } = request.params const { type } = request.params
if (type === 'wordpress') { if (type === 'wordpress') {
@ -588,7 +660,7 @@ export async function setSettingsService(request: FastifyRequest, reply: Fastify
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function setWordpressSettings(request: FastifyRequest, reply: FastifyReply) { async function setWordpressSettings(request: FastifyRequest<ServiceStartStop & SetWordpressSettings>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const { ownMysql } = request.body const { ownMysql } = request.body
@ -602,7 +674,7 @@ async function setWordpressSettings(request: FastifyRequest, reply: FastifyReply
} }
} }
async function startPlausibleAnalyticsService(request: FastifyRequest) { async function startPlausibleAnalyticsService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params const { id } = request.params
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -796,7 +868,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopPlausibleAnalyticsService(request: FastifyRequest) { async function stopPlausibleAnalyticsService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -825,7 +897,7 @@ async function stopPlausibleAnalyticsService(request: FastifyRequest) {
} }
} }
async function startNocodbService(request: FastifyRequest) { async function startNocodbService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -891,7 +963,7 @@ async function startNocodbService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopNocodbService(request: FastifyRequest) { async function stopNocodbService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -910,7 +982,7 @@ async function stopNocodbService(request: FastifyRequest) {
} }
} }
async function startMinioService(request: FastifyRequest) { async function startMinioService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -994,7 +1066,7 @@ async function startMinioService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopMinioService(request: FastifyRequest) { async function stopMinioService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1014,7 +1086,7 @@ async function stopMinioService(request: FastifyRequest) {
} }
} }
async function startVscodeService(request: FastifyRequest) { async function startVscodeService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1119,7 +1191,7 @@ async function startVscodeService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopVscodeService(request: FastifyRequest) { async function stopVscodeService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1138,7 +1210,7 @@ async function stopVscodeService(request: FastifyRequest) {
} }
} }
async function startWordpressService(request: FastifyRequest) { async function startWordpressService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1146,7 +1218,6 @@ async function startWordpressService(request: FastifyRequest) {
const { const {
type, type,
version, version,
fqdn,
destinationDockerId, destinationDockerId,
serviceSecret, serviceSecret,
destinationDocker, destinationDocker,
@ -1264,7 +1335,7 @@ async function startWordpressService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopWordpressService(request: FastifyRequest) { async function stopWordpressService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1313,7 +1384,7 @@ async function stopWordpressService(request: FastifyRequest) {
} }
} }
async function startVaultwardenService(request: FastifyRequest) { async function startVaultwardenService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1380,7 +1451,7 @@ async function startVaultwardenService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopVaultwardenService(request: FastifyRequest) { async function stopVaultwardenService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1404,7 +1475,7 @@ async function stopVaultwardenService(request: FastifyRequest) {
} }
} }
async function startLanguageToolService(request: FastifyRequest) { async function startLanguageToolService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1472,7 +1543,7 @@ async function startLanguageToolService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopLanguageToolService(request: FastifyRequest) { async function stopLanguageToolService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1496,7 +1567,7 @@ async function stopLanguageToolService(request: FastifyRequest) {
} }
} }
async function startN8nService(request: FastifyRequest) { async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1564,7 +1635,7 @@ async function startN8nService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopN8nService(request: FastifyRequest) { async function stopN8nService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1588,7 +1659,7 @@ async function stopN8nService(request: FastifyRequest) {
} }
} }
async function startUptimekumaService(request: FastifyRequest) { async function startUptimekumaService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1655,7 +1726,7 @@ async function startUptimekumaService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopUptimekumaService(request: FastifyRequest) { async function stopUptimekumaService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1679,7 +1750,7 @@ async function stopUptimekumaService(request: FastifyRequest) {
} }
} }
async function startGhostService(request: FastifyRequest) { async function startGhostService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1708,6 +1779,7 @@ async function startGhostService(request: FastifyRequest) {
const { workdir } = await createDirectories({ repository: type, buildId: id }); const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type); const image = getServiceImage(type);
const domain = getDomain(fqdn); const domain = getDomain(fqdn);
const port = getServiceMainPort('ghost');
const isHttps = fqdn.startsWith('https://'); const isHttps = fqdn.startsWith('https://');
const config = { const config = {
ghost: { ghost: {
@ -1806,7 +1878,7 @@ async function startGhostService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopGhostService(request: FastifyRequest) { async function stopGhostService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1834,7 +1906,7 @@ async function stopGhostService(request: FastifyRequest) {
} }
} }
async function startMeilisearchService(request: FastifyRequest) { async function startMeilisearchService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1907,7 +1979,7 @@ async function startMeilisearchService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopMeilisearchService(request: FastifyRequest) { async function stopMeilisearchService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -1931,7 +2003,7 @@ async function stopMeilisearchService(request: FastifyRequest) {
} }
} }
async function startUmamiService(request: FastifyRequest) { async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -2126,7 +2198,7 @@ async function startUmamiService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopUmamiService(request: FastifyRequest) { async function stopUmamiService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -2158,7 +2230,7 @@ async function stopUmamiService(request: FastifyRequest) {
} }
} }
async function startHasuraService(request: FastifyRequest) { async function startHasuraService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -2262,7 +2334,7 @@ async function startHasuraService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopHasuraService(request: FastifyRequest) { async function stopHasuraService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -2294,7 +2366,7 @@ async function stopHasuraService(request: FastifyRequest) {
} }
} }
async function startFiderService(request: FastifyRequest) { async function startFiderService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -2425,7 +2497,7 @@ async function startFiderService(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
async function stopFiderService(request: FastifyRequest) { async function stopFiderService(request: FastifyRequest<ServiceStartStop>) {
try { try {
const { id } = request.params; const { id } = request.params;
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -2457,7 +2529,171 @@ async function stopFiderService(request: FastifyRequest) {
} }
} }
export async function activatePlausibleUsers(request: FastifyRequest, reply: FastifyReply) { async function startMoodleService(request: FastifyRequest<ServiceStartStop>) {
try {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const {
type,
version,
fqdn,
destinationDockerId,
destinationDocker,
serviceSecret,
exposePort,
moodle: {
defaultUsername,
defaultPassword,
defaultEmail,
mariadbRootUser,
mariadbRootUserPassword,
mariadbDatabase,
mariadbPassword,
mariadbUser
}
} = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const port = getServiceMainPort('moodle');
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const domain = getDomain(fqdn);
const config = {
moodle: {
image: `${image}:${version}`,
volume: `${id}-data:/bitnami/moodle`,
environmentVariables: {
MOODLE_USERNAME: defaultUsername,
MOODLE_PASSWORD: defaultPassword,
MOODLE_EMAIL: defaultEmail,
MOODLE_DATABASE_HOST: `${id}-mariadb`,
MOODLE_DATABASE_USER: mariadbUser,
MOODLE_DATABASE_PASSWORD: mariadbPassword,
MOODLE_DATABASE_NAME: mariadbDatabase,
MOODLE_REVERSEPROXY: 'yes'
}
},
mariadb: {
image: 'bitnami/mariadb:latest',
volume: `${id}-mariadb-data:/bitnami/mariadb`,
environmentVariables: {
MARIADB_USER: mariadbUser,
MARIADB_PASSWORD: mariadbPassword,
MARIADB_DATABASE: mariadbDatabase,
MARIADB_ROOT_USER: mariadbRootUser,
MARIADB_ROOT_PASSWORD: mariadbRootUserPassword
}
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.moodle.environmentVariables[secret.name] = secret.value;
});
}
const composeFile: ComposeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.moodle.image,
environment: config.moodle.environmentVariables,
networks: [network],
volumes: [],
restart: 'always',
labels: makeLabelForServices('moodle'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
},
depends_on: [`${id}-mariadb`]
},
[`${id}-mariadb`]: {
container_name: `${id}-mariadb`,
image: config.mariadb.image,
environment: config.mariadb.environmentVariables,
networks: [network],
volumes: [],
restart: 'always',
deploy: {
restart_policy: {
condition: 'on-failure',
delay: '5s',
max_attempts: 3,
window: '120s'
}
},
depends_on: []
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[config.moodle.volume.split(':')[0]]: {
name: config.moodle.volume.split(':')[0]
},
[config.mariadb.volume.split(':')[0]]: {
name: config.mariadb.volume.split(':')[0]
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`);
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
async function stopMoodleService(request: FastifyRequest<ServiceStartStop>) {
try {
const { id } = request.params;
const teamId = request.user.teamId;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
if (found) {
await removeContainer({ id, engine });
}
} catch (error) {
console.error(error);
}
try {
const found = await checkContainer(engine, `${id}-mariadb`);
if (found) {
await removeContainer({ id: `${id}-mariadb`, engine });
}
} catch (error) {
console.error(error);
}
}
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function activatePlausibleUsers(request: FastifyRequest<OnlyId>, reply: FastifyReply) {
try { try {
const { id } = request.params const { id } = request.params
const teamId = request.user.teamId; const teamId = request.user.teamId;
@ -2482,10 +2718,8 @@ export async function activatePlausibleUsers(request: FastifyRequest, reply: Fas
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function activateWordpressFtp(request: FastifyRequest, reply: FastifyReply) { export async function activateWordpressFtp(request: FastifyRequest<ActivateWordpressFtp>, reply: FastifyReply) {
const { id } = request.params const { id } = request.params
const teamId = request.user.teamId;
const { ftpEnabled } = request.body; const { ftpEnabled } = request.body;
const publicPort = await getFreePort(); const publicPort = await getFreePort();

View File

@ -27,46 +27,49 @@ import {
stopService stopService
} from './handlers'; } from './handlers';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { import type { OnlyId } from '../../../../types';
fastify.addHook('onRequest', async (request, reply) => { import type { ActivateWordpressFtp, CheckService, DeleteServiceSecret, DeleteServiceStorage, GetServiceLogs, SaveService, SaveServiceDestination, SaveServiceSecret, SaveServiceSettings, SaveServiceStorage, SaveServiceType, SaveServiceVersion, ServiceStartStop, SetWordpressSettings } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify() return await request.jwtVerify()
}) })
fastify.get('/', async (request) => await listServices(request)); fastify.get('/', async (request) => await listServices(request));
fastify.post('/new', async (request, reply) => await newService(request, reply)); fastify.post('/new', async (request, reply) => await newService(request, reply));
fastify.get('/:id', async (request) => await getService(request)); fastify.get<OnlyId>('/:id', async (request) => await getService(request));
fastify.post('/:id', async (request, reply) => await saveService(request, reply)); fastify.post<SaveService>('/:id', async (request, reply) => await saveService(request, reply));
fastify.delete('/:id', async (request) => await deleteService(request)); fastify.delete<OnlyId>('/:id', async (request) => await deleteService(request));
fastify.post('/:id/check', async (request) => await checkService(request)); fastify.post<CheckService>('/:id/check', async (request) => await checkService(request));
fastify.post('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply)); fastify.post<SaveServiceSettings>('/:id/settings', async (request, reply) => await saveServiceSettings(request, reply));
fastify.get('/:id/secrets', async (request) => await getServiceSecrets(request)); fastify.get<OnlyId>('/:id/secrets', async (request) => await getServiceSecrets(request));
fastify.post('/:id/secrets', async (request, reply) => await saveServiceSecret(request, reply)); fastify.post<SaveServiceSecret>('/:id/secrets', async (request, reply) => await saveServiceSecret(request, reply));
fastify.delete('/:id/secrets', async (request) => await deleteServiceSecret(request)); fastify.delete<DeleteServiceSecret>('/:id/secrets', async (request) => await deleteServiceSecret(request));
fastify.get('/:id/storages', async (request) => await getServiceStorages(request)); fastify.get<OnlyId>('/:id/storages', async (request) => await getServiceStorages(request));
fastify.post('/:id/storages', async (request, reply) => await saveServiceStorage(request, reply)); fastify.post<SaveServiceStorage>('/:id/storages', async (request, reply) => await saveServiceStorage(request, reply));
fastify.delete('/:id/storages', async (request) => await deleteServiceStorage(request)); fastify.delete<DeleteServiceStorage>('/:id/storages', async (request) => await deleteServiceStorage(request));
fastify.get('/:id/configuration/type', async (request) => await getServiceType(request)); fastify.get('/:id/configuration/type', async (request) => await getServiceType(request));
fastify.post('/:id/configuration/type', async (request, reply) => await saveServiceType(request, reply)); fastify.post<SaveServiceType>('/:id/configuration/type', async (request, reply) => await saveServiceType(request, reply));
fastify.get('/:id/configuration/version', async (request) => await getServiceVersions(request)); fastify.get<OnlyId>('/:id/configuration/version', async (request) => await getServiceVersions(request));
fastify.post('/:id/configuration/version', async (request, reply) => await saveServiceVersion(request, reply)); fastify.post<SaveServiceVersion>('/:id/configuration/version', async (request, reply) => await saveServiceVersion(request, reply));
fastify.post('/:id/configuration/destination', async (request, reply) => await saveServiceDestination(request, reply)); fastify.post<SaveServiceDestination>('/:id/configuration/destination', async (request, reply) => await saveServiceDestination(request, reply));
fastify.get('/:id/usage', async (request) => await getServiceUsage(request)); fastify.get<OnlyId>('/:id/usage', async (request) => await getServiceUsage(request));
fastify.get('/:id/logs', async (request) => await getServiceLogs(request)); fastify.get<GetServiceLogs>('/:id/logs', async (request) => await getServiceLogs(request));
fastify.post('/:id/:type/start', async (request) => await startService(request)); fastify.post<ServiceStartStop>('/:id/:type/start', async (request) => await startService(request));
fastify.post('/:id/:type/stop', async (request) => await stopService(request)); fastify.post<ServiceStartStop>('/:id/:type/stop', async (request) => await stopService(request));
fastify.post('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply)); fastify.post<ServiceStartStop & SetWordpressSettings>('/:id/:type/settings', async (request, reply) => await setSettingsService(request, reply));
fastify.post('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply)); fastify.post<OnlyId>('/:id/plausibleanalytics/activate', async (request, reply) => await activatePlausibleUsers(request, reply));
fastify.post('/:id/wordpress/ftp', async (request, reply) => await activateWordpressFtp(request, reply)); fastify.post<ActivateWordpressFtp>('/:id/wordpress/ftp', async (request, reply) => await activateWordpressFtp(request, reply));
}; };
export default root; export default root;

View File

@ -0,0 +1,87 @@
import { OnlyId } from "../../../../types";
export interface SaveServiceType extends OnlyId {
Body: {
type: string
}
}
export interface SaveServiceVersion extends OnlyId {
Body: {
version: string
}
}
export interface SaveServiceDestination extends OnlyId {
Body: {
destinationId: string
}
}
export interface GetServiceLogs extends OnlyId {
Querystring: {
since: number
}
}
export interface SaveServiceSettings extends OnlyId {
Body: {
dualCerts: boolean
}
}
export interface CheckService extends OnlyId {
Body: {
fqdn: string,
exposePort: number,
otherFqdns: Array<string>
}
}
export interface SaveService extends OnlyId {
Body: {
name: string,
fqdn: string,
exposePort: number,
type: string
}
}
export interface SaveServiceSecret extends OnlyId {
Body: {
name: string,
value: string,
isNew: string,
}
}
export interface DeleteServiceSecret extends OnlyId {
Body: {
name: string,
}
}
export interface SaveServiceStorage extends OnlyId {
Body: {
path: string,
newStorage: string,
storageId: string,
}
}
export interface DeleteServiceStorage extends OnlyId {
Body: {
path: string,
}
}
export interface ServiceStartStop {
Params: {
id?: string,
type: string,
}
}
export interface SetWordpressSettings extends OnlyId {
Body: {
ownMysql: boolean
}
}
export interface ActivateWordpressFtp extends OnlyId {
Body: {
ftpEnabled: boolean
}
}

View File

@ -2,6 +2,7 @@ import { promises as dns } from 'dns';
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from 'fastify';
import { checkDomainsIsValidInDNS, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common'; import { checkDomainsIsValidInDNS, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types';
export async function listAllSettings(request: FastifyRequest) { export async function listAllSettings(request: FastifyRequest) {
@ -14,7 +15,7 @@ export async function listAllSettings(request: FastifyRequest) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveSettings(request: FastifyRequest, reply: FastifyReply) { export async function saveSettings(request: FastifyRequest<SaveSettings>, reply: FastifyReply) {
try { try {
const { const {
fqdn, fqdn,
@ -41,7 +42,7 @@ export async function saveSettings(request: FastifyRequest, reply: FastifyReply)
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function deleteDomain(request: FastifyRequest, reply: FastifyReply) { export async function deleteDomain(request: FastifyRequest<DeleteDomain>, reply: FastifyReply) {
try { try {
const { fqdn } = request.body const { fqdn } = request.body
let ip; let ip;
@ -57,7 +58,7 @@ export async function deleteDomain(request: FastifyRequest, reply: FastifyReply)
} }
} }
export async function checkDomain(request: FastifyRequest, reply: FastifyReply) { export async function checkDomain(request: FastifyRequest<CheckDomain>) {
try { try {
const { id } = request.params; const { id } = request.params;
let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body let { fqdn, forceSave, dualCerts, isDNSCheckEnabled } = request.body
@ -74,9 +75,9 @@ export async function checkDomain(request: FastifyRequest, reply: FastifyReply)
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function checkDNS(request: FastifyRequest, reply: FastifyReply) { export async function checkDNS(request: FastifyRequest<CheckDNS>) {
try { try {
const { id, domain } = request.params; const { domain } = request.params;
await isDNSValid(request.hostname, domain); await isDNSValid(request.hostname, domain);
return {} return {}
} catch ({ status, message }) { } catch ({ status, message }) {

View File

@ -1,17 +1,18 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { checkDNS, checkDomain, deleteDomain, listAllSettings, saveSettings } from './handlers'; import { checkDNS, checkDomain, deleteDomain, listAllSettings, saveSettings } from './handlers';
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request, reply) => { fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify() return await request.jwtVerify()
}) })
fastify.get('/', async (request) => await listAllSettings(request)); fastify.get('/', async (request) => await listAllSettings(request));
fastify.post('/', async (request, reply) => await saveSettings(request, reply)); fastify.post<SaveSettings>('/', async (request, reply) => await saveSettings(request, reply));
fastify.delete('/', async (request, reply) => await deleteDomain(request, reply)); fastify.delete<DeleteDomain>('/', async (request, reply) => await deleteDomain(request, reply));
fastify.get('/check', async (request, reply) => await checkDNS(request, reply)); fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request));
fastify.post('/check', async (request, reply) => await checkDomain(request, reply)); fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request));
}; };
export default root; export default root;

View File

@ -0,0 +1,31 @@
import { OnlyId } from "../../../../types"
export interface SaveSettings {
Body: {
fqdn: string,
isRegistrationEnabled: boolean,
dualCerts: boolean,
minPort: number,
maxPort: number,
isAutoUpdateEnabled: boolean,
isDNSCheckEnabled: boolean
}
}
export interface DeleteDomain {
Body: {
fqdn: string
}
}
export interface CheckDomain extends OnlyId {
Body: {
fqdn: string,
forceSave: boolean,
dualCerts: boolean,
isDNSCheckEnabled: boolean,
}
}
export interface CheckDNS {
Params: {
domain: string,
}
}

View File

@ -2,6 +2,8 @@ import cuid from 'cuid';
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common'; import { decrypt, encrypt, errorHandler, prisma } from '../../../../lib/common';
import { OnlyId } from '../../../../types';
import { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
export async function listSources(request: FastifyRequest) { export async function listSources(request: FastifyRequest) {
try { try {
@ -31,7 +33,7 @@ export async function saveSource(request, reply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function getSource(request: FastifyRequest) { export async function getSource(request: FastifyRequest<OnlyId>) {
try { try {
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
@ -97,12 +99,12 @@ export async function deleteSource(request) {
} }
} }
export async function saveGitHubSource(request: FastifyRequest, reply: FastifyReply) { export async function saveGitHubSource(request: FastifyRequest<SaveGitHubSource>) {
try { try {
const { teamId } = request.user const { teamId } = request.user
const { id } = request.params const { id } = request.params
let { name, type, htmlUrl, apiUrl, organization, customPort } = request.body let { name, htmlUrl, apiUrl, organization, customPort } = request.body
if (customPort) customPort = Number(customPort) if (customPort) customPort = Number(customPort)
if (id === 'new') { if (id === 'new') {
@ -128,7 +130,7 @@ export async function saveGitHubSource(request: FastifyRequest, reply: FastifyRe
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveGitLabSource(request: FastifyRequest, reply: FastifyReply) { export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>) {
try { try {
const { id } = request.params const { id } = request.params
const { teamId } = request.user const { teamId } = request.user
@ -175,7 +177,7 @@ export async function saveGitLabSource(request: FastifyRequest, reply: FastifyRe
} }
} }
export async function checkGitLabOAuthID(request: FastifyRequest) { export async function checkGitLabOAuthID(request: FastifyRequest<CheckGitLabOAuthId>) {
try { try {
const { oauthId } = request.body const { oauthId } = request.body
const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } }); const found = await prisma.gitlabApp.findFirst({ where: { oauthId: Number(oauthId) } });

View File

@ -1,21 +1,22 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { checkGitLabOAuthID, deleteSource, getSource, listSources, saveGitHubSource, saveGitLabSource, saveSource } from './handlers'; import { checkGitLabOAuthID, deleteSource, getSource, listSources, saveGitHubSource, saveGitLabSource, saveSource } from './handlers';
import type { OnlyId } from '../../../../types';
import type { CheckGitLabOAuthId, SaveGitHubSource, SaveGitLabSource } from './types';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request, reply) => { fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify() return await request.jwtVerify()
}) })
fastify.get('/', async (request) => await listSources(request)); fastify.get('/', async (request) => await listSources(request));
fastify.get('/:id', async (request) => await getSource(request)); fastify.get<OnlyId>('/:id', async (request) => await getSource(request));
fastify.post('/:id', async (request, reply) => await saveSource(request, reply)); fastify.post('/:id', async (request, reply) => await saveSource(request, reply));
fastify.delete('/:id', async (request) => await deleteSource(request)); fastify.delete('/:id', async (request) => await deleteSource(request));
fastify.post('/:id/check', async (request) => await checkGitLabOAuthID(request)); fastify.post<CheckGitLabOAuthId>('/:id/check', async (request) => await checkGitLabOAuthID(request));
fastify.post('/:id/github', async (request, reply) => await saveGitHubSource(request, reply)); fastify.post<SaveGitHubSource>('/:id/github', async (request) => await saveGitHubSource(request));
fastify.post('/:id/gitlab', async (request, reply) => await saveGitLabSource(request, reply)); fastify.post<SaveGitLabSource>('/:id/gitlab', async (request) => await saveGitLabSource(request));
}; };
export default root; export default root;

View File

@ -0,0 +1,29 @@
import { OnlyId } from "../../../../types";
export interface SaveGitHubSource extends OnlyId {
Body: {
name: string,
htmlUrl: string,
apiUrl: string,
organization: string,
customPort: number,
}
}
export interface SaveGitLabSource extends OnlyId {
Body: {
type: string,
name: string,
htmlUrl: string,
apiUrl: string,
oauthId: number,
appId: string,
appSecret: string,
groupName: string,
customPort: number,
}
}
export interface CheckGitLabOAuthId extends OnlyId {
Body: {
oauthId: number,
}
}

View File

@ -0,0 +1,3 @@
export interface GetCurrentUser {
Querystring: { teamId: string }
}

View File

@ -1,13 +1,15 @@
import axios from "axios"; import axios from "axios";
import cuid from "cuid"; import cuid from "cuid";
import crypto from "crypto"; import crypto from "crypto";
import type { FastifyReply, FastifyRequest } from "fastify"; import { encrypt, errorHandler, getUIUrl, isDev, prisma } from "../../../lib/common";
import { encrypt, errorHandler, getAPIUrl, getUIUrl, isDev, prisma } from "../../../lib/common";
import { checkContainer, removeContainer } from "../../../lib/docker"; import { checkContainer, removeContainer } from "../../../lib/docker";
import { scheduler } from "../../../lib/scheduler"; import { scheduler } from "../../../lib/scheduler";
import { getApplicationFromDB, getApplicationFromDBWebhook } from "../../api/v1/applications/handlers"; import { getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
export async function installGithub(request: FastifyRequest, reply: FastifyReply): Promise<any> { import type { FastifyReply, FastifyRequest } from "fastify";
import type { GitHubEvents, InstallGithub } from "./types";
export async function installGithub(request: FastifyRequest<InstallGithub>, reply: FastifyReply): Promise<any> {
try { try {
const { gitSourceId, installation_id } = request.query; const { gitSourceId, installation_id } = request.query;
const source = await prisma.gitSource.findUnique({ const source = await prisma.gitSource.findUnique({
@ -63,7 +65,7 @@ export async function configureGitHubApp(request, reply) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function gitHubEvents(request: FastifyRequest, reply: FastifyReply): Promise<any> { export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promise<any> {
try { try {
const buildId = cuid(); const buildId = cuid();
const allowedGithubEvents = ['push', 'pull_request']; const allowedGithubEvents = ['push', 'pull_request'];

View File

@ -1,10 +1,12 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { configureGitHubApp, gitHubEvents, installGithub } from './handlers'; import { configureGitHubApp, gitHubEvents, installGithub } from './handlers';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { import type { GitHubEvents, InstallGithub } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/', async (request, reply) => configureGitHubApp(request, reply)); fastify.get('/', async (request, reply) => configureGitHubApp(request, reply));
fastify.get('/install', async (request, reply) => installGithub(request, reply)); fastify.get<InstallGithub>('/install', async (request, reply) => installGithub(request, reply));
fastify.post('/events', async (request, reply) => gitHubEvents(request, reply)); fastify.post<GitHubEvents>('/events', async (request, reply) => gitHubEvents(request));
}; };
export default root; export default root;

View File

@ -0,0 +1,20 @@
export interface InstallGithub {
Querystring: {
gitSourceId: string,
installation_id: string
}
}
export interface GitHubEvents {
Body: {
number: string,
action: string,
repository: string,
ref: string,
pull_request: {
head: {
ref: string,
repo: string
}
}
}
}

View File

@ -2,16 +2,18 @@ import axios from "axios";
import cuid from "cuid"; import cuid from "cuid";
import crypto from "crypto"; import crypto from "crypto";
import type { FastifyReply, FastifyRequest } from "fastify"; import type { FastifyReply, FastifyRequest } from "fastify";
import { encrypt, errorHandler, getAPIUrl, isDev, listSettings, prisma } from "../../../lib/common"; import { errorHandler, getAPIUrl, isDev, listSettings, prisma } from "../../../lib/common";
import { checkContainer, removeContainer } from "../../../lib/docker"; import { checkContainer, removeContainer } from "../../../lib/docker";
import { scheduler } from "../../../lib/scheduler"; import { scheduler } from "../../../lib/scheduler";
import { getApplicationFromDB, getApplicationFromDBWebhook } from "../../api/v1/applications/handlers"; import { getApplicationFromDB, getApplicationFromDBWebhook } from "../../api/v1/applications/handlers";
export async function configureGitLabApp(request: FastifyRequest, reply: FastifyReply) { import type { ConfigureGitLabApp, GitLabEvents } from "./types";
export async function configureGitLabApp(request: FastifyRequest<ConfigureGitLabApp>, reply: FastifyReply) {
try { try {
const { code, state } = request.query; const { code, state } = request.query;
const { fqdn } = await listSettings(); const { fqdn } = await listSettings();
const { gitSource: { gitlabApp: { appId, appSecret }, htmlUrl } } = await getApplicationFromDB(state, undefined); const { gitSource: { gitlabApp: { appId, appSecret }, htmlUrl } }: any = await getApplicationFromDB(state, undefined);
let domain = `http://${request.hostname}`; let domain = `http://${request.hostname}`;
if (fqdn) domain = fqdn; if (fqdn) domain = fqdn;
@ -36,12 +38,13 @@ export async function configureGitLabApp(request: FastifyRequest, reply: Fastify
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function gitLabEvents(request: FastifyRequest, reply: FastifyReply) { export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
const { object_kind: objectKind, ref, project_id } = request.body
try { try {
const buildId = cuid(); const buildId = cuid();
const allowedActions = ['opened', 'reopen', 'close', 'open', 'update']; const allowedActions = ['opened', 'reopen', 'close', 'open', 'update'];
const { object_kind: objectKind, ref, project_id } = request.body
const webhookToken = request.headers['x-gitlab-token']; const webhookToken = request.headers['x-gitlab-token'];
if (!webhookToken) { if (!webhookToken) {
throw { status: 500, message: 'Invalid webhookToken.' } throw { status: 500, message: 'Invalid webhookToken.' }

View File

@ -1,9 +1,11 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { configureGitLabApp, gitLabEvents } from './handlers'; import { configureGitLabApp, gitLabEvents } from './handlers';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { import type { ConfigureGitLabApp, GitLabEvents } from './types';
fastify.get('/', async (request, reply) => configureGitLabApp(request, reply));
fastify.post('/events', async (request, reply) => gitLabEvents(request, reply)); const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<ConfigureGitLabApp>('/', async (request, reply) => configureGitLabApp(request, reply));
fastify.post<GitLabEvents>('/events', async (request) => gitLabEvents(request));
}; };
export default root; export default root;

View File

@ -0,0 +1,24 @@
export interface ConfigureGitLabApp {
Querystring: {
code: string,
state: string
}
}
export interface GitLabEvents {
Body: {
object_attributes: {
work_in_progress: string
isDraft: string
action: string
source_branch: string
target_branch: string
iid: string
},
project: {
id: string
},
object_kind: string,
ref: string,
project_id: string
}
}

View File

@ -1,6 +1,7 @@
import { FastifyRequest } from "fastify"; import { FastifyRequest } from "fastify";
import { asyncExecShell, errorHandler, getDomain, isDev, listServicesWithIncludes, prisma, supportedServiceTypesAndVersions } from "../../../lib/common"; import { asyncExecShell, errorHandler, getDomain, isDev, listServicesWithIncludes, prisma, supportedServiceTypesAndVersions } from "../../../lib/common";
import { getEngine } from "../../../lib/docker"; import { getEngine } from "../../../lib/docker";
import { TraefikOtherConfiguration } from "./types";
function configureMiddleware( function configureMiddleware(
{ id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type }, { id, container, port, domain, nakedDomain, isHttps, isWWW, isDualCerts, scriptName, type },
@ -362,7 +363,7 @@ export async function traefikConfiguration(request, reply) {
} }
} }
export async function traefikOtherConfiguration(request: FastifyRequest, reply) { export async function traefikOtherConfiguration(request: FastifyRequest<TraefikOtherConfiguration>) {
try { try {
const { id } = request.query const { id } = request.query
if (id) { if (id) {

View File

@ -1,9 +1,10 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { traefikConfiguration, traefikOtherConfiguration } from './handlers'; import { traefikConfiguration, traefikOtherConfiguration } from './handlers';
import { TraefikOtherConfiguration } from './types';
const root: FastifyPluginAsync = async (fastify, opts): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply)); fastify.get('/main.json', async (request, reply) => traefikConfiguration(request, reply));
fastify.get('/other.json', async (request, reply) => traefikOtherConfiguration(request, reply)); fastify.get<TraefikOtherConfiguration>('/other.json', async (request, reply) => traefikOtherConfiguration(request));
}; };
export default root; export default root;

View File

@ -0,0 +1,9 @@
export interface TraefikOtherConfiguration {
Querystring: {
id: string,
privatePort: number,
publicPort: number,
type: string,
address: string
}
}

38
apps/api/src/types.ts Normal file
View File

@ -0,0 +1,38 @@
export interface OnlyId {
Params: { id: string },
}
export interface SaveVersion extends OnlyId {
Body: {
version: string
}
}
export interface SaveDatabaseDestination extends OnlyId {
Body: {
destinationId: string
}
}
export interface GetDatabaseLogs extends OnlyId {
Querystring: {
since: number
}
}
export interface SaveDatabase extends OnlyId {
Body: {
name: string,
defaultDatabase: string,
dbUser: string,
dbUserPassword: string,
rootUser: string,
rootUserPassword: string,
version: string,
isRunning: boolean
}
}
export interface SaveDatabaseSettings extends OnlyId {
Body: {
isPublic: boolean,
appendOnly: boolean
}
}

View File

@ -15,12 +15,11 @@
"format": "prettier --write --plugin-search-dir=. ." "format": "prettier --write --plugin-search-dir=. ."
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.23.1", "@playwright/test": "1.23.3",
"@sveltejs/adapter-auto": "1.0.0-next.53", "@sveltejs/kit": "1.0.0-next.375",
"@sveltejs/kit": "1.0.0-next.359",
"@types/js-cookie": "3.0.2", "@types/js-cookie": "3.0.2",
"@typescript-eslint/eslint-plugin": "5.30.5", "@typescript-eslint/eslint-plugin": "5.30.6",
"@typescript-eslint/parser": "5.30.5", "@typescript-eslint/parser": "5.30.6",
"autoprefixer": "10.4.7", "autoprefixer": "10.4.7",
"eslint": "8.19.0", "eslint": "8.19.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
@ -28,18 +27,18 @@
"postcss": "8.4.14", "postcss": "8.4.14",
"prettier": "2.7.1", "prettier": "2.7.1",
"prettier-plugin-svelte": "2.7.0", "prettier-plugin-svelte": "2.7.0",
"svelte": "3.48.0", "svelte": "3.49.0",
"svelte-check": "2.8.0", "svelte-check": "2.8.0",
"svelte-preprocess": "4.10.7", "svelte-preprocess": "4.10.7",
"tailwindcss": "3.1.4", "tailwindcss": "3.1.6",
"tailwindcss-scrollbar": "0.1.0", "tailwindcss-scrollbar": "0.1.0",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4", "typescript": "4.7.4",
"vite": "^2.9.13" "vite": "^3.0.0"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@sveltejs/adapter-static": "1.0.0-next.34", "@sveltejs/adapter-static": "1.0.0-next.36",
"@zerodevx/svelte-toast": "0.7.2", "@zerodevx/svelte-toast": "0.7.2",
"cuid": "2.1.8", "cuid": "2.1.8",
"js-cookie": "3.0.1", "js-cookie": "3.0.1",

View File

@ -1,264 +1,4 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
export const asyncSleep = (delay: number) =>
new Promise((resolve) => setTimeout(resolve, delay));
export function errorNotification(error: any): void {
if (error.message) {
if (error.message === 'Cannot read properties of undefined (reading \'postMessage\')') {
toast.push('Currently there is background process in progress. Please try again later.');
return;
}
toast.push(error.message);
} else {
toast.push('Ooops, something is not okay, are you okay?');
}
console.error(JSON.stringify(error));
}
export function getDomain(domain: string) {
return domain?.replace('https://', '').replace('http://', '');
}
export function dashify(str: string, options?: any): string {
if (typeof str !== 'string') return str;
return str
.trim()
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
.toLowerCase();
}
export const dateOptions: any = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
};
export const staticDeployments = [
'react',
'vuejs',
'static',
'svelte',
'gatsby',
'php',
'astro',
'eleventy'
];
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel'];
export function generateRemoteEngine(destination: any) {
return `ssh://${destination.user}@${destination.ipAddress}:${destination.port}`;
}
export function changeQueryParams(buildId: string) {
const queryParams = new URLSearchParams(window.location.search);
queryParams.set('buildId', buildId);
// @ts-ignore
return history.pushState(null, null, '?' + queryParams.toString());
}
// export const supportedDatabaseTypesAndVersions = [
// {
// name: 'mongodb',
// fancyName: 'MongoDB',
// baseImage: 'bitnami/mongodb',
// versions: ['5.0', '4.4', '4.2']
// },
// { name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
// {
// name: 'mariadb',
// fancyName: 'MariaDB',
// baseImage: 'bitnami/mariadb',
// versions: ['10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
// },
// {
// name: 'postgresql',
// fancyName: 'PostgreSQL',
// baseImage: 'bitnami/postgresql',
// versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
// },
// {
// name: 'redis',
// fancyName: 'Redis',
// baseImage: 'bitnami/redis',
// versions: ['6.2', '6.0', '5.0']
// },
// { name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
// ];
// export const supportedServiceTypesAndVersions = [
// {
// name: 'plausibleanalytics',
// fancyName: 'Plausible Analytics',
// baseImage: 'plausible/analytics',
// images: ['bitnami/postgresql:13.2.0', 'yandex/clickhouse-server:21.3.2.5'],
// versions: ['latest', 'stable'],
// recommendedVersion: 'stable',
// ports: {
// main: 8000
// }
// },
// {
// name: 'nocodb',
// fancyName: 'NocoDB',
// baseImage: 'nocodb/nocodb',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 8080
// }
// },
// {
// name: 'minio',
// fancyName: 'MinIO',
// baseImage: 'minio/minio',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 9001
// }
// },
// {
// name: 'vscodeserver',
// fancyName: 'VSCode Server',
// baseImage: 'codercom/code-server',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 8080
// }
// },
// {
// name: 'wordpress',
// fancyName: 'Wordpress',
// baseImage: 'wordpress',
// images: ['bitnami/mysql:5.7'],
// versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3'],
// recommendedVersion: 'latest',
// ports: {
// main: 80
// }
// },
// {
// name: 'vaultwarden',
// fancyName: 'Vaultwarden',
// baseImage: 'vaultwarden/server',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 80
// }
// },
// {
// name: 'languagetool',
// fancyName: 'LanguageTool',
// baseImage: 'silviof/docker-languagetool',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 8010
// }
// },
// {
// name: 'n8n',
// fancyName: 'n8n',
// baseImage: 'n8nio/n8n',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 5678
// }
// },
// {
// name: 'uptimekuma',
// fancyName: 'Uptime Kuma',
// baseImage: 'louislam/uptime-kuma',
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 3001
// }
// },
// {
// name: 'ghost',
// fancyName: 'Ghost',
// baseImage: 'bitnami/ghost',
// images: ['bitnami/mariadb'],
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 2368
// }
// },
// {
// name: 'meilisearch',
// fancyName: 'Meilisearch',
// baseImage: 'getmeili/meilisearch',
// images: [],
// versions: ['latest'],
// recommendedVersion: 'latest',
// ports: {
// main: 7700
// }
// },
// {
// name: 'umami',
// fancyName: 'Umami',
// baseImage: 'ghcr.io/mikecao/umami',
// images: ['postgres:12-alpine'],
// versions: ['postgresql-latest'],
// recommendedVersion: 'postgresql-latest',
// ports: {
// main: 3000
// }
// },
// {
// name: 'hasura',
// fancyName: 'Hasura',
// baseImage: 'hasura/graphql-engine',
// images: ['postgres:12-alpine'],
// versions: ['latest', 'v2.5.1'],
// recommendedVersion: 'v2.5.1',
// ports: {
// main: 8080
// }
// },
// {
// name: 'fider',
// fancyName: 'Fider',
// baseImage: 'getfider/fider',
// images: ['postgres:12-alpine'],
// versions: ['stable'],
// recommendedVersion: 'stable',
// ports: {
// main: 3000
// }
// // },
// // {
// // name: 'appwrite',
// // fancyName: 'AppWrite',
// // baseImage: 'appwrite/appwrite',
// // images: ['appwrite/influxdb', 'appwrite/telegraf', 'mariadb:10.7', 'redis:6.0-alpine3.12'],
// // versions: ['latest', '0.13.0'],
// // recommendedVersion: '0.13.0',
// // ports: {
// // main: 3000
// // }
// // }
// }
// ];
export const getServiceMainPort = (service: string) => {
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
if (serviceType) {
return serviceType.ports.main;
}
return null;
};
export const supportedServiceTypesAndVersions = [ export const supportedServiceTypesAndVersions = [
{ {
@ -407,9 +147,122 @@ export const supportedServiceTypesAndVersions = [
ports: { ports: {
main: 3000 main: 3000
} }
} },
// {
// name: 'moodle',
// fancyName: 'Moodle',
// baseImage: 'bitnami/moodle',
// images: [],
// versions: ['latest', 'v4.0.2'],
// recommendedVersion: 'latest',
// ports: {
// main: 8080
// }
// }
]; ];
export const asyncSleep = (delay: number) =>
new Promise((resolve) => setTimeout(resolve, delay));
export function errorNotification(error: any): void {
if (error.message) {
if (error.message === 'Cannot read properties of undefined (reading \'postMessage\')') {
toast.push('Currently there is background process in progress. Please try again later.');
return;
}
toast.push(error.message);
} else {
toast.push('Ooops, something is not okay, are you okay?');
}
console.error(JSON.stringify(error));
}
export function getDomain(domain: string) {
return domain?.replace('https://', '').replace('http://', '');
}
export function dashify(str: string, options?: any): string {
if (typeof str !== 'string') return str;
return str
.trim()
.replace(/\W/g, (m) => (/[À-ž]/.test(m) ? m : '-'))
.replace(/^-+|-+$/g, '')
.replace(/-{2,}/g, (m) => (options && options.condense ? '-' : m))
.toLowerCase();
}
export const dateOptions: any = {
year: 'numeric',
month: 'short',
day: '2-digit',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
hour12: false
};
export const staticDeployments = [
'react',
'vuejs',
'static',
'svelte',
'gatsby',
'php',
'astro',
'eleventy'
];
export const notNodeDeployments = ['php', 'docker', 'rust', 'python', 'deno', 'laravel'];
export function generateRemoteEngine(destination: any) {
return `ssh://${destination.user}@${destination.ipAddress}:${destination.port}`;
}
export function changeQueryParams(buildId: string) {
const queryParams = new URLSearchParams(window.location.search);
queryParams.set('buildId', buildId);
// @ts-ignore
return history.pushState(null, null, '?' + queryParams.toString());
}
export const supportedDatabaseTypesAndVersions = [
{
name: 'mongodb',
fancyName: 'MongoDB',
baseImage: 'bitnami/mongodb',
versions: ['5.0', '4.4', '4.2']
},
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0', '5.7'] },
{
name: 'mariadb',
fancyName: 'MariaDB',
baseImage: 'bitnami/mariadb',
versions: ['10.7', '10.6', '10.5', '10.4', '10.3', '10.2']
},
{
name: 'postgresql',
fancyName: 'PostgreSQL',
baseImage: 'bitnami/postgresql',
versions: ['14.2.0', '13.6.0', '12.10.0 ', '11.15.0', '10.20.0']
},
{
name: 'redis',
fancyName: 'Redis',
baseImage: 'bitnami/redis',
versions: ['6.2', '6.0', '5.0']
},
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
];
export const getServiceMainPort = (service: string) => {
const serviceType = supportedServiceTypesAndVersions.find((s) => s.name === service);
if (serviceType) {
return serviceType.ports.main;
}
return null;
};
export function handlerNotFoundLoad(error: any, url: URL) { export function handlerNotFoundLoad(error: any, url: URL) {
if (error?.status === 404) { if (error?.status === 404) {
return { return {

View File

@ -0,0 +1,8 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<img
alt="moodle logo"
class={isAbsolute ? 'w-9 absolute top-0 left-0 -m-3 -mt-5' : 'w-8 mx-auto'}
src="/moodle.png"
/>

View File

@ -0,0 +1,17 @@
//@ts-nocheck
export { default as PlausibleAnalytics } from './PlausibleAnalytics.svelte';
export { default as NocoDb } from './NocoDB.svelte';
export { default as MinIo } from './MinIO.svelte';
export { default as VsCodeServer } from './VSCodeServer.svelte';
export { default as Wordpress } from './Wordpress.svelte';
export { default as VaultWarden } from './VaultWarden.svelte';
export { default as LanguageTool } from './LanguageTool.svelte';
export { default as N8n } from './N8n.svelte';
export { default as UptimeKuma } from './UptimeKuma.svelte';
export { default as Ghost } from './Ghost.svelte';
export { default as MeiliSearch } from './MeiliSearch.svelte';
export { default as Umami } from './Umami.svelte';
export { default as Hasura } from './Hasura.svelte';
export { default as Fider } from './Fider.svelte';
export { default as Moodle } from './Moodle.svelte';

View File

@ -57,3 +57,17 @@ export const features = readable({
beta: window.localStorage.getItem('beta') === 'true', beta: window.localStorage.getItem('beta') === 'true',
latestVersion: window.localStorage.getItem('latestVersion') latestVersion: window.localStorage.getItem('latestVersion')
}); });
export const location: Writable<null | string> = writable(null)
export const setLocation = (resource: any) => {
console.log(GITPOD_WORKSPACE_URL)
if (GITPOD_WORKSPACE_URL && resource.exposePort) {
const { href } = new URL(GITPOD_WORKSPACE_URL);
const newURL = href
.replace('https://', `https://${resource.exposePort}-`)
.replace(/\/$/, '');
location.set(newURL)
} else {
location.set(resource.fqdn)
}
}

View File

@ -62,12 +62,13 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { appSession, disabledButton, status } from '$lib/store'; import { appSession, disabledButton, status, location, setLocation } from '$lib/store';
import { errorNotification, handlerNotFoundLoad } from '$lib/common'; import { errorNotification, handlerNotFoundLoad } from '$lib/common';
import Loading from '$lib/components/Loading.svelte'; import Loading from '$lib/components/Loading.svelte';
let loading = false; let loading = false;
let statusInterval: any; let statusInterval: any;
$disabledButton = $disabledButton =
!$appSession.isAdmin || !$appSession.isAdmin ||
!application.fqdn || !application.fqdn ||
@ -75,6 +76,7 @@
!application.repository || !application.repository ||
!application.destinationDocker || !application.destinationDocker ||
!application.buildPack; !application.buildPack;
const { id } = $page.params; const { id } = $page.params;
async function handleDeploySubmit() { async function handleDeploySubmit() {
@ -126,9 +128,12 @@
} }
onDestroy(() => { onDestroy(() => {
$status.application.initialLoading = true; $status.application.initialLoading = true;
$location = null;
clearInterval(statusInterval); clearInterval(statusInterval);
}); });
onMount(async () => { onMount(async () => {
setLocation(application);
$status.application.isRunning = false; $status.application.isRunning = false;
$status.application.isExited = false; $status.application.isExited = false;
$status.application.loading = false; $status.application.loading = false;
@ -147,6 +152,30 @@
{#if loading} {#if loading}
<Loading fullscreen cover /> <Loading fullscreen cover />
{:else} {:else}
{#if $location}
<a
href={$location}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<div class="border border-coolgray-500 h-8" />
{#if $status.application.isExited} {#if $status.application.isExited}
<a <a
href={!$disabledButton ? `/applications/${id}/logs` : null} href={!$disabledButton ? `/applications/${id}/logs` : null}

View File

@ -35,12 +35,14 @@
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import cuid from 'cuid'; import cuid from 'cuid';
import { browser } from '$app/env'; import { browser } from '$app/env';
import { appSession, disabledButton, status } from '$lib/store'; import { appSession, disabledButton, setLocation, status } from '$lib/store';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common'; import { errorNotification, getDomain, notNodeDeployments, staticDeployments } from '$lib/common';
import Setting from './_Setting.svelte'; import Setting from './_Setting.svelte';
const { id } = $page.params; const { id } = $page.params;
$: isDisabled = !$appSession.isAdmin || $status.application.isRunning;
let domainEl: HTMLInputElement; let domainEl: HTMLInputElement;
let loading = false; let loading = false;
@ -63,7 +65,6 @@
let isNonWWWDomainOK = false; let isNonWWWDomainOK = false;
let isWWWDomainOK = false; let isWWWDomainOK = false;
$: isDisabled = !$appSession.isAdmin || $status.application.isRunning;
let wsgis = [ let wsgis = [
{ {
value: 'None', value: 'None',
@ -168,6 +169,7 @@
exposePort: application.exposePort exposePort: application.exposePort
}); });
await post(`/applications/${id}`, { ...application }); await post(`/applications/${id}`, { ...application });
setLocation(application)
$disabledButton = false; $disabledButton = false;
forceSave = false; forceSave = false;
return toast.push('Configurations saved.'); return toast.push('Configurations saved.');
@ -229,31 +231,9 @@
<div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block"> <div class="md:max-w-64 truncate text-base tracking-tight md:text-2xl lg:block">
Configuration Configuration
</div> </div>
<span class="text-xs">{application.name} </span> <span class="text-xs">{application.name}</span>
</div> </div>
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<a <a
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}" href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank" target="_blank"
@ -295,6 +275,7 @@
</svg> </svg>
{/if} {/if}
</a> </a>
{/if}
</div> </div>
<div class="mx-auto max-w-4xl px-6 py-4"> <div class="mx-auto max-w-4xl px-6 py-4">
@ -349,7 +330,6 @@
<div class="mt-2 grid grid-cols-2 items-center"> <div class="mt-2 grid grid-cols-2 items-center">
<label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label> <label for="name" class="text-base font-bold text-stone-100">{$t('forms.name')}</label>
<input <input
readonly={!isDisabled}
name="name" name="name"
id="name" id="name"
bind:value={application.name} bind:value={application.name}

View File

@ -88,29 +88,7 @@
</div> </div>
<span class="text-xs">{application.name} </span> <span class="text-xs">{application.name} </span>
</div> </div>
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<a <a
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}" href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank" target="_blank"
@ -152,6 +130,7 @@
</svg> </svg>
{/if} {/if}
</a> </a>
{/if}
</div> </div>
<div class="block flex-row justify-start space-x-2 px-5 pt-6 sm:px-10 md:flex"> <div class="block flex-row justify-start space-x-2 px-5 pt-6 sm:px-10 md:flex">
<div class="mb-4 min-w-[16rem] space-y-2 md:mb-0 "> <div class="mb-4 min-w-[16rem] space-y-2 md:mb-0 ">

View File

@ -104,29 +104,7 @@
</div> </div>
<span class="text-xs">{application.name}</span> <span class="text-xs">{application.name}</span>
</div> </div>
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<a <a
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}" href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank" target="_blank"
@ -168,6 +146,7 @@
</svg> </svg>
{/if} {/if}
</a> </a>
{/if}
</div> </div>
<div class="flex flex-row justify-center space-x-2 px-10 pt-6"> <div class="flex flex-row justify-center space-x-2 px-10 pt-6">
{#if logs.length === 0} {#if logs.length === 0}

View File

@ -64,29 +64,7 @@
</div> </div>
<span class="text-xs">{application.name} </span> <span class="text-xs">{application.name} </span>
</div> </div>
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<a <a
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}" href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank" target="_blank"
@ -128,6 +106,7 @@
</svg> </svg>
{/if} {/if}
</a> </a>
{/if}
</div> </div>
<div class="mx-auto max-w-6xl px-6 pt-4"> <div class="mx-auto max-w-6xl px-6 pt-4">
<div class="flex justify-center py-4 text-center"> <div class="flex justify-center py-4 text-center">

View File

@ -70,29 +70,7 @@
</div> </div>
<span class="text-xs">{application.name} </span> <span class="text-xs">{application.name} </span>
</div> </div>
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<a <a
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}" href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank" target="_blank"
@ -134,6 +112,7 @@
</svg> </svg>
{/if} {/if}
</a> </a>
{/if}
</div> </div>
<div class="mx-auto max-w-6xl px-6 pt-4"> <div class="mx-auto max-w-6xl px-6 pt-4">
<table class="mx-auto border-separate text-left"> <table class="mx-auto border-separate text-left">

View File

@ -41,29 +41,7 @@
</div> </div>
<span class="text-xs">{application.name} </span> <span class="text-xs">{application.name} </span>
</div> </div>
{#if application.gitSource?.htmlUrl && application.repository && application.branch}
{#if application.fqdn}
<a
href={application.fqdn}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<a <a
href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}" href="{application.gitSource.htmlUrl}/{application.repository}/tree/{application.branch}"
target="_blank" target="_blank"
@ -105,6 +83,7 @@
</svg> </svg>
{/if} {/if}
</a> </a>
{/if}
</div> </div>
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4"> <div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">

View File

@ -140,7 +140,7 @@
{#if application.fqdn} {#if application.fqdn}
<div class="truncate text-center">{getDomain(application.fqdn) || ''}</div> <div class="truncate text-center">{getDomain(application.fqdn) || ''}</div>
{/if} {/if}
{#if !application.gitSourceId} {#if !application.gitSourceId || !application.repository || !application.branch}
<div class="truncate text-center font-bold text-red-500 group-hover:text-white"> <div class="truncate text-center font-bold text-red-500 group-hover:text-white">
Git Source Missing Git Source Missing
</div> </div>

View File

@ -53,7 +53,7 @@
return; return;
} }
try { try {
await del(`/iam/user/remove`, { uid: id }); await del(`/iam/user/remove`, { id });
toast.push('Account deleted.'); toast.push('Account deleted.');
const data = await get('/iam'); const data = await get('/iam');
accounts = data.accounts; accounts = data.accounts;

View File

@ -1,76 +1,63 @@
<script lang="ts"> <script lang="ts">
export let service: any; export let service: any;
import * as Icons from '$lib/components/svg/services';
import Fider from '$lib/components/svg/services/Fider.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
import Hasura from '$lib/components/svg/services/Hasura.svelte';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
import MinIo from '$lib/components/svg/services/MinIO.svelte';
import N8n from '$lib/components/svg/services/N8n.svelte';
import NocoDb from '$lib/components/svg/services/NocoDB.svelte';
import PlausibleAnalytics from '$lib/components/svg/services/PlausibleAnalytics.svelte';
import Umami from '$lib/components/svg/services/Umami.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
</script> </script>
{#if service.type === 'plausibleanalytics'} {#if service.type === 'plausibleanalytics'}
<a href="https://plausible.io" target="_blank"> <a href="https://plausible.io" target="_blank">
<PlausibleAnalytics /> <Icons.PlausibleAnalytics />
</a> </a>
{:else if service.type === 'nocodb'} {:else if service.type === 'nocodb'}
<a href="https://nocodb.com" target="_blank"> <a href="https://nocodb.com" target="_blank">
<NocoDb /> <Icons.NocoDb />
</a> </a>
{:else if service.type === 'minio'} {:else if service.type === 'minio'}
<a href="https://min.io" target="_blank"> <a href="https://min.io" target="_blank">
<MinIo /> <Icons.MinIo />
</a> </a>
{:else if service.type === 'vscodeserver'} {:else if service.type === 'vscodeserver'}
<a href="https://coder.com" target="_blank"> <a href="https://coder.com" target="_blank">
<VsCodeServer /> <Icons.VsCodeServer />
</a> </a>
{:else if service.type === 'wordpress'} {:else if service.type === 'wordpress'}
<a href="https://wordpress.org" target="_blank"> <a href="https://wordpress.org" target="_blank">
<Wordpress /> <Icons.Wordpress />
</a> </a>
{:else if service.type === 'vaultwarden'} {:else if service.type === 'vaultwarden'}
<a href="https://github.com/dani-garcia/vaultwarden" target="_blank"> <a href="https://github.com/dani-garcia/vaultwarden" target="_blank">
<VaultWarden /> <Icons.VaultWarden />
</a> </a>
{:else if service.type === 'languagetool'} {:else if service.type === 'languagetool'}
<a href="https://languagetool.org/dev" target="_blank"> <a href="https://languagetool.org/dev" target="_blank">
<LanguageTool /> <Icons.LanguageTool />
</a> </a>
{:else if service.type === 'n8n'} {:else if service.type === 'n8n'}
<a href="https://n8n.io" target="_blank"> <a href="https://n8n.io" target="_blank">
<N8n /> <Icons.N8n />
</a> </a>
{:else if service.type === 'uptimekuma'} {:else if service.type === 'uptimekuma'}
<a href="https://github.com/louislam/uptime-kuma" target="_blank"> <a href="https://github.com/louislam/uptime-kuma" target="_blank">
<UptimeKuma /> <Icons.UptimeKuma />
</a> </a>
{:else if service.type === 'ghost'} {:else if service.type === 'ghost'}
<a href="https://ghost.org" target="_blank"> <a href="https://ghost.org" target="_blank">
<Ghost /> <Icons.Ghost />
</a> </a>
{:else if service.type === 'umami'} {:else if service.type === 'umami'}
<a href="https://umami.is" target="_blank"> <a href="https://umami.is" target="_blank">
<Umami /> <Icons.Umami />
</a> </a>
{:else if service.type === 'hasura'} {:else if service.type === 'hasura'}
<a href="https://hasura.io" target="_blank"> <a href="https://hasura.io" target="_blank">
<Hasura /> <Icons.Hasura />
</a> </a>
{:else if service.type === 'fider'} {:else if service.type === 'fider'}
<a href="https://fider.io" target="_blank"> <a href="https://fider.io" target="_blank">
<Fider /> <Icons.Fider />
</a>
{:else if service.type === 'moodle'}
<a href="https://moodle.org" target="_blank">
<Icons.Moodle />
</a> </a>
{/if} {/if}

View File

@ -0,0 +1,36 @@
<script lang="ts">
export let type: string;
import * as Icons from '$lib/components/svg/services';
</script>
{#if type === 'plausibleanalytics'}
<Icons.PlausibleAnalytics isAbsolute />
{:else if type === 'nocodb'}
<Icons.NocoDb isAbsolute />
{:else if type === 'minio'}
<Icons.MinIo isAbsolute />
{:else if type === 'vscodeserver'}
<Icons.VsCodeServer isAbsolute />
{:else if type === 'wordpress'}
<Icons.Wordpress isAbsolute />
{:else if type === 'vaultwarden'}
<Icons.VaultWarden isAbsolute />
{:else if type === 'languagetool'}
<Icons.LanguageTool isAbsolute />
{:else if type === 'n8n'}
<Icons.N8n isAbsolute />
{:else if type === 'uptimekuma'}
<Icons.UptimeKuma isAbsolute />
{:else if type === 'ghost'}
<Icons.Ghost isAbsolute />
{:else if type === 'meilisearch'}
<Icons.MeiliSearch isAbsolute />
{:else if type === 'umami'}
<Icons.Umami isAbsolute />
{:else if type === 'hasura'}
<Icons.Hasura isAbsolute />
{:else if type === 'fider'}
<Icons.Fider isAbsolute />
{:else if type === 'moodle'}
<Icons.Moodle isAbsolute />
{/if}

View File

@ -0,0 +1,102 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { t } from '$lib/translations';
export let readOnly: any;
export let service: any;
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">Moodle</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="email">{$t('forms.default_email_address')}</label>
<input
name="email"
id="email"
required
readonly={readOnly}
disabled={readOnly}
placeholder={$t('forms.email')}
value={service.moodle.defaultEmail}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultUsername">Default Username</label>
<CopyPasswordField
id="defaultUsername"
required
readonly={readOnly}
disabled={readOnly}
name="defaultUsername"
value={service.moodle.defaultUsername}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="defaultPassword">{$t('forms.default_password')}</label>
<CopyPasswordField
id="defaultPassword"
isPasswordField
required
readonly={readOnly}
disabled={readOnly}
name="defaultPassword"
value={service.moodle.defaultPassword}
/>
</div>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MariaDB</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbUser">{$t('forms.username')}</label>
<CopyPasswordField
name="mariadbUser"
id="mariadbUser"
value={service.moodle.mariadbUser}
readonly
disabled
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbPassword">{$t('forms.password')}</label>
<CopyPasswordField
id="mariadbPassword"
isPasswordField
readonly
disabled
name="mariadbPassword"
value={service.moodle.mariadbPassword}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbDatabase">{$t('index.database')}</label>
<input
name="mariadbDatabase"
id="mariadbDatabase"
required
readonly={readOnly}
disabled={readOnly}
bind:value={service.moodle.mariadbDatabase}
placeholder="{$t('forms.eg')}: moodle_db"
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUser">{$t('forms.root_db_user')}</label>
<CopyPasswordField
id="mariadbRootUser"
readonly
disabled
name="mariadbRootUser"
value={service.moodle.mariadbRootUser}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="mariadbRootUserPassword">{$t('forms.root_db_password')}</label>
<CopyPasswordField
id="mariadbRootUserPassword"
isPasswordField
readonly
disabled
name="mariadbRootUserPassword"
value={service.moodle.mariadbRootUserPassword}
/>
</div>

View File

@ -13,10 +13,10 @@
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import { errorNotification } from '$lib/common'; import { errorNotification } from '$lib/common';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { appSession, disabledButton, status } from '$lib/store'; import { appSession, disabledButton, status, location, setLocation } from '$lib/store';
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte'; import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import Explainer from '$lib/components/Explainer.svelte'; import Explainer from '$lib/components/Explainer.svelte';
import Setting from '$lib/components/Setting.svelte' import Setting from '$lib/components/Setting.svelte';
import Fider from './_Fider.svelte'; import Fider from './_Fider.svelte';
import Ghost from './_Ghost.svelte'; import Ghost from './_Ghost.svelte';
@ -27,6 +27,7 @@
import Umami from './_Umami.svelte'; import Umami from './_Umami.svelte';
import VsCodeServer from './_VSCodeServer.svelte'; import VsCodeServer from './_VSCodeServer.svelte';
import Wordpress from './_Wordpress.svelte'; import Wordpress from './_Wordpress.svelte';
import Moodle from './_Moodle.svelte';
const { id } = $page.params; const { id } = $page.params;
@ -44,6 +45,7 @@
exposePort: service.exposePort exposePort: service.exposePort
}); });
await post(`/services/${id}`, { ...service }); await post(`/services/${id}`, { ...service });
setLocation(service)
$disabledButton = false; $disabledButton = false;
toast.push('Configuration saved.'); toast.push('Configuration saved.');
} catch (error) { } catch (error) {
@ -97,6 +99,7 @@
} }
}); });
</script> </script>
<div class="mx-auto max-w-4xl px-6 pb-12"> <div class="mx-auto max-w-4xl px-6 pb-12">
<form on:submit|preventDefault={handleSubmit} class="py-4"> <form on:submit|preventDefault={handleSubmit} class="py-4">
<div class="flex space-x-1 pb-5 font-bold"> <div class="flex space-x-1 pb-5 font-bold">
@ -271,6 +274,8 @@
<Hasura bind:service /> <Hasura bind:service />
{:else if service.type === 'fider'} {:else if service.type === 'fider'}
<Fider bind:service {readOnly} /> <Fider bind:service {readOnly} />
{:else if service.type === 'moodle'}
<Moodle bind:service {readOnly} />
{/if} {/if}
</div> </div>
</form> </form>

View File

@ -47,6 +47,7 @@
} }
}; };
} catch (error) { } catch (error) {
console.log(error);
return handlerNotFoundLoad(error, url); return handlerNotFoundLoad(error, url);
} }
}; };
@ -60,7 +61,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification, handlerNotFoundLoad } from '$lib/common'; import { errorNotification, handlerNotFoundLoad } from '$lib/common';
import { appSession, disabledButton, status } from '$lib/store'; import { appSession, disabledButton, status, location, setLocation } from '$lib/store';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
const { id } = $page.params; const { id } = $page.params;
@ -109,7 +110,7 @@
loading = true; loading = true;
try { try {
await post(`/services/${service.id}/${service.type}/start`, {}); await post(`/services/${service.id}/${service.type}/start`, {});
return window.location.reload() return window.location.reload();
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
@ -126,9 +127,11 @@
} }
onDestroy(() => { onDestroy(() => {
$status.service.initialLoading = true; $status.service.initialLoading = true;
$location = null;
clearInterval(statusInterval); clearInterval(statusInterval);
}); });
onMount(async () => { onMount(async () => {
setLocation(service);
$status.service.isRunning = false; $status.service.isRunning = false;
$status.service.loading = false; $status.service.loading = false;
if (service.type && service.destinationDockerId && service.version && service.fqdn) { if (service.type && service.destinationDockerId && service.version && service.fqdn) {
@ -147,6 +150,29 @@
<Loading fullscreen cover /> <Loading fullscreen cover />
{:else} {:else}
{#if service.type && service.destinationDockerId && service.version} {#if service.type && service.destinationDockerId && service.version}
{#if $location}
<a
href={$location}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
<div class="border border-stone-700 h-8" />
{/if}
{#if $status.service.initialLoading} {#if $status.service.initialLoading}
<button <button
class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out" class="icons tooltip-bottom flex animate-spin items-center space-x-2 bg-transparent text-sm duration-500 ease-in-out"

View File

@ -32,21 +32,7 @@
import { get, post } from '$lib/api'; import { get, post } from '$lib/api';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification } from '$lib/common'; import { errorNotification } from '$lib/common';
import Services from '../_Services.svelte';
import PlausibleAnalytics from '$lib/components/svg/services/PlausibleAnalytics.svelte';
import NocoDb from '$lib/components/svg/services/NocoDB.svelte';
import MinIo from '$lib/components/svg/services/MinIO.svelte';
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
import Umami from '$lib/components/svg/services/Umami.svelte';
import Hasura from '$lib/components/svg/services/Hasura.svelte';
import Fider from '$lib/components/svg/services/Fider.svelte';
const { id } = $page.params; const { id } = $page.params;
const from = $page.url.searchParams.get('from'); const from = $page.url.searchParams.get('from');
@ -70,35 +56,8 @@
<div class="p-2"> <div class="p-2">
<form on:submit|preventDefault={() => handleSubmit(type.name)}> <form on:submit|preventDefault={() => handleSubmit(type.name)}>
<button type="submit" class="box-selection relative text-xl font-bold hover:bg-pink-600"> <button type="submit" class="box-selection relative text-xl font-bold hover:bg-pink-600">
{#if type.name === 'plausibleanalytics'} <Services type={type.name} />
<PlausibleAnalytics isAbsolute /> {type.fancyName}
{:else if type.name === 'nocodb'}
<NocoDb isAbsolute />
{:else if type.name === 'minio'}
<MinIo isAbsolute />
{:else if type.name === 'vscodeserver'}
<VsCodeServer isAbsolute />
{:else if type.name === 'wordpress'}
<Wordpress isAbsolute />
{:else if type.name === 'vaultwarden'}
<VaultWarden isAbsolute />
{:else if type.name === 'languagetool'}
<LanguageTool isAbsolute />
{:else if type.name === 'n8n'}
<N8n isAbsolute />
{:else if type.name === 'uptimekuma'}
<UptimeKuma isAbsolute />
{:else if type.name === 'ghost'}
<Ghost isAbsolute />
{:else if type.name === 'meilisearch'}
<MeiliSearch isAbsolute />
{:else if type.name === 'umami'}
<Umami isAbsolute />
{:else if type.name === 'hasura'}
<Hasura isAbsolute />
{:else if type.name === 'fider'}
<Fider isAbsolute />
{/if}{type.fancyName}
</button> </button>
</form> </form>
</div> </div>

View File

@ -57,30 +57,6 @@
</div> </div>
<span class="text-xs">{service.name}</span> <span class="text-xs">{service.name}</span>
</div> </div>
{#if service.fqdn}
<a
href={service.fqdn}
target="_blank"
class="icons tooltip-bottom flex items-center bg-transparent text-sm"
><svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M11 7h-5a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-5" />
<line x1="10" y1="14" x2="20" y2="4" />
<polyline points="15 4 20 4 20 9" />
</svg></a
>
{/if}
<ServiceLinks {service} /> <ServiceLinks {service} />
</div> </div>
<div class="mx-auto max-w-4xl px-6 py-4"> <div class="mx-auto max-w-4xl px-6 py-4">

View File

@ -24,22 +24,9 @@
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { appSession } from '$lib/store'; import { appSession } from '$lib/store';
import PlausibleAnalytics from '$lib/components/svg/services/PlausibleAnalytics.svelte'; import * as Icons from '$lib/components/svg/services';
import NocoDb from '$lib/components/svg/services/NocoDB.svelte';
import MinIo from '$lib/components/svg/services/MinIO.svelte';
import VsCodeServer from '$lib/components/svg/services/VSCodeServer.svelte';
import Wordpress from '$lib/components/svg/services/Wordpress.svelte';
import VaultWarden from '$lib/components/svg/services/VaultWarden.svelte';
import LanguageTool from '$lib/components/svg/services/LanguageTool.svelte';
import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
import Umami from '$lib/components/svg/services/Umami.svelte';
import Hasura from '$lib/components/svg/services/Hasura.svelte';
import Fider from '$lib/components/svg/services/Fider.svelte';
import { getDomain } from '$lib/common'; import { getDomain } from '$lib/common';
import Services from './[id]/_Services.svelte';
async function newService() { async function newService() {
const { id } = await post('/services/new', {}); const { id } = await post('/services/new', {});
@ -88,35 +75,7 @@
{#each ownServices as service} {#each ownServices as service}
<a href="/services/{service.id}" class="w-96 p-2 no-underline"> <a href="/services/{service.id}" class="w-96 p-2 no-underline">
<div class="box-selection group relative hover:bg-pink-600"> <div class="box-selection group relative hover:bg-pink-600">
{#if service.type === 'plausibleanalytics'} <Services type={service.type} />
<PlausibleAnalytics isAbsolute />
{:else if service.type === 'nocodb'}
<NocoDb isAbsolute />
{:else if service.type === 'minio'}
<MinIo isAbsolute />
{:else if service.type === 'vscodeserver'}
<VsCodeServer isAbsolute />
{:else if service.type === 'wordpress'}
<Wordpress isAbsolute />
{:else if service.type === 'vaultwarden'}
<VaultWarden isAbsolute />
{:else if service.type === 'languagetool'}
<LanguageTool isAbsolute />
{:else if service.type === 'n8n'}
<N8n isAbsolute />
{:else if service.type === 'uptimekuma'}
<UptimeKuma isAbsolute />
{:else if service.type === 'ghost'}
<Ghost isAbsolute />
{:else if service.type === 'meilisearch'}
<MeiliSearch isAbsolute />
{:else if service.type === 'umami'}
<Umami isAbsolute />
{:else if service.type === 'hasura'}
<Hasura isAbsolute />
{:else if service.type === 'fider'}
<Fider isAbsolute />
{/if}
<div class="truncate text-center text-xl font-bold"> <div class="truncate text-center text-xl font-bold">
{service.name} {service.name}
</div> </div>
@ -141,35 +100,7 @@
{#each otherServices as service} {#each otherServices as service}
<a href="/services/{service.id}" class="w-96 p-2 no-underline"> <a href="/services/{service.id}" class="w-96 p-2 no-underline">
<div class="box-selection group relative hover:bg-pink-600"> <div class="box-selection group relative hover:bg-pink-600">
{#if service.type === 'plausibleanalytics'} <Services type={service.type} />
<PlausibleAnalytics isAbsolute />
{:else if service.type === 'nocodb'}
<NocoDb isAbsolute />
{:else if service.type === 'minio'}
<MinIo isAbsolute />
{:else if service.type === 'vscodeserver'}
<VsCodeServer isAbsolute />
{:else if service.type === 'wordpress'}
<Wordpress isAbsolute />
{:else if service.type === 'vaultwarden'}
<VaultWarden isAbsolute />
{:else if service.type === 'languagetool'}
<LanguageTool isAbsolute />
{:else if service.type === 'n8n'}
<N8n isAbsolute />
{:else if service.type === 'uptimekuma'}
<UptimeKuma isAbsolute />
{:else if service.type === 'ghost'}
<Ghost isAbsolute />
{:else if service.type === 'meilisearch'}
<MeiliSearch isAbsolute />
{:else if service.type === 'umami'}
<Umami isAbsolute />
{:else if service.type === 'hasura'}
<Hasura isAbsolute />
{:else if service.type === 'fider'}
<Fider isAbsolute />
{/if}
<div class="truncate text-center text-xl font-bold"> <div class="truncate text-center text-xl font-bold">
{service.name} {service.name}
</div> </div>

View File

@ -225,7 +225,7 @@
disabled={source.gitlabAppId} disabled={source.gitlabAppId}
readonly={source.gitlabAppId} readonly={source.gitlabAppId}
required required
value={source.apiUrl} bind:value={source.apiUrl}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores'; import { page } from '$app/stores';
const token = $page.url.searchParams.get('token'); const token = $page.url.searchParams.get('token');
localStorage.setItem('gitLabToken', token); localStorage.setItem('gitLabToken', token || '');
window.close(); window.close();
</script> </script>

BIN
apps/ui/static/moodle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

View File

@ -7,6 +7,7 @@ export default {
'GITPOD_WORKSPACE_URL': JSON.stringify(process.env.GITPOD_WORKSPACE_URL) 'GITPOD_WORKSPACE_URL': JSON.stringify(process.env.GITPOD_WORKSPACE_URL)
}, },
server: { server: {
port: 3000,
hmr: process.env.GITPOD_WORKSPACE_URL hmr: process.env.GITPOD_WORKSPACE_URL
? { ? {
// Due to port fowarding, we have to replace // Due to port fowarding, we have to replace

View File

@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.", "description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "3.1.2", "version": "3.1.3",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"db:studio": "pnpm run --filter coolify-api db:studio", "db:studio": "pnpm run --filter coolify-api db:studio",
@ -20,7 +20,7 @@
"build:ui": "NODE_ENV=production pnpm run --filter coolify-ui build", "build:ui": "NODE_ENV=production pnpm run --filter coolify-ui build",
"dockerlogin":"echo $DOCKER_PASS | docker login --username=$DOCKER_USER --password-stdin", "dockerlogin":"echo $DOCKER_PASS | docker login --username=$DOCKER_USER --password-stdin",
"release:staging:amd": "cross-var docker buildx build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .", "release:staging:amd": "cross-var docker buildx build --platform linux/amd64 -t coollabsio/coolify:$npm_package_version --push .",
"release:local":"rm -fr ./local-serve && mkdir ./local-serve && pnpm build && cp -Rp apps/api/build/* ./local-serve && cp -Rp apps/ui/build/ ./local-serve/public && cp -Rp apps/api/prisma/ ./local-serve/prisma && cp -Rp apps/api/package.json ./local-serve && cp .env ./local-serve && cd ./local-serve && pnpm install . && pnpm start" "release:local":"rm -fr ./local-serve && mkdir ./local-serve && pnpm build && cp -Rp apps/api/build/* ./local-serve && cp -Rp apps/ui/build/ ./local-serve/public && cp -Rp apps/api/prisma/ ./local-serve/prisma && cp -Rp apps/api/package.json ./local-serve && env | grep '^COOLIFY_' > ./local-serve/.env && cd ./local-serve && pnpm install . && pnpm start"
}, },
"devDependencies": { "devDependencies": {
"cross-var": "1.1.0", "cross-var": "1.1.0",

875
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff