Features: 
- Build packs for popular frontend frameworks. It will help to understand which build packs should be chosen.

Fixes:
- Github queries optimized.
- Save repositories to store (faster navigation).
- Remove unnecessary data on dashboard requests.
- Speed up static site builds with a lot.

UI:
- Redesign of the application deployment page.
- Redesign of database deployments page.
This commit is contained in:
Andras Bacsai 2021-04-30 22:43:21 +02:00 committed by GitHub
parent b416e3ab3e
commit cccb9a5fec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 1309 additions and 797 deletions

123
README.md
View File

@ -1,93 +1,64 @@
# About
https://andrasbacsai.com/farewell-netlify-and-heroku-after-3-days-of-coding
# Coolify
# Features
- Deploy your Node.js, static sites, PHP or any custom application (with custom Dockerfile) just by pushing code to git.
- Hassle-free installation and upgrade process.
- One-click MongoDB, MySQL, PostgreSQL, CouchDB deployments!
An open-source, hassle-free, self-hostable Heroku & Netlify alternative.
# Upcoming features
- Backups & monitoring.
- User analytics with privacy in mind.
- And much more (see [Roadmap](https://github.com/coollabsio/coolify/projects/1)).
## Demo
[Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm)
# FAQ
Q: What is a buildpack?
A: It defines your application's final form.
`Static` means that it will be hosted as a static site.
`NodeJs` means that it will be started as a node application.
# Screenshots
[Login](https://coolify.io/login.jpg)
[Applications](https://coolify.io/applications.jpg)
[Databases](https://coolify.io/databases.jpg)
[Configuration](https://coolify.io/configuration.jpg)
[Settings](https://coolify.io/settings.jpg)
[Logs](https://coolify.io/logs.jpg)
# Getting Started
Automatically: `/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"`
Manually:
### Requirements before installation
- [Docker](https://docs.docker.com/engine/install/) version 20+
- Docker in [swarm mode enabled](https://docs.docker.com/engine/reference/commandline/swarm_init/) (should be set manually before installation)
- A [MongoDB](https://docs.mongodb.com/manual/installation/) instance.
- We have a [simple installation](https://github.com/coollabsio/infrastructure/tree/main/mongo) if you need one
- A configured DNS entry (see `.env.template`)
- [Github App](https://docs.github.com/en/developers/apps/creating-a-github-app)
- GitHub App name: could be anything weird
- Homepage URL: https://yourdomain
Identifying and authorizing users:
- Callback URL: https://yourdomain/api/v1/login/github/app
- Request user authorization (OAuth) during installation -> Check!
Webhook:
- Active -> Check!
- Webhook URL: https://yourdomain/api/v1/webhooks/deploy
- Webhook Secret: it should be super secret
Repository permissions:
- Contents: Read-only
- Metadata: Read-only
User permissions:
- Email: Read-only
## Installation
Subscribe to events:
- Push -> Check!
Installation is automated with the following command:
### Installation
- Clone this repository: `git clone git@github.com:coollabsio/coolify.git`
- Set `.env` (see `.env.template`)
- Installation: `bash install.sh all`
```bash
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
```
## Manual updating process (You probably never need to do this!)
### Update everything (proxy+coolify)
- `bash install.sh all`
## Features
You can deploy any of the following applications, databases and services easily.
### Update coolify only
- `bash install.sh coolify`
(constantly growing lists)
### Update proxy only
- `bash install.sh proxy`
### Applications
With Github integration
- Static sites
- NodeJS
- VueJS
- NuxtJS
- React/Preact
- NextJS
- Gatsby
- Svelte
- PHP
- Rust
- or any custom dockerfile
### Databases
- MongoDB
- MySQL
- PostgreSQL
- CouchDB
### Services
- [Plausible Analytics](https://plausible.io)
## Support
# Contact
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
- Email: [andras@coollabs.io](mailto:andras@coollabs.io)
- Discord: [Invitation](https://discord.com/invite/bvS3WhR)
## Roadmap
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
## License
# License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text.

View File

@ -0,0 +1,25 @@
const fs = require('fs').promises
const { buildImage } = require('../helpers')
const { streamEvents, docker } = require('../../libs/docker')
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
const publishStaticDocker = (configuration) => {
return [
'FROM nginx:stable-alpine',
'COPY nginx.conf /etc/nginx/nginx.conf',
'WORKDIR /usr/share/nginx/html',
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
'EXPOSE 80',
'CMD ["nginx", "-g", "daemon off;"]'
].join('\n')
}
module.exports = async function (configuration) {
await buildImage(configuration, true)
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
)
await streamEvents(stream, configuration)
}

View File

@ -10,11 +10,11 @@ const buildImageNodeDocker = (configuration) => {
`RUN ${configuration.build.command.build}`
].join('\n')
}
async function buildImage (configuration) {
async function buildImage (configuration, cacheBuild) {
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, buildImageNodeDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
{ t: `${configuration.build.container.name}:${cacheBuild ? `${configuration.build.container.tag}-cache` : configuration.build.container.tag}` }
)
await streamEvents(stream, configuration)
}

View File

@ -1,7 +1,13 @@
const static = require('./static')
const Static = require('./static')
const react = require('./react')
const nextjs = require('./nextjs')
const nuxtjs = require('./nuxtjs')
const gatsby = require('./gatsby')
const vuejs = require('./vuejs')
const svelte = require('./svelte')
const nodejs = require('./nodejs')
const php = require('./php')
const custom = require('./custom')
const docker = require('./docker')
const rust = require('./rust')
module.exports = { static, nodejs, php, custom, rust }
module.exports = { static: Static, nodejs, php, docker, rust, react, vuejs, nextjs, nuxtjs, svelte, gatsby }

View File

@ -0,0 +1,28 @@
const fs = require('fs').promises
const { buildImage } = require('../helpers')
const { streamEvents, docker } = require('../../libs/docker')
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
const publishNodejsDocker = (configuration) => {
return [
'FROM node:lts',
'WORKDIR /usr/src/app',
configuration.build.command.build
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
: `
COPY ${configuration.build.directory}/package*.json ./
RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]'
].join('\n')
}
module.exports = async function (configuration) {
await buildImage(configuration)
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishNodejsDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
)
await streamEvents(stream, configuration)
}

View File

@ -0,0 +1,28 @@
const fs = require('fs').promises
const { buildImage } = require('../helpers')
const { streamEvents, docker } = require('../../libs/docker')
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
const publishNodejsDocker = (configuration) => {
return [
'FROM node:lts',
'WORKDIR /usr/src/app',
configuration.build.command.build
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
: `
COPY ${configuration.build.directory}/package*.json ./
RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]'
].join('\n')
}
module.exports = async function (configuration) {
await buildImage(configuration)
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishNodejsDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
)
await streamEvents(stream, configuration)
}

View File

@ -0,0 +1,25 @@
const fs = require('fs').promises
const { buildImage } = require('../helpers')
const { streamEvents, docker } = require('../../libs/docker')
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
const publishStaticDocker = (configuration) => {
return [
'FROM nginx:stable-alpine',
'COPY nginx.conf /etc/nginx/nginx.conf',
'WORKDIR /usr/share/nginx/html',
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
'EXPOSE 80',
'CMD ["nginx", "-g", "daemon off;"]'
].join('\n')
}
module.exports = async function (configuration) {
await buildImage(configuration, true)
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
)
await streamEvents(stream, configuration)
}

View File

@ -9,7 +9,7 @@ const publishStaticDocker = (configuration) => {
'COPY nginx.conf /etc/nginx/nginx.conf',
'WORKDIR /usr/share/nginx/html',
configuration.build.command.build
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`
: `COPY ./${configuration.build.directory} ./`,
'EXPOSE 80',
'CMD ["nginx", "-g", "daemon off;"]'
@ -17,9 +17,8 @@ const publishStaticDocker = (configuration) => {
}
module.exports = async function (configuration) {
if (configuration.build.command.build) await buildImage(configuration)
if (configuration.build.command.build) await buildImage(configuration, true)
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }

View File

@ -0,0 +1,25 @@
const fs = require('fs').promises
const { buildImage } = require('../helpers')
const { streamEvents, docker } = require('../../libs/docker')
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
const publishStaticDocker = (configuration) => {
return [
'FROM nginx:stable-alpine',
'COPY nginx.conf /etc/nginx/nginx.conf',
'WORKDIR /usr/share/nginx/html',
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
'EXPOSE 80',
'CMD ["nginx", "-g", "daemon off;"]'
].join('\n')
}
module.exports = async function (configuration) {
await buildImage(configuration, true)
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
)
await streamEvents(stream, configuration)
}

View File

@ -0,0 +1,25 @@
const fs = require('fs').promises
const { buildImage } = require('../helpers')
const { streamEvents, docker } = require('../../libs/docker')
// 'HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost/ || exit 1',
const publishStaticDocker = (configuration) => {
return [
'FROM nginx:stable-alpine',
'COPY nginx.conf /etc/nginx/nginx.conf',
'WORKDIR /usr/share/nginx/html',
`COPY --from=${configuration.build.container.name}:${configuration.build.container.tag}-cache /usr/src/app/${configuration.publish.directory} ./`,
'EXPOSE 80',
'CMD ["nginx", "-g", "daemon off;"]'
].join('\n')
}
module.exports = async function (configuration) {
await buildImage(configuration, true)
await fs.writeFile(`${configuration.general.workdir}/Dockerfile`, publishStaticDocker(configuration))
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
)
await streamEvents(stream, configuration)
}

View File

@ -2,11 +2,16 @@ const { docker } = require('../../docker')
const { execShellAsync } = require('../../common')
const Deployment = require('../../../models/Deployment')
async function purgeImagesContainers (configuration) {
async function purgeImagesContainers (configuration, deleteAll = false) {
const { name, tag } = configuration.build.container
await execShellAsync('docker container prune -f')
const IDsToDelete = (await execShellAsync(`docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'`)).trim().replace(/"/g, '').split('\n')
if (IDsToDelete.length !== 0) for (const id of IDsToDelete) await execShellAsync(`docker rmi -f ${id}`)
if (deleteAll) {
const IDsToDelete = (await execShellAsync(`docker images ls --filter=reference='${name}' --format '{{json .ID }}'`)).trim().replace(/"/g, '').split('\n')
if (IDsToDelete.length > 0) await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`)
} else {
const IDsToDelete = (await execShellAsync(`docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'`)).trim().replace(/"/g, '').split('\n')
if (IDsToDelete.length > 1) await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`)
}
await execShellAsync('docker image prune -f')
}

View File

@ -23,14 +23,10 @@ function setDefaultConfiguration (configuration) {
if (!configuration.publish.path) configuration.publish.path = '/'
if (!configuration.publish.port) {
if (configuration.build.pack === 'php') {
configuration.publish.port = 80
} else if (configuration.build.pack === 'static') {
configuration.publish.port = 80
} else if (configuration.build.pack === 'nodejs') {
configuration.publish.port = 3000
} else if (configuration.build.pack === 'rust') {
if (configuration.build.pack === 'nodejs' && configuration.build.pack === 'vuejs' && configuration.build.pack === 'nuxtjs' && configuration.build.pack === 'rust' && configuration.build.pack === 'nextjs') {
configuration.publish.port = 3000
} else {
configuration.publish.port = 80
}
}

View File

@ -1,5 +1,6 @@
const fs = require('fs').promises
module.exports = async function (configuration) {
const staticDeployments = ['react', 'vuejs', 'static', 'svelte', 'gatsby']
try {
// TODO: Write full .dockerignore for all deployments!!
if (configuration.build.pack === 'php') {
@ -12,7 +13,7 @@ module.exports = async function (configuration) {
`)
}
// await fs.writeFile(`${configuration.general.workdir}/.dockerignore`, 'node_modules')
if (configuration.build.pack === 'static') {
if (staticDeployments.includes(configuration.build.pack)) {
await fs.writeFile(
`${configuration.general.workdir}/nginx.conf`,
`user nginx;

View File

@ -1,38 +1,44 @@
const jwt = require('jsonwebtoken')
const axios = require('axios')
const { execShellAsync, cleanupTmp } = require('../../common')
const { execShellAsync } = require('../../common')
module.exports = async function (configuration) {
const { workdir } = configuration.general
const { organization, name, branch } = configuration.repository
const github = configuration.github
const githubPrivateKey = process.env.GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n').replace(/"/g, '')
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60),
iss: parseInt(github.app.id)
}
const jwtToken = jwt.sign(payload, githubPrivateKey, {
algorithm: 'RS256'
})
const accessToken = await axios({
method: 'POST',
url: `https://api.github.com/app/installations/${github.installation.id}/access_tokens`,
data: {},
headers: {
Authorization: 'Bearer ' + jwtToken,
Accept: 'application/vnd.github.machine-man-preview+json'
try {
const { workdir } = configuration.general
const { organization, name, branch } = configuration.repository
const github = configuration.github
if (!github.installation.id || !github.app.id) {
throw new Error('Github installation ID is invalid.')
}
})
await execShellAsync(
`mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${accessToken.data.token}@github.com/${organization}/${name}.git ${workdir}/`
)
configuration.build.container.tag = (
await execShellAsync(`cd ${configuration.general.workdir}/ && git rev-parse HEAD`)
)
.replace('\n', '')
.slice(0, 7)
const githubPrivateKey = process.env.GITHUB_APP_PRIVATE_KEY.replace(/\\n/g, '\n').replace(/"/g, '')
const payload = {
iat: Math.round(new Date().getTime() / 1000),
exp: Math.round(new Date().getTime() / 1000 + 60),
iss: parseInt(github.app.id)
}
const jwtToken = jwt.sign(payload, githubPrivateKey, {
algorithm: 'RS256'
})
const accessToken = await axios({
method: 'POST',
url: `https://api.github.com/app/installations/${github.installation.id}/access_tokens`,
data: {},
headers: {
Authorization: 'Bearer ' + jwtToken,
Accept: 'application/vnd.github.machine-man-preview+json'
}
})
await execShellAsync(
`mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${accessToken.data.token}@github.com/${organization}/${name}.git ${workdir}/`
)
configuration.build.container.tag = (
await execShellAsync(`cd ${configuration.general.workdir}/ && git rev-parse HEAD`)
)
.replace('\n', '')
.slice(0, 7)
} catch (error) {
throw new Error(error)
}
}

View File

@ -1,7 +1,8 @@
const { docker } = require('../../../libs/docker')
const { execShellAsync } = require('../../../libs/common')
const { execShellAsync, delay } = require('../../../libs/common')
const ApplicationLog = require('../../../models/Logs/Application')
const Deployment = require('../../../models/Deployment')
const { purgeImagesContainers } = require('../../../libs/applications/cleanup')
module.exports = async function (fastify) {
fastify.post('/', async (request, reply) => {
@ -25,6 +26,8 @@ module.exports = async function (fastify) {
}
await execShellAsync(`docker stack rm ${found.build.container.name}`)
reply.code(200).send({ organization, name, branch })
await delay(10000)
await purgeImagesContainers(found, true)
} else {
reply.code(500).send({ message: 'Nothing to do.' })
}

View File

@ -1,27 +1,10 @@
const { docker } = require('../../../libs/docker')
const Deployment = require('../../../models/Deployment')
const ServerLog = require('../../../models/Logs/Server')
const { saveServerLog } = require('../../../libs/logging')
module.exports = async function (fastify) {
fastify.get('/', async (request, reply) => {
try {
const latestDeployments = await Deployment.aggregate([
{
$sort: { createdAt: -1 }
},
{
$group:
{
_id: {
repoId: '$repoId',
branch: '$branch'
},
createdAt: { $last: '$createdAt' },
progress: { $first: '$progress' }
}
}
])
const serverLogs = await ServerLog.find()
const dockerServices = await docker.engine.listServices()
let applications = dockerServices.filter(r => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' && r.Spec.Labels.configuration)
@ -29,25 +12,31 @@ module.exports = async function (fastify) {
let services = dockerServices.filter(r => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'service' && r.Spec.Labels.configuration)
applications = applications.map(r => {
if (JSON.parse(r.Spec.Labels.configuration)) {
const configuration = JSON.parse(r.Spec.Labels.configuration)
const status = latestDeployments.find(l => configuration.repository.id === l._id.repoId && configuration.repository.branch === l._id.branch)
if (status && status.progress) r.progress = status.progress
r.Spec.Labels.configuration = configuration
return r
return {
configuration: JSON.parse(r.Spec.Labels.configuration),
UpdatedAt: r.UpdatedAt
}
}
return {}
})
databases = databases.map(r => {
const configuration = r.Spec.Labels.configuration ? JSON.parse(r.Spec.Labels.configuration) : null
r.Spec.Labels.configuration = configuration
return r
if (JSON.parse(r.Spec.Labels.configuration)) {
return {
configuration: JSON.parse(r.Spec.Labels.configuration)
}
}
return {}
})
services = services.map(r => {
const configuration = r.Spec.Labels.configuration ? JSON.parse(r.Spec.Labels.configuration) : null
r.Spec.Labels.configuration = configuration
return r
if (JSON.parse(r.Spec.Labels.configuration)) {
return {
serviceName: r.Spec.Labels.serviceName,
configuration: JSON.parse(r.Spec.Labels.configuration)
}
}
return {}
})
applications = [...new Map(applications.map(item => [item.Spec.Labels.configuration.publish.domain + item.Spec.Labels.configuration.publish.path, item])).values()]
applications = [...new Map(applications.map(item => [item.configuration.publish.domain + item.configuration.publish.path, item])).values()]
return {
serverLogs,
applications: {

View File

@ -66,7 +66,6 @@ module.exports = async function (fastify) {
if (!defaultDatabaseName) defaultDatabaseName = nickname
reply.code(201).send({ message: 'Deploying.' })
// TODO: Persistent volume, custom inputs
const deployId = cuid()
const configuration = {
general: {

View File

@ -12,7 +12,6 @@ const cloneRepository = require('../../../libs/applications/github/cloneReposito
const { purgeImagesContainers } = require('../../../libs/applications/cleanup')
module.exports = async function (fastify) {
// TODO: Add this to fastify plugin
const postSchema = {
body: {
type: 'object',

View File

@ -1,12 +1,11 @@
require('dotenv').config()
const fs = require('fs')
const util = require('util')
const axios = require('axios')
const mongoose = require('mongoose')
const path = require('path')
const { saveServerLog } = require('./libs/logging')
const { execShellAsync } = require('./libs/common')
const { purgeImagesContainers, cleanupStuckedDeploymentsInDB } = require('./libs/applications/cleanup')
const { cleanupStuckedDeploymentsInDB } = require('./libs/applications/cleanup')
const fastify = require('fastify')({
trustProxy: true,
logger: {

View File

@ -6,7 +6,7 @@
<link rel="icon" href="/favicon.png" />
<link rel="preload" as="image" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>coolify: Heroku & Netlify alternative</title>
<title>Coolify</title>
<link rel="dns-prefetch" href="https://cdn.coollabs.io/" />
<link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" />
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/montserrat/montserrat.css" />

View File

@ -1,7 +1,7 @@
{
"name": "coolify",
"description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.",
"version": "1.0.10",
"version": "1.0.11",
"license": "AGPL-3.0",
"scripts": {
"lint": "standard",

View File

@ -1,24 +0,0 @@
<script>
import { application } from "@store";
import Tooltip from "../../../Tooltip/TooltipInfo.svelte";
</script>
<div class="grid grid-cols-1 max-w-2xl md:mx-auto mx-6 text-center">
<label for="installCommand"
>Install Command <Tooltip label="Command to run for installing dependencies. eg: yarn install." />
</label>
<input
class="mb-6"
id="installCommand"
bind:value="{$application.build.command.installation}"
placeholder="eg: yarn install"
/>
<label for="buildCommand">Build Command <Tooltip label="Command to run for building your application. If empty, no build phase initiated in the deploy process." /></label>
<input
class="mb-6"
id="buildCommand"
bind:value="{$application.build.command.build}"
placeholder="eg: yarn build"
/>
</div>

View File

@ -1,56 +1,213 @@
<style lang="postcss">
.buildpack {
@apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out transform hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100;
}
</style>
<script>
import { application} from "@store";
import { application } from "@store";
import { onMount } from "svelte";
import TooltipInfo from "../../../Tooltip/TooltipInfo.svelte";
const showPorts = ['nodejs','custom','rust']
let domainInput;
const buildpacks = {
static: {
port: {
active: false,
number: 80,
},
build: true,
},
nodejs: {
port: {
active: true,
number: 3000,
},
build: true,
},
vuejs: {
port: {
active: false,
number: 80,
},
build: true,
},
nuxtjs: {
port: {
active: true,
number: 3000,
},
build: true,
},
react: {
port: {
active: false,
number: 80,
},
build: true,
},
nextjs: {
port: {
active: true,
number: 3000,
},
build: true,
},
gatsby: {
port: {
active: true,
number: 3000,
},
build: true,
},
svelte: {
port: {
active: false,
number: 80,
},
build: true,
},
php: {
port: {
active: false,
number: 80,
},
build: false,
},
rust: {
port: {
active: true,
number: 3000,
},
build: false,
},
docker: {
port: {
active: true,
number: 3000,
},
build: false,
},
};
function selectBuildPack(event) {
if (event.target.innerText === "React/Preact") {
$application.build.pack = "react";
} else {
$application.build.pack = event.target.innerText
.replace(/\./g, "")
.toLowerCase();
}
}
onMount(()=> {
domainInput.focus();
})
</script>
<div>
<div
class="grid grid-cols-1 text-sm max-w-2xl md:mx-auto mx-6 pb-6 auto-cols-max "
class="grid grid-cols-1 text-sm max-w-4xl md:mx-auto mx-6 pb-16 auto-cols-max "
>
<label for="buildPack"
>Build Pack
{#if $application.build.pack === 'custom'}
<TooltipInfo
label="Your custom Dockerfile will be used from the root directory (or from 'Base Directory' specified below) of your repository. "
/>
{:else if $application.build.pack === 'static'}
<TooltipInfo
label="Published as a static site (for build phase see 'Build Step' tab)."
/>
{:else if $application.build.pack === 'nodejs'}
<TooltipInfo
label="Published as a Node.js application (for build phase see 'Build Step' tab)."
/>
{:else if $application.build.pack === 'php'}
<TooltipInfo
size="large"
label="Published as a PHP application."
/>
{:else if $application.build.pack === 'rust'}
<TooltipInfo
size="large"
label="Published as a Rust application."
/>
{/if}
</label
>
<select id="buildPack" bind:value="{$application.build.pack}">
<option selected class="font-bold">static</option>
<option class="font-bold">nodejs</option>
<option class="font-bold">php</option>
<option class="font-bold">custom</option>
<option class="font-bold">rust</option>
</select>
<div class="text-2xl font-bold border-gradient w-40">Build Packs</div>
<div class="flex font-bold flex-wrap justify-center pt-10">
<div
class="{$application.build.pack === 'static'
? 'buildpack bg-red-500'
: 'buildpack hover:border-red-500'}"
on:click="{selectBuildPack}"
>
Static
</div>
<div
class="{$application.build.pack === 'nodejs'
? 'buildpack bg-emerald-600'
: 'buildpack hover:border-emerald-600'}"
on:click="{selectBuildPack}"
>
NodeJS
</div>
<div
class="{$application.build.pack === 'vuejs'
? 'buildpack bg-green-500'
: 'buildpack hover:border-green-500'}"
on:click="{selectBuildPack}"
>
VueJS
</div>
<div
class="{$application.build.pack === 'nuxtjs'
? 'buildpack bg-green-500'
: 'buildpack hover:border-green-500'}"
on:click="{selectBuildPack}"
>
NuxtJS
</div>
<div
class="{$application.build.pack === 'react'
? 'buildpack bg-gradient-to-r from-blue-500 to-purple-500'
: 'buildpack hover:border-blue-500'}"
on:click="{selectBuildPack}"
>
React/Preact
</div>
<div
class="{$application.build.pack === 'nextjs'
? 'buildpack bg-blue-500'
: 'buildpack hover:border-blue-500'}"
on:click="{selectBuildPack}"
>
NextJS
</div>
<div
class="{$application.build.pack === 'gatsby'
? 'buildpack bg-blue-500'
: 'buildpack hover:border-blue-500'}"
on:click="{selectBuildPack}"
>
Gatsby
</div>
<div
class="{$application.build.pack === 'svelte'
? 'buildpack bg-orange-600'
: 'buildpack hover:border-orange-600'}"
on:click="{selectBuildPack}"
>
Svelte
</div>
<div
class="{$application.build.pack === 'php'
? 'buildpack bg-indigo-500'
: 'buildpack hover:border-indigo-500'}"
on:click="{selectBuildPack}"
>
PHP
</div>
<div
class="{$application.build.pack === 'rust'
? 'buildpack bg-pink-500'
: 'buildpack hover:border-pink-500'}"
on:click="{selectBuildPack}"
>
Rust
</div>
<div
class="{$application.build.pack === 'docker'
? 'buildpack bg-purple-500'
: 'buildpack hover:border-purple-500'}"
on:click="{selectBuildPack}"
>
Docker
</div>
</div>
</div>
<div class="text-2xl font-bold border-gradient w-52">General settings</div>
<div
class="grid grid-cols-1 max-w-2xl md:mx-auto mx-6 justify-center items-center"
class="grid grid-cols-1 max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10"
>
<div class="grid grid-flow-col gap-2 items-center pb-6">
<div class="grid grid-flow-row">
<label for="Domain" class="">Domain</label>
<input
bind:this={domainInput}
class="border-2"
class:placeholder-red-500="{$application.publish.domain == null ||
$application.publish.domain == ''}"
class:border-red-500="{$application.publish.domain == null ||
@ -63,7 +220,9 @@
<div class="grid grid-flow-row">
<label for="Path"
>Path <TooltipInfo
label="{`Path to deploy your application on your domain. eg: /api means it will be deployed to -> https://${$application.publish.domain || '<yourdomain>'}/api`}"
label="{`Path to deploy your application on your domain. eg: /api means it will be deployed to -> https://${
$application.publish.domain || '<yourdomain>'
}/api`}"
/></label
>
<input
@ -73,16 +232,27 @@
/>
</div>
</div>
{#if showPorts.includes($application.build.pack)}
<label for="Port" >Port</label>
<label
for="Port"
class:text-warmGray-800="{!buildpacks[$application.build.pack].port
.active}">Port</label
>
<input
disabled="{!buildpacks[$application.build.pack].port.active}"
id="Port"
class="mb-6"
class:bg-warmGray-900="{!buildpacks[$application.build.pack].port.active}"
class:text-warmGray-900="{!buildpacks[$application.build.pack].port
.active}"
class:placeholder-warmGray-800="{!buildpacks[$application.build.pack].port
.active}"
class:hover:bg-warmGray-900="{!buildpacks[$application.build.pack].port
.active}"
class:cursor-not-allowed="{!buildpacks[$application.build.pack].port
.active}"
bind:value="{$application.publish.port}"
placeholder="{$application.build.pack === 'static' ? '80' : '3000'}"
placeholder="{buildpacks[$application.build.pack].port.number}"
/>
{/if}
<div class="grid grid-flow-col gap-2 items-center pt-12">
<div class="grid grid-flow-col gap-2 items-center pt-6 pb-12">
<div class="grid grid-flow-row">
<label for="baseDir"
>Base Directory <TooltipInfo
@ -109,4 +279,62 @@
</div>
</div>
</div>
<div
class="text-2xl font-bold w-40"
class:border-gradient="{buildpacks[$application.build.pack].build}"
class:text-warmGray-800="{!buildpacks[$application.build.pack].build}"
>
Commands
</div>
<div
class=" max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10 pb-32"
>
<div class="grid grid-flow-col gap-2 items-center">
<div class="grid grid-flow-row">
<label
for="installCommand"
class:text-warmGray-800="{!buildpacks[$application.build.pack].build}"
>Install Command <TooltipInfo
label="Command to run for installing dependencies. eg: yarn install."
/>
</label>
<input
class="mb-6"
class:bg-warmGray-900="{!buildpacks[$application.build.pack].build}"
class:text-warmGray-900="{!buildpacks[$application.build.pack].build}"
class:placeholder-warmGray-800="{!buildpacks[$application.build.pack]
.build}"
class:hover:bg-warmGray-900="{!buildpacks[$application.build.pack]
.build}"
class:cursor-not-allowed="{!buildpacks[$application.build.pack]
.build}"
id="installCommand"
bind:value="{$application.build.command.installation}"
placeholder="eg: yarn install"
/>
<label
for="buildCommand"
class:text-warmGray-800="{!buildpacks[$application.build.pack].build}"
>Build Command <TooltipInfo
label="Command to run for building your application. If empty, no build phase initiated in the deploy process."
/></label
>
<input
class="mb-6"
class:bg-warmGray-900="{!buildpacks[$application.build.pack].build}"
class:text-warmGray-900="{!buildpacks[$application.build.pack].build}"
class:placeholder-warmGray-800="{!buildpacks[$application.build.pack]
.build}"
class:hover:bg-warmGray-900="{!buildpacks[$application.build.pack]
.build}"
class:cursor-not-allowed="{!buildpacks[$application.build.pack]
.build}"
id="buildCommand"
bind:value="{$application.build.command.build}"
placeholder="eg: yarn build"
/>
</div>
</div>
</div>
</div>

View File

@ -36,40 +36,43 @@
];
}
</script>
<div class="max-w-2xl md:mx-auto mx-6 text-center">
<div class="text-2xl font-bold border-gradient w-24">Secrets</div>
<div class="max-w-xl mx-auto text-center pt-4">
<div class="text-left text-base font-bold tracking-tight text-warmGray-400">
New Secret
</div>
<div class="grid md:grid-flow-col grid-flow-row gap-2">
<input id="secretName" bind:value="{secret.name}" placeholder="Name" />
<input id="secretValue" bind:value="{secret.value}" placeholder="Value" />
<button
class="button p-1 w-20 bg-green-600 hover:bg-green-500 text-white"
on:click="{saveSecret}">Save</button
>
<div class="flex space-x-4">
<input id="secretName" bind:value="{secret.name}" placeholder="Name" class="w-64 border-2 border-transparent" />
<input id="secretValue" bind:value="{secret.value}" placeholder="Value" class="w-64 border-2 border-transparent" />
<button class="icon hover:text-green-500" on:click="{saveSecret}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
{#if $application.publish.secrets.length > 0}
<div class="py-4">
{#each $application.publish.secrets as s}
<div class="grid md:grid-flow-col grid-flow-row gap-2">
<div class="flex space-x-4">
<input
id="{s.name}"
value="{s.name}"
disabled
class="border-2 bg-transparent border-transparent"
class="border-2 bg-transparent border-transparent w-64"
class:border-red-600="{foundSecret && foundSecret.name === s.name}"
/>
<input
id="{s.createdAt}"
value="SAVED"
disabled
class="bg-transparent border-transparent"
class="border-2 bg-transparent border-transparent w-64"
/>
<button
class="button w-20 bg-red-600 hover:bg-red-500 text-white"
on:click="{() => removeSecret(s.name)}">Delete</button
>
<button class="icon hover:text-red-500" on:click="{() => removeSecret(s.name)}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
{/each}
</div>

View File

@ -1,11 +1,10 @@
<script>
export let loading, branches;
import { isActive } from "@roxi/routify";
import { application } from "@store";
import { application, activePage } from "@store";
import Select from "svelte-select";
const selectedValue =
!$isActive("/application/new") && $application.repository.branch
$activePage.application !== "new" && $application.repository.branch;
function handleSelect(event) {
$application.repository.branch = null;
@ -36,10 +35,10 @@
selectedValue="{selectedValue}"
isClearable="{false}"
items="{branches.map(b => ({ label: b.name, value: b.name }))}"
showIndicator="{$isActive('/application/new')}"
showIndicator="{$activePage.new}"
noOptionsMessage="No branches found"
placeholder="Select a branch"
isDisabled="{!$isActive('/application/new')}"
isDisabled="{!$activePage.new}"
/>
</div>
</div>

View File

@ -1,7 +1,15 @@
<script>
import { redirect, isActive } from "@roxi/routify";
import { fade } from "svelte/transition";
import { session, application, fetch, initialApplication } from "@store";
import {
session,
application,
fetch,
initialApplication,
githubRepositories,
githubInstallations,
activePage,
} from "@store";
import Login from "./Login.svelte";
import Loading from "../../Loading.svelte";
@ -15,8 +23,6 @@
};
let branches = [];
let repositories = [];
function dashify(str, options) {
if (typeof str !== "string") return str;
return str
@ -29,8 +35,8 @@
async function loadBranches() {
loading.branches = true;
if ($isActive("/application/new")) $application.repository.branch = null;
const selectedRepository = repositories.find(
if ($activePage.new) $application.repository.branch = null;
const selectedRepository = $githubRepositories.find(
r => r.id === $application.repository.id,
);
@ -54,6 +60,23 @@
}
async function loadGithub() {
if ($githubRepositories.length > 0) {
$application.github.installation.id = $githubInstallations.id;
$application.github.app.id = $githubInstallations.app_id;
const foundRepositoryOnGithub = $githubRepositories.find(
r =>
r.full_name ===
`${$application.repository.organization}/${$application.repository.name}`,
);
if (foundRepositoryOnGithub) {
$application.repository.id = foundRepositoryOnGithub.id;
$application.repository.organization = foundRepositoryOnGithub.owner.login;
$application.repository.name = foundRepositoryOnGithub.name;
// await loadBranches();
}
return;
}
loading.github = true;
try {
const { installations } = await $fetch(
@ -64,6 +87,7 @@
}
$application.github.installation.id = installations[0].id;
$application.github.app.id = installations[0].app_id;
$githubInstallations = installations[0];
let page = 1;
let userRepos = 0;
@ -72,21 +96,20 @@
page,
);
repositories = repositories.concat(data.repositories);
$githubRepositories = $githubRepositories.concat(data.repositories);
userRepos = data.total_count;
if (userRepos > repositories.length) {
while (userRepos > repositories.length) {
if (userRepos > $githubRepositories.length) {
while (userRepos > $githubRepositories.length) {
page = page + 1;
const repos = await getGithubRepos(
$application.github.installation.id,
page,
);
repositories = repositories.concat(repos.repositories);
$githubRepositories = $githubRepositories.concat(repos.repositories);
}
}
const foundRepositoryOnGithub = repositories.find(
const foundRepositoryOnGithub = $githubRepositories.find(
r =>
r.full_name ===
`${$application.repository.organization}/${$application.repository.name}`,
@ -120,7 +143,7 @@
if (newWindow.closed) {
clearInterval(timer);
loading.github = true;
if (!$isActive("/application/new")) {
if (!$activePage.new) {
try {
const config = await $fetch(`/api/v1/config`, {
body: {
@ -137,28 +160,46 @@
$application = JSON.parse(JSON.stringify(initialApplication));
}
branches = [];
repositories = [];
$githubRepositories = [];
await loadGithub();
}
}, 100);
}
</script>
{#if !$isActive("/application/new")}
{#if !$activePage.new}
<div class="min-h-full text-white">
<div
class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center"
>
{$application.publish.domain
? `${$application.publish.domain}${
$application.publish.path !== "/" ? $application.publish.path : ""
}`
: "example.com"}
<a
target="_blank"
class="text-green-500 hover:underline cursor-pointer px-2"
class="icon mx-2"
href="{'https://' +
$application.publish.domain +
$application.publish.path}"
>{$application.publish.domain
? `${$application.publish.domain}${$application.publish.path !== '/' ? $application.publish.path : ''}`
: "<yourdomain>"}</a
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
></path>
</svg></a
>
<a
target="_blank"
class="icon"
@ -180,7 +221,7 @@
>
</div>
</div>
{:else if $isActive("/application/new")}
{:else if $activePage.new}
<div class="min-h-full text-white">
<div
class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center"
@ -205,11 +246,10 @@
in:fade="{{ duration: 100 }}"
>
<Repositories
bind:repositories
on:loadBranches="{loadBranches}"
on:modifyGithubAppConfig="{modifyGithubAppConfig}"
/>
{#if $application.repository.organization !== "new"}
{#if $application.repository.organization}
<Branches loading="{loading.branches}" branches="{branches}" />
{/if}

View File

@ -1,43 +1,42 @@
<script>
import { createEventDispatcher } from "svelte";
import { isActive } from "@roxi/routify";
import { application } from "@store";
import { application, githubRepositories, activePage } from "@store";
import Select from "svelte-select";
function handleSelect(event) {
$application.build.pack = 'static'
$application.repository.id = parseInt(event.detail.value, 10);
dispatch("loadBranches");
}
export let repositories;
let items = repositories.map(repo => ({
let items = $githubRepositories.map(repo => ({
label: `${repo.owner.login}/${repo.name}`,
value: repo.id.toString(),
}));
const selectedValue =
!$isActive("/application/new") &&
!$activePage.new &&
`${$application.repository.organization}/${$application.repository.name}`;
const dispatch = createEventDispatcher();
const modifyGithubAppConfig = () => dispatch("modifyGithubAppConfig");
</script>
<div class="grid grid-cols-1">
{#if repositories.length !== 0}
<div class="grid grid-cols-1 pt-4">
{#if $githubRepositories.length !== 0}
<label for="repository">Organization / Repository</label>
<div class="grid grid-cols-3 ">
<div class="repository-select-search col-span-2">
<Select
isFocused="true"
containerClasses="w-full border-none bg-transparent"
on:select="{handleSelect}"
selectedValue="{selectedValue}"
isClearable="{false}"
items="{items}"
showIndicator="{$isActive('/application/new')}"
showIndicator="{$activePage.new}"
noOptionsMessage="No Repositories found"
placeholder="Select a Repository"
isDisabled="{!$isActive('/application/new')}"
isDisabled="{!$activePage.new}"
/>
</div>
<button

View File

@ -1,17 +1,57 @@
<script>
import { redirect, isActive } from "@roxi/routify";
import { redirect } from "@roxi/routify";
import { onMount } from "svelte";
import { toast } from "@zerodevx/svelte-toast";
import templates from "../../../utils/templates";
import { application, fetch, deployments } from "@store";
import { application, fetch, deployments, activePage } from "@store";
import General from "./ActiveTab/General.svelte";
import BuildStep from "./ActiveTab/BuildStep.svelte";
import Secrets from "./ActiveTab/Secrets.svelte";
import Loading from "../../Loading.svelte";
const buildPhaseActive = ["nodejs", "static"];
let loading = false;
onMount(async () => {
if (!$isActive("/application/new")) {
let activeTab = {
general: true,
buildStep: false,
secrets: false,
};
function activateTab(tab) {
if (activeTab.hasOwnProperty(tab)) {
activeTab = {
general: false,
buildStep: false,
secrets: false,
};
activeTab[tab] = true;
}
}
async function load() {
const found = $deployments?.applications?.deployed.find(deployment => {
if (
deployment.configuration.repository.organization ===
$application.repository.organization &&
deployment.configuration.repository.name ===
$application.repository.name &&
deployment.configuration.repository.branch ===
$application.repository.branch
) {
return deployment;
}
});
if (found) {
$application = { ...found.configuration };
if ($activePage.new) {
$activePage.new = false;
toast.push(
"This repository & branch is already defined. Redirecting...",
);
$redirect(`/application/:organization/:name/:branch/configuration`, {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
});
}
return;
}
if (!$activePage.new) {
const config = await $fetch(`/api/v1/config`, {
body: {
name: $application.repository.name,
@ -20,29 +60,7 @@
},
});
$application = { ...config };
$redirect(`/application/:organization/:name/:branch/configuration`, {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
});
} else {
loading = true;
$deployments?.applications?.deployed.find(d => {
const conf = d?.Spec?.Labels.configuration;
if (
conf?.repository?.organization ===
$application.repository.organization &&
conf?.repository?.name === $application.repository.name &&
conf?.repository?.branch === $application.repository.branch
) {
$redirect(`/application/:organization/:name/:branch/configuration`, {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
});
toast.push("This repository & branch is already defined. Redirecting...");
}
});
try {
const dir = await $fetch(
`https://api.github.com/repos/${$application.repository.organization}/${$application.repository.name}/contents/?ref=${$application.repository.branch}`,
@ -70,9 +88,11 @@
if (checkPackageJSONContents(dep)) {
const config = templates[dep];
$application.build.pack = config.pack;
if (config.installation) $application.build.command.installation = config.installation;
if (config.installation)
$application.build.command.installation = config.installation;
if (config.port) $application.publish.port = config.port;
if (config.directory) $application.publish.directory = config.directory;
if (config.directory)
$application.publish.directory = config.directory;
if (
packageJsonContent.scripts.hasOwnProperty("build") &&
@ -80,43 +100,27 @@
) {
$application.build.command.build = config.build;
}
toast.push(`${config.name} App detected. Default values set.`);
toast.push(`${config.name} detected. Default values set.`);
}
});
} else if (CargoToml) {
$application.build.pack = "rust";
toast.push(`Rust language detected. Default values set.`);
} else if (Dockerfile) {
$application.build.pack = "custom";
toast.push("Custom Dockerfile found. Build pack set to custom.");
$application.build.pack = "docker";
toast.push("Custom Dockerfile found. Build pack set to docker.");
}
} catch (error) {
// Nothing detected
}
}
loading = false;
});
let activeTab = {
general: true,
buildStep: false,
secrets: false,
};
function activateTab(tab) {
if (activeTab.hasOwnProperty(tab)) {
activeTab = {
general: false,
buildStep: false,
secrets: false,
};
activeTab[tab] = true;
}
}
</script>
{#if loading}
{#await load()}
<Loading github githubLoadingText="Scanning repository..." />
{:else}
<div class="block text-center py-4">
{:then}
<div class="block text-center py-8">
<nav
class="flex space-x-4 justify-center font-bold text-md text-white"
aria-label="Tabs"
@ -124,47 +128,26 @@
<div
on:click="{() => activateTab('general')}"
class:text-green-500="{activeTab.general}"
class="px-3 py-2 cursor-pointer hover:text-green-500"
class="px-3 py-2 cursor-pointer hover:bg-warmGray-700 rounded-lg transition duration-100"
>
General
</div>
{#if !buildPhaseActive.includes($application.build.pack)}
<div disabled class="px-3 py-2 text-warmGray-700 cursor-not-allowed">
Build Step
</div>
{:else}
<div
on:click="{() => activateTab('buildStep')}"
class:text-green-500="{activeTab.buildStep}"
class="px-3 py-2 cursor-pointer hover:text-green-500"
>
Build Step
</div>
{/if}
{#if $application.build.pack === "custom"}
<div disabled class="px-3 py-2 text-warmGray-700 cursor-not-allowed">
Secrets
</div>
{:else}
<div
on:click="{() => activateTab('secrets')}"
class:text-green-500="{activeTab.secrets}"
class="px-3 py-2 cursor-pointer hover:text-green-500"
>
Secrets
</div>
{/if}
<div
on:click="{() => activateTab('secrets')}"
class:text-green-500="{activeTab.secrets}"
class="px-3 py-2 cursor-pointer hover:bg-warmGray-700 rounded-lg transition duration-100"
>
Secrets
</div>
</nav>
</div>
<div class="max-w-4xl mx-auto">
<div class="h-full">
{#if activeTab.general}
<General />
{:else if activeTab.buildStep}
<BuildStep />
{:else if activeTab.secrets}
<Secrets />
{/if}
</div>
</div>
{/if}
{/await}

View File

@ -0,0 +1,195 @@
<script>
import { params, goto, redirect } from "@roxi/routify";
import {
application,
fetch,
initialApplication,
initConf,
activePage,
} from "@store";
import { onDestroy } from "svelte";
import { toast } from "@zerodevx/svelte-toast";
import Tooltip from "../../components/Tooltip/Tooltip.svelte";
$application.repository.organization = $params.organization;
$application.repository.name = $params.name;
$application.repository.branch = $params.branch;
async function removeApplication() {
await $fetch(`/api/v1/application/remove`, {
body: {
organization: $params.organization,
name: $params.name,
branch: $params.branch,
},
});
toast.push("Application removed.");
$application = JSON.parse(JSON.stringify(initialApplication));
$redirect(`/dashboard/applications`);
}
onDestroy(() => {
$application = JSON.parse(JSON.stringify(initialApplication));
});
async function deploy() {
try {
toast.push("Checking configuration.");
await $fetch(`/api/v1/application/check`, {
body: $application,
});
const { nickname, name, deployId } = await $fetch(
`/api/v1/application/deploy`,
{
body: $application,
},
);
$application.general.nickname = nickname;
$application.build.container.name = name;
$application.general.deployId = deployId;
$initConf = JSON.parse(JSON.stringify($application));
toast.push("Application deployment queued.");
$redirect(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs/${$application.general.deployId}`,
);
} catch (error) {
console.log(error);
toast.push(error.error || error || "Ooops something went wrong.");
}
}
</script>
<nav
class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4 z-50"
>
<Tooltip position="bottom" label="Deploy">
<button
disabled="{$application.publish.domain === '' ||
$application.publish.domain === null}"
class:cursor-not-allowed="{$application.publish.domain === '' ||
$application.publish.domain === null}"
class:hover:bg-green-500="{$application.publish.domain}"
class:bg-green-600="{$application.publish.domain}"
class:hover:bg-transparent="{$activePage.new}"
class:text-warmGray-700="{$application.publish.domain === '' ||
$application.publish.domain === null}"
class="icon"
on:click="{deploy}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><polyline points="16 16 12 12 8 16"></polyline><line
x1="12"
y1="12"
x2="12"
y2="21"></line><path
d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"
></path><polyline points="16 16 12 12 8 16"></polyline></svg
>
</button>
</Tooltip>
<Tooltip position="bottom" label="Delete">
<button
disabled="{$application.publish.domain === '' ||
$application.publish.domain === null ||
$activePage.new}"
class:cursor-not-allowed="{$application.publish.domain === '' ||
$application.publish.domain === null ||
$activePage.new}"
class:hover:text-red-500="{$application.publish.domain &&
!$activePage.new}"
class:hover:bg-warmGray-700="{$application.publish.domain &&
!$activePage.new}"
class:hover:bg-transparent="{$activePage.new}"
class:text-warmGray-700="{$application.publish.domain === '' ||
$application.publish.domain === null ||
$activePage.new}"
class="icon"
on:click="{removeApplication}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Tooltip>
<div class="border border-warmGray-700 h-8"></div>
<Tooltip position="bottom" label="Logs">
<button
class="icon"
class:text-warmGray-700="{$activePage.new}"
disabled="{$activePage.new}"
class:hover:text-blue-400="{!$activePage.new}"
class:hover:bg-transparent="{$activePage.new}"
class:cursor-not-allowed="{$activePage.new}"
class:text-blue-400="{$activePage.application === 'logs'}"
class:bg-warmGray-700="{$activePage.application === 'logs'}"
on:click="{() =>
$goto(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs`,
)}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
></path>
</svg>
</button>
</Tooltip>
<Tooltip position="bottom-left" label="Configuration">
<button
class="icon hover:text-yellow-400"
disabled="{$activePage.new}"
class:text-yellow-400="{$activePage.application === 'configuration' ||
$activePage.new}"
class:bg-warmGray-700="{$activePage.application === 'configuration' ||
$activePage.new}"
on:click="{() =>
$goto(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/configuration`,
)}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
></path>
</svg>
</button>
</Tooltip>
</nav>

View File

@ -3,6 +3,10 @@
import { isActive, redirect } from "@roxi/routify/runtime";
import { fade } from "svelte/transition";
import { toast } from "@zerodevx/svelte-toast";
import MongoDb from "../SVGs/MongoDb.svelte";
import Postgresql from "../SVGs/Postgresql.svelte";
import Mysql from "../SVGs/Mysql.svelte";
import CouchDb from "../SVGs/CouchDb.svelte";
let type;
let defaultDatabaseName;
@ -15,7 +19,7 @@
defaultDatabaseName,
},
});
$dbInprogress = true
$dbInprogress = true;
toast.push("Database deployment queued.");
$redirect(`/dashboard/databases`);
} catch (error) {
@ -30,34 +34,47 @@
>
{#if $isActive("/database/new")}
<div class="flex justify-center space-x-4 font-bold pb-6">
<button
class="button bg-gray-500 p-2 text-white hover:bg-green-600 cursor-pointer w-32"
<div
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-green-600 p-2 rounded bg-warmGray-800 w-32"
class:border-green-600="{type === 'mongodb'}"
on:click="{() => (type = 'mongodb')}"
class:bg-green-600="{type === 'mongodb'}"
>
MongoDB
</button>
<button
class="button bg-gray-500 p-2 text-white hover:bg-blue-600 cursor-pointer w-32"
<div class="flex items-center justify-center my-2">
<MongoDb customClass="w-6" />
</div>
<div class="text-white">MongoDB</div>
</div>
<div
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-600 p-2 rounded bg-warmGray-800 w-32"
class:border-red-600="{type === 'couchdb'}"
on:click="{() => (type = 'couchdb')}"
>
<div class="flex items-center justify-center my-2">
<CouchDb customClass="w-12 text-red-600 fill-current" />
</div>
<div class="text-white">Couchdb</div>
</div>
<div
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-600 p-2 rounded bg-warmGray-800 w-32"
class:border-blue-600="{type === 'postgresql'}"
on:click="{() => (type = 'postgresql')}"
class:bg-blue-600="{type === 'postgresql'}"
>
PostgreSQL
</button>
<button
class="button bg-gray-500 p-2 text-white hover:bg-orange-600 cursor-pointer w-32"
<div class="flex items-center justify-center my-2">
<Postgresql customClass="w-12" />
</div>
<div class="text-white">PostgreSQL</div>
</div>
<div
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-orange-600 p-2 rounded bg-warmGray-800 w-32"
class:border-orange-600="{type === 'mysql'}"
on:click="{() => (type = 'mysql')}"
class:bg-orange-600="{type === 'mysql'}"
>
MySQL
</button>
<button
class="button bg-gray-500 p-2 text-white hover:bg-red-600 cursor-pointer w-32"
on:click="{() => (type = 'couchdb')}"
class:bg-red-600="{type === 'couchdb'}"
>
Couchdb
</button>
<div class="flex items-center justify-center">
<Mysql customClass="w-10" />
</div>
<div class="text-white">MySQL</div>
</div>
<!-- <button
class="button bg-gray-500 p-2 text-white hover:bg-yellow-500 cursor-pointer w-32"
on:click="{() => (type = 'clickhouse')}"
@ -67,18 +84,15 @@
</button> -->
</div>
{#if type}
<div>
<div
class="grid grid-rows-1 justify-center items-center text-center pb-5"
>
<label for="defaultDB">Default database</label>
<input
id="defaultDB"
class="w-64"
placeholder="random"
bind:value="{defaultDatabaseName}"
/>
</div>
<div class="flex justify-center space-x-4 items-center">
<label for="defaultDB">Default database</label>
<input
id="defaultDB"
class="w-64"
placeholder="random"
bind:value="{defaultDatabaseName}"
/>
<button
class:bg-green-600="{type === 'mongodb'}"
class:hover:bg-green-500="{type === 'mongodb'}"

View File

@ -34,7 +34,7 @@
<div class="text-left max-w-5xl mx-auto px-6" in:fade="{{ duration: 100 }}">
<div class="pb-2 pt-5 space-y-4">
<div class="flex space-x-5 items-center">
<div class="text-2xl font-bold py-4 border-gradient">General</div>
<div class="text-2xl font-bold border-gradient">General</div>
<div class="flex-1"></div>
<Tooltip
position="bottom"
@ -48,7 +48,7 @@
</Tooltip>
</div>
<div class="flex items-center">
<div class="flex items-center pt-4">
<div class="font-bold w-64 text-warmGray-400">Domain</div>
<input class="w-full" value="{service.config.baseURL}" disabled />
</div>
@ -64,8 +64,8 @@
<div class="font-bold w-64 text-warmGray-400">Password</div>
<PasswordField value="{service.config.userPassword}" />
</div>
<div class="text-2xl font-bold py-4 border-gradient w-32">PostgreSQL</div>
<div class="flex items-center">
<div class="text-2xl font-bold pt-4 border-gradient w-32">PostgreSQL</div>
<div class="flex items-center pt-4">
<div class="font-bold w-64 text-warmGray-400">Username</div>
<input class="w-full" value="{service.config.generateEnvsPostgres.POSTGRESQL_USERNAME}" disabled />
</div>

View File

@ -5,8 +5,8 @@
</style>
<script>
import { goto, route, isActive } from "@roxi/routify/runtime";
import { loggedIn, session, fetch, deployments } from "@store";
import { goto, route, isChangingPage } from "@roxi/routify/runtime";
import { loggedIn, session, fetch, deployments, activePage } from "@store";
import { toast } from "@zerodevx/svelte-toast";
import { onMount } from "svelte";
import compareVersions from "compare-versions";
@ -23,6 +23,34 @@
window.location.hostname !== "test.andrasbacsai.dev"
? "main"
: "next";
$: if ($isChangingPage) {
const path = window.location.pathname;
if (path === "/dashboard/applications" || path.match(/\/application/)) {
$activePage.mainmenu = "applications";
} else if (path === "/dashboard/databases" || path.match(/\/database/)) {
$activePage.mainmenu = "databases";
} else if (path === "/dashboard/services" || path.match(/\/service/)) {
$activePage.mainmenu = "services";
} else if (path === "/settings") {
$activePage.mainmenu = "settings";
} else {
$activePage.mainmenu = null;
}
if (path.match(/\/application\/.*\/logs/)) {
$activePage.application = "logs";
$activePage.new = false;
} else if (path === "/application/new") {
$activePage.application = "configuration";
$activePage.new = true;
} else if (path.match(/\/application\/.*\/configuration/)) {
$activePage.application = "configuration";
$activePage.new = false;
} else {
$activePage.application = null;
}
}
onMount(async () => {
if ($session.token) {
upgradeAvailable = await checkUpgrade();
@ -139,18 +167,16 @@
>
<div
class="flex flex-col w-full h-screen items-center transition-all duration-100"
class:border-green-500="{$isActive('/dashboard/applications')}"
class:border-purple-500="{$isActive('/dashboard/databases')}"
class:border-green-500="{$activePage.mainmenu === 'applications'}"
class:border-purple-500="{$activePage.mainmenu === 'databases'}"
>
<img class="w-10 pt-4 pb-4" src="/favicon.png" alt="coolLabs logo" />
<Tooltip position="right" label="Applications">
<div
class="p-2 hover:bg-warmGray-700 rounded hover:text-green-500 mt-4 transition-all duration-100 cursor-pointer"
on:click="{() => $goto('/dashboard/applications')}"
class:text-green-500="{$isActive('/dashboard/applications') ||
$isActive('/application')}"
class:bg-warmGray-700="{$isActive('/dashboard/applications') ||
$isActive('/application')}"
class:text-green-500="{$activePage.mainmenu === 'applications'}"
class:bg-warmGray-700="{$activePage.mainmenu === 'applications'}"
>
<svg
class="w-8"
@ -187,10 +213,8 @@
<div
class="p-2 hover:bg-warmGray-700 rounded hover:text-purple-500 my-4 transition-all duration-100 cursor-pointer"
on:click="{() => $goto('/dashboard/databases')}"
class:text-purple-500="{$isActive('/dashboard/databases') ||
$isActive('/database')}"
class:bg-warmGray-700="{$isActive('/dashboard/databases') ||
$isActive('/database')}"
class:text-purple-500="{$activePage.mainmenu === 'databases'}"
class:bg-warmGray-700="{$activePage.mainmenu === 'databases'}"
>
<svg
class="w-8"
@ -212,22 +236,31 @@
<div
class="p-2 hover:bg-warmGray-700 rounded hover:text-blue-500 transition-all duration-100 cursor-pointer"
on:click="{() => $goto('/dashboard/services')}"
class:text-blue-500="{$isActive('/dashboard/services') ||
$isActive('/service')}"
class:bg-warmGray-700="{$isActive('/dashboard/services') ||
$isActive('/service')}"
class:text-blue-500="{$activePage.mainmenu === 'services'}"
class:bg-warmGray-700="{$activePage.mainmenu === 'services'}"
>
<svg xmlns="http://www.w3.org/2000/svg" class="w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z" />
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-8"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"
></path>
</svg>
</div>
</Tooltip>
<div class="flex-1"></div>
<Tooltip position="right" label="Settings">
<button
class="p-2 hover:bg-warmGray-700 rounded hover:text-yellow-500 transition-all duration-100 cursor-pointer"
class:text-yellow-500="{$isActive('/settings')}"
class:bg-warmGray-700="{$isActive('/settings')}"
class:text-yellow-500="{$activePage.mainmenu === 'settings'}"
class:bg-warmGray-700="{$activePage.mainmenu === 'settings'}"
on:click="{() => $goto('/settings')}"
>
<svg
@ -284,7 +317,7 @@
{/if}
{#if upgradeAvailable}
<footer
class="absolute bottom-0 right-0 p-4 px-6 w-auto rounded-tl text-white "
class="fixed bottom-0 right-0 p-4 px-6 w-auto rounded-tl text-white hover:scale-110 transform transition duration-100"
>
<div class="flex items-center">
<div></div>

View File

@ -1,224 +1,75 @@
<script>
import { params, goto, redirect, isActive } from "@roxi/routify";
import { application, fetch, initialApplication, initConf } from "@store";
import { params, redirect } from "@roxi/routify";
import {
application,
fetch,
initialApplication,
initConf,
deployments,
activePage,
} from "@store";
import { onDestroy } from "svelte";
import { fade } from "svelte/transition";
import Loading from "../../components/Loading.svelte";
import { toast } from "@zerodevx/svelte-toast";
import Tooltip from "../../components/Tooltip/Tooltip.svelte";
import Navbar from "../../components/Application/Navbar.svelte";
$application.repository.organization = $params.organization;
$application.repository.name = $params.name;
$application.repository.branch = $params.branch;
async function setConfiguration() {
try {
const config = await $fetch(`/api/v1/config`, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
},
});
$application = { ...config };
$initConf = JSON.parse(JSON.stringify($application));
} catch (error) {
toast.push("Configuration not found.");
$redirect("/dashboard/applications");
}
}
async function loadConfiguration() {
if (!$isActive("/application/new")) {
try {
const config = await $fetch(`/api/v1/config`, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
},
if (!$activePage.new) {
if ($deployments.length === 0) {
await setConfiguration();
} else {
const found = $deployments.applications.deployed.find(app => {
const { organization, name, branch } = app.configuration;
if (
organization === $application.repository.organization &&
name === $application.repository.name &&
branch === $application.repository.branch
) {
return app;
}
});
$application = { ...config };
$initConf = JSON.parse(JSON.stringify($application));
} catch (error) {
toast.push("Configuration not found.");
$redirect("/dashboard/applications");
if (found) {
$application = { ...found.configuration };
$initConf = JSON.parse(JSON.stringify($application));
} else {
await setConfiguration();
}
}
} else {
$application = JSON.parse(JSON.stringify(initialApplication));
}
}
async function removeApplication() {
await $fetch(`/api/v1/application/remove`, {
body: {
organization: $params.organization,
name: $params.name,
branch: $params.branch,
},
});
toast.push("Application removed.");
$redirect(`/dashboard/applications`);
}
onDestroy(() => {
$application = JSON.parse(JSON.stringify(initialApplication));
});
async function deploy() {
try {
$application.build.pack = $application.build.pack.replace('.','').toLowerCase()
toast.push("Checking inputs.");
await $fetch(`/api/v1/application/check`, {
body: $application,
});
const { nickname, name, deployId } = await $fetch(`/api/v1/application/deploy`, {
body: $application,
});
$application.general.nickname = nickname;
$application.build.container.name = name;
$application.general.deployId = deployId;
$initConf = JSON.parse(JSON.stringify($application));
toast.push("Application deployment queued.");
$goto(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs/${$application.general.deployId}`,
);
} catch (error) {
console.log(error);
toast.push(error.error || error || "Ooops something went wrong.");
}
}
</script>
{#await loadConfiguration()}
<Loading />
{:then}
<nav
class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4"
>
<Tooltip position="bottom" label="Deploy" >
<button
disabled="{$application.publish.domain === '' ||
$application.publish.domain === null}"
class:cursor-not-allowed="{$application.publish.domain === '' ||
$application.publish.domain === null}"
class:hover:text-green-500="{$application.publish.domain}"
class:hover:bg-warmGray-700="{$application.publish.domain}"
class:hover:bg-transparent="{$isActive('/application/new')}"
class:text-warmGray-700="{$application.publish.domain === '' ||
$application.publish.domain === null}"
class="icon"
on:click="{deploy}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
><polyline points="16 16 12 12 8 16"></polyline><line
x1="12"
y1="12"
x2="12"
y2="21"></line><path
d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path><polyline
points="16 16 12 12 8 16"></polyline></svg
>
</button>
</Tooltip>
<Tooltip position="bottom" label="Delete" >
<button
disabled="{$application.publish.domain === '' ||
$application.publish.domain === null ||
$isActive('/application/new')}"
class:cursor-not-allowed="{$application.publish.domain === '' ||
$application.publish.domain === null ||
$isActive('/application/new')}"
class:hover:text-red-500="{$application.publish.domain &&
!$isActive('/application/new')}"
class:hover:bg-warmGray-700="{$application.publish.domain &&
!$isActive('/application/new')}"
class:hover:bg-transparent="{$isActive('/application/new')}"
class:text-warmGray-700="{$application.publish.domain === '' ||
$application.publish.domain === null ||
$isActive('/application/new')}"
class="icon"
on:click="{removeApplication}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Tooltip>
<div class="border border-warmGray-700 h-8"></div>
<Tooltip position="bottom" label="Logs" >
<button
class="icon"
class:text-warmGray-700="{$isActive('/application/new')}"
disabled="{$isActive('/application/new')}"
class:hover:text-blue-400="{!$isActive('/application/new')}"
class:hover:bg-transparent="{$isActive('/application/new')}"
class:cursor-not-allowed="{$isActive('/application/new')}"
class:text-blue-400="{$isActive(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs`,
)}"
class:bg-warmGray-700="{$isActive(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs`,
)}"
on:click="{() =>
$goto(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/logs`,
)}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
></path>
</svg>
</button>
</Tooltip>
<Tooltip position="bottom-left" label="Configuration" >
<button
class="icon hover:text-yellow-400"
disabled="{$isActive(`/application/new`)}"
class:text-yellow-400="{$isActive(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/configuration`,
) || $isActive(`/application/new`)}"
class:bg-warmGray-700="{$isActive(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/configuration`,
) || $isActive(`/application/new`)}"
on:click="{() =>
$goto(
`/application/${$application.repository.organization}/${$application.repository.name}/${$application.repository.branch}/configuration`,
)}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
></path>
</svg>
</button>
</Tooltip>
</nav>
<div class="text-white">
<slot />
</div>
<Navbar />
<div class="text-white">
<slot />
</div>
{/await}

View File

@ -1,8 +1,6 @@
<script>
import { fetch, deployments } from "@store";
import { onDestroy, onMount } from "svelte";
import { fade } from "svelte/transition";
import { goto, isActive } from "@roxi/routify/runtime";
import { toast } from "@zerodevx/svelte-toast";
let loadDashboardInterval = null;

View File

@ -5,7 +5,7 @@
function switchTo(application) {
const { branch, name, organization } = application;
$goto(`/application/:organization/:name/:branch`, {
$goto(`/application/:organization/:name/:branch/configuration`, {
name,
organization,
branch,
@ -48,15 +48,15 @@
on:click="{() =>
switchTo({
branch:
application.Spec.Labels.configuration.repository.branch,
name: application.Spec.Labels.configuration.repository.name,
application.configuration.repository.branch,
name: application.configuration.repository.name,
organization:
application.Spec.Labels.configuration.repository
application.configuration.repository
.organization,
})}"
>
<div class="flex items-center">
{#if application.Spec.Labels.configuration.build.pack === "static"}
{#if application.configuration.build.pack === "static"}
<svg
class="text-white w-10 h-10 absolute top-0 left-0 -m-4"
viewBox="0 0 32 32"
@ -83,7 +83,74 @@
></defs
></svg
>
{:else if application.Spec.Labels.configuration.build.pack === "nodejs"}
{:else if application.configuration.build.pack === "react"}
<svg
class="text-blue-500 w-10 h-10 absolute top-0 left-0 -m-4"
viewBox="0 0 128 128"
>
<g fill="#61DAFB"
><circle cx="64" cy="64" r="11.4"></circle><path
d="M107.3 45.2c-2.2-.8-4.5-1.6-6.9-2.3.6-2.4 1.1-4.8 1.5-7.1 2.1-13.2-.2-22.5-6.6-26.1-1.9-1.1-4-1.6-6.4-1.6-7 0-15.9 5.2-24.9 13.9-9-8.7-17.9-13.9-24.9-13.9-2.4 0-4.5.5-6.4 1.6-6.4 3.7-8.7 13-6.6 26.1.4 2.3.9 4.7 1.5 7.1-2.4.7-4.7 1.4-6.9 2.3-12.5 4.8-19.3 11.4-19.3 18.8s6.9 14 19.3 18.8c2.2.8 4.5 1.6 6.9 2.3-.6 2.4-1.1 4.8-1.5 7.1-2.1 13.2.2 22.5 6.6 26.1 1.9 1.1 4 1.6 6.4 1.6 7.1 0 16-5.2 24.9-13.9 9 8.7 17.9 13.9 24.9 13.9 2.4 0 4.5-.5 6.4-1.6 6.4-3.7 8.7-13 6.6-26.1-.4-2.3-.9-4.7-1.5-7.1 2.4-.7 4.7-1.4 6.9-2.3 12.5-4.8 19.3-11.4 19.3-18.8s-6.8-14-19.3-18.8zm-14.8-30.5c4.1 2.4 5.5 9.8 3.8 20.3-.3 2.1-.8 4.3-1.4 6.6-5.2-1.2-10.7-2-16.5-2.5-3.4-4.8-6.9-9.1-10.4-13 7.4-7.3 14.9-12.3 21-12.3 1.3 0 2.5.3 3.5.9zm-11.2 59.3c-1.8 3.2-3.9 6.4-6.1 9.6-3.7.3-7.4.4-11.2.4-3.9 0-7.6-.1-11.2-.4-2.2-3.2-4.2-6.4-6-9.6-1.9-3.3-3.7-6.7-5.3-10 1.6-3.3 3.4-6.7 5.3-10 1.8-3.2 3.9-6.4 6.1-9.6 3.7-.3 7.4-.4 11.2-.4 3.9 0 7.6.1 11.2.4 2.2 3.2 4.2 6.4 6 9.6 1.9 3.3 3.7 6.7 5.3 10-1.7 3.3-3.4 6.6-5.3 10zm8.3-3.3c1.5 3.5 2.7 6.9 3.8 10.3-3.4.8-7 1.4-10.8 1.9 1.2-1.9 2.5-3.9 3.6-6 1.2-2.1 2.3-4.2 3.4-6.2zm-25.6 27.1c-2.4-2.6-4.7-5.4-6.9-8.3 2.3.1 4.6.2 6.9.2 2.3 0 4.6-.1 6.9-.2-2.2 2.9-4.5 5.7-6.9 8.3zm-18.6-15c-3.8-.5-7.4-1.1-10.8-1.9 1.1-3.3 2.3-6.8 3.8-10.3 1.1 2 2.2 4.1 3.4 6.1 1.2 2.2 2.4 4.1 3.6 6.1zm-7-25.5c-1.5-3.5-2.7-6.9-3.8-10.3 3.4-.8 7-1.4 10.8-1.9-1.2 1.9-2.5 3.9-3.6 6-1.2 2.1-2.3 4.2-3.4 6.2zm25.6-27.1c2.4 2.6 4.7 5.4 6.9 8.3-2.3-.1-4.6-.2-6.9-.2-2.3 0-4.6.1-6.9.2 2.2-2.9 4.5-5.7 6.9-8.3zm22.2 21l-3.6-6c3.8.5 7.4 1.1 10.8 1.9-1.1 3.3-2.3 6.8-3.8 10.3-1.1-2.1-2.2-4.2-3.4-6.2zm-54.5-16.2c-1.7-10.5-.3-17.9 3.8-20.3 1-.6 2.2-.9 3.5-.9 6 0 13.5 4.9 21 12.3-3.5 3.8-7 8.2-10.4 13-5.8.5-11.3 1.4-16.5 2.5-.6-2.3-1-4.5-1.4-6.6zm-24.7 29c0-4.7 5.7-9.7 15.7-13.4 2-.8 4.2-1.5 6.4-2.1 1.6 5 3.6 10.3 6 15.6-2.4 5.3-4.5 10.5-6 15.5-13.8-4-22.1-10-22.1-15.6zm28.5 49.3c-4.1-2.4-5.5-9.8-3.8-20.3.3-2.1.8-4.3 1.4-6.6 5.2 1.2 10.7 2 16.5 2.5 3.4 4.8 6.9 9.1 10.4 13-7.4 7.3-14.9 12.3-21 12.3-1.3 0-2.5-.3-3.5-.9zm60.8-20.3c1.7 10.5.3 17.9-3.8 20.3-1 .6-2.2.9-3.5.9-6 0-13.5-4.9-21-12.3 3.5-3.8 7-8.2 10.4-13 5.8-.5 11.3-1.4 16.5-2.5.6 2.3 1 4.5 1.4 6.6zm9-15.6c-2 .8-4.2 1.5-6.4 2.1-1.6-5-3.6-10.3-6-15.6 2.4-5.3 4.5-10.5 6-15.5 13.8 4 22.1 10 22.1 15.6 0 4.7-5.8 9.7-15.7 13.4z"
></path></g
>
</svg>
{:else if application.configuration.build.pack === "gatsby"}
<svg class="w-10 h-10 absolute top-0 left-0 -m-4" viewBox="0 0 128 128">
<path fill="#64328B" d="M64,0C28.7,0,0,28.7,0,64v0c0,35.3,28.7,64,64,64s64-28.7,64-64v0C128,28.7,99.3,0,64,0z M13.2,64L64,114.8 C35.9,114.8,13.2,92.1,13.2,64z M75.4,113.5l-60.9-61C19.7,30,39.9,13.2,64,13.2c16.6,0,31.3,7.9,40.5,20.2l-7.5,7.2 C89.7,30.2,77.7,23.5,64,23.5c-17.6,0-32.5,11.2-38.1,26.8C33.1,57,75.4,98.8,78.1,102c12.7-4.7,22.3-15.5,25.4-28.9H81.9v-9.4 l33,0.2C114.8,88.2,98,108.4,75.4,113.5z"></path>
</svg>
{:else if application.configuration.build.pack === "nuxtjs"}
<svg class="w-10 h-10 absolute top-0 left-0 -m-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<g fill-rule="nonzero" transform="translate(0 50)" fill="none">
<path d="M227.92099 83.45116l-13.6889 24.10141-46.8148-82.44693L23.7037 278.17052h97.3037c0 13.31084 10.61252 24.10142 23.70371 24.10142H23.70371c-8.46771 0-16.29145-4.59601-20.5246-12.05272-4.23315-7.4567-4.23272-16.64312.00114-24.0994L146.89383 13.05492c4.23415-7.45738 12.0596-12.05138 20.5284-12.05138 8.46878 0 16.29423 4.594 20.52839 12.05138l39.97037 70.39623z" fill="#00C58E"/>
<path d="M331.6642 266.11981l-90.05432-158.56724-13.6889-24.10141-13.68888 24.10141-90.04445 158.56724c-4.23385 7.45629-4.23428 16.64271-.00113 24.09941 4.23314 7.4567 12.05689 12.05272 20.5246 12.05272h166.4c8.46946 0 16.29644-4.591 20.532-12.04837 4.23555-7.45736 4.23606-16.64592.00132-24.10376h.01976zM144.7111 278.17052L227.921 131.65399l83.19012 146.51653h-166.4z" fill="#FFF"/>
<path d="M396.04938 290.22123c-4.23344 7.45557-12.05656 12.0507-20.52345 12.0507H311.1111c13.0912 0 23.7037-10.79057 23.7037-24.10141h40.66173L260.09877 74.98553l-18.4889 32.56704L227.921 83.45116l11.65432-20.51634c4.23416-7.45738 12.0596-12.05138 20.5284-12.05138 8.46879 0 16.29423 4.594 20.52839 12.05138l115.41728 203.185c4.23426 7.457 4.23426 16.6444 0 24.1014z" fill="#108775"/>
</g>
</svg>
{:else if application.configuration.build.pack === "svelte"}
<svg
class="w-10 h-10 absolute top-0 left-0 -m-4"
version="1.1"
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 98.1 118"
style="enable-background:new 0 0 98.1 118;"
xml:space="preserve"
>
<path
fill="#FF3E00"
d="M91.8,15.6C80.9-0.1,59.2-4.7,43.6,5.2L16.1,22.8C8.6,27.5,3.4,35.2,1.9,43.9c-1.3,7.3-0.2,14.8,3.3,21.3 c-2.4,3.6-4,7.6-4.7,11.8c-1.6,8.9,0.5,18.1,5.7,25.4c11,15.7,32.6,20.3,48.2,10.4l27.5-17.5c7.5-4.7,12.7-12.4,14.2-21.1 c1.3-7.3,0.2-14.8-3.3-21.3c2.4-3.6,4-7.6,4.7-11.8C99.2,32.1,97.1,22.9,91.8,15.6"
></path>
<path
fill="#FFFFFF"
d="M40.9,103.9c-8.9,2.3-18.2-1.2-23.4-8.7c-3.2-4.4-4.4-9.9-3.5-15.3c0.2-0.9,0.4-1.7,0.6-2.6l0.5-1.6l1.4,1 c3.3,2.4,6.9,4.2,10.8,5.4l1,0.3l-0.1,1c-0.1,1.4,0.3,2.9,1.1,4.1c1.6,2.3,4.4,3.4,7.1,2.7c0.6-0.2,1.2-0.4,1.7-0.7L65.5,72 c1.4-0.9,2.3-2.2,2.6-3.8c0.3-1.6-0.1-3.3-1-4.6c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7l-10.5,6.7 c-1.7,1.1-3.6,1.9-5.6,2.4c-8.9,2.3-18.2-1.2-23.4-8.7c-3.1-4.4-4.4-9.9-3.4-15.3c0.9-5.2,4.1-9.9,8.6-12.7l27.5-17.5 c1.7-1.1,3.6-1.9,5.6-2.5c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.2,0.9-0.4,1.7-0.7,2.6l-0.5,1.6l-1.4-1 c-3.3-2.4-6.9-4.2-10.8-5.4l-1-0.3l0.1-1c0.1-1.4-0.3-2.9-1.1-4.1c-1.6-2.3-4.4-3.3-7.1-2.6c-0.6,0.2-1.2,0.4-1.7,0.7L32.4,46.1 c-1.4,0.9-2.3,2.2-2.6,3.8s0.1,3.3,1,4.6c1.6,2.3,4.4,3.3,7.1,2.6c0.6-0.2,1.2-0.4,1.7-0.7l10.5-6.7c1.7-1.1,3.6-1.9,5.6-2.5 c8.9-2.3,18.2,1.2,23.4,8.7c3.2,4.4,4.4,9.9,3.5,15.3c-0.9,5.2-4.1,9.9-8.6,12.7l-27.5,17.5C44.8,102.5,42.9,103.3,40.9,103.9"
></path>
</svg>
{:else if application.configuration.build.pack === "vuejs"}
<svg
class="text-green-500 w-10 h-10 absolute top-0 left-0 -m-4"
viewBox="0 0 128 128"
>
<path
d="m-2.3125e-8 8.9337 49.854 0.1586 14.167 24.47 14.432-24.47 49.547-0.1577-63.834 110.14zm126.98 0.6374-24.36 0.0207-38.476 66.052-38.453-66.052-24.749-0.0194 63.211 107.89zm-25.149-0.008-22.745 0.16758l-15.053 24.647-14.817-24.647-22.794-0.1679 37.731 64.476zM25.997 9.3929l23.002 0.0087M25.997 9.3929l23.002 0.0087"
fill="none"></path><path
d="m25.997 9.3929 23.002 0.0087l15.036 24.958 14.983-24.956 22.982-0.0057-37.85 65.655z"
fill="#35495e"></path><path
d="m0.91068 9.5686 25.066-0.1711 38.151 65.658 37.852-65.654 25.11 0.0263-62.966 108.06z"
fill="#41b883"></path>
</svg>
{:else if application.configuration.build.pack === "nextjs"}
<svg
class="text-blue-500 w-10 h-10 absolute top-0 left-0 -m-4 fill-current"
viewBox="0 0 128 128"
>
<path
d="M64 0C28.7 0 0 28.7 0 64s28.7 64 64 64c11.2 0 21.7-2.9 30.8-7.9L48.4 55.3v36.6h-6.8V41.8h6.8l50.5 75.8C116.4 106.2 128 86.5 128 64c0-35.3-28.7-64-64-64zm22.1 84.6l-7.5-11.3V41.8h7.5v42.8z"
></path>
</svg>
{:else if application.configuration.build.pack === "nodejs"}
<svg
class="text-green-400 w-10 h-10 absolute top-0 left-0 -m-4"
xmlns="http://www.w3.org/2000/svg"
@ -98,7 +165,7 @@
d="M224 508c-6.7 0-13.5-1.8-19.4-5.2l-61.7-36.5c-9.2-5.2-4.7-7-1.7-8 12.3-4.3 14.8-5.2 27.9-12.7 1.4-.8 3.2-.5 4.6.4l47.4 28.1c1.7 1 4.1 1 5.7 0l184.7-106.6c1.7-1 2.8-3 2.8-5V149.3c0-2.1-1.1-4-2.9-5.1L226.8 37.7c-1.7-1-4-1-5.7 0L36.6 144.3c-1.8 1-2.9 3-2.9 5.1v213.1c0 2 1.1 4 2.9 4.9l50.6 29.2c27.5 13.7 44.3-2.4 44.3-18.7V167.5c0-3 2.4-5.3 5.4-5.3h23.4c2.9 0 5.4 2.3 5.4 5.3V378c0 36.6-20 57.6-54.7 57.6-10.7 0-19.1 0-42.5-11.6l-48.4-27.9C8.1 389.2.7 376.3.7 362.4V149.3c0-13.8 7.4-26.8 19.4-33.7L204.6 9c11.7-6.6 27.2-6.6 38.8 0l184.7 106.7c12 6.9 19.4 19.8 19.4 33.7v213.1c0 13.8-7.4 26.7-19.4 33.7L243.4 502.8c-5.9 3.4-12.6 5.2-19.4 5.2zm149.1-210.1c0-39.9-27-50.5-83.7-58-57.4-7.6-63.2-11.5-63.2-24.9 0-11.1 4.9-25.9 47.4-25.9 37.9 0 51.9 8.2 57.7 33.8.5 2.4 2.7 4.2 5.2 4.2h24c1.5 0 2.9-.6 3.9-1.7s1.5-2.6 1.4-4.1c-3.7-44.1-33-64.6-92.2-64.6-52.7 0-84.1 22.2-84.1 59.5 0 40.4 31.3 51.6 81.8 56.6 60.5 5.9 65.2 14.8 65.2 26.7 0 20.6-16.6 29.4-55.5 29.4-48.9 0-59.6-12.3-63.2-36.6-.4-2.6-2.6-4.5-5.3-4.5h-23.9c-3 0-5.3 2.4-5.3 5.3 0 31.1 16.9 68.2 97.8 68.2 58.4-.1 92-23.2 92-63.4z"
></path>
</svg>
{:else if application.Spec.Labels.configuration.build.pack === "php"}
{:else if application.configuration.build.pack === "php"}
<svg
viewBox="0 0 128 128"
class="text-white w-14 h-14 absolute top-0 left-0 -m-6"
@ -108,7 +175,7 @@
d="M64 33.039c-33.74 0-61.094 13.862-61.094 30.961s27.354 30.961 61.094 30.961 61.094-13.862 61.094-30.961-27.354-30.961-61.094-30.961zm-15.897 36.993c-1.458 1.364-3.077 1.927-4.86 2.507-1.783.581-4.052.461-6.811.461h-6.253l-1.733 10h-7.301l6.515-34h14.04c4.224 0 7.305 1.215 9.242 3.432 1.937 2.217 2.519 5.364 1.747 9.337-.319 1.637-.856 3.159-1.614 4.515-.759 1.357-1.75 2.624-2.972 3.748zm21.311 2.968l2.881-14.42c.328-1.688.208-2.942-.361-3.555-.57-.614-1.782-1.025-3.635-1.025h-5.79l-3.731 19h-7.244l6.515-33h7.244l-1.732 9h6.453c4.061 0 6.861.815 8.402 2.231s2.003 3.356 1.387 6.528l-3.031 15.241h-7.358zm40.259-11.178c-.318 1.637-.856 3.133-1.613 4.488-.758 1.357-1.748 2.598-2.971 3.722-1.458 1.364-3.078 1.927-4.86 2.507-1.782.581-4.053.461-6.812.461h-6.253l-1.732 10h-7.301l6.514-34h14.041c4.224 0 7.305 1.215 9.241 3.432 1.935 2.217 2.518 5.418 1.746 9.39zM95.919 54h-5.001l-2.727 14h4.442c2.942 0 5.136-.29 6.576-1.4 1.442-1.108 2.413-2.828 2.918-5.421.484-2.491.264-4.434-.66-5.458-.925-1.024-2.774-1.721-5.548-1.721zM38.934 54h-5.002l-2.727 14h4.441c2.943 0 5.136-.29 6.577-1.4 1.441-1.108 2.413-2.828 2.917-5.421.484-2.491.264-4.434-.66-5.458s-2.772-1.721-5.546-1.721z"
></path>
</svg>
{:else if application.Spec.Labels.configuration.build.pack === "custom"}
{:else if application.configuration.build.pack === "docker"}
<svg
viewBox="0 0 128 128"
class="w-16 h-16 absolute top-0 left-0 -m-8"
@ -185,7 +252,7 @@
></path></g
>
</svg>
{:else if application.Spec.Labels.configuration.build.pack === "rust"}
{:else if application.configuration.build.pack === "rust"}
<svg
class="w-14 h-14 absolute top-0 left-0 -m-6"
viewBox="0 0 128 128"
@ -201,10 +268,10 @@
<div
class="text-base font-bold text-center w-full text-white pb-6"
>
{application.Spec.Labels.configuration.publish
.domain}{application.Spec.Labels.configuration.publish
{application.configuration.publish
.domain}{application.configuration.publish
.path !== "/"
? application.Spec.Labels.configuration.publish.path
? application.configuration.publish.path
: ""}
</div>
<div

View File

@ -91,26 +91,26 @@
class="px-4 pb-4"
on:click="{() =>
$goto(
`/database/${database.Spec.Labels.configuration.general.deployId}/configuration`,
`/database/${database.configuration.general.deployId}/configuration`,
)}"
>
<div
class="relative rounded-xl p-6 bg-warmGray-800 border-2 border-dashed border-transparent hover:border-purple-500 text-white shadow-md cursor-pointer ease-in-out transform hover:scale-105 duration-100 group"
>
<div class="flex items-center">
{#if database.Spec.Labels.configuration.general.type == "mongodb"}
{#if database.configuration.general.type == "mongodb"}
<MongoDb customClass="w-10 h-10 absolute top-0 left-0 -m-4" />
{:else if database.Spec.Labels.configuration.general.type == "postgresql"}
{:else if database.configuration.general.type == "postgresql"}
<Postgresql
customClass="w-10 h-10 absolute top-0 left-0 -m-4"
/>
{:else if database.Spec.Labels.configuration.general.type == "mysql"}
{:else if database.configuration.general.type == "mysql"}
<Mysql customClass="w-10 h-10 absolute top-0 left-0 -m-4" />
{:else if database.Spec.Labels.configuration.general.type == "couchdb"}
{:else if database.configuration.general.type == "couchdb"}
<CouchDb
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"
/>
{:else if database.Spec.Labels.configuration.general.type == "clickhouse"}
{:else if database.configuration.general.type == "clickhouse"}
<Clickhouse
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"
/>
@ -119,10 +119,10 @@
<div
class="text-base font-bold text-white group-hover:text-white"
>
{database.Spec.Labels.configuration.general.nickname}
{database.configuration.general.nickname}
</div>
<div class="text-xs font-bold text-warmGray-300 ">
({database.Spec.Labels.configuration.general.type})
({database.configuration.general.type})
</div>
</div>
</div>

View File

@ -38,21 +38,21 @@
</button>
</div>
<div in:fade="{{ duration: 100 }}">
{#if $deployments.services?.deployed.length > 0}
{#if $deployments?.services?.deployed.length > 0}
<div class="px-4 mx-auto py-5">
<div class="flex items-center justify-center flex-wrap">
{#each $deployments.services.deployed as service}
{#each $deployments?.services?.deployed as service}
<div
in:fade="{{ duration: 200 }}"
class="px-4 pb-4"
on:click="{() =>
$goto(`/service/${service.Spec.Labels.serviceName}/configuration`)}"
$goto(`/service/${service.serviceName}/configuration`)}"
>
<div
class="relative rounded-xl p-6 bg-warmGray-800 border-2 border-dashed border-transparent hover:border-blue-500 text-white shadow-md cursor-pointer ease-in-out transform hover:scale-105 duration-100 group"
>
<div class="flex items-center">
{#if service.Spec.Labels.serviceName == "plausible"}
{#if service.serviceName == "plausible"}
<div>
<img
alt="plausible logo"

View File

@ -17,7 +17,6 @@
if (name) {
try {
$database = await $fetch(`/api/v1/databases/${name}`);
console.log($database);
} catch (error) {
toast.push(`Cannot find database ${name}`);
$redirect(`/dashboard/databases`);
@ -49,8 +48,8 @@
</div>
<div class="text-left max-w-6xl mx-auto px-6" in:fade="{{ duration: 100 }}">
<div class="pb-2 pt-5 space-y-4">
<div class="text-2xl font-bold py-4 border-gradient w-32">Database</div>
<div class="flex items-center">
<div class="text-2xl font-bold border-gradient w-32">Database</div>
<div class="flex items-center pt-4">
<div class="font-bold w-64 text-warmGray-400">Connection string</div>
{#if $database.config.general.type === "mongodb"}
<PasswordField
@ -80,11 +79,8 @@
</div>
{#if $database.config.general.type === "mongodb"}
<div class="flex items-center">
<div class="font-bold w-48 text-warmGray-400">Root password</div>
<textarea
disabled
class="w-full"
value="{$database.envs.MONGODB_ROOT_PASSWORD}"></textarea>
<div class="font-bold w-64 text-warmGray-400">Root password</div>
<PasswordField value="{$database.envs.MONGODB_ROOT_PASSWORD}" />
</div>
{/if}
</div>

View File

@ -1,11 +1,11 @@
<script>
import { params, goto, isActive, redirect, url } from "@roxi/routify";
import { params, goto, isActive, redirect } from "@roxi/routify";
import { fetch, database, initialDatabase } from "@store";
import { toast } from "@zerodevx/svelte-toast";
import { onDestroy } from "svelte";
import Tooltip from "../../components/Tooltip/Tooltip.svelte";
import Tooltip from "../../components/Tooltip/Tooltip.svelte";
$: name = $params.name
$: name = $params.name;
onDestroy(() => {
$database = JSON.parse(JSON.stringify(initialDatabase));
@ -24,43 +24,38 @@ import Tooltip from "../../components/Tooltip/Tooltip.svelte";
<nav
class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4"
>
<Tooltip position="bottom" label="Delete" >
<button
title="Delete"
class="icon hover:text-red-500"
on:click="{removeDB}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
<Tooltip position="bottom" label="Delete">
<button
title="Delete"
class="icon hover:text-red-500"
on:click="{removeDB}"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Tooltip>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Tooltip>
<div class="border border-warmGray-700 h-8"></div>
<Tooltip position="bottom-left" label="Configuration" >
<Tooltip position="bottom-left" label="Configuration">
<button
class="icon hover:text-yellow-400"
disabled="{$isActive(`/database/new`)}"
class:text-yellow-400="{$isActive(
`/database/${name}/configuration`,
) || $isActive(`/application/new`)}"
class:bg-warmGray-700="{$isActive(
`/database/${name}/configuration`,
) || $isActive(`/database/new`)}"
on:click="{() =>
$goto(
`/database/${name}/configuration`,
)}"
class:text-yellow-400="{$isActive(`/database/${name}/configuration`) ||
$isActive(`/application/new`)}"
class:bg-warmGray-700="{$isActive(`/database/${name}/configuration`) ||
$isActive(`/database/new`)}"
on:click="{() => $goto(`/database/${name}/configuration`)}"
>
<svg
class="w-6"

View File

@ -52,10 +52,10 @@
</h2>
<div class="text-center py-10">
{#if !$loggedIn}
<button class="text-white bg-warmGray-700 hover:bg-warmGray-600 rounded p-2 px-10 font-bold" on:click="{login}">Login with Github</button
<button class="text-white bg-warmGray-800 hover:bg-warmGray-700 rounded p-2 px-10 font-bold" on:click="{login}">Login with Github</button
>
{:else}
<button class="text-white bg-warmGray-700 hover:bg-warmGray-600 rounded p-2 px-10 font-bold" on:click="{() => $goto('/dashboard/applications')}"
<button class="text-white bg-warmGray-800 hover:bg-warmGray-700 rounded p-2 px-10 font-bold" on:click="{() => $goto('/dashboard/applications')}"
>Get Started</button
>
{/if}

View File

@ -1,81 +1,74 @@
<script>
import { params, goto, isActive, redirect, url } from "@roxi/routify";
import { fetch } from "@store";
import { toast } from "@zerodevx/svelte-toast";
import Tooltip from "../../../components/Tooltip/Tooltip.svelte";
$: name = $params.name
async function removeService() {
await $fetch(`/api/v1/services/${name}`, {
method: "DELETE",
});
toast.push("Service removed.");
$redirect(`/dashboard/services`);
}
</script>
import { params, goto, isActive, redirect } from "@roxi/routify";
import { fetch } from "@store";
import { toast } from "@zerodevx/svelte-toast";
import Tooltip from "../../../components/Tooltip/Tooltip.svelte";
<nav
class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4"
$: name = $params.name;
async function removeService() {
await $fetch(`/api/v1/services/${name}`, {
method: "DELETE",
});
toast.push("Service removed.");
$redirect(`/dashboard/services`);
}
</script>
<nav
class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4"
>
<Tooltip position="bottom" label="Delete">
<button
title="Delete"
class="icon hover:text-red-500"
on:click="{removeService}"
>
<Tooltip position="bottom" label="Delete" >
<button
title="Delete"
class="icon hover:text-red-500"
on:click="{removeService}"
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Tooltip>
<div class="border border-warmGray-700 h-8"></div>
<Tooltip position="bottom-left" label="Configuration" >
<button
class="icon hover:text-yellow-400"
disabled="{$isActive(`/application/new`)}"
class:text-yellow-400="{$isActive(
`/service/${name}/configuration`,
) || $isActive(`/application/new`)}"
class:bg-warmGray-700="{$isActive(
`/service/${name}/configuration`,
) || $isActive(`/application/new`)}"
on:click="{() =>
$goto(
`/service/${name}/configuration`,
)}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
></path>
</svg>
</button>
</Tooltip>
</nav>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
></path>
</svg>
</button>
</Tooltip>
<div class="border border-warmGray-700 h-8"></div>
<Tooltip position="bottom-left" label="Configuration">
<button
class="icon hover:text-yellow-400"
disabled="{$isActive(`/application/new`)}"
class:text-yellow-400="{$isActive(`/service/${name}/configuration`) ||
$isActive(`/application/new`)}"
class:bg-warmGray-700="{$isActive(`/service/${name}/configuration`) ||
$isActive(`/application/new`)}"
on:click="{() => $goto(`/service/${name}/configuration`)}"
>
<svg
class="w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
></path>
</svg>
</button>
</Tooltip>
</nav>
<div class="text-white">
<slot />
</div>
<div class="text-white">
<slot />
</div>

View File

@ -61,7 +61,7 @@
<div class="space-y-2 max-w-4xl mx-auto px-6" in:fade="{{ duration: 100 }}">
<div class="block text-center py-4">
{#if name === "plausible"}
<Plausible {service}/>
<Plausible service="{service}" />
{/if}
</div>
</div>

View File

@ -1,8 +1,7 @@
<script>
import { params, goto, isActive, redirect, url } from "@roxi/routify";
import { params, redirect } from "@roxi/routify";
import { fetch, newService, initialNewService } from "@store";
import { toast } from "@zerodevx/svelte-toast";
import Tooltip from "../../../../components/Tooltip/Tooltip.svelte";
import { onDestroy } from "svelte";
import Loading from "../../../../components/Loading.svelte";
$: type = $params.type;

View File

@ -1,5 +1,5 @@
<script>
import { isActive, redirect, goto } from "@roxi/routify/runtime";
import { isActive, goto } from "@roxi/routify/runtime";
import { fade } from "svelte/transition";
</script>

View File

@ -38,10 +38,10 @@
<div in:fade="{{ duration: 100 }}">
<div class="max-w-4xl mx-auto px-6 pb-4">
<div>
<div class="text-2xl font-bold py-4 border-gradient w-32 text-white">General</div>
<div class="divide-y divide-gray-200">
<div class="text-2xl font-bold border-gradient w-32 pt-4 text-white">General</div>
<div class=" pt-4">
<div class="px-4 sm:px-6">
<ul class="mt-2 divide-y divide-gray-200">
<ul class="mt-2 divide-y divide-warmGray-800">
<li class="py-4 flex items-center justify-between">
<div class="flex flex-col">
<p class="text-base font-bold text-warmGray-100">

View File

@ -43,7 +43,8 @@ export const fetch = writable(
if (body) {
config.body = JSON.stringify(body)
}
const response = await waitAtLeast(350, window.fetch(url, config))
// const response = await waitAtLeast(350, window.fetch(url, config))
const response = await window.fetch(url, config)
if (response.status >= 200 && response.status <= 299) {
if (response.headers.get('content-type').match(/application\/json/)) {
return await response.json()
@ -77,10 +78,17 @@ export const fetch = writable(
}
}
)
export const activePage = writable({
application: null,
new: false,
mainmenu: null
})
export const session = writable(sessionStore)
export const loggedIn = derived(session, ($session) => {
return $session.token
})
export const githubRepositories = writable([])
export const githubInstallations = writable({})
export const savedBranch = writable()
export const dateOptions = readable({
@ -93,7 +101,7 @@ export const dateOptions = readable({
hour12: false
})
export const deployments = writable({})
export const deployments = writable([])
export const initConf = writable({})
export const application = writable({
@ -149,8 +157,8 @@ export const initialApplication = {
},
repository: {
id: null,
organization: 'new',
name: 'start',
organization: null,
name: null,
branch: null
},
general: {

View File

@ -4,23 +4,29 @@ const defaultBuildAndDeploy = {
}
const templates = {
svelte: {
pack: 'svelte',
...defaultBuildAndDeploy,
directory: 'public',
name: 'Svelte'
},
next: {
pack: 'nodejs',
pack: 'nextjs',
...defaultBuildAndDeploy,
port: 3000,
name: 'Next.js'
name: 'NextJS'
},
nuxt: {
pack: 'nodejs',
pack: 'nuxtjs',
...defaultBuildAndDeploy,
port: 3000,
name: 'Nuxt'
name: 'NuxtJS'
},
'react-scripts': {
pack: 'static',
pack: 'react',
...defaultBuildAndDeploy,
directory: 'build',
name: 'Create React'
name: 'React'
},
'parcel-bundler': {
pack: 'static',
@ -29,22 +35,22 @@ const templates = {
name: 'Parcel'
},
'@vue/cli-service': {
pack: 'static',
pack: 'vuejs',
...defaultBuildAndDeploy,
directory: 'dist',
name: 'Vue CLI'
name: 'Vue'
},
gatsby: {
pack: 'static',
pack: 'gatsby',
...defaultBuildAndDeploy,
directory: 'public',
name: 'Gatsby'
},
'preact-cli': {
pack: 'static',
pack: 'react',
...defaultBuildAndDeploy,
directory: 'build',
name: 'Preact CLI'
name: 'Preact'
}
}

View File

@ -16,7 +16,7 @@ module.exports = {
],
preserveHtmlElements: true,
options: {
safelist: [/svelte-/, 'border-green-500', 'border-yellow-300', 'border-red-500', 'hover:border-green-500', 'hover:border-red-200', 'hover:bg-red-200'],
safelist: [/svelte-/, 'border-green-500', 'border-yellow-300', 'border-red-500', 'hover:border-green-500', 'hover:border-red-200', 'hover:bg-red-200', 'hover:bg-warmGray-900', 'hover:bg-transparent'],
defaultExtractor: (content) => {
// WARNING: tailwindExtractor is internal tailwind api
// if this breaks after a tailwind update, report to svite repo