From bf047e2a3ceea5c674322a3b1507438a4ee87d15 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 17 Feb 2022 22:14:06 +0100 Subject: [PATCH] feat: Dual certificates desing: Lots of design/css updates version++ --- package.json | 3 +- pnpm-lock.yaml | 21 ++- .../20220217211304_dualcerts/migration.sql | 47 +++++++ prisma/schema.prisma | 5 +- src/lib/components/Explainer.svelte | 4 +- src/lib/components/Setting.svelte | 13 +- src/lib/database/applications.ts | 4 +- src/lib/database/services.ts | 9 +- src/lib/haproxy/index.ts | 1 - src/lib/letsencrypt.ts | 73 ++++++---- src/routes/applications/[id]/index.svelte | 35 +++-- src/routes/applications/[id]/settings.json.ts | 4 +- .../databases/[id]/_Databases/_CouchDb.svelte | 106 +++++++-------- .../[id]/_Databases/_Databases.svelte | 126 ++++++++---------- .../databases/[id]/_Databases/_MongoDB.svelte | 42 +++--- .../databases/[id]/_Databases/_MySQL.svelte | 106 +++++++-------- .../[id]/_Databases/_PostgreSQL.svelte | 64 ++++----- .../databases/[id]/_Databases/_Redis.svelte | 35 ++--- .../destinations/[id]/_LocalDocker.svelte | 27 ++-- .../services/[id]/_Services/_MinIO.svelte | 60 ++++----- .../[id]/_Services/_PlausibleAnalytics.svelte | 120 ++++++++--------- .../services/[id]/_Services/_Services.svelte | 35 ++++- .../[id]/_Services/_VSCodeServer.svelte | 20 ++- .../services/[id]/_Services/_Wordpress.svelte | 114 +++++++--------- .../[id]/configuration/version.json.ts | 2 +- src/routes/services/[id]/settings.json.ts | 19 +++ src/routes/settings/index.json.ts | 19 ++- src/routes/settings/index.svelte | 108 ++++++++------- src/routes/sources/[id]/_Gitlab.svelte | 2 +- src/routes/teams/[id]/index.svelte | 24 ++-- src/tailwind.css | 6 +- tailwind.config.cjs | 3 +- 32 files changed, 670 insertions(+), 587 deletions(-) create mode 100644 prisma/migrations/20220217211304_dualcerts/migration.sql create mode 100644 src/routes/services/[id]/settings.json.ts diff --git a/package.json b/package.json index e03c678b0..50dba74e9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "coolify", "description": "An open-source & self-hostable Heroku / Netlify alternative.", - "version": "2.0.13", + "version": "2.0.14", "license": "AGPL-3.0", "scripts": { "dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0", @@ -76,6 +76,7 @@ "jsonwebtoken": "8.5.1", "node-forge": "1.2.1", "svelte-kit-cookie-session": "2.0.2", + "tailwindcss-scrollbar": "^0.1.0", "unique-names-generator": "4.6.0" }, "prisma": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58ddfd8c6..b6e75d0bc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,9 +43,10 @@ specifiers: prisma: 3.9.2 svelte: 3.46.4 svelte-check: 2.4.3 - svelte-kit-cookie-session: 2.0.5 + svelte-kit-cookie-session: 2.0.2 svelte-preprocess: 4.10.3 tailwindcss: 3.0.22 + tailwindcss-scrollbar: ^0.1.0 ts-node: 10.5.0 tslib: 2.3.1 typescript: 4.5.5 @@ -70,7 +71,8 @@ dependencies: js-yaml: 4.1.0 jsonwebtoken: 8.5.1 node-forge: 1.2.1 - svelte-kit-cookie-session: 2.0.5 + svelte-kit-cookie-session: 2.0.2 + tailwindcss-scrollbar: 0.1.0_tailwindcss@3.0.22 unique-names-generator: 4.6.0 devDependencies: @@ -5203,10 +5205,10 @@ packages: svelte: 3.46.4 dev: true - /svelte-kit-cookie-session/2.0.5: + /svelte-kit-cookie-session/2.0.2: resolution: { - integrity: sha512-IX1IXtn42UTz/isem1LqH0SAZdCx6Z6Iu2V4Q83V2EScFbXZWfeFY08Azl8ZrPKdIDhSNHBLAAumRjA6TBxCvQ== + integrity: sha512-+JfunYbraIOkecOJlC1iYqH9g6YOY8MXyUdE3hTZquR1JrODmOZZ+pVPmZuVIFpM5sStJf/jF1NT5306TWE9Gw== } dev: false @@ -5288,6 +5290,17 @@ packages: strip-ansi: 6.0.1 dev: true + /tailwindcss-scrollbar/0.1.0_tailwindcss@3.0.22: + resolution: + { + integrity: sha512-egipxw4ooQDh94x02XQpPck0P0sfwazwoUGfA9SedPATIuYDR+6qe8d31Gl7YsSMRiOKDkkqfI0kBvEw9lT/Hg== + } + peerDependencies: + tailwindcss: '>= 2.x.x' + dependencies: + tailwindcss: 3.0.22_c940fbabf228b85b1c73d314b43e31f1 + dev: false + /tailwindcss/3.0.22_c940fbabf228b85b1c73d314b43e31f1: resolution: { diff --git a/prisma/migrations/20220217211304_dualcerts/migration.sql b/prisma/migrations/20220217211304_dualcerts/migration.sql new file mode 100644 index 000000000..a6ea0a57d --- /dev/null +++ b/prisma/migrations/20220217211304_dualcerts/migration.sql @@ -0,0 +1,47 @@ +-- RedefineTables +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Setting" ( + "id" TEXT NOT NULL PRIMARY KEY, + "fqdn" TEXT, + "isRegistrationEnabled" BOOLEAN NOT NULL DEFAULT false, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "proxyPassword" TEXT NOT NULL, + "proxyUser" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); +INSERT INTO "new_Setting" ("createdAt", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt") SELECT "createdAt", "fqdn", "id", "isRegistrationEnabled", "proxyPassword", "proxyUser", "updatedAt" FROM "Setting"; +DROP TABLE "Setting"; +ALTER TABLE "new_Setting" RENAME TO "Setting"; +CREATE UNIQUE INDEX "Setting_fqdn_key" ON "Setting"("fqdn"); +CREATE TABLE "new_ApplicationSettings" ( + "id" TEXT NOT NULL PRIMARY KEY, + "applicationId" TEXT NOT NULL, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "debug" BOOLEAN NOT NULL DEFAULT false, + "previews" BOOLEAN NOT NULL DEFAULT false, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "ApplicationSettings_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE +); +INSERT INTO "new_ApplicationSettings" ("applicationId", "createdAt", "debug", "id", "previews", "updatedAt") SELECT "applicationId", "createdAt", "debug", "id", "previews", "updatedAt" FROM "ApplicationSettings"; +DROP TABLE "ApplicationSettings"; +ALTER TABLE "new_ApplicationSettings" RENAME TO "ApplicationSettings"; +CREATE UNIQUE INDEX "ApplicationSettings_applicationId_key" ON "ApplicationSettings"("applicationId"); +CREATE TABLE "new_Service" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "fqdn" TEXT, + "dualCerts" BOOLEAN NOT NULL DEFAULT false, + "type" TEXT, + "version" TEXT, + "destinationDockerId" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Service_destinationDockerId_fkey" FOREIGN KEY ("destinationDockerId") REFERENCES "DestinationDocker" ("id") ON DELETE SET NULL ON UPDATE CASCADE +); +INSERT INTO "new_Service" ("createdAt", "destinationDockerId", "fqdn", "id", "name", "type", "updatedAt", "version") SELECT "createdAt", "destinationDockerId", "fqdn", "id", "name", "type", "updatedAt", "version" FROM "Service"; +DROP TABLE "Service"; +ALTER TABLE "new_Service" RENAME TO "Service"; +PRAGMA foreign_key_check; +PRAGMA foreign_keys=ON; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b064d09ab..cf9684f53 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -11,6 +11,7 @@ model Setting { id String @id @default(cuid()) fqdn String? @unique isRegistrationEnabled Boolean @default(false) + dualCerts Boolean @default(false) proxyPassword String proxyUser String createdAt DateTime @default(now()) @@ -97,6 +98,7 @@ model ApplicationSettings { id String @id @default(cuid()) application Application @relation(fields: [applicationId], references: [id]) applicationId String @unique + dualCerts Boolean @default(false) debug Boolean @default(false) previews Boolean @default(false) createdAt DateTime @default(now()) @@ -105,7 +107,7 @@ model ApplicationSettings { model Secret { id String @id @default(cuid()) - name String + name String value String isBuildSecret Boolean @default(false) createdAt DateTime @default(now()) @@ -234,6 +236,7 @@ model Service { id String @id @default(cuid()) name String fqdn String? + dualCerts Boolean @default(false) type String? version String? teams Team[] diff --git a/src/lib/components/Explainer.svelte b/src/lib/components/Explainer.svelte index 4eaf3a398..b47c03ec1 100644 --- a/src/lib/components/Explainer.svelte +++ b/src/lib/components/Explainer.svelte @@ -1,6 +1,6 @@ -
{@html text}
+
{@html text}
diff --git a/src/lib/components/Setting.svelte b/src/lib/components/Setting.svelte index b26e3e072..c431273e6 100644 --- a/src/lib/components/Setting.svelte +++ b/src/lib/components/Setting.svelte @@ -4,15 +4,17 @@ export let setting; export let title; export let description; - export let isPadding = true; + export let isCenter = true; export let disabled = false; -
  • -
    -

    {title}

    +
    +
    +
    {title}
    +
    +
    - -
  • + diff --git a/src/lib/database/applications.ts b/src/lib/database/applications.ts index 738ebe594..f2e779bee 100644 --- a/src/lib/database/applications.ts +++ b/src/lib/database/applications.ts @@ -209,10 +209,10 @@ export async function configureApplication({ }); } -export async function setApplicationSettings({ id, debug, previews }) { +export async function setApplicationSettings({ id, debug, previews, dualCerts }) { return await prisma.application.update({ where: { id }, - data: { settings: { update: { debug, previews } } }, + data: { settings: { update: { debug, previews, dualCerts } } }, include: { destinationDocker: true } }); } diff --git a/src/lib/database/services.ts b/src/lib/database/services.ts index 5568cd4a7..5cbf2cbfb 100644 --- a/src/lib/database/services.ts +++ b/src/lib/database/services.ts @@ -107,13 +107,20 @@ export async function configureServiceType({ id, type }) { }); } } -export async function setService({ id, version }) { +export async function setServiceVersion({ id, version }) { return await prisma.service.update({ where: { id }, data: { version } }); } +export async function setServiceSettings({ id, dualCerts }) { + return await prisma.service.update({ + where: { id }, + data: { dualCerts } + }); +} + export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) { await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } }); await prisma.service.update({ where: { id }, data: { name, fqdn } }); diff --git a/src/lib/haproxy/index.ts b/src/lib/haproxy/index.ts index 0eaee0109..0daf1d493 100644 --- a/src/lib/haproxy/index.ts +++ b/src/lib/haproxy/index.ts @@ -2,7 +2,6 @@ import { dev } from '$app/env'; import { asyncExecShell, getDomain, getEngine } from '$lib/common'; import got from 'got'; import * as db from '$lib/database'; -import { letsEncrypt } from '$lib/letsencrypt'; const url = dev ? 'http://localhost:5555' : 'http://coolify-haproxy:5555'; diff --git a/src/lib/letsencrypt.ts b/src/lib/letsencrypt.ts index 6e166d8f2..6ee29f812 100644 --- a/src/lib/letsencrypt.ts +++ b/src/lib/letsencrypt.ts @@ -3,49 +3,70 @@ import { forceSSLOnApplication } from '$lib/haproxy'; import { asyncExecShell, getEngine } from './common'; import * as db from '$lib/database'; import cuid from 'cuid'; +import getPort from 'get-port'; export async function letsEncrypt({ domain, isCoolify = false, id = null }) { try { const nakedDomain = domain.replace('www.', ''); const wwwDomain = `www.${nakedDomain}`; const randomCuid = cuid(); - if (dev) { - return await forceSSLOnApplication({ domain }); + const randomPort = getPort(); + + let host; + let dualCerts = false; + if (isCoolify) { + const data = await db.prisma.setting.findFirst(); + dualCerts = data.dualCerts; + host = '/var/run/docker.sock'; } else { - if (isCoolify) { - await asyncExecShell( - `docker run --rm --name certbot-${randomCuid} -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email` - ); - - const { stderr: copyError } = await asyncExecShell( - `docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${nakedDomain}/ && cat /etc/letsencrypt/live/${nakedDomain}/fullchain.pem /etc/letsencrypt/live/${nakedDomain}/privkey.pem > /app/ssl/${nakedDomain}.pem || cat /etc/letsencrypt/live/${wwwDomain}/fullchain.pem /etc/letsencrypt/live/${wwwDomain}/privkey.pem > /app/ssl/${wwwDomain}.pem"` - ); - - if (copyError) throw copyError; - return; + // Check Application + const applicationData = await db.prisma.application.findUnique({ + where: { id }, + include: { destinationDocker: true, settings: true } + }); + if (applicationData) { + if (applicationData?.destinationDockerId && applicationData?.destinationDocker) { + host = getEngine(applicationData.destinationDocker.engine); + } + if (applicationData?.settings?.dualCerts) { + dualCerts = applicationData.settings.dualCerts; + } } - let data: any = await db.prisma.application.findUnique({ + // Check Service + const serviceData = await db.prisma.service.findUnique({ where: { id }, include: { destinationDocker: true } }); - if (!data) { - data = await db.prisma.service.findUnique({ - where: { id }, - include: { destinationDocker: true } - }); + if (serviceData) { + if (serviceData?.destinationDockerId && serviceData?.destinationDocker) { + host = getEngine(serviceData.destinationDocker.engine); + } + if (serviceData?.dualCerts) { + dualCerts = serviceData.dualCerts; + } } - // Set SSL with Let's encrypt - if (data.destinationDockerId && data.destinationDocker) { - const host = getEngine(data.destinationDocker.engine); + } + if (!dev) { + if (dualCerts) { await asyncExecShell( - `DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p 9080:9080 -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port 9080 -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email` + `DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p ${randomPort}:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${nakedDomain} -d ${wwwDomain} --expand --agree-tos --non-interactive --register-unsafely-without-email` ); - const { stderr: copyError } = await asyncExecShell( + await asyncExecShell( `DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "test -d /etc/letsencrypt/live/${nakedDomain}/ && cat /etc/letsencrypt/live/${nakedDomain}/fullchain.pem /etc/letsencrypt/live/${nakedDomain}/privkey.pem > /app/ssl/${nakedDomain}.pem || cat /etc/letsencrypt/live/${wwwDomain}/fullchain.pem /etc/letsencrypt/live/${wwwDomain}/privkey.pem > /app/ssl/${wwwDomain}.pem"` ); - if (copyError) throw copyError; - await forceSSLOnApplication({ domain }); + } else { + await asyncExecShell( + `DOCKER_HOST=${host} docker run --rm --name certbot-${randomCuid} -p ${randomPort}:${randomPort} -v "coolify-letsencrypt:/etc/letsencrypt" certbot/certbot --logs-dir /etc/letsencrypt/logs certonly --standalone --preferred-challenges http --http-01-address 0.0.0.0 --http-01-port ${randomPort} -d ${domain} --expand --agree-tos --non-interactive --register-unsafely-without-email` + ); + await asyncExecShell( + `DOCKER_HOST=${host} docker run --rm -v "coolify-letsencrypt:/etc/letsencrypt" -v "coolify-ssl-certs:/app/ssl" alpine:latest sh -c "cat /etc/letsencrypt/live/${domain}/fullchain.pem /etc/letsencrypt/live/${domain}/privkey.pem > /app/ssl/${domain}.pem"` + ); } + } else { + console.log({ dualCerts, host, wwwDomain, nakedDomain, domain }); + } + if (!isCoolify) { + await forceSSLOnApplication({ domain }); } } catch (error) { console.log(error); diff --git a/src/routes/applications/[id]/index.svelte b/src/routes/applications/[id]/index.svelte index 930088ec5..a5d19eb6b 100644 --- a/src/routes/applications/[id]/index.svelte +++ b/src/routes/applications/[id]/index.svelte @@ -52,6 +52,7 @@ let loading = false; let debug = application.settings.debug; let previews = application.settings.previews; + let dualCerts = application.settings.dualCerts; onMount(() => { domainEl.focus(); @@ -64,8 +65,11 @@ if (name === 'previews') { previews = !previews; } + if (name === 'dualCerts') { + dualCerts = !dualCerts; + } try { - await post(`/applications/${id}/settings.json`, { previews, debug }); + await post(`/applications/${id}/settings.json`, { previews, debug, dualCerts }); return toast.push('Settings saved.'); } catch ({ error }) { return errorNotification(error); @@ -252,7 +256,7 @@
    - +
    - +
    + changeSettings('dualCerts')} + /> +
    {#if !staticDeployments.includes(application.buildPack)}
    @@ -285,6 +297,7 @@
    {/if} + {#if !notNodeDeployments.includes(application.buildPack)}
    @@ -361,8 +374,7 @@
    Features
    -
    - -
      +
      +
      changeSettings('previews')} title="Enable MR/PR Previews" description="Creates previews from pull and merge requests." /> -
    -
      +
    +
    changeSettings('debug')} title="Debug Logs" description="Enable debug logs during build phase.
    (sensitive information could be visible in logs)" /> - +
    diff --git a/src/routes/applications/[id]/settings.json.ts b/src/routes/applications/[id]/settings.json.ts index ddd7bb5ca..6b0b3f808 100644 --- a/src/routes/applications/[id]/settings.json.ts +++ b/src/routes/applications/[id]/settings.json.ts @@ -8,10 +8,10 @@ export const post: RequestHandler = async (event) => { if (status === 401) return { status, body }; const { id } = event.params; - const { debug, previews } = await event.request.json(); + const { debug, previews, dualCerts } = await event.request.json(); try { - await db.setApplicationSettings({ id, debug, previews }); + await db.setApplicationSettings({ id, debug, previews, dualCerts }); return { status: 201 }; } catch (error) { return ErrorHandler(error); diff --git a/src/routes/databases/[id]/_Databases/_CouchDb.svelte b/src/routes/databases/[id]/_Databases/_CouchDb.svelte index 0d41ec184..3cba4ef93 100644 --- a/src/routes/databases/[id]/_Databases/_CouchDb.svelte +++ b/src/routes/databases/[id]/_Databases/_CouchDb.svelte @@ -7,72 +7,62 @@
    CouchDB
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    diff --git a/src/routes/databases/[id]/_Databases/_Databases.svelte b/src/routes/databases/[id]/_Databases/_Databases.svelte index cd91c8d6a..e09e8d278 100644 --- a/src/routes/databases/[id]/_Databases/_Databases.svelte +++ b/src/routes/databases/[id]/_Databases/_Databases.svelte @@ -88,70 +88,60 @@
    -
    +
    -
    - -
    +
    -
    +
    -
    - {#if database.destinationDockerId} -
    - -
    - {/if} -
    + {#if database.destinationDockerId} +
    + +
    + {/if}
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    @@ -166,44 +156,42 @@ {:else if database.type === 'couchdb'} {/if} -
    +
    -
    - -
    +
    Features
    -
    -
      +
      +
      changeSettings('isPublic')} title="Set it public" description="Your database will be reachable over the internet.
      Take security seriously in this case!" /> -
    +
    {#if database.type === 'redis'} -
      +
      changeSettings('appendOnly')} title="Change append only mode" description="Useful if you would like to restore redis data from a backup.
      Database restart is required." /> -
    +
    {/if}
    diff --git a/src/routes/databases/[id]/_Databases/_MongoDB.svelte b/src/routes/databases/[id]/_Databases/_MongoDB.svelte index cbf3ffe35..54518c012 100644 --- a/src/routes/databases/[id]/_Databases/_MongoDB.svelte +++ b/src/routes/databases/[id]/_Databases/_MongoDB.svelte @@ -7,31 +7,27 @@
    MongoDB
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    diff --git a/src/routes/databases/[id]/_Databases/_MySQL.svelte b/src/routes/databases/[id]/_Databases/_MySQL.svelte index e361cc9fe..4f24862b3 100644 --- a/src/routes/databases/[id]/_Databases/_MySQL.svelte +++ b/src/routes/databases/[id]/_Databases/_MySQL.svelte @@ -7,72 +7,62 @@
    MySQL
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    diff --git a/src/routes/databases/[id]/_Databases/_PostgreSQL.svelte b/src/routes/databases/[id]/_Databases/_PostgreSQL.svelte index dc585c8e4..758aaccb0 100644 --- a/src/routes/databases/[id]/_Databases/_PostgreSQL.svelte +++ b/src/routes/databases/[id]/_Databases/_PostgreSQL.svelte @@ -7,45 +7,39 @@
    PostgreSQL
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    -
    +
    -
    - -
    +
    diff --git a/src/routes/databases/[id]/_Databases/_Redis.svelte b/src/routes/databases/[id]/_Databases/_Redis.svelte index ff8f83113..7c02a313e 100644 --- a/src/routes/databases/[id]/_Databases/_Redis.svelte +++ b/src/routes/databases/[id]/_Databases/_Redis.svelte @@ -7,32 +7,17 @@
    Redis
    - -
    +
    -
    - -
    +