feat: remote docker engine init

This commit is contained in:
Andras Bacsai 2022-07-18 14:02:53 +00:00
parent 0a8fd0516d
commit 537209d3fb
20 changed files with 809 additions and 429 deletions

View File

@ -16,7 +16,7 @@
"dependencies": { "dependencies": {
"@breejs/ts-worker": "2.0.0", "@breejs/ts-worker": "2.0.0",
"@fastify/autoload": "5.1.0", "@fastify/autoload": "5.1.0",
"@fastify/cookie": "7.1.0", "@fastify/cookie": "7.2.0",
"@fastify/cors": "8.0.0", "@fastify/cors": "8.0.0",
"@fastify/env": "4.0.0", "@fastify/env": "4.0.0",
"@fastify/jwt": "6.3.1", "@fastify/jwt": "6.3.1",
@ -43,16 +43,17 @@
"node-forge": "1.3.1", "node-forge": "1.3.1",
"node-os-utils": "1.3.7", "node-os-utils": "1.3.7",
"p-queue": "7.2.0", "p-queue": "7.2.0",
"ssh-config": "4.1.6",
"strip-ansi": "7.0.1", "strip-ansi": "7.0.1",
"unique-names-generator": "4.7.1" "unique-names-generator": "4.7.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.0.4", "@types/node": "18.0.6",
"@types/node-os-utils": "1.3.0", "@types/node-os-utils": "1.3.0",
"@typescript-eslint/eslint-plugin": "5.30.6", "@typescript-eslint/eslint-plugin": "5.30.6",
"@typescript-eslint/parser": "5.30.6", "@typescript-eslint/parser": "5.30.6",
"esbuild": "0.14.49", "esbuild": "0.14.49",
"eslint": "8.19.0", "eslint": "8.20.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",
"nodemon": "2.0.19", "nodemon": "2.0.19",

View File

@ -0,0 +1,21 @@
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_DestinationDocker" (
"id" TEXT NOT NULL PRIMARY KEY,
"network" TEXT NOT NULL,
"name" TEXT NOT NULL,
"engine" TEXT,
"remoteEngine" BOOLEAN NOT NULL DEFAULT false,
"remoteIpAddress" TEXT,
"remoteUser" TEXT,
"remotePort" INTEGER,
"isCoolifyProxyUsed" BOOLEAN DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "updatedAt" FROM "DestinationDocker";
DROP TABLE "DestinationDocker";
ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker";
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;

View File

@ -0,0 +1,36 @@
-- CreateTable
CREATE TABLE "SshKey" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"privateKey" TEXT NOT NULL,
"destinationDockerId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "SshKey_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_DestinationDocker" (
"id" TEXT NOT NULL PRIMARY KEY,
"network" TEXT NOT NULL,
"name" TEXT NOT NULL,
"engine" TEXT,
"remoteEngine" BOOLEAN NOT NULL DEFAULT false,
"remoteIpAddress" TEXT,
"remoteUser" TEXT,
"remotePort" INTEGER,
"remoteVerified" BOOLEAN NOT NULL DEFAULT false,
"isCoolifyProxyUsed" BOOLEAN DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_DestinationDocker" ("createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt") SELECT "createdAt", "engine", "id", "isCoolifyProxyUsed", "name", "network", "remoteEngine", "remoteIpAddress", "remotePort", "remoteUser", "updatedAt" FROM "DestinationDocker";
DROP TABLE "DestinationDocker";
ALTER TABLE "new_DestinationDocker" RENAME TO "DestinationDocker";
CREATE UNIQUE INDEX "DestinationDocker_network_key" ON "DestinationDocker"("network");
PRAGMA foreign_key_check;
PRAGMA foreign_keys=ON;
-- CreateIndex
CREATE UNIQUE INDEX "SshKey_destinationDockerId_key" ON "SshKey"("destinationDockerId");

View File

@ -200,8 +200,12 @@ model DestinationDocker {
id String @id @default(cuid()) id String @id @default(cuid())
network String @unique network String @unique
name String name String
engine String engine String?
remoteEngine Boolean @default(false) remoteEngine Boolean @default(false)
remoteIpAddress String?
remoteUser String?
remotePort Int?
remoteVerified Boolean @default(false)
isCoolifyProxyUsed Boolean? @default(false) isCoolifyProxyUsed Boolean? @default(false)
teams Team[] teams Team[]
application Application[] application Application[]
@ -209,6 +213,17 @@ model DestinationDocker {
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
database Database[] database Database[]
service Service[] service Service[]
sshKey SshKey?
}
model SshKey {
id String @id @default(cuid())
name String
privateKey String
destinationDocker DestinationDocker? @relation(fields: [destinationDockerId], references: [id])
destinationDockerId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
} }
model GitSource { model GitSource {

View File

@ -1,6 +1,8 @@
import type { FastifyRequest } from 'fastify'; import type { FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { asyncExecShell, errorHandler, listSettings, prisma, startCoolifyProxy, startTraefikProxy, stopTraefikProxy } from '../../../../lib/common'; import sshConfig from 'ssh-config'
import fs from 'fs/promises'
import { asyncExecShell, decrypt, 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 { OnlyId } from '../../../../types';
@ -44,7 +46,8 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
const { id } = request.params const { id } = request.params
const teamId = request.user?.teamId; const teamId = request.user?.teamId;
const destination = await prisma.destinationDocker.findFirst({ const destination = await prisma.destinationDocker.findFirst({
where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } } where: { id, teams: { some: { id: teamId === '0' ? undefined : teamId } } },
include: { sshKey: true }
}); });
if (!destination && id !== 'new') { if (!destination && id !== 'new') {
throw { status: 404, message: `Destination not found.` }; throw { status: 404, message: `Destination not found.` };
@ -80,9 +83,10 @@ export async function getDestination(request: FastifyRequest<OnlyId>) {
export async function newDestination(request: FastifyRequest<NewDestination>, 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, ipAddress, user, port, sshPrivateKey } = request.body
const teamId = request.user.teamId; const teamId = request.user.teamId;
if (id === 'new') { if (id === 'new') {
if (engine) {
const host = getEngine(engine); const host = getEngine(engine);
const docker = dockerInstance({ destinationDocker: { engine, network } }); const docker = dockerInstance({ destinationDocker: { engine, network } });
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } }); const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
@ -113,6 +117,17 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
} }
} }
return reply.code(201).send({ id: destination.id }); return reply.code(201).send({ id: destination.id });
}
if (ipAddress) {
await prisma.destinationDocker.create({
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed, remoteEngine: true, remoteIpAddress: ipAddress, remoteUser: user, remotePort: port }
});
return reply.code(201).send()
}
throw {
message: `Cannot save Docker Engine.`
};
} else { } else {
await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } }); await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
return reply.code(201).send(); return reply.code(201).send();
@ -120,6 +135,8 @@ export async function newDestination(request: FastifyRequest<NewDestination>, re
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} finally {
await fs.rm('./id_rsa')
} }
} }
export async function deleteDestination(request: FastifyRequest<OnlyId>) { export async function deleteDestination(request: FastifyRequest<OnlyId>) {
@ -195,3 +212,45 @@ export async function restartProxy(request: FastifyRequest<Proxy>) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function assignSSHKey(request: FastifyRequest) {
try {
const { id: sshKeyId } = request.body;
const { id } = request.params;
console.log({ id, sshKeyId })
await prisma.destinationDocker.update({ where: { id }, data: { sshKey: { connect: { id: sshKeyId } } } })
return {}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function verifyRemoteDockerEngine(request: FastifyRequest, reply: FastifyReply) {
try {
const { id } = request.params;
const { sshKey: { privateKey }, remoteIpAddress, remotePort, remoteUser, network } = await prisma.destinationDocker.findFirst({ where: { id }, include: { sshKey: true } })
await fs.writeFile('./id_rsa', decrypt(privateKey) + '\n', { encoding: 'utf8', mode: 400 })
const host = `ssh://${remoteUser}@${remoteIpAddress}`
const config = sshConfig.parse('')
const found = config.find({ Host: remoteIpAddress })
if (!found) {
config.append({
Host: remoteIpAddress,
Port: remotePort.toString(),
User: remoteUser,
IdentityFile: '/workspace/coolify/apps/api/id_rsa',
StrictHostKeyChecking: 'no'
})
}
await fs.writeFile('/home/gitpod/.ssh/config', sshConfig.stringify(config))
const { stdout } = await asyncExecShell(`DOCKER_HOST=${host} docker network ls --filter 'name=${network}' --no-trunc --format "{{json .}}"`);
console.log({ stdout })
if (!stdout) {
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
}
await prisma.destinationDocker.update({ where: { id }, data: { remoteVerified: true } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@ -1,5 +1,5 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy } from './handlers'; import { assignSSHKey, checkDestination, deleteDestination, getDestination, listDestinations, newDestination, restartProxy, saveDestinationSettings, startProxy, stopProxy, verifyRemoteDockerEngine } from './handlers';
import type { OnlyId } from '../../../../types'; import type { OnlyId } from '../../../../types';
import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types'; import type { CheckDestination, NewDestination, Proxy, SaveDestinationSettings } from './types';
@ -15,10 +15,14 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply)); fastify.post<NewDestination>('/:id', async (request, reply) => await newDestination(request, reply));
fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request)); fastify.delete<OnlyId>('/:id', async (request) => await deleteDestination(request));
fastify.post<SaveDestinationSettings>('/:id/settings', async (request, reply) => await saveDestinationSettings(request)); fastify.post<SaveDestinationSettings>('/:id/settings', async (request) => await saveDestinationSettings(request));
fastify.post<Proxy>('/:id/start', async (request, reply) => await startProxy(request)); fastify.post<Proxy>('/:id/start', async (request,) => await startProxy(request));
fastify.post<Proxy>('/:id/stop', async (request, reply) => await stopProxy(request)); fastify.post<Proxy>('/:id/stop', async (request) => await stopProxy(request));
fastify.post<Proxy>('/:id/restart', async (request, reply) => await restartProxy(request)); fastify.post<Proxy>('/:id/restart', async (request) => await restartProxy(request));
fastify.post('/:id/configuration/sshKey', async (request) => await assignSSHKey(request));
fastify.post('/:id/verify', async (request, reply) => await verifyRemoteDockerEngine(request, reply));
}; };
export default root; export default root;

View File

@ -1,15 +1,23 @@
import { promises as dns } from 'dns'; 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, decrypt, encrypt, errorHandler, getDomain, isDNSValid, isDomainConfigured, listSettings, prisma } from '../../../../lib/common';
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types'; import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
export async function listAllSettings(request: FastifyRequest) { export async function listAllSettings(request: FastifyRequest) {
try { try {
const settings = await listSettings(); const settings = await listSettings();
const sshKeys = await prisma.sshKey.findMany()
const unencryptedKeys = []
if (sshKeys.length > 0) {
for (const key of sshKeys) {
unencryptedKeys.push({ id: key.id, name: key.name, privateKey: decrypt(key.privateKey), createdAt: key.createdAt })
}
}
return { return {
settings settings,
sshKeys: unencryptedKeys
} }
} catch ({ status, message }) { } catch ({ status, message }) {
return errorHandler({ status, message }) return errorHandler({ status, message })
@ -84,3 +92,29 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
return errorHandler({ status, message }) return errorHandler({ status, message })
} }
} }
export async function saveSSHKey(request: FastifyRequest<SaveSSHKey>, reply: FastifyReply) {
try {
const { privateKey, name } = request.body;
const found = await prisma.sshKey.findMany({ where: { name } })
if (found.length > 0) {
throw {
message: "Name already used. Choose another one please."
}
}
const encryptedSSHKey = encrypt(privateKey)
await prisma.sshKey.create({ data: { name, privateKey: encryptedSSHKey } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function deleteSSHKey(request: FastifyRequest<DeleteSSHKey>, reply: FastifyReply) {
try {
const { id } = request.body;
await prisma.sshKey.delete({ where: { id } })
return reply.code(201).send()
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}

View File

@ -1,6 +1,6 @@
import { FastifyPluginAsync } from 'fastify'; import { FastifyPluginAsync } from 'fastify';
import { checkDNS, checkDomain, deleteDomain, listAllSettings, saveSettings } from './handlers'; import { checkDNS, checkDomain, deleteDomain, deleteSSHKey, listAllSettings, saveSettings, saveSSHKey } from './handlers';
import { CheckDNS, CheckDomain, DeleteDomain, SaveSettings } from './types'; import { CheckDNS, CheckDomain, DeleteDomain, DeleteSSHKey, SaveSettings, SaveSSHKey } from './types';
const root: FastifyPluginAsync = async (fastify): Promise<void> => { const root: FastifyPluginAsync = async (fastify): Promise<void> => {
@ -13,6 +13,9 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request)); fastify.get<CheckDNS>('/check', async (request) => await checkDNS(request));
fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request)); fastify.post<CheckDomain>('/check', async (request) => await checkDomain(request));
fastify.post<SaveSSHKey>('/sshKey', async (request, reply) => await saveSSHKey(request, reply));
fastify.delete<DeleteSSHKey>('/sshKey', async (request, reply) => await deleteSSHKey(request, reply));
}; };
export default root; export default root;

View File

@ -29,3 +29,14 @@ export interface CheckDNS {
domain: string, domain: string,
} }
} }
export interface SaveSSHKey {
Body: {
privateKey: string,
name: string
}
}
export interface DeleteSSHKey {
Body: {
id: string
}
}

View File

@ -15,13 +15,13 @@
"format": "prettier --write --plugin-search-dir=. ." "format": "prettier --write --plugin-search-dir=. ."
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "1.23.3", "@playwright/test": "1.23.4",
"@sveltejs/kit": "1.0.0-next.375", "@sveltejs/kit": "1.0.0-next.377",
"@types/js-cookie": "3.0.2", "@types/js-cookie": "3.0.2",
"@typescript-eslint/eslint-plugin": "5.30.6", "@typescript-eslint/eslint-plugin": "5.30.6",
"@typescript-eslint/parser": "5.30.6", "@typescript-eslint/parser": "5.30.6",
"autoprefixer": "10.4.7", "autoprefixer": "10.4.7",
"eslint": "8.19.0", "eslint": "8.20.0",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.5.0",
"eslint-plugin-svelte3": "4.0.0", "eslint-plugin-svelte3": "4.0.0",
"postcss": "8.4.14", "postcss": "8.4.14",
@ -34,11 +34,11 @@
"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": "^3.0.0" "vite": "3.0.1"
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@sveltejs/adapter-static": "1.0.0-next.36", "@sveltejs/adapter-static": "1.0.0-next.37",
"@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

@ -142,9 +142,6 @@
: $t('destination.force_restart_proxy')}</button : $t('destination.force_restart_proxy')}</button
> >
{/if} {/if}
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
>Scan for applications</button
> -->
</div> </div>
<div class="grid grid-cols-2 items-center px-10 "> <div class="grid grid-cols-2 items-center px-10 ">
<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>
@ -168,10 +165,6 @@
value={destination.engine} value={destination.engine}
/> />
</div> </div>
<!-- <div class="flex items-center">
<label for="remoteEngine">Remote Engine?</label>
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
</div> -->
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label> <label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<CopyPasswordField <CopyPasswordField

View File

@ -44,7 +44,7 @@
<button class="w-32" on:click={() => setPredefined('localDocker')} <button class="w-32" on:click={() => setPredefined('localDocker')}
>{$t('sources.local_docker')}</button >{$t('sources.local_docker')}</button
> >
<!-- <button class="w-32" on:click={() => setPredefined('remoteDocker')}>Remote Docker</button> --> <button class="w-32" on:click={() => setPredefined('remoteDocker')}>Remote Docker</button>
<!-- <button class="w-32" on:click={() => setPredefined('kubernetes')}>Kubernetes</button> --> <!-- <button class="w-32" on:click={() => setPredefined('kubernetes')}>Kubernetes</button> -->
</div> </div>
</div> </div>

View File

@ -11,13 +11,19 @@
let loading = false; let loading = false;
async function handleSubmit() { async function handleSubmit() {
if (loading) return;
try { try {
const { id } = await post('/new/destination/docker', { loading = true;
await post(`/destinations/check`, { network: payload.network });
const { id } = await post(`/destinations/new`, {
...payload ...payload
}); });
return await goto(`/destinations/${id}`); await goto(`/destinations/${id}`);
window.location.reload();
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally {
loading = false;
} }
} }
</script> </script>
@ -64,20 +70,6 @@
<label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label> <label for="port" class="text-base font-bold text-stone-100">{$t('forms.port')}</label>
<input required name="port" placeholder="{$t('forms.eg')}: 22" bind:value={payload.port} /> <input required name="port" placeholder="{$t('forms.eg')}: 22" bind:value={payload.port} />
</div> </div>
<div class="grid grid-cols-2 items-center px-10">
<label for="sshPrivateKey" class="text-base font-bold text-stone-100"
>{$t('forms.ssh_private_key')}</label
>
<textarea
rows="10"
class="resize-none"
required
name="sshPrivateKey"
placeholder="{$t('forms.eg')}: -----BEGIN...."
bind:value={payload.sshPrivateKey}
/>
</div>
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label> <label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<input <input

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
export let destination: any; export let destination: any;
export let settings: any export let settings: any;
export let state: any export let state: any;
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
@ -10,50 +10,45 @@
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte'; import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from '$lib/translations'; import { t } from '$lib/translations';
import { errorNotification, generateRemoteEngine } from '$lib/common'; import { errorNotification, generateRemoteEngine } from '$lib/common';
import { appSession } from '$lib/store'; import { appSession } from '$lib/store';
const { id } = $page.params; const { id } = $page.params;
let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock'; let cannotDisable = settings.fqdn && destination.engine === '/var/run/docker.sock';
// let scannedApps = [];
let loading = false; let loading = false;
let restarting = false; let restarting = false;
$: isDisabled = !$appSession.isAdmin;
async function handleSubmit() { async function handleSubmit() {
loading = true; loading = true;
try { try {
return await post(`/destinations/${id}.json`, { ...destination }); return await post(`/destinations/${id}`, { ...destination });
} catch (error ) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loading = false; loading = false;
} }
} }
// async function scanApps() {
// scannedApps = [];
// const data = await fetch(`/destinations/${id}/scan.json`);
// const { containers } = await data.json();
// scannedApps = containers;
// }
onMount(async () => { onMount(async () => {
if (state === false && destination.isCoolifyProxyUsed === true) { if (state === false && destination.isCoolifyProxyUsed === true) {
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed; destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try { try {
await post(`/destinations/${id}/settings.json`, { await post(`/destinations/${id}/settings`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed, isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine engine: destination.engine
}); });
await stopProxy(); await stopProxy();
} catch (error ) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} }
} else if (state === true && destination.isCoolifyProxyUsed === false) { } else if (state === true && destination.isCoolifyProxyUsed === false) {
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed; destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try { try {
await post(`/destinations/${id}/settings.json`, { await post(`/destinations/${id}/settings`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed, isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine engine: destination.engine
}); });
await startProxy(); await startProxy();
} catch ( error ) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} }
} }
@ -73,7 +68,7 @@ import { appSession } from '$lib/store';
} }
destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed; destination.isCoolifyProxyUsed = !destination.isCoolifyProxyUsed;
try { try {
await post(`/destinations/${id}/settings.json`, { await post(`/destinations/${id}/settings`, {
isCoolifyProxyUsed: destination.isCoolifyProxyUsed, isCoolifyProxyUsed: destination.isCoolifyProxyUsed,
engine: destination.engine engine: destination.engine
}); });
@ -89,8 +84,7 @@ import { appSession } from '$lib/store';
} }
async function stopProxy() { async function stopProxy() {
try { try {
const engine = generateRemoteEngine(destination); await post(`/destinations/${id}/stop`, { engine: destination.engine });
await post(`/destinations/${id}/stop.json`, { engine });
return toast.push($t('destination.coolify_proxy_stopped')); return toast.push($t('destination.coolify_proxy_stopped'));
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
@ -98,8 +92,7 @@ import { appSession } from '$lib/store';
} }
async function startProxy() { async function startProxy() {
try { try {
const engine = generateRemoteEngine(destination); await post(`/destinations/${id}/start`, { engine: destination.engine });
await post(`/destinations/${id}/start.json`, { engine });
return toast.push($t('destination.coolify_proxy_started')); return toast.push($t('destination.coolify_proxy_started'));
} catch (error) { } catch (error) {
return errorNotification(error); return errorNotification(error);
@ -111,7 +104,7 @@ import { appSession } from '$lib/store';
try { try {
restarting = true; restarting = true;
toast.push($t('destination.coolify_proxy_restarting')); toast.push($t('destination.coolify_proxy_restarting'));
await post(`/destinations/${id}/restart.json`, { await post(`/destinations/${id}/restart`, {
engine: destination.engine, engine: destination.engine,
fqdn: settings.fqdn fqdn: settings.fqdn
}); });
@ -119,9 +112,21 @@ import { appSession } from '$lib/store';
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 5000); }, 5000);
} finally {
restarting = false;
} }
} }
} }
async function verifyRemoteDocker() {
try {
loading = true;
return await post(`/destinations/${id}/verify`, {});
} catch (error) {
return errorNotification(error);
} finally {
loading = false;
}
}
</script> </script>
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4"> <form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
@ -136,6 +141,11 @@ import { appSession } from '$lib/store';
disabled={loading} disabled={loading}
>{loading ? $t('forms.saving') : $t('forms.save')} >{loading ? $t('forms.saving') : $t('forms.save')}
</button> </button>
{#if !destination.remoteVerified}
<button on:click|preventDefault|stopPropagation={verifyRemoteDocker}
>Verify Remote Docker Engine</button
>
{/if}
<button <button
class={restarting ? '' : 'bg-red-600 hover:bg-red-500'} class={restarting ? '' : 'bg-red-600 hover:bg-red-500'}
disabled={restarting} disabled={restarting}
@ -145,9 +155,6 @@ import { appSession } from '$lib/store';
: $t('destination.force_restart_proxy')}</button : $t('destination.force_restart_proxy')}</button
> >
{/if} {/if}
<!-- <button type="button" class="bg-coollabs hover:bg-coollabs-100" on:click={scanApps}
>Scan for applications</button
> -->
</div> </div>
<div class="grid grid-cols-2 items-center px-10 "> <div class="grid grid-cols-2 items-center px-10 ">
<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>
@ -159,22 +166,6 @@ import { appSession } from '$lib/store';
bind:value={destination.name} bind:value={destination.name}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center px-10">
<label for="engine" class="text-base font-bold text-stone-100">{$t('forms.engine')}</label>
<CopyPasswordField
id="engine"
readonly
disabled
name="engine"
placeholder="{$t('forms.eg')}: /var/run/docker.sock"
value={destination.engine}
/>
</div>
<!-- <div class="flex items-center">
<label for="remoteEngine">Remote Engine?</label>
<input name="remoteEngine" type="checkbox" bind:checked={payload.remoteEngine} />
</div> -->
<div class="grid grid-cols-2 items-center px-10"> <div class="grid grid-cols-2 items-center px-10">
<label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label> <label for="network" class="text-base font-bold text-stone-100">{$t('forms.network')}</label>
<CopyPasswordField <CopyPasswordField
@ -186,6 +177,49 @@ import { appSession } from '$lib/store';
value={destination.network} value={destination.network}
/> />
</div> </div>
<div class="grid grid-cols-2 items-center px-10">
<label for="remoteIpAddress" class="text-base font-bold text-stone-100">IP Address</label>
<CopyPasswordField
id="remoteIpAddress"
readonly
disabled
name="remoteIpAddress"
value={destination.remoteIpAddress}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="remoteUser" class="text-base font-bold text-stone-100">User</label>
<CopyPasswordField
id="remoteUser"
readonly
disabled
name="remoteUser"
value={destination.remoteUser}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="remotePort" class="text-base font-bold text-stone-100">Port</label>
<CopyPasswordField
id="remotePort"
readonly
disabled
name="remotePort"
value={destination.remotePort}
/>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="sshKey" class="text-base font-bold text-stone-100">SSH Key</label>
<a
href={!isDisabled ? `/destinations/${id}/configuration/sshkey?from=/destinations/${id}` : ''}
class="no-underline"
><input
value={destination.sshKey.name}
id="sshKey"
disabled
class="cursor-pointer hover:bg-coolgray-500"
/></a
>
</div>
<div class="grid grid-cols-2 items-center"> <div class="grid grid-cols-2 items-center">
<Setting <Setting
disabled={cannotDisable} disabled={cannotDisable}
@ -200,27 +234,3 @@ import { appSession } from '$lib/store';
/> />
</div> </div>
</form> </form>
<!-- <div class="flex justify-center">
{#if payload.isCoolifyProxyUsed}
{#if state}
<button on:click={stopProxy}>Stop proxy</button>
{:else}
<button on:click={startProxy}>Start proxy</button>
{/if}
{/if}
</div> -->
<!-- {#if scannedApps.length > 0}
<div class="flex justify-center px-6 pb-10">
<div class="flex space-x-2 h-8 items-center">
<div class="font-bold text-xl text-white">Found applications</div>
</div>
</div>
<div class="max-w-4xl mx-auto px-6">
<div class="flex space-x-2 justify-center">
{#each scannedApps as app}
<FoundApp {app} />
{/each}
</div>
</div>
{/if} -->

View File

@ -1,6 +1,14 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import type { Load } from '@sveltejs/kit'; import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, url, params }) => { function checkConfiguration(destination: any): string | null {
let configurationPhase = null;
if (!destination?.remoteEngine) return configurationPhase;
if (!destination?.sshKey) {
configurationPhase = 'sshkey';
}
return configurationPhase;
}
export const load: Load = async ({ url, params }) => {
try { try {
const { id } = params; const { id } = params;
const response = await get(`/destinations/${id}`); const response = await get(`/destinations/${id}`);
@ -11,6 +19,17 @@
redirect: '/destinations' redirect: '/destinations'
}; };
} }
const configurationPhase = checkConfiguration(destination);
if (
configurationPhase &&
url.pathname !== `/destinations/${params.id}/configuration/${configurationPhase}`
) {
return {
status: 302,
redirect: `/destinations/${params.id}/configuration/${configurationPhase}`
};
}
return { return {
props: { props: {
destination destination
@ -22,6 +41,7 @@
} }
}; };
} catch (error) { } catch (error) {
console.log(error)
return handlerNotFoundLoad(error, url); return handlerNotFoundLoad(error, url);
} }
}; };

View File

@ -0,0 +1,59 @@
<script context="module" lang="ts">
import type { Load } from '@sveltejs/kit';
export const load: Load = async ({ fetch, params, url, stuff }) => {
try {
const response = await get(`/settings`);
return {
props: {
...response
}
};
} catch (error) {
return {
status: 500,
error: new Error(`Could not load ${url}`)
};
}
};
</script>
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { get, post } from '$lib/api';
import { errorNotification } from '$lib/common';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
export let sshKeys: any;
async function handleSubmit(sshKeyId: string) {
try {
await post(`/destinations/${id}/configuration/sshKey`, { id: sshKeyId });
return await goto(from || `/destinations/${id}`);
} catch (error) {
return errorNotification(error);
}
}
</script>
<div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">SSH Keys</div>
</div>
<div class="flex flex-col justify-center">
<div class="flex flex-col flex-wrap justify-center px-2 md:flex-row ">
{#each sshKeys as sshKey}
<div class="p-2 relative">
<form on:submit|preventDefault={() => handleSubmit(sshKey.id)}>
<button
type="submit"
class="disabled:opacity-95 bg-coolgray-200 disabled:text-white box-selection hover:bg-orange-700 group"
>
<div class="font-bold text-xl text-center truncate">{sshKey.name}</div>
</button>
</form>
</div>
{/each}
</div>
</div>

View File

@ -73,6 +73,9 @@
<div class="truncate text-center">{destination.teams[0].name}</div> <div class="truncate text-center">{destination.teams[0].name}</div>
{/if} {/if}
<div class="truncate text-center">{destination.network}</div> <div class="truncate text-center">{destination.network}</div>
{#if $appSession.teamId === '0' && destination.remoteVerified === false && destination.remoteEngine}
<div class="truncate text-center text-sm text-red-500">Not verified yet</div>
{/if}
</div> </div>
</a> </a>
{/each} {/each}

View File

@ -19,7 +19,7 @@
<script lang="ts"> <script lang="ts">
export let settings: any; export let settings: any;
export let sshKeys: any;
import Setting from '$lib/components/Setting.svelte'; import Setting from '$lib/components/Setting.svelte';
import Explainer from '$lib/components/Explainer.svelte'; import Explainer from '$lib/components/Explainer.svelte';
import { del, get, post } from '$lib/api'; import { del, get, post } from '$lib/api';
@ -51,13 +51,20 @@
proxyMigration: false proxyMigration: false
}; };
let subMenuActive: any = 'globalsettings';
let isModalActive = false;
let newSSHKey = {
name: null,
privateKey: null
};
async function removeFqdn() { async function removeFqdn() {
if (fqdn) { if (fqdn) {
loading.remove = true; loading.remove = true;
try { try {
const { redirect } = await del(`/settings`, { fqdn }); const { redirect } = await del(`/settings`, { fqdn });
return redirect ? window.location.replace(redirect) : window.location.reload(); return redirect ? window.location.replace(redirect) : window.location.reload();
} catch (error ) { } catch (error) {
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loading.remove = false; loading.remove = false;
@ -107,7 +114,7 @@
settings.maxPort = maxPort; settings.maxPort = maxPort;
} }
forceSave = false; forceSave = false;
} catch (error ) { } catch (error) {
if (error.message?.startsWith($t('application.dns_not_set_partial_error'))) { if (error.message?.startsWith($t('application.dns_not_set_partial_error'))) {
forceSave = true; forceSave = true;
if (dualCerts) { if (dualCerts) {
@ -122,7 +129,7 @@
} }
} }
} }
console.log(error) console.log(error);
return errorNotification(error); return errorNotification(error);
} finally { } finally {
loading.save = false; loading.save = false;
@ -143,18 +150,23 @@
function resetView() { function resetView() {
forceSave = false; forceSave = false;
} }
async function migrateProxy(to: any) { async function saveSSHKey() {
if (loading.proxyMigration) return;
try { try {
loading.proxyMigration = true; await post(`/settings/sshKey`, { ...newSSHKey });
await post(`/update`, { type: to }); return window.location.reload();
const data = await get(`/settings`);
$isTraefikUsed = data.settings.isTraefikUsed;
return toast.push('Proxy migration started, it takes a few seconds.');
} catch (error) { } catch (error) {
return errorNotification(error); errorNotification(error);
} finally { return false;
loading.proxyMigration = false; }
}
async function deleteSSHKey(id: string) {
try {
if (!id) return
await del(`/settings/sshKey`, { id });
return window.location.reload();
} catch (error) {
errorNotification(error);
return false;
} }
} }
</script> </script>
@ -162,16 +174,35 @@
<div class="flex space-x-1 p-6 font-bold"> <div class="flex space-x-1 p-6 font-bold">
<div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div> <div class="mr-4 text-2xl tracking-tight">{$t('index.settings')}</div>
</div> </div>
{#if $appSession.teamId === '0'} <div class="mx-auto w-full">
<div class="mx-auto max-w-4xl px-6"> <div class="flex flex-row">
<div class="flex flex-col pt-4 space-y-6 w-96 px-20">
<div
class="sub-menu"
class:sub-menu-active={subMenuActive === 'globalsettings'}
on:click={() => (subMenuActive = 'globalsettings')}
>
Global Settings
</div>
<div
class="sub-menu"
class:sub-menu-active={subMenuActive === 'sshkey'}
on:click={() => (subMenuActive = 'sshkey')}
>
SSH Keys
</div>
</div>
<div class="pl-40">
{#if $appSession.teamId === '0'}
{#if subMenuActive === 'globalsettings'}
<form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4"> <form on:submit|preventDefault={handleSubmit} class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 pb-6"> <div class="flex space-x-1 pb-6">
<div class="title font-bold">{$t('index.global_settings')}</div> <div class="title font-bold">{$t('index.global_settings')}</div>
<button <button
type="submit" type="submit"
class:bg-green-600={!loading.save} class:bg-yellow-500={!loading.save}
class:bg-orange-600={forceSave} class:bg-orange-600={forceSave}
class:hover:bg-green-500={!loading.save} class:hover:bg-yellow-500={!loading.save}
class:hover:bg-orange-400={forceSave} class:hover:bg-orange-400={forceSave}
disabled={loading.save} disabled={loading.save}
>{loading.save >{loading.save
@ -318,7 +349,9 @@
<Explainer <Explainer
text={$t('setting.credential_stat_explainer', { text={$t('setting.credential_stat_explainer', {
link: fqdn link: fqdn
? `http://${settings.proxyUser}:${settings.proxyPassword}@` + getDomain(fqdn) + ':8404' ? `http://${settings.proxyUser}:${settings.proxyPassword}@` +
getDomain(fqdn) +
':8404'
: browser && : browser &&
`http://${settings.proxyUser}:${settings.proxyPassword}@` + `http://${settings.proxyUser}:${settings.proxyPassword}@` +
window.location.hostname + window.location.hostname +
@ -349,9 +382,95 @@
</div> </div>
</div> </div>
{/if} {/if}
{/if}
{#if subMenuActive === 'sshkey'}
<div class="grid grid-flow-row gap-2 py-4">
<div class="flex space-x-1 pb-6">
<div class="title font-bold">SSH Keys</div>
<button
on:click={() => (isModalActive = true)}
class:bg-yellow-500={!loading.save}
class:hover:bg-yellow-400={!loading.save}
disabled={loading.save}>New SSH Key</button
>
</div>
<div class="grid grid-flow-col gap-2 px-10">
{#if sshKeys.length === 0}
<div class="text-sm ">No SSH keys found</div>
{:else}
{#each sshKeys as key}
<div class="box-selection group relative">
<div class="text-xl font-bold">{key.name}</div>
<div class="py-3 text-stone-600">Added on {key.createdAt}</div>
<button on:click={() => deleteSSHKey(key.id)} class="bg-red-500">Delete</button>
</div>
{/each}
{/if}
</div>
</div>
{/if}
{/if}
</div>
</div>
</div>
{#if isModalActive}
<div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-coolgray-500 bg-opacity-75 transition-opacity" />
<div class="fixed z-10 inset-0 overflow-y-auto text-white">
<div class="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0">
<div
class="relative bg-coolblack rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full sm:p-6 border border-coolgray-500"
>
<div class="hidden sm:block absolute top-0 right-0 pt-4 pr-4">
<button
on:click={() => (isModalActive = false)}
type="button"
class=" rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
<span class="sr-only">Close</span>
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="2"
stroke="currentColor"
aria-hidden="true"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium pb-4" id="modal-title">New SSH Key</h3>
<div class="text-xs text-stone-400">Add an SSH key to your Coolify instance.</div>
<div class="mt-2">
<label for="privateKey" class="pb-2">Key</label>
<textarea
id="privateKey"
required
bind:value={newSSHKey.privateKey}
class="w-full"
rows={15}
/>
</div>
<div class="mt-2">
<label for="name" class="pb-2">Name</label>
<input id="name" required bind:value={newSSHKey.name} class="w-full" />
</div>
</div>
</div>
<div class="mt-5 flex space-x-4 justify-end">
<button on:click={saveSSHKey} type="button" class="bg-green-600 hover:bg-green-500"
>Save</button
>
<button on:click={() => (isModalActive = false)} type="button" class="">Cancel</button>
</div>
</div>
</div>
</div> </div>
{:else}
<div class="mx-auto max-w-4xl px-6">
<!-- <Language /> -->
</div> </div>
{/if} {/if}

View File

@ -395,3 +395,11 @@ a {
transform: scale(0.9); transform: scale(0.9);
} }
} }
.sub-menu {
@apply text-xl font-bold hover:bg-coolgray-500 rounded p-2 hover:text-white text-stone-200 cursor-pointer;
}
.sub-menu-active {
@apply bg-coolgray-500 text-white;
}

182
pnpm-lock.yaml generated
View File

@ -14,14 +14,14 @@ importers:
specifiers: specifiers:
'@breejs/ts-worker': 2.0.0 '@breejs/ts-worker': 2.0.0
'@fastify/autoload': 5.1.0 '@fastify/autoload': 5.1.0
'@fastify/cookie': 7.1.0 '@fastify/cookie': 7.2.0
'@fastify/cors': 8.0.0 '@fastify/cors': 8.0.0
'@fastify/env': 4.0.0 '@fastify/env': 4.0.0
'@fastify/jwt': 6.3.1 '@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
'@types/node': 18.0.4 '@types/node': 18.0.6
'@types/node-os-utils': 1.3.0 '@types/node-os-utils': 1.3.0
'@typescript-eslint/eslint-plugin': 5.30.6 '@typescript-eslint/eslint-plugin': 5.30.6
'@typescript-eslint/parser': 5.30.6 '@typescript-eslint/parser': 5.30.6
@ -35,7 +35,7 @@ importers:
dockerode: 3.3.2 dockerode: 3.3.2
dotenv-extended: 2.9.0 dotenv-extended: 2.9.0
esbuild: 0.14.49 esbuild: 0.14.49
eslint: 8.19.0 eslint: 8.20.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
fastify: 4.2.1 fastify: 4.2.1
@ -53,15 +53,15 @@ importers:
prettier: 2.7.1 prettier: 2.7.1
prisma: 3.15.2 prisma: 3.15.2
rimraf: 3.0.2 rimraf: 3.0.2
shared: workspace:* ssh-config: ^4.1.6
strip-ansi: 7.0.1 strip-ansi: 7.0.1
tsconfig-paths: 4.0.0 tsconfig-paths: 4.0.0
typescript: 4.7.4 typescript: 4.7.4
unique-names-generator: 4.7.1 unique-names-generator: 4.7.1
dependencies: dependencies:
'@breejs/ts-worker': 2.0.0_t3dw2jfpvj5qtbx4qztd4nt754 '@breejs/ts-worker': 2.0.0_25g7irgsr6ywin2g3nrhhgteo4
'@fastify/autoload': 5.1.0 '@fastify/autoload': 5.1.0
'@fastify/cookie': 7.1.0 '@fastify/cookie': 7.2.0
'@fastify/cors': 8.0.0 '@fastify/cors': 8.0.0
'@fastify/env': 4.0.0 '@fastify/env': 4.0.0
'@fastify/jwt': 6.3.1 '@fastify/jwt': 6.3.1
@ -88,18 +88,18 @@ importers:
node-forge: 1.3.1 node-forge: 1.3.1
node-os-utils: 1.3.7 node-os-utils: 1.3.7
p-queue: 7.2.0 p-queue: 7.2.0
shared: link:../shared ssh-config: 4.1.6
strip-ansi: 7.0.1 strip-ansi: 7.0.1
unique-names-generator: 4.7.1 unique-names-generator: 4.7.1
devDependencies: devDependencies:
'@types/node': 18.0.4 '@types/node': 18.0.6
'@types/node-os-utils': 1.3.0 '@types/node-os-utils': 1.3.0
'@typescript-eslint/eslint-plugin': 5.30.6_2vt5mtrqleafs33qg2bhpmbaqm '@typescript-eslint/eslint-plugin': 5.30.6_b7n364ggt6o4xlkgyoaww3ph3q
'@typescript-eslint/parser': 5.30.6_4x5o4skxv6sl53vpwefgt23khm '@typescript-eslint/parser': 5.30.6_he2ccbldppg44uulnyq4rwocfa
esbuild: 0.14.49 esbuild: 0.14.49
eslint: 8.19.0 eslint: 8.20.0
eslint-config-prettier: 8.5.0_eslint@8.19.0 eslint-config-prettier: 8.5.0_eslint@8.20.0
eslint-plugin-prettier: 4.2.1_7uxdfn2xinezdgvmbammh6ev5i eslint-plugin-prettier: 4.2.1_g4fztgbwjyq2fvmcscny2sj6fy
nodemon: 2.0.19 nodemon: 2.0.19
prettier: 2.7.1 prettier: 2.7.1
prisma: 3.15.2 prisma: 3.15.2
@ -107,28 +107,18 @@ importers:
tsconfig-paths: 4.0.0 tsconfig-paths: 4.0.0
typescript: 4.7.4 typescript: 4.7.4
apps/shared:
specifiers:
esbuild: 0.14.49
nodemon: 2.0.19
rimraf: 3.0.2
devDependencies:
esbuild: 0.14.49
nodemon: 2.0.19
rimraf: 3.0.2
apps/ui: apps/ui:
specifiers: specifiers:
'@playwright/test': 1.23.3 '@playwright/test': 1.23.4
'@sveltejs/adapter-static': 1.0.0-next.36 '@sveltejs/adapter-static': 1.0.0-next.37
'@sveltejs/kit': 1.0.0-next.375 '@sveltejs/kit': 1.0.0-next.377
'@types/js-cookie': 3.0.2 '@types/js-cookie': 3.0.2
'@typescript-eslint/eslint-plugin': 5.30.6 '@typescript-eslint/eslint-plugin': 5.30.6
'@typescript-eslint/parser': 5.30.6 '@typescript-eslint/parser': 5.30.6
'@zerodevx/svelte-toast': 0.7.2 '@zerodevx/svelte-toast': 0.7.2
autoprefixer: 10.4.7 autoprefixer: 10.4.7
cuid: 2.1.8 cuid: 2.1.8
eslint: 8.19.0 eslint: 8.20.0
eslint-config-prettier: 8.5.0 eslint-config-prettier: 8.5.0
eslint-plugin-svelte3: 4.0.0 eslint-plugin-svelte3: 4.0.0
js-cookie: 3.0.1 js-cookie: 3.0.1
@ -136,7 +126,6 @@ importers:
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
shared: workspace:*
svelte: 3.49.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
@ -146,26 +135,25 @@ importers:
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: ^3.0.0 vite: 3.0.1
dependencies: dependencies:
'@sveltejs/adapter-static': 1.0.0-next.36 '@sveltejs/adapter-static': 1.0.0-next.37
'@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
p-limit: 4.0.0 p-limit: 4.0.0
shared: link:../shared
svelte-select: 4.4.7 svelte-select: 4.4.7
sveltekit-i18n: 2.2.2_svelte@3.49.0 sveltekit-i18n: 2.2.2_svelte@3.49.0
devDependencies: devDependencies:
'@playwright/test': 1.23.3 '@playwright/test': 1.23.4
'@sveltejs/kit': 1.0.0-next.375_svelte@3.49.0+vite@3.0.0 '@sveltejs/kit': 1.0.0-next.377_svelte@3.49.0+vite@3.0.1
'@types/js-cookie': 3.0.2 '@types/js-cookie': 3.0.2
'@typescript-eslint/eslint-plugin': 5.30.6_2vt5mtrqleafs33qg2bhpmbaqm '@typescript-eslint/eslint-plugin': 5.30.6_b7n364ggt6o4xlkgyoaww3ph3q
'@typescript-eslint/parser': 5.30.6_4x5o4skxv6sl53vpwefgt23khm '@typescript-eslint/parser': 5.30.6_he2ccbldppg44uulnyq4rwocfa
autoprefixer: 10.4.7_postcss@8.4.14 autoprefixer: 10.4.7_postcss@8.4.14
eslint: 8.19.0 eslint: 8.20.0
eslint-config-prettier: 8.5.0_eslint@8.19.0 eslint-config-prettier: 8.5.0_eslint@8.20.0
eslint-plugin-svelte3: 4.0.0_jxmmfmurkts274jdspwh3cyqve eslint-plugin-svelte3: 4.0.0_piwa6j2njmnknm35bh3wz5v52y
postcss: 8.4.14 postcss: 8.4.14
prettier: 2.7.1 prettier: 2.7.1
prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi
@ -176,7 +164,7 @@ importers:
tailwindcss-scrollbar: 0.1.0_tailwindcss@3.1.6 tailwindcss-scrollbar: 0.1.0_tailwindcss@3.1.6
tslib: 2.4.0 tslib: 2.4.0
typescript: 4.7.4 typescript: 4.7.4
vite: 3.0.0 vite: 3.0.1
packages: packages:
@ -213,14 +201,14 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
dev: false dev: false
/@breejs/ts-worker/2.0.0_t3dw2jfpvj5qtbx4qztd4nt754: /@breejs/ts-worker/2.0.0_25g7irgsr6ywin2g3nrhhgteo4:
resolution: {integrity: sha512-6anHRcmgYlF7mrm/YVRn6rx2cegLuiY3VBxkkimOTWC/dVQeH336imVSuIKEGKTwiuNTPr2hswVdDSneNuXg3A==} resolution: {integrity: sha512-6anHRcmgYlF7mrm/YVRn6rx2cegLuiY3VBxkkimOTWC/dVQeH336imVSuIKEGKTwiuNTPr2hswVdDSneNuXg3A==}
engines: {node: '>= 12.11'} engines: {node: '>= 12.11'}
peerDependencies: peerDependencies:
bree: '>=9.0.0' bree: '>=9.0.0'
dependencies: dependencies:
bree: 9.1.1 bree: 9.1.1
ts-node: 10.8.2_2zqz24ol5yhbv2blv4fh7akzrq ts-node: 10.8.2_tdn3ypgnfy6bmey2q4hu5jonwi
tsconfig-paths: 4.0.0 tsconfig-paths: 4.0.0
transitivePeerDependencies: transitivePeerDependencies:
- '@swc/core' - '@swc/core'
@ -267,8 +255,8 @@ packages:
pkg-up: 3.1.0 pkg-up: 3.1.0
dev: false dev: false
/@fastify/cookie/7.1.0: /@fastify/cookie/7.2.0:
resolution: {integrity: sha512-ofAlIthvJ2aWOrzdbUen1Lx09AKk2zvdaUrWh2+0aNt+gajRA7KyR8bzwCD2AwS+2nacjEuSRIyckotMHG95hQ==} resolution: {integrity: sha512-eM/OoTPEW/83uTEWVVZhVVQCtwRx3vmMs7J68U1DFNf42Ar4nTTZ7qGNYXvJPLUQqGKYS/gxML2soNMmZD8z0Q==}
dependencies: dependencies:
cookie: 0.5.0 cookie: 0.5.0
cookie-signature: 1.2.0 cookie-signature: 1.2.0
@ -385,13 +373,13 @@ packages:
fastq: 1.13.0 fastq: 1.13.0
dev: true dev: true
/@playwright/test/1.23.3: /@playwright/test/1.23.4:
resolution: {integrity: sha512-kR4vo2UGHC84DGqE6EwvAeaehj3xCAK6LoC1P1eu6ZGdC79rlqRKf8cFDx6q6c9T8MQSL1O9eOlup0BpwqNF0w==} resolution: {integrity: sha512-iIsoMJDS/lyuhw82FtcV/B3PXikgVD3hNe5hyvOpRM0uRr1OIpN3LgPeRbBjhzBWmyf6RgRg5fqK5sVcpA03yA==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
dependencies: dependencies:
'@types/node': 18.0.3 '@types/node': 18.0.4
playwright-core: 1.23.3 playwright-core: 1.23.4
dev: true dev: true
/@prisma/client/3.15.2_prisma@3.15.2: /@prisma/client/3.15.2_prisma@3.15.2:
@ -429,31 +417,31 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: false dev: false
/@sveltejs/adapter-static/1.0.0-next.36: /@sveltejs/adapter-static/1.0.0-next.37:
resolution: {integrity: sha512-1g3W4wHPyBtUGy5zCDBA2nMG3mM36FKTP1zb0vNRBpoUmtNuzVFF74UVsHCpMC1GpPyrgOq9idfjkm4gRabisw==} resolution: {integrity: sha512-BDFkx4CGAd6pG4e3+zYjy/eM9UDbhkRgXqavUzCO5oT8xXao5TeprY1AIbdzjMTmFjsWdeSXE9TbIsT0iikpyQ==}
dependencies: dependencies:
tiny-glob: 0.2.9 tiny-glob: 0.2.9
dev: false dev: false
/@sveltejs/kit/1.0.0-next.375_svelte@3.49.0+vite@3.0.0: /@sveltejs/kit/1.0.0-next.377_svelte@3.49.0+vite@3.0.1:
resolution: {integrity: sha512-9+gKm97TW/xIz6DfWOqdsIwGY4yckUkmMFlsJmEGkjcTy60Q6ZCfrQhMULzL/fILLydF0wZcD/fWE/urAbp2nw==} resolution: {integrity: sha512-DH2v2yUBUuDZ7vzjPXUd/yt1AMR3BIkZN0ubLAvS2C+q5Wbvk7ZvAJhfPZ3OYc3ZpQXe4ZGEcptOjvEYvd1lLA==}
engines: {node: '>=16.9'} engines: {node: '>=16.9'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
svelte: ^3.44.0 svelte: ^3.44.0
vite: ^3.0.0 vite: ^3.0.0
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.0 '@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.1
chokidar: 3.5.3 chokidar: 3.5.3
sade: 1.8.1 sade: 1.8.1
svelte: 3.49.0 svelte: 3.49.0
vite: 3.0.0 vite: 3.0.1
transitivePeerDependencies: transitivePeerDependencies:
- diff-match-patch - diff-match-patch
- supports-color - supports-color
dev: true dev: true
/@sveltejs/vite-plugin-svelte/1.0.1_svelte@3.49.0+vite@3.0.0: /@sveltejs/vite-plugin-svelte/1.0.1_svelte@3.49.0+vite@3.0.1:
resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==} resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==}
engines: {node: ^14.18.0 || >= 16} engines: {node: ^14.18.0 || >= 16}
peerDependencies: peerDependencies:
@ -471,7 +459,7 @@ packages:
magic-string: 0.26.2 magic-string: 0.26.2
svelte: 3.49.0 svelte: 3.49.0
svelte-hmr: 0.14.12_svelte@3.49.0 svelte-hmr: 0.14.12_svelte@3.49.0
vite: 3.0.0 vite: 3.0.1
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@ -518,7 +506,7 @@ packages:
dependencies: dependencies:
'@types/http-cache-semantics': 4.0.1 '@types/http-cache-semantics': 4.0.1
'@types/keyv': 3.1.4 '@types/keyv': 3.1.4
'@types/node': 18.0.4 '@types/node': 18.0.6
'@types/responselike': 1.0.0 '@types/responselike': 1.0.0
dev: false dev: false
@ -541,7 +529,7 @@ packages:
/@types/keyv/3.1.4: /@types/keyv/3.1.4:
resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
dependencies: dependencies:
'@types/node': 18.0.4 '@types/node': 18.0.6
dev: false dev: false
/@types/lodash/4.14.182: /@types/lodash/4.14.182:
@ -552,12 +540,12 @@ packages:
resolution: {integrity: sha512-XwVteWQx/XkfRPyaGkw8dEbrCAkoRZ73pI3XznUYIpzbCfpQB3UnDlR5TnmdhetlT889tUJGF8QWo9xrgTpsiA==} resolution: {integrity: sha512-XwVteWQx/XkfRPyaGkw8dEbrCAkoRZ73pI3XznUYIpzbCfpQB3UnDlR5TnmdhetlT889tUJGF8QWo9xrgTpsiA==}
dev: true dev: true
/@types/node/18.0.3:
resolution: {integrity: sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==}
dev: true
/@types/node/18.0.4: /@types/node/18.0.4:
resolution: {integrity: sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==} resolution: {integrity: sha512-M0+G6V0Y4YV8cqzHssZpaNCqvYwlCiulmm0PwpNLF55r/+cT8Ol42CHRU1SEaYFH2rTwiiE1aYg/2g2rrtGdPA==}
dev: true
/@types/node/18.0.6:
resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==}
/@types/normalize-package-data/2.4.1: /@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@ -570,16 +558,16 @@ packages:
/@types/responselike/1.0.0: /@types/responselike/1.0.0:
resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
dependencies: dependencies:
'@types/node': 18.0.4 '@types/node': 18.0.6
dev: false dev: false
/@types/sass/1.43.1: /@types/sass/1.43.1:
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
dependencies: dependencies:
'@types/node': 18.0.3 '@types/node': 18.0.4
dev: true dev: true
/@typescript-eslint/eslint-plugin/5.30.6_2vt5mtrqleafs33qg2bhpmbaqm: /@typescript-eslint/eslint-plugin/5.30.6_b7n364ggt6o4xlkgyoaww3ph3q:
resolution: {integrity: sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==} resolution: {integrity: sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -590,12 +578,12 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.30.6_4x5o4skxv6sl53vpwefgt23khm '@typescript-eslint/parser': 5.30.6_he2ccbldppg44uulnyq4rwocfa
'@typescript-eslint/scope-manager': 5.30.6 '@typescript-eslint/scope-manager': 5.30.6
'@typescript-eslint/type-utils': 5.30.6_4x5o4skxv6sl53vpwefgt23khm '@typescript-eslint/type-utils': 5.30.6_he2ccbldppg44uulnyq4rwocfa
'@typescript-eslint/utils': 5.30.6_4x5o4skxv6sl53vpwefgt23khm '@typescript-eslint/utils': 5.30.6_he2ccbldppg44uulnyq4rwocfa
debug: 4.3.4 debug: 4.3.4
eslint: 8.19.0 eslint: 8.20.0
functional-red-black-tree: 1.0.1 functional-red-black-tree: 1.0.1
ignore: 5.2.0 ignore: 5.2.0
regexpp: 3.2.0 regexpp: 3.2.0
@ -606,7 +594,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser/5.30.6_4x5o4skxv6sl53vpwefgt23khm: /@typescript-eslint/parser/5.30.6_he2ccbldppg44uulnyq4rwocfa:
resolution: {integrity: sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==} resolution: {integrity: sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -620,7 +608,7 @@ packages:
'@typescript-eslint/types': 5.30.6 '@typescript-eslint/types': 5.30.6
'@typescript-eslint/typescript-estree': 5.30.6_typescript@4.7.4 '@typescript-eslint/typescript-estree': 5.30.6_typescript@4.7.4
debug: 4.3.4 debug: 4.3.4
eslint: 8.19.0 eslint: 8.20.0
typescript: 4.7.4 typescript: 4.7.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@ -634,7 +622,7 @@ packages:
'@typescript-eslint/visitor-keys': 5.30.6 '@typescript-eslint/visitor-keys': 5.30.6
dev: true dev: true
/@typescript-eslint/type-utils/5.30.6_4x5o4skxv6sl53vpwefgt23khm: /@typescript-eslint/type-utils/5.30.6_he2ccbldppg44uulnyq4rwocfa:
resolution: {integrity: sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==} resolution: {integrity: sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -644,9 +632,9 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/utils': 5.30.6_4x5o4skxv6sl53vpwefgt23khm '@typescript-eslint/utils': 5.30.6_he2ccbldppg44uulnyq4rwocfa
debug: 4.3.4 debug: 4.3.4
eslint: 8.19.0 eslint: 8.20.0
tsutils: 3.21.0_typescript@4.7.4 tsutils: 3.21.0_typescript@4.7.4
typescript: 4.7.4 typescript: 4.7.4
transitivePeerDependencies: transitivePeerDependencies:
@ -679,7 +667,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils/5.30.6_4x5o4skxv6sl53vpwefgt23khm: /@typescript-eslint/utils/5.30.6_he2ccbldppg44uulnyq4rwocfa:
resolution: {integrity: sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==} resolution: {integrity: sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@ -689,9 +677,9 @@ packages:
'@typescript-eslint/scope-manager': 5.30.6 '@typescript-eslint/scope-manager': 5.30.6
'@typescript-eslint/types': 5.30.6 '@typescript-eslint/types': 5.30.6
'@typescript-eslint/typescript-estree': 5.30.6_typescript@4.7.4 '@typescript-eslint/typescript-estree': 5.30.6_typescript@4.7.4
eslint: 8.19.0 eslint: 8.20.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.19.0 eslint-utils: 3.0.0_eslint@8.20.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
@ -2531,16 +2519,16 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/eslint-config-prettier/8.5.0_eslint@8.19.0: /eslint-config-prettier/8.5.0_eslint@8.20.0:
resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
eslint: '>=7.0.0' eslint: '>=7.0.0'
dependencies: dependencies:
eslint: 8.19.0 eslint: 8.20.0
dev: true dev: true
/eslint-plugin-prettier/4.2.1_7uxdfn2xinezdgvmbammh6ev5i: /eslint-plugin-prettier/4.2.1_g4fztgbwjyq2fvmcscny2sj6fy:
resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
peerDependencies: peerDependencies:
@ -2551,19 +2539,19 @@ packages:
eslint-config-prettier: eslint-config-prettier:
optional: true optional: true
dependencies: dependencies:
eslint: 8.19.0 eslint: 8.20.0
eslint-config-prettier: 8.5.0_eslint@8.19.0 eslint-config-prettier: 8.5.0_eslint@8.20.0
prettier: 2.7.1 prettier: 2.7.1
prettier-linter-helpers: 1.0.0 prettier-linter-helpers: 1.0.0
dev: true dev: true
/eslint-plugin-svelte3/4.0.0_jxmmfmurkts274jdspwh3cyqve: /eslint-plugin-svelte3/4.0.0_piwa6j2njmnknm35bh3wz5v52y:
resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==} resolution: {integrity: sha512-OIx9lgaNzD02+MDFNLw0GEUbuovNcglg+wnd/UY0fbZmlQSz7GlQiQ1f+yX0XvC07XPcDOnFcichqI3xCwp71g==}
peerDependencies: peerDependencies:
eslint: '>=8.0.0' eslint: '>=8.0.0'
svelte: ^3.2.0 svelte: ^3.2.0
dependencies: dependencies:
eslint: 8.19.0 eslint: 8.20.0
svelte: 3.49.0 svelte: 3.49.0
dev: true dev: true
@ -2583,13 +2571,13 @@ packages:
estraverse: 5.3.0 estraverse: 5.3.0
dev: true dev: true
/eslint-utils/3.0.0_eslint@8.19.0: /eslint-utils/3.0.0_eslint@8.20.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies: peerDependencies:
eslint: '>=5' eslint: '>=5'
dependencies: dependencies:
eslint: 8.19.0 eslint: 8.20.0
eslint-visitor-keys: 2.1.0 eslint-visitor-keys: 2.1.0
dev: true dev: true
@ -2603,8 +2591,8 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint/8.19.0: /eslint/8.20.0:
resolution: {integrity: sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==} resolution: {integrity: sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
@ -2617,7 +2605,7 @@ packages:
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.1.1 eslint-scope: 7.1.1
eslint-utils: 3.0.0_eslint@8.19.0 eslint-utils: 3.0.0_eslint@8.20.0
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
espree: 9.3.2 espree: 9.3.2
esquery: 1.4.0 esquery: 1.4.0
@ -4259,8 +4247,8 @@ packages:
find-up: 3.0.0 find-up: 3.0.0
dev: false dev: false
/playwright-core/1.23.3: /playwright-core/1.23.4:
resolution: {integrity: sha512-x35yzsXDyo0BIXYimLnUFNyb42c//NadUNH6IPGOteZm96oTGA1kn4Hq6qJTI1/f9wEc1F9O1DsznXIgXMil7A==} resolution: {integrity: sha512-h5V2yw7d8xIwotjyNrkLF13nV9RiiZLHdXeHo+nVJIYGVlZ8U2qV0pMxNJKNTvfQVT0N8/A4CW6/4EW2cOcTiA==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
dev: true dev: true
@ -4860,6 +4848,10 @@ packages:
engines: {node: '>= 10.x'} engines: {node: '>= 10.x'}
dev: false dev: false
/ssh-config/4.1.6:
resolution: {integrity: sha512-YdPYn/2afoBonSFoMSvC1FraA/LKKrvy8UvbvAFGJ8gdlKuANvufLLkf8ynF2uq7Tl5+DQBIFyN37//09nAgNQ==}
dev: false
/ssh2/1.10.0: /ssh2/1.10.0:
resolution: {integrity: sha512-OnKAAmf4j8wCRrXXZv3Tp5lCZkLJZtgZbn45ELiShCg27djDQ3XFGvIzuGsIsf4hdHslP+VdhA9BhUQdTdfd9w==} resolution: {integrity: sha512-OnKAAmf4j8wCRrXXZv3Tp5lCZkLJZtgZbn45ELiShCg27djDQ3XFGvIzuGsIsf4hdHslP+VdhA9BhUQdTdfd9w==}
engines: {node: '>=10.16.0'} engines: {node: '>=10.16.0'}
@ -5217,7 +5209,7 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/ts-node/10.8.2_2zqz24ol5yhbv2blv4fh7akzrq: /ts-node/10.8.2_tdn3ypgnfy6bmey2q4hu5jonwi:
resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -5236,7 +5228,7 @@ packages:
'@tsconfig/node12': 1.0.9 '@tsconfig/node12': 1.0.9
'@tsconfig/node14': 1.0.1 '@tsconfig/node14': 1.0.1
'@tsconfig/node16': 1.0.2 '@tsconfig/node16': 1.0.2
'@types/node': 18.0.4 '@types/node': 18.0.6
acorn: 8.7.1 acorn: 8.7.1
acorn-walk: 8.2.0 acorn-walk: 8.2.0
arg: 4.1.3 arg: 4.1.3
@ -5373,9 +5365,9 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: false dev: false
/vite/3.0.0: /vite/3.0.1:
resolution: {integrity: sha512-M7phQhY3+fRZa0H+1WzI6N+/onruwPTBTMvaj7TzgZ0v2TE+N2sdLKxJOfOv9CckDWt5C4HmyQP81xB4dwRKzA==} resolution: {integrity: sha512-nefKSglkoEsDpYUkBuT2++L04ktcP8fz8dxLtmZdDdMyhubFSOLFw6BTh/46Fc6tIX/cibs/NVYWNrsqn0k6pQ==}
engines: {node: '>=14.18.0'} engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
less: '*' less: '*'