diff --git a/Dockerfile b/Dockerfile index 6cc8e8c..10c9bfa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,13 @@ RUN yarn build # Regenerate node modules as production RUN rm -rf ./node_modules -RUN yarn install --production --frozen-lockfile +RUN apk --no-cache add cmake clang clang-dev make gcc g++ libc-dev linux-headers +RUN apk add --no-cache --virtual .gyp \ + python \ + make \ + g++ \ + && yarn install --production --frozen-lockfile \ + && apk del .gyp # Bundle stage FROM node:15-alpine AS production @@ -30,4 +36,4 @@ COPY --from=build /build/.next ./.next # Start script USER node EXPOSE 3000 -CMD ["yarn", "start:prod"] \ No newline at end of file +CMD ["yarn", "start:prod"] diff --git a/lib/components/DeploymentTable.tsx b/lib/components/DeploymentTable.tsx index d6c377b..da837ef 100644 --- a/lib/components/DeploymentTable.tsx +++ b/lib/components/DeploymentTable.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react' import { useApi } from '@hooks' import { Spinner, Status } from '@components' -import * as timeago from 'timeago.js' +import {dateFormat} from '@utils' export function DeploymentTable({ id, setCount = (n) => {}, limit = 0 }) { const [deployments, setDeployments] = useState(null) @@ -28,11 +28,11 @@ export function DeploymentTable({ id, setCount = (n) => {}, limit = 0 }) { {i.message || `Deployment #${deployments.length - k}`} {i.rollback && } -

Deploying from {i.type}

+

Deploying from {i.type} {i.branch ? `(${i.branch})` : ''}

-

{timeago.format(i.created)}

+

{dateFormat(i.created)}

diff --git a/lib/components/Nav.tsx b/lib/components/Nav.tsx index f96455c..298b1c2 100644 --- a/lib/components/Nav.tsx +++ b/lib/components/Nav.tsx @@ -48,7 +48,7 @@ export function Nav({ active }) { style={{ backgroundSize: 'cover', backgroundPosition: 'center', - backgroundImage: `url(${user && user.avatar})`, + backgroundImage: `url(${user && user.avatar ? user.avatar : '/icons/avatar-default.png'})`, }} /> diff --git a/lib/utils/date.tsx b/lib/utils/date.tsx new file mode 100644 index 0000000..bd59ef4 --- /dev/null +++ b/lib/utils/date.tsx @@ -0,0 +1,12 @@ +import moment from 'moment'; +import * as timeago from 'timeago.js'; + +export function dateFormat(dateTime: Date) { + let formated = timeago.format(dateTime) + const now = moment(new Date()); + const then = moment(dateTime); + const diff = now.diff(then, 'minutes'); + if(diff > 60) + formated = then.local().format('DD/MM/YYYY hh:mm:ss A'); + return formated; +} \ No newline at end of file diff --git a/lib/utils/index.ts b/lib/utils/index.ts new file mode 100644 index 0000000..32cb75e --- /dev/null +++ b/lib/utils/index.ts @@ -0,0 +1 @@ +export * from './date' \ No newline at end of file diff --git a/package.json b/package.json index 456a29d..d89e88b 100644 --- a/package.json +++ b/package.json @@ -22,12 +22,14 @@ "dsn-parser": "^1.0.3", "js-cookie": "^3.0.1", "jsonwebtoken": "^8.5.1", + "moment": "^2.29.1", "next": "latest", "node-ssh": "^12.0.0", "otplib": "^12.0.1", "prisma": "^3.1.1", "qrcode": "^1.4.4", "react": "^17.0.2", + "react-copy-to-clipboard": "^5.0.4", "react-dom": "^17.0.2", "react-hot-toast": "^2.1.1", "request-ip": "^2.1.3", diff --git a/pages/activity.tsx b/pages/activity.tsx index 2caf659..133ccce 100644 --- a/pages/activity.tsx +++ b/pages/activity.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import { Nav, Spinner } from '@components' import { useApi, useValidSession } from '@hooks' -import * as timeago from 'timeago.js' +import {dateFormat} from '@utils' export default function Activity() { const [activity, setActivity] = useState(null) @@ -28,7 +28,7 @@ export default function Activity() { return (
- +

{i.action}

@@ -36,7 +36,7 @@ export default function Activity() {

-

{timeago.format(i.created)}

+

{dateFormat(i.created)}

) })} diff --git a/pages/admin.tsx b/pages/admin.tsx index 56950d6..04144eb 100644 --- a/pages/admin.tsx +++ b/pages/admin.tsx @@ -3,7 +3,7 @@ import { useApi, useValidSession } from '@hooks' import { Disclosure } from '@headlessui/react' import { Button, Nav, Input, Select } from '@components' import { useRouter } from 'next/router' -import * as timeago from 'timeago.js' +import {dateFormat} from '@utils' import toast from 'react-hot-toast' import cookie from 'js-cookie' @@ -161,7 +161,7 @@ export default function Admin() { style={{ backgroundSize: 'cover', backgroundPosition: 'center', - backgroundImage: `url(${i.avatar})`, + backgroundImage: `url(${i.avatar || '/icons/avatar-default.png'})`, }} /> {i.name} {i.email}{' '} @@ -176,7 +176,7 @@ export default function Admin() {

Has MFA Enabled

{i.mfa_enabled ? 'Yes' : 'No'}

Created

-

{timeago.format(i.created)}

+

{dateFormat(i.created)}


diff --git a/pages/api/git/webhook.ts b/pages/api/git/webhook.ts new file mode 100644 index 0000000..8cf3709 --- /dev/null +++ b/pages/api/git/webhook.ts @@ -0,0 +1,92 @@ +import log from '@server/log' +import prisma from '@server/db' +import build from '@server/build' +import moment from 'moment' + +function allowDeployment(lastDeployment: Date) { + const now = moment(new Date()); + const then = moment(lastDeployment); + const diff = now.diff(then, 'minutes'); + return (diff > 5); +} + +export default async function (req, res) { + try { + if (req.method === 'POST') { + let {projectId, branch} = req.query; + if (!projectId) res.status(400).send(); + + let project = await prisma.projects.findFirst({ + where: { + id: projectId, + }, + include: { + accounts: true, + deployments: { + take: 1, + orderBy: { + created: 'desc', + } + } + } + }); + let lastDeployment = project?.deployments?.pop(); + + if (project && lastDeployment) { + let { ref, after, head_commit, commit, commits } = req.body; + let { origin } = lastDeployment; + + if (!head_commit) head_commit = (commit||(commits && commits[0])); + + if( !branch ) branch = (lastDeployment?.branch || 'master'); + + let commitedBranch = ref?.split('refs/heads/')[1]; + let message = head_commit?.message; + + if(lastDeployment?.type !== 'git' || !allowDeployment(lastDeployment.created) || (commitedBranch && branch !== commitedBranch)) { + return res.status(400).send(); + } else { + let deployment = await prisma.deployments.create({ + data: { + branch, + origin, + commit: after, + message, + type: 'git', + status: 'BUILDING', + manual: false, + projects: { + connect: { + id: project.id, + }, + }, + accounts: { + connect: { + id: project.accounts.id, + }, + }, + }, + }) + + await log( + req, + project.accounts.id, + `Deployment for ${project.name} was triggered on branch ${branch} by Git commit ${after}` + ) + + res.status(202).json(deployment) + + await build(project.id, deployment.id, origin, branch) + } + + } else { + res.status(400).send() + } + } else { + return res.status(405).send() + } + } catch (e) { + if (typeof e == 'undefined') return e + res.status(500).send() + } +} diff --git a/pages/api/github/connect.ts b/pages/api/github/connect.ts index 2b572f4..90e23d7 100644 --- a/pages/api/github/connect.ts +++ b/pages/api/github/connect.ts @@ -6,8 +6,11 @@ import axios from 'axios' export default async function (req, res) { try { if (req.method === 'GET') { - let { code, state } = req.query - let { host } = req.headers + let { code, state } = req.query; + let { host, referer } = req.headers; + + const scheme = req.headers['x-forwarded-proto'] || (referer && referer.includes("https://") ? "https": "http"); + const baseUri = `${scheme}://${host}`; if (!host) return res.status(409).send() @@ -36,7 +39,7 @@ export default async function (req, res) { }) res.redirect( - `https://github.com/login/oauth/authorize?client_id=${client_id}&redirect_uri=http://${host}/api/github/connect&scope=repo%20read:user&allow_signup=false&state=gh_authorize:${accountId}` + `https://github.com/login/oauth/authorize?client_id=${client_id}&redirect_uri=${baseUri}/api/github/connect&scope=repo%20read:user&allow_signup=false&state=gh_authorize:${accountId}` ) await ssh('dokku', ['config:set', 'admin', `GH_CLIENT_ID=${client_id}`, `GH_CLIENT_SECRET=${client_secret}`]) @@ -58,7 +61,7 @@ export default async function (req, res) { { client_id: (tokens as any).github.setup.client_id, client_secret: (tokens as any).github.setup.client_secret, - redirect_uri: `http://${host}/api/github/connect`, + redirect_uri: `${baseUri}/api/github/connect`, code: code, }, { diff --git a/pages/api/github/webhook.ts b/pages/api/github/webhook.ts index f71c038..44ce9a9 100644 --- a/pages/api/github/webhook.ts +++ b/pages/api/github/webhook.ts @@ -2,6 +2,14 @@ import log from '@server/log' import prisma from '@server/db' import github from '@server/github' import build from '@server/build' +import moment from 'moment' + +function allowDeployment(lastDeployment: Date) { + const now = moment(new Date()); + const then = moment(lastDeployment); + const diff = now.diff(then, 'minutes'); + return (diff > 5); +} export default async function (req, res) { try { @@ -26,11 +34,22 @@ export default async function (req, res) { tokens: true, }, }, + deployments: { + take: 1, + orderBy: { + created: 'desc', + } + } }, }) if (!project) return res.status(404).send() + let lastDeployment = project?.deployments?.pop(); + + if(!lastDeployment || !allowDeployment(lastDeployment.created)) + return res.status(400).send(); + let { access_token } = await github(req, res, project.accounts.id) let commit = after diff --git a/pages/api/projects/index.ts b/pages/api/projects/index.ts index 66d78cc..5d4f969 100644 --- a/pages/api/projects/index.ts +++ b/pages/api/projects/index.ts @@ -36,6 +36,7 @@ export default async function (req, res) { }, }) await ssh('dokku', ['apps:create', projects.id]) + await ssh('dokku', ['checks:enable', projects.id]) await log(req, accountId, `New project created: ${name}`) res.status(201).json(projects) diff --git a/pages/database/[id]/backups.tsx b/pages/database/[id]/backups.tsx index d5de11a..b33a3c0 100644 --- a/pages/database/[id]/backups.tsx +++ b/pages/database/[id]/backups.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { useRouter } from 'next/router' import { Button, Nav, Input, DatabaseSidebar } from '@components' import { useApi, useValidSession } from '@hooks' -import * as timeago from 'timeago.js' +import {dateFormat} from '@utils' import toast from 'react-hot-toast' import cronstrue from 'cronstrue' @@ -96,7 +96,7 @@ export default function Project() {

Status

{database.backup !== null ? 'Enabled' : 'Disabled'}

Initialized

-

{timeago.format(database.backup && database.backup.initialized)}

+

{dateFormat(database.backup && database.backup.initialized)}

Schedule

{humanCron(database.backup.schedule)}

Encrypted

diff --git a/pages/database/[id]/index.tsx b/pages/database/[id]/index.tsx index 2d2cf9d..2643f28 100644 --- a/pages/database/[id]/index.tsx +++ b/pages/database/[id]/index.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { useRouter } from 'next/router' import { Status, Nav, DatabaseSidebar } from '@components' import { useApi, useValidSession } from '@hooks' -import * as timeago from 'timeago.js' +import {dateFormat} from '@utils' export default function Project() { const [database, setDatabase] = useState(null) @@ -34,7 +34,7 @@ export default function Project() { {database.name} -

Created {timeago.format(database.created)}

+

Created {dateFormat(database.created)}

@@ -52,7 +52,7 @@ export default function Project() {

Created

-

{timeago.format(database.created)}

+

{dateFormat(database.created)}

Type

diff --git a/pages/index.tsx b/pages/index.tsx index 684567e..851e9d7 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react' import { useApi, useValidSession } from '@hooks' import { Spinner, Nav, Button, Status } from '@components' -import * as timeago from 'timeago.js' +import {dateFormat} from '@utils' import Link from 'next/link' export default function Home() { @@ -50,7 +50,7 @@ export default function Home() { {i.domains.length !== 0 ? ( <>{i.domains[0].domain} ) : ( - <>Created {timeago.format(i.created)} + <>Created {dateFormat(i.created)} )}

@@ -110,7 +110,7 @@ export default function Home() { return (
- +

{i.action}

@@ -118,7 +118,7 @@ export default function Home() {

-

{timeago.format(i.created)}

+

{dateFormat(i.created)}

) }) diff --git a/pages/project/[id]/deployments/[deployId].tsx b/pages/project/[id]/deployments/[deployId].tsx index d029d9f..6d2c540 100644 --- a/pages/project/[id]/deployments/[deployId].tsx +++ b/pages/project/[id]/deployments/[deployId].tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from 'react' import { useInterval, useApi, useValidSession } from '@hooks' import { Status, Nav, ProjectSidebar, Spinner, Button } from '@components' import { useRouter } from 'next/router' -import * as timeago from 'timeago.js' +import {dateFormat} from '@utils' import toast from 'react-hot-toast' import ansi from 'ansi_up' @@ -70,10 +70,10 @@ export default function Deployments() {

Triggered by {deployment.type} - {deployment.manual && ' (Manual)'} {timeago.format(deployment.created)} + {deployment.manual && ' (Manual)'} {dateFormat(deployment.created)}

- {deployment.status === 'COMPLETED' && ( + {deployment.status === 'COMPLETED' && deployment.commit && (
Latest Builds
- +
@@ -78,8 +110,13 @@ export default function Project() { } export async function getServerSideProps(context) { + const {referer} = context.req.headers; + const scheme = context.req.headers['x-forwarded-proto'] || (referer && referer.includes("https://") ? "https": "http"); return { - props: {}, + props: { + scheme, + host: context.req.headers['host'] || null, + }, ...useValidSession(context), } } diff --git a/pages/settings.tsx b/pages/settings.tsx index b3bcb82..72e9ca4 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -4,7 +4,8 @@ import { useApi, useValidSession } from '@hooks' import toast from 'react-hot-toast' import qrcode from 'qrcode' -export default function Settings({ host }) { +export default function Settings({scheme, host}) { + const baseUri = `${scheme}://${host}`; const [user, setUser] = useState({}) const [ips, setIps] = useState([]) const [mfa, setMfa] = useState(null) @@ -136,11 +137,11 @@ export default function Settings({ host }) { name: 'My Github Connection', public: false, request_oauth_on_install: true, - url: `http://${host}`, - redirect_url: `http://${host}/api/github/connect`, - callback_urls: [`http://${host}/api/github/connect`], + url: baseUri, + redirect_url: `${baseUri}/api/github/connect`, + callback_url: `${baseUri}/api/github/connect`, hook_attributes: { - url: `http://${host}/api/github/webhook`, + url: `${baseUri}/api/github/webhook`, active: true, }, default_permissions: { @@ -225,8 +226,11 @@ export default function Settings({ host }) { } export async function getServerSideProps(context) { + const {referer} = context.req.headers; + const scheme = context.req.headers['x-forwarded-proto'] || (referer && referer.includes("https://") ? "https": "http"); return { props: { + scheme, host: context.req.headers['host'] || null, }, ...useValidSession(context), diff --git a/prisma/migrations/20211012052206_init/migration.sql b/prisma/migrations/20211012052206_init/migration.sql index 823e003..7a1d898 100644 --- a/prisma/migrations/20211012052206_init/migration.sql +++ b/prisma/migrations/20211012052206_init/migration.sql @@ -25,7 +25,7 @@ CREATE TABLE "activity" ( "id" TEXT NOT NULL, "action" TEXT NOT NULL, "ip" TEXT NOT NULL, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "activity_pkey" PRIMARY KEY ("id") @@ -42,7 +42,7 @@ CREATE TABLE "certs" ( "logs" TEXT, "project" TEXT NOT NULL, "domain" TEXT NOT NULL, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "expires" TIMESTAMP(3), "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated" TIMESTAMP(3) NOT NULL, @@ -55,7 +55,7 @@ CREATE TABLE "databases" ( "id" TEXT NOT NULL, "name" TEXT NOT NULL, "description" TEXT, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "type" "DatabaseType" NOT NULL, "version" TEXT NOT NULL, "status" TEXT NOT NULL, @@ -81,7 +81,7 @@ CREATE TABLE "deployments" ( "rollback" BOOLEAN NOT NULL DEFAULT false, "logs" TEXT, "project" TEXT NOT NULL, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated" TIMESTAMP(3) NOT NULL, @@ -93,7 +93,7 @@ CREATE TABLE "domains" ( "id" TEXT NOT NULL, "domain" TEXT NOT NULL, "project" TEXT NOT NULL, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated" TIMESTAMP(3) NOT NULL, @@ -106,7 +106,7 @@ CREATE TABLE "environment_variables" ( "key" TEXT NOT NULL, "value" TEXT NOT NULL, "project" TEXT NOT NULL, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated" TIMESTAMP(3) NOT NULL, @@ -130,7 +130,7 @@ CREATE TABLE "port_mappings" ( "host" TEXT NOT NULL, "container" TEXT NOT NULL, "project" TEXT NOT NULL, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated" TIMESTAMP(3) NOT NULL, @@ -144,7 +144,7 @@ CREATE TABLE "projects" ( "description" TEXT, "maintenance" BOOLEAN NOT NULL DEFAULT false, "origin" TEXT, - "owner" TEXT NOT NULL, + "owner" TEXT DEFAULT 'Deleted User', "created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated" TIMESTAMP(3) NOT NULL, "deleted" TIMESTAMP(3), @@ -159,34 +159,34 @@ CREATE UNIQUE INDEX "accounts_email_key" ON "accounts"("email"); CREATE UNIQUE INDEX "databases_dsn_key" ON "databases"("dsn"); -- AddForeignKey -ALTER TABLE "activity" ADD CONSTRAINT "activity_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "activity" ADD CONSTRAINT "activity_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "certs" ADD CONSTRAINT "certs_domain_fkey" FOREIGN KEY ("domain") REFERENCES "domains"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "certs" ADD CONSTRAINT "certs_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "certs" ADD CONSTRAINT "certs_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "certs" ADD CONSTRAINT "certs_project_fkey" FOREIGN KEY ("project") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "databases" ADD CONSTRAINT "databases_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "databases" ADD CONSTRAINT "databases_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "deployments" ADD CONSTRAINT "deployments_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "deployments" ADD CONSTRAINT "deployments_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "deployments" ADD CONSTRAINT "deployments_project_fkey" FOREIGN KEY ("project") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "domains" ADD CONSTRAINT "domains_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "domains" ADD CONSTRAINT "domains_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "domains" ADD CONSTRAINT "domains_project_fkey" FOREIGN KEY ("project") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "environment_variables" ADD CONSTRAINT "environment_variables_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "environment_variables" ADD CONSTRAINT "environment_variables_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "environment_variables" ADD CONSTRAINT "environment_variables_project_fkey" FOREIGN KEY ("project") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE; @@ -198,10 +198,10 @@ ALTER TABLE "links" ADD CONSTRAINT "links_project_fkey" FOREIGN KEY ("project") ALTER TABLE "links" ADD CONSTRAINT "links_database_fkey" FOREIGN KEY ("database") REFERENCES "databases"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "port_mappings" ADD CONSTRAINT "port_mappings_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "port_mappings" ADD CONSTRAINT "port_mappings_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; -- AddForeignKey ALTER TABLE "port_mappings" ADD CONSTRAINT "port_mappings_project_fkey" FOREIGN KEY ("project") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE CASCADE; -- AddForeignKey -ALTER TABLE "projects" ADD CONSTRAINT "projects_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET NULL ON UPDATE CASCADE; +ALTER TABLE "projects" ADD CONSTRAINT "projects_owner_fkey" FOREIGN KEY ("owner") REFERENCES "accounts"("id") ON DELETE SET DEFAULT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 568eac7..fed5739 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -32,9 +32,9 @@ model activity { id String @id @default(cuid()) action String ip String - owner String + owner String? @default("Deleted User") created DateTime @default(now()) - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts? @relation(fields: [owner], references: [id], onDelete: SetDefault) } model certs { @@ -52,7 +52,7 @@ model certs { created DateTime @default(now()) updated DateTime @updatedAt domains domains @relation(fields: [domain], references: [id], onDelete: Cascade) - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts @relation(fields: [owner], references: [id], onDelete: SetDefault) projects projects @relation(fields: [project], references: [id], onDelete: Cascade) } @@ -69,7 +69,7 @@ model databases { backup String? created DateTime @default(now()) updated DateTime @updatedAt - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts @relation(fields: [owner], references: [id], onDelete: SetDefault) links links[] } @@ -85,10 +85,10 @@ model deployments { rollback Boolean @default(false) logs String? project String - owner String + owner String? @default("Deleted User") created DateTime @default(now()) updated DateTime @updatedAt - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts? @relation(fields: [owner], references: [id], onDelete: SetDefault) projects projects @relation(fields: [project], references: [id], onDelete: Cascade) } @@ -96,10 +96,10 @@ model domains { id String @id @default(cuid()) domain String project String - owner String + owner String? @default("Deleted User") created DateTime @default(now()) updated DateTime @updatedAt - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts? @relation(fields: [owner], references: [id], onDelete: SetDefault) projects projects @relation(fields: [project], references: [id], onDelete: Cascade) certs certs[] } @@ -109,10 +109,10 @@ model environment_variables { key String value String project String - owner String + owner String? @default("Deleted User") created DateTime @default(now()) updated DateTime @updatedAt - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts? @relation(fields: [owner], references: [id], onDelete: SetDefault) projects projects @relation(fields: [project], references: [id], onDelete: Cascade) } @@ -134,7 +134,7 @@ model port_mappings { owner String created DateTime @default(now()) updated DateTime @updatedAt - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts @relation(fields: [owner], references: [id], onDelete: SetDefault) projects projects @relation(fields: [project], references: [id], onDelete: Cascade) } @@ -148,7 +148,7 @@ model projects { created DateTime @default(now()) updated DateTime @updatedAt deleted DateTime? - accounts accounts @relation(fields: [owner], references: [id], onDelete: SetNull) + accounts accounts @relation(fields: [owner], references: [id], onDelete: SetDefault) certs certs[] deployments deployments[] domains domains[] diff --git a/public/icons/avatar-default.png b/public/icons/avatar-default.png new file mode 100644 index 0000000..0dbdf6d Binary files /dev/null and b/public/icons/avatar-default.png differ diff --git a/public/icons/copy.svg b/public/icons/copy.svg new file mode 100644 index 0000000..371f64c --- /dev/null +++ b/public/icons/copy.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/tsconfig.json b/tsconfig.json index 392b613..fc6704f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "baseUrl": ".", "paths": { "@components": ["lib/components"], + "@utils": ["lib/utils"], "@server/*": ["lib/server/*"], "@hooks": ["lib/hooks"] } diff --git a/yarn.lock b/yarn.lock index 4af9650..a4877fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -858,6 +858,13 @@ convert-source-map@1.7.0: dependencies: safe-buffer "~5.1.1" +copy-to-clipboard@^3: + version "3.3.1" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" + integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== + dependencies: + toggle-selection "^1.0.6" + core-util-is@~1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" @@ -1905,7 +1912,7 @@ lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.1.0: +loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -2008,6 +2015,11 @@ modern-normalize@^1.1.0: resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7" integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA== +moment@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -2507,6 +2519,15 @@ process@0.11.10, process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= +prop-types@^15.5.8: + version "15.7.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" + integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.8.1" + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -2614,6 +2635,14 @@ raw-body@2.4.1: iconv-lite "0.4.24" unpipe "1.0.0" +react-copy-to-clipboard@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.4.tgz#42ec519b03eb9413b118af92d1780c403a5f19bf" + integrity sha512-IeVAiNVKjSPeGax/Gmkqfa/+PuMTBhutEvFUaMQLwE2tS0EXrAdgOpWDX26bWTXF3HrioorR7lr08NqeYUWQCQ== + dependencies: + copy-to-clipboard "^3" + prop-types "^15.5.8" + react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -2635,6 +2664,11 @@ react-is@17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-is@^16.8.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + react-refresh@0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" @@ -3189,6 +3223,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toggle-selection@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" + integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= + toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"