Compare commits

...

13 Commits

Author SHA1 Message Date
Andras Bacsai
2d0f22b379 v1.0.22 (#67) 2021-06-24 23:31:08 +02:00
Andras Bacsai
a8e9668c2b Haha 2021-06-22 12:19:12 +02:00
Andras Bacsai
425feba0e2 It's working! 2021-06-22 12:12:11 +02:00
Andras Bacsai
c09b8d888f Hmm, it should work now, right? (Please... work..) 2021-06-22 12:07:51 +02:00
Andras Bacsai
748e691a58 Hmm, ok 2021-06-22 12:02:06 +02:00
Andras Bacsai
f8c81ff95f Nooo 2021-06-22 11:54:19 +02:00
Andras Bacsai
d11c4a3cd7 Fix the fix of fix 2021-06-22 11:45:46 +02:00
Andras Bacsai
3f3ea151ef Fixes are not fixing 2021-06-22 11:40:02 +02:00
Andras Bacsai
7e2f68870c Hm, soemthing is not working 2021-06-22 11:20:21 +02:00
Andras Bacsai
df41cf14da Fix login again 2021-06-22 11:12:30 +02:00
Andras Bacsai
111370c025 Fix email login 2021-06-22 10:56:38 +02:00
Andras Bacsai
bcb2ba0b1b Fix Get Started button 2021-06-22 10:44:08 +02:00
Andras Bacsai
807d526ffa v1.0.21 (#66) 2021-06-22 10:19:20 +02:00
28 changed files with 2836 additions and 1170 deletions

View File

@@ -54,7 +54,6 @@ With Github integration
- [VSCode Server](https://github.com/cdr/code-server) - [VSCode Server](https://github.com/cdr/code-server)
- [MinIO](https://min.io) - [MinIO](https://min.io)
## Support ## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) - Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)

View File

@@ -1,12 +1,12 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.", "description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.",
"version": "1.0.20", "version": "1.0.22",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d", "dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d",
"dev:docker:stop": "docker-compose -f docker-compose-dev.yml down", "dev:docker:stop": "docker-compose -f docker-compose-dev.yml down",
"dev": "NODE_ENV=development svelte-kit dev --host 0.0.0.0", "dev": "TAILWIND_MODE=watch NODE_ENV=development svelte-kit dev --host 0.0.0.0",
"build": "NODE_ENV=production svelte-kit build", "build": "NODE_ENV=production svelte-kit build",
"preview": "svelte-kit preview", "preview": "svelte-kit preview",
"start": "node build", "start": "node build",
@@ -39,6 +39,7 @@
"dependencies": { "dependencies": {
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@zerodevx/svelte-toast": "^0.3.0", "@zerodevx/svelte-toast": "^0.3.0",
"bcrypt": "^5.0.1",
"commander": "^7.2.0", "commander": "^7.2.0",
"compare-versions": "^3.6.0", "compare-versions": "^3.6.0",
"cookie": "^0.4.1", "cookie": "^0.4.1",
@@ -49,6 +50,7 @@
"generate-password": "^1.6.0", "generate-password": "^1.6.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"microtip": "^0.2.2",
"mongoose": "^5.12.13", "mongoose": "^5.12.13",
"shelljs": "^0.8.4", "shelljs": "^0.8.4",
"svelte-kit-cookie-session": "^1.0.6", "svelte-kit-cookie-session": "^1.0.6",

2660
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,6 @@
<link rel="dns-prefetch" href="https://cdn.coollabs.io/" /> <link rel="dns-prefetch" href="https://cdn.coollabs.io/" />
<link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" /> <link rel="preconnect" href="https://cdn.coollabs.io/" crossorigin="" />
<link rel="stylesheet" href="https://cdn.coollabs.io/fonts/montserrat/montserrat.css" /> <link rel="stylesheet" href="https://cdn.coollabs.io/fonts/montserrat/montserrat.css" />
<link rel="stylesheet" href="https://cdn.coollabs.io/css/microtip-0.2.2.min.css" />
%svelte.head% %svelte.head%
</head> </head>
<body> <body>

View File

@@ -52,8 +52,7 @@
); );
} }
} catch (error) { } catch (error) {
// console.log(error); browser && toast.push(error.error || error || 'Ooops something went wrong.');
// toast.push(error.error || error || 'Ooops something went wrong.');
} }
} }

View File

@@ -1,80 +1,83 @@
<script>
export let github = false;
export let githubLoadingText = 'Loading GitHub...';
export let fullscreen = true;
</script>
{#if fullscreen}
{#if github}
<div class="fixed left-0 top-0 flex flex-wrap content-center h-full w-full">
<div class="main flex justify-center items-center">
<div class="w-64">
<svg
class=" w-28 animate-bounce mx-auto"
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"
><path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
/></svg
>
<div class="text-xl font-bold text-center">
{githubLoadingText}
</div>
</div>
</div>
</div>
{:else}
<div class="main fixed left-0 top-0 flex flex-wrap content-center h-full">
<span class="loader" />
</div>
{/if}
{:else}
<div class="main h-64 py-24 left-0 top-0 flex flex-wrap content-center mx-auto">
<span class="loader" />
</div>
{/if}
<style lang="postcss"> <style lang="postcss">
.loader { .loader {
width: 8px; width: 8px;
height: 40px; height: 40px;
border-radius: 4px; border-radius: 4px;
display: block; display: block;
margin: 20px auto; margin: 20px auto;
position: relative; position: relative;
background: currentColor; background: currentColor;
color: #fff; color: #fff;
box-sizing: border-box; box-sizing: border-box;
animation: animloader 0.3s 0.3s linear infinite alternate; animation: animloader 0.3s 0.3s linear infinite alternate;
} }
.loader::after, .loader::after,
.loader::before { .loader::before {
content: ""; content: '';
width: 8px; width: 8px;
height: 40px; height: 40px;
border-radius: 4px; border-radius: 4px;
background: currentColor; background: currentColor;
position: absolute; position: absolute;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
left: 20px; left: 20px;
box-sizing: border-box; box-sizing: border-box;
animation: animloader 0.3s 0.45s linear infinite alternate; animation: animloader 0.3s 0.45s linear infinite alternate;
} }
.loader::before { .loader::before {
left: -20px; left: -20px;
animation-delay: 0s; animation-delay: 0s;
} }
@keyframes animloader { @keyframes animloader {
0% { 0% {
height: 48px; height: 48px;
} }
100% { 100% {
height: 4px; height: 4px;
} }
} }
</style> </style>
<script>
export let github = false;
export let githubLoadingText = "Loading GitHub...";
export let fullscreen = true;
</script>
{#if fullscreen}
{#if github}
<div class="fixed left-0 top-0 flex flex-wrap content-center h-full w-full">
<div class="main flex justify-center items-center">
<div class="w-64">
<svg
class=" w-28 animate-bounce mx-auto"
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"
><path
d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"
></path></svg
>
<div class="text-xl font-bold text-center">
{githubLoadingText}
</div>
</div>
</div>
</div>
{:else}
<div class="main fixed left-0 top-0 flex flex-wrap content-center h-full">
<span class=" loader"></span>
</div>
{/if}
{/if}

View File

@@ -1,54 +1,56 @@
<script> <script>
export let value; export let value;
let showPassword = false; let showPassword = false;
export let isEditable = false;
</script> </script>
<div class="relative w-full"> <div class="relative w-full">
<input {#if showPassword}
type="{showPassword ? 'text' : 'password'}" <input type="text" class="w-full" bind:value disabled={!isEditable} />
class="w-full " {:else}
{value} <input type="password" class="w-full" bind:value disabled={!isEditable} />
disabled {/if}
/>
<div <div
class="absolute top-0 my-2 mx-2 right-0 cursor-pointer text-warmGray-600 hover:text-white" class="absolute top-0 my-2 mx-2 right-0 cursor-pointer text-warmGray-600 hover:text-white"
on:click="{() => showPassword = !showPassword}" on:click={() => (showPassword = !showPassword)}
> >
{#if showPassword} {#if showPassword}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6" class="h-6 w-6"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
> >
<path <path
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
stroke-width="2" stroke-width="2"
d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"
></path> />
</svg> </svg>
{:else} {:else}
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6" class="h-6 w-6"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
> >
<path <path
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
stroke-width="2" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path> d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
<path />
stroke-linecap="round" <path
stroke-linejoin="round" stroke-linecap="round"
stroke-width="2" stroke-linejoin="round"
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" stroke-width="2"
></path> d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
</svg> />
{/if} </svg>
</div> {/if}
</div>
</div> </div>

View File

@@ -98,10 +98,10 @@ export async function handle({ request, resolve }) {
try { try {
session = initializeSession(request.headers, { session = initializeSession(request.headers, {
secret: SECRETS_ENCRYPTION_KEY, secret: SECRETS_ENCRYPTION_KEY,
cookie: { path: '/' } cookie: { path: '/', secure: true }
}); });
} catch (error) { } catch (error) {
console.log(error) console.log(error);
return { return {
status: 302, status: 302,
headers: { headers: {
@@ -124,7 +124,7 @@ export async function handle({ request, resolve }) {
if (!session['set-cookie']) { if (!session['set-cookie']) {
if (!session?.data?.coolToken && !publicPages.includes(request.path)) { if (!session?.data?.coolToken && !publicPages.includes(request.path)) {
return { return {
status: 301, status: 302,
headers: { headers: {
location: '/' location: '/'
} }
@@ -146,6 +146,6 @@ export function getSession(request) {
isLoggedIn: data && Object.keys(data).length !== 0 ? true : false, isLoggedIn: data && Object.keys(data).length !== 0 ? true : false,
expires: data.expires, expires: data.expires,
coolToken: data.coolToken, coolToken: data.coolToken,
ghToken: data.ghToken ghToken: data.ghToken || null
}; };
} }

View File

@@ -70,9 +70,9 @@ export default async function (configuration, imageChanged) {
} }
async function purgeImagesAsync(found) { async function purgeImagesAsync(found) {
await delay(10000); await delay(10000);
await purgeImagesContainers(found, true); await purgeImagesContainers(found);
} }
purgeImagesAsync(configuration); //purgeImagesAsync(configuration);
await saveAppLog('### Published done!', configuration); await saveAppLog('### Published done!', configuration);
} }

View File

@@ -1,2 +1,8 @@
export const publicPages = ['/', '/api/v1/login/github/app', '/api/v1/webhooks/deploy', '/success']; export const publicPages = [
'/',
'/api/v1/login/github/app',
'/api/v1/webhooks/deploy',
'/success',
'/api/v1/login/email'
];
export const VITE_GITHUB_APP_NAME = import.meta.env.VITE_GITHUB_APP_NAME; export const VITE_GITHUB_APP_NAME = import.meta.env.VITE_GITHUB_APP_NAME;

View File

@@ -4,12 +4,16 @@ export interface IUser extends Document {
email: string; email: string;
avatar?: string; avatar?: string;
uid: string; uid: string;
type: string;
password: string;
} }
const UserSchema = new Schema({ const UserSchema = new Schema({
email: { type: String, required: true, unique: true }, email: { type: String, required: true, unique: true },
avatar: { type: String }, avatar: { type: String },
uid: { type: String, required: true } uid: { type: String, required: true },
type: { type: String, required: true, default: 'github' },
password: { type: String }
}); });
UserSchema.set('timestamps', true); UserSchema.set('timestamps', true);

View File

@@ -9,7 +9,7 @@
if (!publicPages.includes(path)) { if (!publicPages.includes(path)) {
if (!session.session.isLoggedIn) { if (!session.session.isLoggedIn) {
return { return {
status: 301, status: 302,
redirect: '/' redirect: '/'
}; };
} }
@@ -17,16 +17,16 @@
} }
if (!publicPages.includes(path)) { if (!publicPages.includes(path)) {
return { return {
status: 301, status: 302,
redirect: '/' redirect: '/'
}; };
} }
return {}; return {};
} }
</script> </script>
<script lang="ts"> <script lang="ts">
import 'microtip/microtip.css';
import '../app.postcss'; import '../app.postcss';
export let initDashboard; export let initDashboard;
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@@ -37,8 +37,9 @@
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import compareVersions from 'compare-versions'; import compareVersions from 'compare-versions';
import packageJson from '../../package.json'; import packageJson from '../../package.json';
import { dashboard } from '$store'; import { dashboard, settings } from '$store';
import { browser } from '$app/env'; import { browser } from '$app/env';
$settings.clientId = import.meta.env.VITE_GITHUB_APP_CLIENTID !== 'null' ? import.meta.env.VITE_GITHUB_APP_CLIENTID : null
$dashboard = initDashboard; $dashboard = initDashboard;
const branch = const branch =
process.env.NODE_ENV === 'production' && process.env.NODE_ENV === 'production' &&
@@ -53,7 +54,7 @@
let upgradeDisabled = false; let upgradeDisabled = false;
let upgradeDone = false; let upgradeDone = false;
let showAck = false; let showAck = false;
let globalFeatureFlag = browser && localStorage.getItem('globalFeatureFlag') let globalFeatureFlag = browser && localStorage.getItem('globalFeatureFlag');
const options = { const options = {
duration: 2000 duration: 2000
}; };
@@ -104,7 +105,6 @@
localStorage.setItem('automaticErrorReportsAck', 'true'); localStorage.setItem('automaticErrorReportsAck', 'true');
showAck = false; showAck = false;
} }
</script> </script>
<SvelteToast {options} /> <SvelteToast {options} />
@@ -136,69 +136,136 @@
class:border-purple-500={$page.path === '/dashboard/databases'} class:border-purple-500={$page.path === '/dashboard/databases'}
> >
<div class="w-10 pt-4 pb-4"><img src="/favicon.png" alt="coolLabs logo" /></div> <div class="w-10 pt-4 pb-4"><img src="/favicon.png" alt="coolLabs logo" /></div>
{#if $settings.clientId}
<Tooltip position="right" label="Applications"> <Tooltip position="right" label="Applications">
<div <div
class="p-2 hover:bg-warmGray-700 rounded hover:text-green-500 mt-4 transition-all duration-100 cursor-pointer" 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')} on:click={() => goto('/dashboard/applications')}
class:text-green-500={$page.path === '/dashboard/applications' || class:text-green-500={$page.path === '/dashboard/applications' ||
$page.path.startsWith('/application')} $page.path.startsWith('/application')}
class:bg-warmGray-700={$page.path === '/dashboard/applications' || class:bg-warmGray-700={$page.path === '/dashboard/applications' ||
$page.path.startsWith('/application')} $page.path.startsWith('/application')}
>
<svg
class="w-8"
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"
><rect x="4" y="4" width="16" height="16" rx="2" ry="2" /><rect
x="9"
y="9"
width="6"
height="6"
/><line x1="9" y1="1" x2="9" y2="4" /><line x1="15" y1="1" x2="15" y2="4" /><line
x1="9"
y1="20"
x2="9"
y2="23"
/><line x1="15" y1="20" x2="15" y2="23" /><line x1="20" y1="9" x2="23" y2="9" /><line
x1="20"
y1="14"
x2="23"
y2="14"
/><line x1="1" y1="9" x2="4" y2="9" /><line x1="1" y1="14" x2="4" y2="14" /></svg
> >
</div> <svg
</Tooltip> class="w-8"
<Tooltip position="right" label="Databases"> xmlns="http://www.w3.org/2000/svg"
<div viewBox="0 0 24 24"
class="p-2 hover:bg-warmGray-700 rounded hover:text-purple-500 my-4 transition-all duration-100 cursor-pointer" fill="none"
on:click={() => goto('/dashboard/databases')} stroke="currentColor"
class:text-purple-500={$page.path === '/dashboard/databases' || stroke-width="2"
$page.path.startsWith('/database')}
class:bg-warmGray-700={$page.path === '/dashboard/databases' ||
$page.path.startsWith('/database')}
>
<svg
class="w-8"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"
><rect x="4" y="4" width="16" height="16" rx="2" ry="2" /><rect
x="9"
y="9"
width="6"
height="6"
/><line x1="9" y1="1" x2="9" y2="4" /><line x1="15" y1="1" x2="15" y2="4" /><line
x1="9"
y1="20"
x2="9"
y2="23"
/><line x1="15" y1="20" x2="15" y2="23" /><line
x1="20"
y1="9"
x2="23"
y2="9"
/><line x1="20" y1="14" x2="23" y2="14" /><line x1="1" y1="9" x2="4" y2="9" /><line
x1="1"
y1="14"
x2="4"
y2="14"
/></svg
>
</div>
</Tooltip>
<Tooltip position="right" label="Databases">
<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={$page.path === '/dashboard/databases' ||
$page.path.startsWith('/database')}
class:bg-warmGray-700={$page.path === '/dashboard/databases' ||
$page.path.startsWith('/database')}
>
<svg
class="w-8"
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="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
/>
</svg>
</div>
</Tooltip>
{:else}
<Tooltip
position="right"
label="Applications disabled, no GitHub Integration detected"
size="large"
>
<div class="p-2 text-warmGray-700 mt-4 transition-all duration-100 cursor-pointer">
<svg
class="w-8"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2" stroke-width="2"
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" stroke-linecap="round"
/> stroke-linejoin="round"
</svg> ><rect x="4" y="4" width="16" height="16" rx="2" ry="2" /><rect
</div> x="9"
</Tooltip> y="9"
width="6"
height="6"
/><line x1="9" y1="1" x2="9" y2="4" /><line x1="15" y1="1" x2="15" y2="4" /><line
x1="9"
y1="20"
x2="9"
y2="23"
/><line x1="15" y1="20" x2="15" y2="23" /><line
x1="20"
y1="9"
x2="23"
y2="9"
/><line x1="20" y1="14" x2="23" y2="14" /><line x1="1" y1="9" x2="4" y2="9" /><line
x1="1"
y1="14"
x2="4"
y2="14"
/></svg
>
</div>
</Tooltip>
<Tooltip position="right" label="Databases disabled, no GitHub Integration detected" size="large">
<div
class="p-2 text-warmGray-700 my-4 transition-all duration-100 cursor-pointer"
>
<svg
class="w-8"
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="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
/>
</svg>
</div>
</Tooltip>
{/if}
<Tooltip position="right" label="Services"> <Tooltip position="right" label="Services">
<div <div
class="p-2 hover:bg-warmGray-700 rounded hover:text-blue-500 transition-all duration-100 cursor-pointer" class="p-2 hover:bg-warmGray-700 rounded hover:text-blue-500 transition-all duration-100 cursor-pointer"
@@ -226,29 +293,29 @@
</Tooltip> </Tooltip>
<div class="flex-1" /> <div class="flex-1" />
{#if globalFeatureFlag} {#if globalFeatureFlag}
<Tooltip position="right" label="Server(s)"> <Tooltip position="right" label="Servers">
<div <div
class="p-2 hover:bg-warmGray-700 rounded hover:text-red-500 mb-4 transition-all duration-100 cursor-pointer" class="p-2 hover:bg-warmGray-700 rounded hover:text-red-500 mb-4 transition-all duration-100 cursor-pointer"
on:click={() => goto('/servers')} on:click={() => goto('/servers')}
class:text-red-500={$page.path === '/servers' || $page.path.startsWith('/servers')} class:text-red-500={$page.path === '/servers' || $page.path.startsWith('/servers')}
class:bg-warmGray-700={$page.path === '/servers' || $page.path.startsWith('/servers')} class:bg-warmGray-700={$page.path === '/servers' || $page.path.startsWith('/servers')}
>
<svg
class="w-8"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
> >
<path <svg
stroke-linecap="round" class="w-8"
stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"
stroke-width="2" fill="none"
d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" viewBox="0 0 24 24"
/> stroke="currentColor"
</svg> >
</div> <path
</Tooltip> stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"
/>
</svg>
</div>
</Tooltip>
{/if} {/if}
<Tooltip position="right" label="Settings"> <Tooltip position="right" label="Settings">
<button <button

View File

@@ -0,0 +1,104 @@
import mongoose from 'mongoose';
import Settings from '$models/Settings';
import User from '$models/User';
import bcrypt from 'bcrypt';
import cuid from 'cuid';
import jsonwebtoken from 'jsonwebtoken';
import type { Request } from '@sveltejs/kit';
const saltRounds = 15;
export async function post(request: Request) {
const { email, password } = request.body;
const { JWT_SIGN_KEY } = process.env;
const settings = await Settings.findOne({ applicationName: 'coolify' });
const registeredUsers = await User.find().countDocuments();
const foundUser = await User.findOne({ email });
try {
let uid = cuid();
if (foundUser) {
if (foundUser.type === 'github') {
return {
status: 500,
body: {
error: 'Wrong password or email address.'
}
};
}
uid = foundUser.uid;
if (!(await bcrypt.compare(password, foundUser.password))) {
return {
status: 500,
body: {
error: 'Wrong password or email address.'
}
};
}
} else {
if (registeredUsers === 0) {
const newUser = new User({
_id: new mongoose.Types.ObjectId(),
email,
uid,
type: 'email',
password: await bcrypt.hash(password, saltRounds)
});
const defaultSettings = new Settings({
_id: new mongoose.Types.ObjectId()
});
try {
await newUser.save();
await defaultSettings.save();
} catch (error) {
return {
status: 500,
error: error.message || error
};
}
} else {
if (!settings?.allowRegistration) {
return {
status: 500,
body: {
error: 'Registration disabled, enable it in settings.'
}
};
} else {
const newUser = new User({
_id: new mongoose.Types.ObjectId(),
email,
uid,
type: 'email',
password: await bcrypt.hash(password, saltRounds)
});
try {
await newUser.save();
} catch (error) {
return {
status: 500,
error: error.message || error
};
}
}
}
}
const coolToken = jsonwebtoken.sign({}, JWT_SIGN_KEY, {
expiresIn: 15778800,
algorithm: 'HS256',
audience: 'coolLabs',
issuer: 'coolLabs',
jwtid: uid,
subject: `User:${uid}`,
notBefore: -1000
});
request.locals.session.data = { coolToken, ghToken: null };
return {
status: 200,
body: {
message: 'Successfully logged in.'
}
};
} catch (error) {
return { status: 500, body: { error: error.message || error } };
}
}

View File

@@ -33,7 +33,8 @@ export async function get(request: Request) {
_id: new mongoose.Types.ObjectId(), _id: new mongoose.Types.ObjectId(),
email, email,
avatar: avatar_url, avatar: avatar_url,
uid uid,
type: 'github'
}); });
const defaultSettings = new Settings({ const defaultSettings = new Settings({
_id: new mongoose.Types.ObjectId() _id: new mongoose.Types.ObjectId()
@@ -68,7 +69,8 @@ export async function get(request: Request) {
_id: new mongoose.Types.ObjectId(), _id: new mongoose.Types.ObjectId(),
email, email,
avatar: avatar_url, avatar: avatar_url,
uid uid,
type: 'github'
}); });
try { try {
await newUser.save(); await newUser.save();

View File

@@ -2,24 +2,26 @@ import { saveServerLog } from '$lib/api/applications/logging';
import { execShellAsync } from '$lib/api/common'; import { execShellAsync } from '$lib/api/common';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function post(request: Request) { export async function post(request: Request) {
try { try {
const output = await execShellAsync('docker builder prune -af') const output = await execShellAsync('docker builder prune -af');
return { return {
status: 200, status: 200,
body: { body: {
message: 'OK', message: 'OK',
output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() output: output
} .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '')
} .split('\n')
} catch (error) { .pop()
await saveServerLog(error); }
return { };
status: 500, } catch (error) {
body: { await saveServerLog(error);
error: error.message || error return {
} status: 500,
}; body: {
} error: error.message || error
}
};
}
} }

View File

@@ -2,24 +2,26 @@ import { saveServerLog } from '$lib/api/applications/logging';
import { execShellAsync } from '$lib/api/common'; import { execShellAsync } from '$lib/api/common';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function post(request: Request) { export async function post(request: Request) {
try { try {
const output = await execShellAsync('docker container prune -f') const output = await execShellAsync('docker container prune -f');
return { return {
status: 200, status: 200,
body: { body: {
message: 'OK', message: 'OK',
output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() output: output
} .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '')
} .split('\n')
} catch (error) { .pop()
await saveServerLog(error); }
return { };
status: 500, } catch (error) {
body: { await saveServerLog(error);
error: error.message || error return {
} status: 500,
}; body: {
} error: error.message || error
}
};
}
} }

View File

@@ -2,24 +2,26 @@ import { saveServerLog } from '$lib/api/applications/logging';
import { execShellAsync } from '$lib/api/common'; import { execShellAsync } from '$lib/api/common';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function post(request: Request) { export async function post(request: Request) {
try { try {
const output = await execShellAsync('docker image prune -af') const output = await execShellAsync('docker image prune -af');
return { return {
status: 200, status: 200,
body: { body: {
message: 'OK', message: 'OK',
output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() output: output
} .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '')
} .split('\n')
} catch (error) { .pop()
await saveServerLog(error); }
return { };
status: 500, } catch (error) {
body: { await saveServerLog(error);
error: error.message || error return {
} status: 500,
}; body: {
} error: error.message || error
}
};
}
} }

View File

@@ -2,24 +2,26 @@ import { saveServerLog } from '$lib/api/applications/logging';
import { execShellAsync } from '$lib/api/common'; import { execShellAsync } from '$lib/api/common';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function post(request: Request) { export async function post(request: Request) {
try { try {
const output = await execShellAsync('docker volume prune -f') const output = await execShellAsync('docker volume prune -f');
return { return {
status: 200, status: 200,
body: { body: {
message: 'OK', message: 'OK',
output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() output: output
} .replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, '')
} .split('\n')
} catch (error) { .pop()
await saveServerLog(error); }
return { };
status: 500, } catch (error) {
body: { await saveServerLog(error);
error: error.message || error return {
} status: 500,
}; body: {
} error: error.message || error
}
};
}
} }

View File

@@ -2,26 +2,26 @@ import { saveServerLog } from '$lib/api/applications/logging';
import { execShellAsync } from '$lib/api/common'; import { execShellAsync } from '$lib/api/common';
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import systeminformation from 'systeminformation' import systeminformation from 'systeminformation';
export async function get(request: Request) { export async function get(request: Request) {
try { try {
const df = await execShellAsync( const df = await execShellAsync(`docker system df --format '{{ json . }}'`);
`docker system df --format '{{ json . }}'`
);
const dockerReclaimable = df const dockerReclaimable = df
.split('\n') .split('\n')
.filter((n) => n) .filter((n) => n)
.map((s) => JSON.parse(s)) .map((s) => JSON.parse(s));
return { return {
status: 200, status: 200,
body: { body: {
hostname: await (await systeminformation.osInfo()).hostname, hostname: await (await systeminformation.osInfo()).hostname,
filesystems: await (await systeminformation.fsSize()).filter(fs => !fs.fs.match('/dev/loop') || !fs.fs.match('/var/lib/docker/')), filesystems: await (
await systeminformation.fsSize()
).filter((fs) => !fs.fs.match('/dev/loop') || !fs.fs.match('/var/lib/docker/')),
dockerReclaimable dockerReclaimable
} }
} };
} catch (error) { } catch (error) {
await saveServerLog(error); await saveServerLog(error);
return { return {

View File

@@ -59,8 +59,8 @@ export async function post(request: Request) {
volumes: { volumes: {
[`${deployId}-code-server-data`]: { [`${deployId}-code-server-data`]: {
external: true external: true
}, }
}, }
}; };
await execShellAsync(`mkdir -p ${workdir}`); await execShellAsync(`mkdir -p ${workdir}`);
await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack));

View File

@@ -1,6 +1,6 @@
import { execShellAsync } from '$lib/api/common'; import { execShellAsync } from '$lib/api/common';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import yaml from "js-yaml" import yaml from 'js-yaml';
export async function get(request: Request) { export async function get(request: Request) {
// const { POSTGRESQL_USERNAME, POSTGRESQL_PASSWORD, POSTGRESQL_DATABASE } = JSON.parse( // const { POSTGRESQL_USERNAME, POSTGRESQL_PASSWORD, POSTGRESQL_DATABASE } = JSON.parse(
@@ -15,10 +15,12 @@ export async function get(request: Request) {
.trim() .trim()
.split('\n'); .split('\n');
const codeServer = containers.find((container) => container.startsWith('code-server')); const codeServer = containers.find((container) => container.startsWith('code-server'));
const configYaml = yaml.load(await execShellAsync( const configYaml = yaml.load(
`docker exec ${codeServer} cat /home/coder/.config/code-server/config.yaml` await execShellAsync(
)) `docker exec ${codeServer} cat /home/coder/.config/code-server/config.yaml`
return { )
);
return {
status: 200, status: 200,
body: { message: 'OK', password: configYaml.password } body: { message: 'OK', password: configYaml.password }
}; };

View File

@@ -13,9 +13,14 @@ export async function post(request: Request) {
const workdir = '/tmp/minio'; const workdir = '/tmp/minio';
const deployId = 'minio'; const deployId = 'minio';
const secrets = [ const secrets = [
{ name: 'MINIO_ROOT_USER', value: generator.generate({ length: 12, numbers: true, strict: true }) }, {
{ name: 'MINIO_ROOT_PASSWORD', value: generator.generate({ length: 24, numbers: true, strict: true }) } name: 'MINIO_ROOT_USER',
value: generator.generate({ length: 12, numbers: true, strict: true })
},
{
name: 'MINIO_ROOT_PASSWORD',
value: generator.generate({ length: 24, numbers: true, strict: true })
}
]; ];
const generateEnvsMinIO = {}; const generateEnvsMinIO = {};
for (const secret of secrets) generateEnvsMinIO[secret.name] = secret.value; for (const secret of secrets) generateEnvsMinIO[secret.name] = secret.value;
@@ -36,18 +41,18 @@ export async function post(request: Request) {
'type=service', 'type=service',
'serviceName=minio', 'serviceName=minio',
'configuration=' + 'configuration=' +
JSON.stringify({ JSON.stringify({
baseURL, baseURL,
generateEnvsMinIO generateEnvsMinIO
}), }),
'traefik.enable=true', 'traefik.enable=true',
'traefik.http.services.' + deployId + '.loadbalancer.server.port=9000', 'traefik.http.services.' + deployId + '.loadbalancer.server.port=9000',
'traefik.http.routers.' + deployId + '.entrypoints=websecure', 'traefik.http.routers.' + deployId + '.entrypoints=websecure',
'traefik.http.routers.' + 'traefik.http.routers.' +
deployId + deployId +
'.rule=Host(`' + '.rule=Host(`' +
traefikURL + traefikURL +
'`) && PathPrefix(`/`)', '`) && PathPrefix(`/`)',
'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt',
'traefik.http.routers.' + deployId + '.middlewares=global-compress' 'traefik.http.routers.' + deployId + '.middlewares=global-compress'
] ]
@@ -62,8 +67,8 @@ export async function post(request: Request) {
volumes: { volumes: {
[`${deployId}-minio-data`]: { [`${deployId}-minio-data`]: {
external: true external: true
}, }
}, }
}; };
await execShellAsync(`mkdir -p ${workdir}`); await execShellAsync(`mkdir -p ${workdir}`);
await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack));

View File

@@ -7,167 +7,166 @@ import { baseServiceConfiguration } from '$lib/api/applications/common';
import { cleanupTmp, execShellAsync } from '$lib/api/common'; import { cleanupTmp, execShellAsync } from '$lib/api/common';
export async function post(request: Request) { export async function post(request: Request) {
let { baseURL, remoteDB, database, wordpressExtraConfiguration } = request.body; let { baseURL, remoteDB, database, wordpressExtraConfiguration } = request.body;
const traefikURL = baseURL; const traefikURL = baseURL;
baseURL = `https://${baseURL}`; baseURL = `https://${baseURL}`;
console.log({ baseURL, remoteDB, database, wordpressExtraConfiguration }) console.log({ baseURL, remoteDB, database, wordpressExtraConfiguration });
const workdir = '/tmp/wordpress'; const workdir = '/tmp/wordpress';
const deployId = `wp-${generator.generate({ length: 5, numbers: true, strict: true })}` const deployId = `wp-${generator.generate({ length: 5, numbers: true, strict: true })}`;
const defaultDatabaseName = generator.generate({ length: 12, numbers: true, strict: true }) const defaultDatabaseName = generator.generate({ length: 12, numbers: true, strict: true });
const defaultDatabaseHost = `${deployId}-mysql` const defaultDatabaseHost = `${deployId}-mysql`;
const defaultDatabaseUser = generator.generate({ length: 12, numbers: true, strict: true }) const defaultDatabaseUser = generator.generate({ length: 12, numbers: true, strict: true });
const defaultDatabasePassword = generator.generate({ length: 24, numbers: true, strict: true }) const defaultDatabasePassword = generator.generate({ length: 24, numbers: true, strict: true });
const defaultDatabaseRootPassword = generator.generate({ length: 24, numbers: true, strict: true }) const defaultDatabaseRootPassword = generator.generate({
const defaultDatabaseRootUser = generator.generate({ length: 12, numbers: true, strict: true }) length: 24,
let secrets = [ numbers: true,
{ name: 'WORDPRESS_DB_HOST', value: defaultDatabaseHost }, strict: true
{ name: 'WORDPRESS_DB_USER', value: defaultDatabaseUser }, });
{ name: 'WORDPRESS_DB_PASSWORD', value: defaultDatabasePassword }, const defaultDatabaseRootUser = generator.generate({ length: 12, numbers: true, strict: true });
{ name: 'WORDPRESS_DB_NAME', value: defaultDatabaseName }, let secrets = [
{ name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration } { name: 'WORDPRESS_DB_HOST', value: defaultDatabaseHost },
]; { name: 'WORDPRESS_DB_USER', value: defaultDatabaseUser },
{ name: 'WORDPRESS_DB_PASSWORD', value: defaultDatabasePassword },
{ name: 'WORDPRESS_DB_NAME', value: defaultDatabaseName },
{ name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration }
];
const generateEnvsMySQL = { const generateEnvsMySQL = {
MYSQL_ROOT_PASSWORD: defaultDatabaseRootPassword, MYSQL_ROOT_PASSWORD: defaultDatabaseRootPassword,
MYSQL_ROOT_USER: defaultDatabaseRootUser, MYSQL_ROOT_USER: defaultDatabaseRootUser,
MYSQL_USER: defaultDatabaseUser, MYSQL_USER: defaultDatabaseUser,
MYSQL_PASSWORD: defaultDatabasePassword, MYSQL_PASSWORD: defaultDatabasePassword,
MYSQL_DATABASE: defaultDatabaseName MYSQL_DATABASE: defaultDatabaseName
}; };
const image = 'bitnami/mysql:8.0'; const image = 'bitnami/mysql:8.0';
const volume = `${deployId}-mysql-data:/bitnami/mysql/data`; const volume = `${deployId}-mysql-data:/bitnami/mysql/data`;
if (remoteDB) { if (remoteDB) {
secrets = [ secrets = [
{ name: 'WORDPRESS_DB_HOST', value: database.host }, { name: 'WORDPRESS_DB_HOST', value: database.host },
{ name: 'WORDPRESS_DB_USER', value: database.user }, { name: 'WORDPRESS_DB_USER', value: database.user },
{ name: 'WORDPRESS_DB_PASSWORD', value: database.password }, { name: 'WORDPRESS_DB_PASSWORD', value: database.password },
{ name: 'WORDPRESS_DB_NAME', value: database.name }, { name: 'WORDPRESS_DB_NAME', value: database.name },
{ name: 'WORDPRESS_TABLE_PREFIX', value: database.tablePrefix }, { name: 'WORDPRESS_TABLE_PREFIX', value: database.tablePrefix },
{ name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration } { name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration }
] ];
} }
const generateEnvsWordpress = {}; const generateEnvsWordpress = {};
for (const secret of secrets) generateEnvsWordpress[secret.name] = secret.value; for (const secret of secrets) generateEnvsWordpress[secret.name] = secret.value;
let stack = { let stack = {
version: '3.8', version: '3.8',
services: { services: {
[deployId]: { [deployId]: {
image: 'wordpress', image: 'wordpress',
networks: [`${docker.network}`], networks: [`${docker.network}`],
environment: generateEnvsWordpress, environment: generateEnvsWordpress,
volumes: [`${deployId}-wordpress-data:/var/www/html`], volumes: [`${deployId}-wordpress-data:/var/www/html`],
deploy: { deploy: {
...baseServiceConfiguration, ...baseServiceConfiguration,
labels: [ labels: [
'managedBy=coolify', 'managedBy=coolify',
'type=service', 'type=service',
'serviceName=' + deployId, 'serviceName=' + deployId,
'configuration=' + 'configuration=' +
JSON.stringify({ JSON.stringify({
deployId, deployId,
baseURL, baseURL,
generateEnvsWordpress generateEnvsWordpress
}), }),
'traefik.enable=true', 'traefik.enable=true',
'traefik.http.services.' + deployId + '.loadbalancer.server.port=80', 'traefik.http.services.' + deployId + '.loadbalancer.server.port=80',
'traefik.http.routers.' + deployId + '.entrypoints=websecure', 'traefik.http.routers.' + deployId + '.entrypoints=websecure',
'traefik.http.routers.' + 'traefik.http.routers.' +
deployId + deployId +
'.rule=Host(`' + '.rule=Host(`' +
traefikURL + traefikURL +
'`) && PathPrefix(`/`)', '`) && PathPrefix(`/`)',
'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt',
'traefik.http.routers.' + deployId + '.middlewares=global-compress' 'traefik.http.routers.' + deployId + '.middlewares=global-compress'
] ]
} }
}, },
[`${deployId}-mysql`]: { [`${deployId}-mysql`]: {
image, image,
networks: [`${docker.network}`], networks: [`${docker.network}`],
environment: generateEnvsMySQL, environment: generateEnvsMySQL,
volumes: [volume], volumes: [volume],
deploy: { deploy: {
...baseServiceConfiguration, ...baseServiceConfiguration,
labels: [ labels: ['managedBy=coolify', 'type=service', 'serviceName=' + deployId]
'managedBy=coolify', }
'type=service', }
'serviceName=' + deployId, },
] networks: {
} [`${docker.network}`]: {
} external: true
}, }
networks: { },
[`${docker.network}`]: { volumes: {
external: true [`${deployId}-wordpress-data`]: {
} external: true
}, },
volumes: { [`${deployId}-mysql-data`]: {
[`${deployId}-wordpress-data`]: { external: true
external: true }
}, }
[`${deployId}-mysql-data`]: { };
external: true if (remoteDB) {
} stack = {
}, version: '3.8',
}; services: {
if (remoteDB) { [deployId]: {
stack = { image: 'wordpress',
version: '3.8', networks: [`${docker.network}`],
services: { environment: generateEnvsWordpress,
[deployId]: { volumes: [`${deployId}-wordpress-data:/var/www/html`],
image: 'wordpress', deploy: {
networks: [`${docker.network}`], ...baseServiceConfiguration,
environment: generateEnvsWordpress, labels: [
volumes: [`${deployId}-wordpress-data:/var/www/html`], 'managedBy=coolify',
deploy: { 'type=service',
...baseServiceConfiguration, 'serviceName=' + deployId,
labels: [ 'configuration=' +
'managedBy=coolify', JSON.stringify({
'type=service', deployId,
'serviceName=' + deployId, baseURL,
'configuration=' + generateEnvsWordpress
JSON.stringify({ }),
deployId, 'traefik.enable=true',
baseURL, 'traefik.http.services.' + deployId + '.loadbalancer.server.port=80',
generateEnvsWordpress 'traefik.http.routers.' + deployId + '.entrypoints=websecure',
}), 'traefik.http.routers.' +
'traefik.enable=true', deployId +
'traefik.http.services.' + deployId + '.loadbalancer.server.port=80', '.rule=Host(`' +
'traefik.http.routers.' + deployId + '.entrypoints=websecure', traefikURL +
'traefik.http.routers.' + '`) && PathPrefix(`/`)',
deployId + 'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt',
'.rule=Host(`' + 'traefik.http.routers.' + deployId + '.middlewares=global-compress'
traefikURL + ]
'`) && PathPrefix(`/`)', }
'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', }
'traefik.http.routers.' + deployId + '.middlewares=global-compress' },
] networks: {
} [`${docker.network}`]: {
} external: true
}, }
networks: { },
[`${docker.network}`]: { volumes: {
external: true [`${deployId}-wordpress-data`]: {
} external: true
}, }
volumes: { }
[`${deployId}-wordpress-data`]: { };
external: true }
} await execShellAsync(`mkdir -p ${workdir}`);
}, await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack));
}; await execShellAsync(`docker stack rm ${deployId}`);
} await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`);
console.log(stack) cleanupTmp(workdir);
await execShellAsync(`mkdir -p ${workdir}`); return {
await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); status: 200,
await execShellAsync(`docker stack rm ${deployId}`); body: { message: 'OK' }
await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`); };
cleanupTmp(workdir);
return {
status: 200,
body: { message: 'OK' }
};
} }

View File

@@ -1,3 +1,18 @@
<script context="module" lang="ts">
/**
* @type {import('@sveltejs/kit').Load}
*/
export async function load(session) {
if (!browser && !process.env.VITE_GITHUB_APP_CLIENTID) {
return {
status: 302,
redirect: '/dashboard/services'
};
}
return {};
}
</script>
<script> <script>
import { application, initialApplication, initConf, dashboard, prApplication } from '$store'; import { application, initialApplication, initConf, dashboard, prApplication } from '$store';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
@@ -58,7 +73,6 @@
onDestroy(() => { onDestroy(() => {
$application = JSON.parse(JSON.stringify(initialApplication)); $application = JSON.parse(JSON.stringify(initialApplication));
}); });
</script> </script>
{#await loadConfiguration()} {#await loadConfiguration()}

View File

@@ -1,8 +1,28 @@
<script context="module" lang="ts">
import { request } from '$lib/request';
/**
* @type {import('@sveltejs/kit').Load}
*/
export async function load(session) {
if (!browser && !process.env.VITE_GITHUB_APP_CLIENTID) {
return {
status: 302,
redirect: '/dashboard/services'
};
}
return {
props: {
initDashboard: await request('/api/v1/dashboard', session)
}
};
}
</script>
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { dashboard, dateOptions } from '$store'; import { dashboard, dateOptions, settings } from '$store';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { browser } from '$app/env';
</script> </script>
<div <div

View File

@@ -1,3 +1,18 @@
<script context="module" lang="ts">
/**
* @type {import('@sveltejs/kit').Load}
*/
export async function load(session) {
if (!browser && !process.env.VITE_GITHUB_APP_CLIENTID) {
return {
status: 302,
redirect: '/dashboard/services'
};
}
return {};
}
</script>
<script> <script>
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import MongoDb from '$components/Database/SVGs/MongoDb.svelte'; import MongoDb from '$components/Database/SVGs/MongoDb.svelte';
@@ -8,7 +23,7 @@
import { dashboard } from '$store'; import { dashboard } from '$store';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Redis from '$components/Database/SVGs/Redis.svelte'; import Redis from '$components/Database/SVGs/Redis.svelte';
import { browser } from '$app/env';
</script> </script>
<div class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center"> <div class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center">
@@ -59,9 +74,7 @@
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4" customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"
/> />
{:else if database.configuration.general.type == 'redis'} {:else if database.configuration.general.type == 'redis'}
<Redis <Redis customClass="w-10 h-10 absolute top-0 left-0 -m-4" />
customClass="w-10 h-10 absolute top-0 left-0 -m-4"
/>
{:else if database.configuration.general.type == 'clickhouse'} {:else if database.configuration.general.type == 'clickhouse'}
<Clickhouse <Clickhouse
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4" customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"

View File

@@ -1,10 +1,15 @@
<script> <script>
import { browser } from '$app/env'; import { browser } from '$app/env';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { toast } from '@zerodevx/svelte-toast';
import PasswordField from '$components/PasswordField.svelte';
import { request } from '$lib/request'; import { request } from '$lib/request';
import { settings } from '$store';
import Loading from '$components/Loading.svelte';
let loading = false;
let email = null;
let password = null;
async function login() { async function login() {
const left = screen.width / 2 - 1020 / 2; const left = screen.width / 2 - 1020 / 2;
const top = screen.height / 2 - 618 / 2; const top = screen.height / 2 - 618 / 2;
@@ -22,11 +27,30 @@
const timer = setInterval(() => { const timer = setInterval(() => {
if (newWindow?.closed) { if (newWindow?.closed) {
clearInterval(timer); clearInterval(timer);
browser && location.reload() // WHY need to navigate to / to get cookies?!
browser && window.location.replace('/')
} }
}, 100); }, 100);
} }
async function loginWithEmail() {
try {
loading = true;
const { message } = await request('/api/v1/login/email', $session, {
body: {
email,
password
}
});
toast.push(message);
setTimeout(() => {
// WHY need to navigate to / to get cookies?!
browser && window.location.replace('/')
}, 1000);
} catch (error) {
loading = false;
browser && toast.push(error.error || error || 'Ooops something went wrong.');
}
}
</script> </script>
<div class="flex justify-center items-center h-screen w-full bg-warmGray-900"> <div class="flex justify-center items-center h-screen w-full bg-warmGray-900">
@@ -37,24 +61,56 @@
> >
<span class="border-gradient">Coolify</span> <span class="border-gradient">Coolify</span>
</p> </p>
<h2 class="text-2xl md:text-3xl font-extrabold text-white"> <h2 class="text-2xl md:text-3xl font-extrabold text-white py-10">
An open-source, hassle-free, self-hostable<br /> An open-source, hassle-free, self-hostable<br />
<span class="text-indigo-400">Heroku</span> <span class="text-indigo-400">Heroku</span>
& <span class="text-green-400">Netlify</span> alternative & <span class="text-green-400">Netlify</span> alternative
</h2> </h2>
<div class="text-center py-10"> {#if loading}
{#if !$session.isLoggedIn} <Loading fullscreen={false} />
<button {:else}
class="text-white bg-warmGray-800 hover:bg-warmGray-700 rounded p-2 px-10 font-bold" <div class="text-center py-10 max-w-7xl">
on:click={login}>Login with Github</button {#if !$session.isLoggedIn}
> {#if $settings.clientId}
{:else} <button
<button class="text-white bg-warmGray-800 hover:bg-warmGray-700 rounded p-2 px-10 font-bold"
class="text-white bg-warmGray-800 hover:bg-warmGray-700 rounded p-2 px-10 font-bold" on:click={login}>Login with GitHub</button
on:click={() => goto('/dashboard/applications')}>Get Started</button >
> {:else}
{/if} <div>
</div> <div class="grid grid-flow-row gap-2 items-center pb-6">
<div class="grid grid-flow-row">
<label for="Email" class="">Email address</label>
<input
class="border-2"
id="Email"
bind:value={email}
placeholder="hi@coollabs.io"
/>
</div>
<div class="grid grid-flow-row">
<label for="Password" class="">Password</label>
<PasswordField bind:value={password} isEditable />
</div>
</div>
<div class="space-x-4 pt-10">
<button
class="text-white bg-warmGray-800 hover:bg-warmGray-700 rounded p-2 px-10 font-bold"
on:click={loginWithEmail}>Login with Email</button
>
</div>
</div>
{/if}
{:else}
<button
class="text-white bg-warmGray-800 hover:bg-warmGray-700 rounded p-2 px-10 font-bold"
on:click={() =>
$settings.clientId ? goto('/dashboard/applications') : goto('/dashboard/services')}
>Get Started</button
>
{/if}
</div>
{/if}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -6,7 +6,9 @@ import type {
GithubInstallations GithubInstallations
} from 'src/global'; } from 'src/global';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
export const settings = writable({
clientId: null
});
export const dashboard = writable<Dashboard>({ export const dashboard = writable<Dashboard>({
databases: { databases: {
deployed: [] deployed: []