Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -30,4 +36,4 @@ COPY --from=build /build/.next ./.next
# Start script
USER node
EXPOSE 3000
CMD ["yarn", "start:prod"]
CMD ["yarn", "start:prod"]
6 changes: 3 additions & 3 deletions lib/components/DeploymentTable.tsx
Original file line number Diff line number Diff line change
@@ -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<any>(null)
Expand All @@ -28,11 +28,11 @@ export function DeploymentTable({ id, setCount = (n) => {}, limit = 0 }) {
<b>{i.message || `Deployment #${deployments.length - k}`}</b>
{i.rollback && <img src='/icons/rollback.svg' className='w-4 opacity-40' />}
</span>
<p className='opacity-60'>Deploying from {i.type}</p>
<p className='opacity-60'>Deploying from {i.type} {i.branch ? `(${i.branch})` : ''}</p>
</div>
<div className='flex flex-col gap-2 items-end text-right w-32'>
<Status status={i.status} />
<p className='opacity-60'>{timeago.format(i.created)}</p>
<p className='opacity-60'>{dateFormat(i.created)}</p>
</div>
</div>
</a>
Expand Down
2 changes: 1 addition & 1 deletion lib/components/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'})`,
}}
/>
</Dropdown>
Expand Down
12 changes: 12 additions & 0 deletions lib/utils/date.tsx
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions lib/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './date'
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions pages/activity.tsx
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -28,15 +28,15 @@ export default function Activity() {
return (
<div className='flex gap-4 bg-white py-3.5 px-5 border rounded-lg items-center justify-between'>
<div className='flex gap-4 items-center max-w-lg truncate'>
<img src={i.accounts.avatar} className='w-10 h-10 rounded-full' />
<img src={i.accounts.avatar || '/icons/avatar-default.png'} className='w-10 h-10 rounded-full' />
<span>
<p>{i.action}</p>
<p className='opacity-40'>
{i.accounts.name} ({i.accounts.email}) — {i.ip}
</p>
</span>
</div>
<p className='opacity-60'>{timeago.format(i.created)}</p>
<p className='opacity-60'>{dateFormat(i.created)}</p>
</div>
)
})}
Expand Down
6 changes: 3 additions & 3 deletions pages/admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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} <span className='opacity-40'>{i.email}</span>{' '}
Expand All @@ -176,7 +176,7 @@ export default function Admin() {
<p className='opacity-40'>Has MFA Enabled</p>
<p>{i.mfa_enabled ? 'Yes' : 'No'}</p>
<p className='opacity-40'>Created</p>
<p>{timeago.format(i.created)}</p>
<p>{dateFormat(i.created)}</p>
</div>
<hr className='my-6' />
<div className='grid grid-cols-2 gap-3'>
Expand Down
92 changes: 92 additions & 0 deletions pages/api/git/webhook.ts
Original file line number Diff line number Diff line change
@@ -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()
}
}
11 changes: 7 additions & 4 deletions pages/api/github/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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}`])
Expand All @@ -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,
},
{
Expand Down
19 changes: 19 additions & 0 deletions pages/api/github/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions pages/api/projects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions pages/database/[id]/backups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -96,7 +96,7 @@ export default function Project() {
<p className='opacity-40'>Status</p>
<p>{database.backup !== null ? 'Enabled' : 'Disabled'}</p>
<p className='opacity-40'>Initialized</p>
<p>{timeago.format(database.backup && database.backup.initialized)}</p>
<p>{dateFormat(database.backup && database.backup.initialized)}</p>
<p className='opacity-40'>Schedule</p>
<p>{humanCron(database.backup.schedule)}</p>
<p className='opacity-40'>Encrypted</p>
Expand Down
6 changes: 3 additions & 3 deletions pages/database/[id]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<any>(null)
Expand Down Expand Up @@ -34,7 +34,7 @@ export default function Project() {
<img src={`/icons/${database.type}.svg`} className='w-10 h-10' />
<span>
<b>{database.name}</b>
<p className='opacity-40'>Created {timeago.format(database.created)}</p>
<p className='opacity-40'>Created {dateFormat(database.created)}</p>
</span>
</div>
<div className='mb-8'>
Expand All @@ -52,7 +52,7 @@ export default function Project() {
</div>
<div className='grid grid-cols-2'>
<p className='opacity-40'>Created</p>
<p>{timeago.format(database.created)}</p>
<p>{dateFormat(database.created)}</p>
</div>
<div className='grid grid-cols-2'>
<p className='opacity-40'>Type</p>
Expand Down
8 changes: 4 additions & 4 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down Expand Up @@ -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)}</>
)}
</p>
</span>
Expand Down Expand Up @@ -110,15 +110,15 @@ export default function Home() {
return (
<div className='flex gap-4 bg-white py-3.5 px-5 border rounded-lg items-center justify-between'>
<div className='flex gap-4 items-center max-w-lg truncate'>
<img src={i.accounts.avatar} className='w-10 h-10 rounded-full' />
<img src={i.accounts.avatar || '/icons/avatar-default.png'} className='w-10 h-10 rounded-full' />
<span>
<p>{i.action}</p>
<p className='opacity-40'>
{i.accounts.email}, {i.ip}
</p>
</span>
</div>
<p className='opacity-60'>{timeago.format(i.created)}</p>
<p className='opacity-60'>{dateFormat(i.created)}</p>
</div>
)
})
Expand Down
Loading