mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-19 12:33:11 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88aa620cb4 | ||
|
|
70d3448110 | ||
|
|
09a1a406a6 | ||
|
|
40939d0b7f | ||
|
|
aec1d184c8 | ||
|
|
69d3cb5dd8 | ||
|
|
3deff162bb | ||
|
|
4ed54568d3 | ||
|
|
004724da55 | ||
|
|
97e9b5ffe3 | ||
|
|
45f920f802 | ||
|
|
2b31532d19 | ||
|
|
e7a6ecf95b | ||
|
|
545c98cee0 | ||
|
|
d29ccbfe37 |
62
README.md
62
README.md
@@ -2,6 +2,66 @@
|
|||||||
|
|
||||||
An open-source & self-hostable Heroku / Netlify alternative.
|
An open-source & self-hostable Heroku / Netlify alternative.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Installation is automated with the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Migration from v1
|
||||||
|
|
||||||
|
A fresh installation is necessary. v2 is not compatible with v1.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
### Git Sources
|
||||||
|
You can use the official ones or your self hosted version!
|
||||||
|
|
||||||
|
- Github
|
||||||
|
- GitLab
|
||||||
|
- Bitbucket (WIP)
|
||||||
|
|
||||||
|
### Destinations
|
||||||
|
|
||||||
|
- Local Docker Engine
|
||||||
|
- Remote Docker Engine (WIP)
|
||||||
|
- Kubernetes (WIP)
|
||||||
|
|
||||||
|
|
||||||
|
### Applications
|
||||||
|
|
||||||
|
- Static sites
|
||||||
|
- NodeJS
|
||||||
|
- VueJS
|
||||||
|
- NuxtJS
|
||||||
|
- NextJS
|
||||||
|
- React/Preact
|
||||||
|
- NextJS
|
||||||
|
- Gatsby
|
||||||
|
- Svelte
|
||||||
|
- PHP
|
||||||
|
- Rust
|
||||||
|
- Dockerfile (you can provide it)
|
||||||
|
|
||||||
|
### Databases
|
||||||
|
|
||||||
|
- MongoDB
|
||||||
|
- MySQL
|
||||||
|
- PostgreSQL
|
||||||
|
- CouchDB
|
||||||
|
- Redis
|
||||||
|
|
||||||
|
### One-click services
|
||||||
|
|
||||||
|
- [WordPress](https://wordpress.org)
|
||||||
|
- [Plausible Analytics](https://plausible.io)
|
||||||
|
- [NocoDB](https://nocodb.com)
|
||||||
|
- [VSCode Server](https://github.com/cdr/code-server)
|
||||||
|
- [MinIO](https://min.io)
|
||||||
|
- [VaultWarden](https://github.com/dani-garcia/vaultwarden)
|
||||||
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
|
||||||
@@ -13,6 +73,8 @@ An open-source & self-hostable Heroku / Netlify alternative.
|
|||||||
|
|
||||||
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
|
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
|
||||||
|
|
||||||
|
(Will be updated soon!)
|
||||||
|
|
||||||
## 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.
|
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.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "coolify",
|
"name": "coolify",
|
||||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||||
"version": "2.0.5",
|
"version": "2.0.7",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
|
"dev": "docker compose -f docker-compose-dev.yaml up -d && NODE_ENV=development svelte-kit dev --host 0.0.0.0",
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
"db:push": "prisma db push && prisma generate",
|
"db:push": "prisma db push && prisma generate",
|
||||||
"db:seed": "prisma db seed",
|
"db:seed": "prisma db seed",
|
||||||
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
|
"release:staging": "cross-var docker build -t coollabsio/coolify:$npm_package_version . && docker push coollabsio/coolify:$npm_package_version",
|
||||||
"release:coolify": "cross-var yarn prerelease && docker push coollabsio/coolify:$npm_package_version && docker image push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
"release:pre": "cross-var docker build -t coollabsio/coolify:$npm_package_version -t coollabsio/coolify:latest .",
|
||||||
|
"release:coolify": "cross-var yarn release:pre && docker push coollabsio/coolify:$npm_package_version && docker push coollabsio/coolify:latest",
|
||||||
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
|
"release:haproxy": "docker build -f haproxy.Dockerfile -t coollabsio/coolify-haproxy-alpine:1.0.0 -t coollabsio/coolify-haproxy-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-alpine",
|
||||||
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",
|
"release:haproxy:tcp": "docker build -f haproxy-tcp.Dockerfile -t coollabsio/coolify-haproxy-tcp-alpine:1.0.0 -t coollabsio/coolify-haproxy-tcp-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-tcp-alpine",
|
||||||
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",
|
"release:haproxy:http": "docker build -f haproxy-http.Dockerfile -t coollabsio/coolify-haproxy-http-alpine:1.0.0 -t coollabsio/coolify-haproxy-http-alpine:latest . && docker image push --all-tags coollabsio/coolify-haproxy-http-alpine",
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[name,applicationId]` on the table `Secret` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "Secret_name_key";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Secret_name_applicationId_key" ON "Secret"("name", "applicationId");
|
||||||
@@ -105,13 +105,15 @@ model ApplicationSettings {
|
|||||||
|
|
||||||
model Secret {
|
model Secret {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
name String
|
||||||
value String
|
value String
|
||||||
isBuildSecret Boolean @default(false)
|
isBuildSecret Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
application Application @relation(fields: [applicationId], references: [id])
|
application Application @relation(fields: [applicationId], references: [id])
|
||||||
applicationId String
|
applicationId String
|
||||||
|
|
||||||
|
@@unique([name, applicationId])
|
||||||
}
|
}
|
||||||
|
|
||||||
model BuildLog {
|
model BuildLog {
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ export const getTeam = (event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getUserDetails = async (event, isAdminRequired = true) => {
|
export const getUserDetails = async (event, isAdminRequired = true) => {
|
||||||
// try {
|
|
||||||
const teamId = getTeam(event);
|
const teamId = getTeam(event);
|
||||||
const userId = event.locals.session.data.uid || null;
|
const userId = event.locals.session.data.uid || null;
|
||||||
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
const { permission = 'read' } = await db.prisma.permission.findFirst({
|
||||||
@@ -91,18 +90,6 @@ export const getUserDetails = async (event, isAdminRequired = true) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
// } catch (err) {
|
|
||||||
// console.log(err);
|
|
||||||
// return {
|
|
||||||
// teamId: null,
|
|
||||||
// userId: null,
|
|
||||||
// permission: 'read',
|
|
||||||
// status: 401,
|
|
||||||
// body: {
|
|
||||||
// message: 'You do not have permission to do this. \nAsk an admin to modify your permissions.'
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getEngine(engine) {
|
export function getEngine(engine) {
|
||||||
|
|||||||
@@ -20,9 +20,6 @@
|
|||||||
|
|
||||||
function showActions(value) {
|
function showActions(value) {
|
||||||
actionsShow = value;
|
actionsShow = value;
|
||||||
// if (value === false) {
|
|
||||||
// showPassword = false;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
if (isHttps && navigator.clipboard) {
|
if (isHttps && navigator.clipboard) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
export let description;
|
export let description;
|
||||||
export let isPadding = true;
|
export let isPadding = true;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
// export let disabledReason = '';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li class="flex items-center py-4">
|
<li class="flex items-center py-4">
|
||||||
@@ -14,11 +13,6 @@
|
|||||||
<p class="text-xs font-bold text-stone-100 md:text-base">{title}</p>
|
<p class="text-xs font-bold text-stone-100 md:text-base">{title}</p>
|
||||||
<Explainer text={description} />
|
<Explainer text={description} />
|
||||||
</div>
|
</div>
|
||||||
<!-- {#if disabled}
|
|
||||||
<div class="flex" class:px-4={isPadding} class:pr-32={!isPadding}>
|
|
||||||
<p class="text-xs font-bold text-stone-400">{disabledReason}</p>
|
|
||||||
</div>
|
|
||||||
{:else} -->
|
|
||||||
<div
|
<div
|
||||||
type="button"
|
type="button"
|
||||||
on:click
|
on:click
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { decrypt, encrypt } from '$lib/crypto';
|
import { decrypt, encrypt } from '$lib/crypto';
|
||||||
import { removeProxyConfiguration } from '$lib/haproxy';
|
import { removeProxyConfiguration, removeWwwRedirection } from '$lib/haproxy';
|
||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getEngine } from '$lib/common';
|
||||||
|
|
||||||
import { getDomain, removeDestinationDocker } from '$lib/common';
|
import { getDomain, removeDestinationDocker } from '$lib/common';
|
||||||
|
|||||||
@@ -109,22 +109,4 @@ export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
|
|||||||
where: { engine },
|
where: { engine },
|
||||||
data: { isCoolifyProxyUsed }
|
data: { isCoolifyProxyUsed }
|
||||||
});
|
});
|
||||||
|
|
||||||
// if (isCoolifyProxyUsed) {
|
|
||||||
// await installCoolifyProxy(engine)
|
|
||||||
// await configureNetworkCoolifyProxy(engine)
|
|
||||||
// } else {
|
|
||||||
// // TODO: must check if other destination is using the proxy??? or not?
|
|
||||||
// const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false })
|
|
||||||
// if (!domain) {
|
|
||||||
// await uninstallCoolifyProxy(engine)
|
|
||||||
// } else {
|
|
||||||
// return {
|
|
||||||
// stastus: 500,
|
|
||||||
// body: {
|
|
||||||
// message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.'
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ export async function login({ email, password }) {
|
|||||||
console.log('Network created');
|
console.log('Network created');
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log('Network already exists');
|
console.log('Network already exists.');
|
||||||
});
|
});
|
||||||
|
|
||||||
startCoolifyProxy('/var/run/docker.sock')
|
startCoolifyProxy('/var/run/docker.sock')
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('Coolify Proxy Started');
|
console.log('Coolify Proxy started.');
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
@@ -97,17 +97,6 @@ export async function login({ email, password }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const token = jsonwebtoken.sign({}, secretKey, {
|
|
||||||
// expiresIn: 15778800,
|
|
||||||
// algorithm: 'HS256',
|
|
||||||
// audience: 'coolify',
|
|
||||||
// issuer: 'coolify',
|
|
||||||
// jwtid: uid,
|
|
||||||
// subject: `User:${uid}`,
|
|
||||||
// notBefore: -1000
|
|
||||||
// });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { dev } from '$app/env';
|
import { dev } from '$app/env';
|
||||||
import { asyncExecShell, getEngine } from '$lib/common';
|
import { asyncExecShell, getDomain, getEngine } from '$lib/common';
|
||||||
import got from 'got';
|
import got from 'got';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
@@ -51,11 +51,11 @@ export async function completeTransaction(transactionId) {
|
|||||||
|
|
||||||
export async function removeProxyConfiguration({ domain }) {
|
export async function removeProxyConfiguration({ domain }) {
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
const transactionId = await getNextTransactionId();
|
|
||||||
const backendFound = await haproxy
|
const backendFound = await haproxy
|
||||||
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
.get(`v2/services/haproxy/configuration/backends/${domain}`)
|
||||||
.json();
|
.json();
|
||||||
if (backendFound) {
|
if (backendFound) {
|
||||||
|
const transactionId = await getNextTransactionId();
|
||||||
await haproxy
|
await haproxy
|
||||||
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||||
searchParams: {
|
searchParams: {
|
||||||
@@ -65,6 +65,7 @@ export async function removeProxyConfiguration({ domain }) {
|
|||||||
.json();
|
.json();
|
||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
}
|
}
|
||||||
|
await removeWwwRedirection(domain);
|
||||||
}
|
}
|
||||||
export async function forceSSLOffApplication({ domain }) {
|
export async function forceSSLOffApplication({ domain }) {
|
||||||
if (!dev) {
|
if (!dev) {
|
||||||
@@ -124,7 +125,9 @@ export async function forceSSLOnApplication({ domain }) {
|
|||||||
.json();
|
.json();
|
||||||
let nextRule = 0;
|
let nextRule = 0;
|
||||||
if (rules.data.length > 0) {
|
if (rules.data.length > 0) {
|
||||||
const rule = rules.data.find((rule) => rule.cond_test.includes(`-i ${domain}`));
|
const rule = rules.data.find((rule) =>
|
||||||
|
rule.cond_test.includes(`{ hdr(host) -i ${domain} } !{ ssl_fc }`)
|
||||||
|
);
|
||||||
if (rule) return;
|
if (rule) return;
|
||||||
nextRule = rules.data[rules.data.length - 1].index + 1;
|
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||||
}
|
}
|
||||||
@@ -138,7 +141,7 @@ export async function forceSSLOnApplication({ domain }) {
|
|||||||
json: {
|
json: {
|
||||||
index: nextRule,
|
index: nextRule,
|
||||||
cond: 'if',
|
cond: 'if',
|
||||||
cond_test: `{ hdr(Host) -i ${domain} } !{ ssl_fc }`,
|
cond_test: `{ hdr(host) -i ${domain} } !{ ssl_fc }`,
|
||||||
type: 'redirect',
|
type: 'redirect',
|
||||||
redir_type: 'scheme',
|
redir_type: 'scheme',
|
||||||
redir_value: 'https',
|
redir_value: 'https',
|
||||||
@@ -188,94 +191,7 @@ export async function deleteProxy({ id }) {
|
|||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// export async function configureProxyForDatabase({ id, port, isPublic, privatePort }) {
|
|
||||||
// const haproxy = await haproxyInstance()
|
|
||||||
// try {
|
|
||||||
// await checkHAProxy()
|
|
||||||
// } catch (error) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let alreadyConfigured = false
|
|
||||||
// try {
|
|
||||||
// const backend: any = await haproxy.get(`v2/services/haproxy/configuration/backends/${id}`).json()
|
|
||||||
// const server: any = await haproxy.get(`v2/services/haproxy/configuration/servers/${id}`, {
|
|
||||||
// searchParams: {
|
|
||||||
// backend: id
|
|
||||||
// },
|
|
||||||
// }).json()
|
|
||||||
// if (backend.data.name === id) {
|
|
||||||
// if (server.data.port === privatePort) {
|
|
||||||
// if (server.data.check === 'enabled') {
|
|
||||||
// if (server.data.address === id) {
|
|
||||||
// alreadyConfigured = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log('error getting backend or server', error.response.body)
|
|
||||||
// }
|
|
||||||
// if (alreadyConfigured) return
|
|
||||||
|
|
||||||
// const transactionId = await getNextTransactionId()
|
|
||||||
// try {
|
|
||||||
// await haproxy.post('v2/services/haproxy/configuration/backends', {
|
|
||||||
// searchParams: {
|
|
||||||
// transaction_id: transactionId
|
|
||||||
// },
|
|
||||||
// json: {
|
|
||||||
// "init-addr": "last,libc,none",
|
|
||||||
// "mode": "tcp",
|
|
||||||
// "name": id
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// await haproxy.post('v2/services/haproxy/configuration/servers', {
|
|
||||||
// searchParams: {
|
|
||||||
// transaction_id: transactionId,
|
|
||||||
// backend: id
|
|
||||||
// },
|
|
||||||
// json: {
|
|
||||||
// "address": id,
|
|
||||||
// "check": "enabled",
|
|
||||||
// "name": id,
|
|
||||||
// "port": privatePort
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// await haproxy.post('v2/services/haproxy/configuration/frontends', {
|
|
||||||
// searchParams: {
|
|
||||||
// transaction_id: transactionId,
|
|
||||||
// backend: id
|
|
||||||
// },
|
|
||||||
// json: {
|
|
||||||
// "default_backend": id,
|
|
||||||
// "mode": "tcp",
|
|
||||||
// "name": id
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// await haproxy.post('v2/services/haproxy/configuration/binds', {
|
|
||||||
// searchParams: {
|
|
||||||
// transaction_id: transactionId,
|
|
||||||
// frontend: id
|
|
||||||
// },
|
|
||||||
// json: {
|
|
||||||
// "address": "*",
|
|
||||||
// "name": id,
|
|
||||||
// "port": port
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error.response.body)
|
|
||||||
// throw error.response.body
|
|
||||||
// } finally {
|
|
||||||
// try {
|
|
||||||
// await completeTransaction(transactionId)
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error.response.body)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// await configureDatabaseVisibility({ id, isPublic })
|
|
||||||
// }
|
|
||||||
export async function reloadHaproxy(engine) {
|
export async function reloadHaproxy(engine) {
|
||||||
const host = getEngine(engine);
|
const host = getEngine(engine);
|
||||||
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
return await asyncExecShell(`DOCKER_HOST=${host} docker exec coolify-haproxy kill -HUP 1`);
|
||||||
@@ -364,7 +280,8 @@ export async function configureProxyForApplication({ domain, imageId, applicatio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function configureCoolifyProxyOff({ domain }) {
|
export async function configureCoolifyProxyOff(fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
try {
|
try {
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
@@ -386,6 +303,7 @@ export async function configureCoolifyProxyOff({ domain }) {
|
|||||||
if (!dev) {
|
if (!dev) {
|
||||||
await forceSSLOffApplication({ domain });
|
await forceSSLOffApplication({ domain });
|
||||||
}
|
}
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error?.response?.body || error;
|
throw error?.response?.body || error;
|
||||||
}
|
}
|
||||||
@@ -398,7 +316,8 @@ export async function checkHAProxy(haproxy) {
|
|||||||
throw 'HAProxy is not running, but it should be!';
|
throw 'HAProxy is not running, but it should be!';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function configureCoolifyProxyOn({ domain }) {
|
export async function configureCoolifyProxyOn(fqdn) {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
const haproxy = await haproxyInstance();
|
const haproxy = await haproxyInstance();
|
||||||
try {
|
try {
|
||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
@@ -605,7 +524,15 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
|||||||
await checkHAProxy(haproxy);
|
await checkHAProxy(haproxy);
|
||||||
try {
|
try {
|
||||||
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
|
await haproxy.get(`v2/services/haproxy/configuration/backends/${domain}`).json();
|
||||||
return;
|
const transactionId = await getNextTransactionId();
|
||||||
|
await haproxy
|
||||||
|
.delete(`v2/services/haproxy/configuration/backends/${domain}`, {
|
||||||
|
searchParams: {
|
||||||
|
transaction_id: transactionId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
await completeTransaction(transactionId);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
try {
|
try {
|
||||||
const transactionId = await getNextTransactionId();
|
const transactionId = await getNextTransactionId();
|
||||||
@@ -631,6 +558,12 @@ export async function configureSimpleServiceProxyOn({ id, domain, port }) {
|
|||||||
port: port
|
port: port
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
console.log({
|
||||||
|
address: id,
|
||||||
|
check: 'enabled',
|
||||||
|
name: id,
|
||||||
|
port: port
|
||||||
|
});
|
||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -657,5 +590,98 @@ export async function configureSimpleServiceProxyOff({ domain }) {
|
|||||||
.json();
|
.json();
|
||||||
await completeTransaction(transactionId);
|
await completeTransaction(transactionId);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
await removeWwwRedirection(domain);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function removeWwwRedirection(domain) {
|
||||||
|
const haproxy = await haproxyInstance();
|
||||||
|
try {
|
||||||
|
await checkHAProxy(haproxy);
|
||||||
|
} catch (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules: any = await haproxy
|
||||||
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
searchParams: {
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
if (rules.data.length > 0) {
|
||||||
|
const rule = rules.data.find((rule) =>
|
||||||
|
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
|
||||||
|
);
|
||||||
|
if (rule) {
|
||||||
|
const transactionId = await getNextTransactionId();
|
||||||
|
await haproxy
|
||||||
|
.delete(`v2/services/haproxy/configuration/http_request_rules/${rule.index}`, {
|
||||||
|
searchParams: {
|
||||||
|
transaction_id: transactionId,
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
await completeTransaction(transactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function setWwwRedirection(fqdn) {
|
||||||
|
const haproxy = await haproxyInstance();
|
||||||
|
try {
|
||||||
|
await checkHAProxy(haproxy);
|
||||||
|
} catch (error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const transactionId = await getNextTransactionId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const domain = getDomain(fqdn);
|
||||||
|
const isHttps = fqdn.startsWith('https://');
|
||||||
|
const isWWW = fqdn.includes('www.');
|
||||||
|
const contTest = `{ req.hdr(host) -i ${isWWW ? domain.replace('www.', '') : `www.${domain}`} }`;
|
||||||
|
const rules: any = await haproxy
|
||||||
|
.get(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
searchParams: {
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
let nextRule = 0;
|
||||||
|
if (rules.data.length > 0) {
|
||||||
|
const rule = rules.data.find((rule) =>
|
||||||
|
rule.redir_value.includes(`${domain}%[capture.req.uri]`)
|
||||||
|
);
|
||||||
|
if (rule) return;
|
||||||
|
nextRule = rules.data[rules.data.length - 1].index + 1;
|
||||||
|
}
|
||||||
|
const redirectValue = `${isHttps ? 'https://' : 'http://'}${domain}%[capture.req.uri]`;
|
||||||
|
await haproxy
|
||||||
|
.post(`v2/services/haproxy/configuration/http_request_rules`, {
|
||||||
|
searchParams: {
|
||||||
|
transaction_id: transactionId,
|
||||||
|
parent_name: 'http',
|
||||||
|
parent_type: 'frontend'
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
index: nextRule,
|
||||||
|
cond: 'if',
|
||||||
|
cond_test: contTest,
|
||||||
|
type: 'redirect',
|
||||||
|
redir_type: 'location',
|
||||||
|
redir_value: redirectValue,
|
||||||
|
redir_code: dev ? 302 : 301
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
await completeTransaction(transactionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as buildpacks from '../buildPacks';
|
|||||||
import * as importers from '../importers';
|
import * as importers from '../importers';
|
||||||
import { dockerInstance } from '../docker';
|
import { dockerInstance } from '../docker';
|
||||||
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
|
import { asyncExecShell, createDirectories, getDomain, getEngine, saveBuildLog } from '../common';
|
||||||
import { configureProxyForApplication, reloadHaproxy } from '../haproxy';
|
import { configureProxyForApplication, reloadHaproxy, setWwwRedirection } from '../haproxy';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { decrypt } from '$lib/crypto';
|
import { decrypt } from '$lib/crypto';
|
||||||
import { sentry } from '$lib/common';
|
import { sentry } from '$lib/common';
|
||||||
@@ -64,10 +64,6 @@ export default async function (job) {
|
|||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
destinationType = 'docker';
|
destinationType = 'docker';
|
||||||
}
|
}
|
||||||
// Not implemented yet
|
|
||||||
// if (destinationKubernetesId) {
|
|
||||||
// destinationType = 'kubernetes'
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (destinationType === 'docker') {
|
if (destinationType === 'docker') {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
const docker = dockerInstance({ destinationDocker });
|
||||||
@@ -209,9 +205,7 @@ export default async function (job) {
|
|||||||
const envs = [];
|
const envs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (!secret.isBuildSecret) {
|
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
@@ -252,6 +246,7 @@ export default async function (job) {
|
|||||||
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
|
saveBuildLog({ line: 'Proxy configuration started!', buildId, applicationId });
|
||||||
await configureProxyForApplication({ domain, imageId, applicationId, port });
|
await configureProxyForApplication({ domain, imageId, applicationId, port });
|
||||||
if (isHttps) await letsEncrypt({ domain, id: applicationId });
|
if (isHttps) await letsEncrypt({ domain, id: applicationId });
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
await reloadHaproxy(destinationDocker.engine);
|
||||||
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
|
saveBuildLog({ line: 'Proxy configuration successful!', buildId, applicationId });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -144,28 +144,6 @@ buildWorker.on('failed', async (job: Bullmq.Job, failedReason) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// const letsEncryptQueueName = dev ? cuid() : 'letsencrypt_queue'
|
|
||||||
// const letsEncryptQueue = new Queue(letsEncryptQueueName, connectionOptions)
|
|
||||||
|
|
||||||
// const letsEncryptWorker = new Worker(letsEncryptQueueName, async (job) => await letsencrypt(job), {
|
|
||||||
// concurrency: 1,
|
|
||||||
// ...connectionOptions
|
|
||||||
// })
|
|
||||||
// letsEncryptWorker.on('completed', async () => {
|
|
||||||
// // TODO: Save letsencrypt logs as build logs!
|
|
||||||
// console.log('[DEBUG] Lets Encrypt job completed')
|
|
||||||
// })
|
|
||||||
|
|
||||||
// letsEncryptWorker.on('failed', async (job: Job, failedReason: string) => {
|
|
||||||
// try {
|
|
||||||
// await prisma.applicationSettings.updateMany({ where: { applicationId: job.data.id }, data: { forceSSL: false } })
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error)
|
|
||||||
// }
|
|
||||||
// console.log('[DEBUG] Lets Encrypt job failed')
|
|
||||||
// console.log(failedReason)
|
|
||||||
// })
|
|
||||||
|
|
||||||
const buildLogQueueName = dev ? cuid() : 'log_queue';
|
const buildLogQueueName = dev ? cuid() : 'log_queue';
|
||||||
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
|
const buildLogQueue = new Queue(buildLogQueueName, connectionOptions);
|
||||||
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {
|
const buildLogWorker = new Worker(buildLogQueueName, async (job) => await logger(job), {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
configureProxyForApplication,
|
configureProxyForApplication,
|
||||||
forceSSLOnApplication,
|
forceSSLOnApplication,
|
||||||
reloadHaproxy,
|
reloadHaproxy,
|
||||||
|
setWwwRedirection,
|
||||||
startCoolifyProxy
|
startCoolifyProxy
|
||||||
} from '$lib/haproxy';
|
} from '$lib/haproxy';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
@@ -40,6 +41,7 @@ export default async function () {
|
|||||||
});
|
});
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
if (isHttps) await forceSSLOnApplication({ domain });
|
if (isHttps) await forceSSLOnApplication({ domain });
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +54,7 @@ export default async function () {
|
|||||||
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
|
const found = await checkContainer('/var/run/docker.sock', 'coolify-haproxy');
|
||||||
if (!found) await startCoolifyProxy('/var/run/docker.sock');
|
if (!found) await startCoolifyProxy('/var/run/docker.sock');
|
||||||
await configureCoolifyProxyOn({ domain });
|
await configureCoolifyProxyOn({ domain });
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
if (isHttps) await forceSSLOnApplication({ domain });
|
if (isHttps) await forceSSLOnApplication({ domain });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,25 +124,6 @@
|
|||||||
updateStatus.success = false;
|
updateStatus.success = false;
|
||||||
updateStatus.loading = false;
|
updateStatus.loading = false;
|
||||||
}
|
}
|
||||||
// } else {
|
|
||||||
// let reachable = false;
|
|
||||||
// let tries = 0;
|
|
||||||
// do {
|
|
||||||
// await asyncSleep(1000);
|
|
||||||
// try {
|
|
||||||
// await get(`/undead.json`);
|
|
||||||
// reachable = true;
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log(error);
|
|
||||||
// reachable = false;
|
|
||||||
// }
|
|
||||||
// if (reachable) break;
|
|
||||||
// tries++;
|
|
||||||
// } while (!reachable || tries < 120);
|
|
||||||
// toast.push('New version reachable. Reloading...');
|
|
||||||
// await asyncSleep(2000);
|
|
||||||
// window.location.reload();
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -142,14 +142,6 @@
|
|||||||
return errorNotification('Branch already configured');
|
return errorNotification('Branch already configured');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// async function saveDeployKey(deployKeyId: number) {
|
|
||||||
// try {
|
|
||||||
// await post(updateDeployKeyIdUrl, { deployKeyId });
|
|
||||||
// } catch (error) {
|
|
||||||
// errorNotification(error);
|
|
||||||
// throw new Error(error);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
async function checkSSHKey(sshkeyUrl) {
|
async function checkSSHKey(sshkeyUrl) {
|
||||||
try {
|
try {
|
||||||
return await post(sshkeyUrl, {});
|
return await post(sshkeyUrl, {});
|
||||||
|
|||||||
@@ -266,7 +266,7 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Explainer
|
<Explainer
|
||||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>To modify the domain, you must first stop the application."
|
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,14 +6,20 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { del, post } from '$lib/api';
|
import { del, post } from '$lib/api';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
if (name) value = 'ENCRYPTED';
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
|
|
||||||
async function removeSecret() {
|
async function removeSecret() {
|
||||||
try {
|
try {
|
||||||
await del(`/applications/${id}/secrets.json`, { name });
|
await del(`/applications/${id}/secrets.json`, { name });
|
||||||
return window.location.reload();
|
dispatch('refresh');
|
||||||
|
if (isNewSecret) {
|
||||||
|
name = '';
|
||||||
|
value = '';
|
||||||
|
isBuildSecret = false;
|
||||||
|
}
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
@@ -21,7 +27,12 @@
|
|||||||
async function saveSecret() {
|
async function saveSecret() {
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
|
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
|
||||||
return window.location.reload();
|
dispatch('refresh');
|
||||||
|
if (isNewSecret) {
|
||||||
|
name = '';
|
||||||
|
value = '';
|
||||||
|
isBuildSecret = false;
|
||||||
|
}
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
@@ -33,39 +44,29 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mx-auto max-w-3xl pt-4">
|
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
||||||
<div class="flex space-x-2">
|
|
||||||
<div class="grid grid-flow-row">
|
|
||||||
<label for="secretName">Name</label>
|
|
||||||
<input
|
<input
|
||||||
id="secretName"
|
id="secretName"
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
placeholder="EXAMPLE_VARIABLE"
|
placeholder="EXAMPLE_VARIABLE"
|
||||||
class="w-64 border-2 border-transparent"
|
class="-mx-2 w-64 border-2 border-transparent"
|
||||||
readonly={!isNewSecret}
|
readonly={!isNewSecret}
|
||||||
class:hover:bg-coolgray-200={!isNewSecret}
|
class:bg-transparent={!isNewSecret}
|
||||||
class:cursor-not-allowed={!isNewSecret}
|
class:cursor-not-allowed={!isNewSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
<div class="grid grid-flow-row">
|
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
||||||
<label for="secretValue">Value (will be encrypted)</label>
|
|
||||||
<input
|
<input
|
||||||
id="secretValue"
|
id="secretValue"
|
||||||
bind:value
|
bind:value
|
||||||
placeholder="J$#@UIO%HO#$U%H"
|
placeholder="J$#@UIO%HO#$U%H"
|
||||||
class="w-64 border-2 border-transparent"
|
class="-mx-2 w-64 border-2 border-transparent"
|
||||||
class:hover:bg-coolgray-200={!isNewSecret}
|
class:bg-transparent={!isNewSecret}
|
||||||
class:cursor-not-allowed={!isNewSecret}
|
class:cursor-not-allowed={!isNewSecret}
|
||||||
readonly={!isNewSecret}
|
readonly={!isNewSecret}
|
||||||
/>
|
/>
|
||||||
</div>
|
</td>
|
||||||
|
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
|
||||||
<div class="w-32 px-2 text-center">
|
|
||||||
<div class="text-xs">Is build variable?</div>
|
|
||||||
|
|
||||||
<div class="mt-2">
|
|
||||||
<ul class="divide-y divide-stone-800">
|
|
||||||
<li>
|
|
||||||
<div
|
<div
|
||||||
type="button"
|
type="button"
|
||||||
on:click={setSecretValue}
|
on:click={setSecretValue}
|
||||||
@@ -73,6 +74,7 @@
|
|||||||
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
class="relative inline-flex h-6 w-11 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out"
|
||||||
class:bg-green-600={isBuildSecret}
|
class:bg-green-600={isBuildSecret}
|
||||||
class:bg-stone-700={!isBuildSecret}
|
class:bg-stone-700={!isBuildSecret}
|
||||||
|
class:opacity-50={!isNewSecret}
|
||||||
class:cursor-not-allowed={!isNewSecret}
|
class:cursor-not-allowed={!isNewSecret}
|
||||||
class:cursor-pointer={isNewSecret}
|
class:cursor-pointer={isNewSecret}
|
||||||
>
|
>
|
||||||
@@ -104,11 +106,7 @@
|
|||||||
class:opacity-100={isBuildSecret}
|
class:opacity-100={isBuildSecret}
|
||||||
class:opacity-0={!isBuildSecret}
|
class:opacity-0={!isBuildSecret}
|
||||||
>
|
>
|
||||||
<svg
|
<svg class="h-3 w-3 bg-white text-green-600" fill="currentColor" viewBox="0 0 12 12">
|
||||||
class="h-3 w-3 bg-white text-green-600"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 12 12"
|
|
||||||
>
|
|
||||||
<path
|
<path
|
||||||
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z"
|
||||||
/>
|
/>
|
||||||
@@ -116,18 +114,15 @@
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</td>
|
||||||
</ul>
|
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{#if isNewSecret}
|
{#if isNewSecret}
|
||||||
<div class="mt-6">
|
<div class="flex items-center justify-center">
|
||||||
<button class="w-20 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
|
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="mt-6">
|
<div class="flex justify-center items-end">
|
||||||
<button class="w-20 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
|
<button class="w-24 bg-red-600 hover:bg-red-500" on:click={removeSecret}>Remove</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</td>
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const found = await db.isSecretExists({ id, name });
|
const found = await db.isSecretExists({ id, name });
|
||||||
if (found) {
|
if (found) {
|
||||||
throw {
|
throw {
|
||||||
error: `Secret ${name} already exists`
|
error: `Secret ${name} already exists.`
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
await db.createSecret({ id, name, value, isBuildSecret });
|
await db.createSecret({ id, name, value, isBuildSecret });
|
||||||
|
|||||||
@@ -24,6 +24,15 @@
|
|||||||
export let application;
|
export let application;
|
||||||
import Secret from './_Secret.svelte';
|
import Secret from './_Secret.svelte';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { get } from '$lib/api';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
|
||||||
|
async function refreshSecrets() {
|
||||||
|
const data = await get(`/applications/${id}/secrets.json`);
|
||||||
|
secrets = [...data.secrets];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@@ -31,11 +40,47 @@
|
|||||||
Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
Secrets for <a href={application.fqdn} target="_blank">{getDomain(application.fqdn)}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mx-auto max-w-4xl px-6">
|
<div class="mx-auto max-w-6xl rounded-xl px-6 pt-4">
|
||||||
<div class="flex-col justify-start space-y-1">
|
<table class="mx-auto">
|
||||||
|
<thead class=" rounded-xl border-b border-coolgray-500">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
||||||
|
>Name</th
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
||||||
|
>Value</th
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
||||||
|
>Need during buildtime?</th
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-warmGray-400"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="">
|
||||||
{#each secrets as secret}
|
{#each secrets as secret}
|
||||||
<Secret name={secret.name} value={secret.value} isBuildSecret={secret.isBuildSecret} />
|
{#key secret.id}
|
||||||
|
<tr class="hover:bg-coolgray-200">
|
||||||
|
<Secret
|
||||||
|
name={secret.name}
|
||||||
|
value={secret.value ? secret.value : 'ENCRYPTED'}
|
||||||
|
isBuildSecret={secret.isBuildSecret}
|
||||||
|
on:refresh={refreshSecrets}
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
{/key}
|
||||||
{/each}
|
{/each}
|
||||||
<Secret isNewSecret />
|
<tr>
|
||||||
</div>
|
<Secret isNewSecret on:refresh={refreshSecrets} />
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,9 +25,7 @@ export const get: RequestHandler = async (event) => {
|
|||||||
state = 'running';
|
state = 'running';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// if (!error.stderr.includes('No such object')) {
|
//
|
||||||
// console.log(error)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const configuration = generateDatabaseConfiguration(database);
|
const configuration = generateDatabaseConfiguration(database);
|
||||||
|
|||||||
@@ -110,25 +110,9 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Explainer
|
<Explainer
|
||||||
text="If you specify <span class='text-green-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you."
|
text="If you specify <span class='text-pink-600 font-bold'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-pink-600 font-bold'>www</span>, the application will be redirected (302) from non-www and vice versa.<br><br>To modify the domain, you must first stop the application."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!-- {:else}
|
|
||||||
<label for="fqdn" class="pt-2">Domain (FQDN)</label>
|
|
||||||
<div class="col-span-2 ">
|
|
||||||
<CopyPasswordField
|
|
||||||
placeholder="eg: https://analytics.coollabs.io"
|
|
||||||
readonly={!$session.isAdmin}
|
|
||||||
name="fqdn"
|
|
||||||
id="fqdn"
|
|
||||||
bind:value={service.fqdn}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Explainer
|
|
||||||
text="If you specify <span class='text-green-600'>https</span>, the application will be accessible only over https. SSL certificate will be generated for you."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{/if} -->
|
|
||||||
</div>
|
</div>
|
||||||
{#if service.type === 'plausibleanalytics'}
|
{#if service.type === 'plausibleanalytics'}
|
||||||
<PlausibleAnalytics bind:service {readOnly} />
|
<PlausibleAnalytics bind:service {readOnly} />
|
||||||
|
|||||||
@@ -46,24 +46,3 @@ export const get: RequestHandler = async (event) => {
|
|||||||
return PrismaErrorHandler(error);
|
return PrismaErrorHandler(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const post: RequestHandler<Locals, FormData> = async (request) => {
|
|
||||||
// const { teamId, status, body } = await getUserDetails(request);
|
|
||||||
// if (status === 401) return { status, body }
|
|
||||||
// const { id } = request.params
|
|
||||||
|
|
||||||
// const name = request.body.get('name')
|
|
||||||
// const defaultDatabase = request.body.get('defaultDatabase')
|
|
||||||
// const dbUser = request.body.get('dbUser')
|
|
||||||
// const dbUserPassword = request.body.get('dbUserPassword')
|
|
||||||
// const rootUser = request.body.get('rootUser')
|
|
||||||
// const rootUserPassword = request.body.get('rootUserPassword')
|
|
||||||
// const version = request.body.get('version')
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// return await db.updateDatabase({ id, name, defaultDatabase, dbUser, dbUserPassword, rootUser, rootUserPassword, version })
|
|
||||||
// } catch (err) {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { letsEncrypt } from '$lib/letsencrypt';
|
|||||||
import {
|
import {
|
||||||
configureSimpleServiceProxyOn,
|
configureSimpleServiceProxyOn,
|
||||||
reloadHaproxy,
|
reloadHaproxy,
|
||||||
|
setWwwRedirection,
|
||||||
startHttpProxy,
|
startHttpProxy,
|
||||||
startTcpProxy
|
startTcpProxy
|
||||||
} from '$lib/haproxy';
|
} from '$lib/haproxy';
|
||||||
@@ -86,13 +87,13 @@ export const post: RequestHandler = async (event) => {
|
|||||||
try {
|
try {
|
||||||
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
|
||||||
await configureSimpleServiceProxyOn({ id, domain, port: consolePort });
|
await configureSimpleServiceProxyOn({ id, domain, port: consolePort });
|
||||||
|
|
||||||
await db.updateMinioService({ id, publicPort });
|
await db.updateMinioService({ id, publicPort });
|
||||||
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
|
await startHttpProxy(destinationDocker, id, publicPort, apiPort);
|
||||||
|
|
||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
await letsEncrypt({ domain, id });
|
||||||
}
|
}
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy';
|
import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
import { PrismaErrorHandler } from '$lib/database';
|
import { PrismaErrorHandler } from '$lib/database';
|
||||||
|
|
||||||
@@ -52,6 +52,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
await letsEncrypt({ domain, id });
|
||||||
}
|
}
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy';
|
import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
import { PrismaErrorHandler } from '$lib/database';
|
import { PrismaErrorHandler } from '$lib/database';
|
||||||
|
|
||||||
@@ -185,6 +185,7 @@ COPY ./init-db.sh /docker-entrypoint-initdb.d/init-db.sh`;
|
|||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
await letsEncrypt({ domain, id });
|
||||||
}
|
}
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy';
|
import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
import { getServiceImage, PrismaErrorHandler } from '$lib/database';
|
import { getServiceImage, PrismaErrorHandler } from '$lib/database';
|
||||||
|
|
||||||
@@ -70,6 +70,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
await letsEncrypt({ domain, id });
|
||||||
}
|
}
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy';
|
import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
import { PrismaErrorHandler } from '$lib/database';
|
import { PrismaErrorHandler } from '$lib/database';
|
||||||
|
|
||||||
@@ -80,6 +80,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
await letsEncrypt({ domain, id });
|
||||||
}
|
}
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { promises as fs } from 'fs';
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import type { RequestHandler } from '@sveltejs/kit';
|
import type { RequestHandler } from '@sveltejs/kit';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
import { configureSimpleServiceProxyOn, reloadHaproxy } from '$lib/haproxy';
|
import { configureSimpleServiceProxyOn, reloadHaproxy, setWwwRedirection } from '$lib/haproxy';
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
import { PrismaErrorHandler } from '$lib/database';
|
import { PrismaErrorHandler } from '$lib/database';
|
||||||
|
|
||||||
@@ -117,6 +117,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (isHttps) {
|
if (isHttps) {
|
||||||
await letsEncrypt({ domain, id });
|
await letsEncrypt({ domain, id });
|
||||||
}
|
}
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
await reloadHaproxy(destinationDocker.engine);
|
await reloadHaproxy(destinationDocker.engine);
|
||||||
return {
|
return {
|
||||||
status: 200
|
status: 200
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
forceSSLOffApplication,
|
forceSSLOffApplication,
|
||||||
forceSSLOnApplication,
|
forceSSLOnApplication,
|
||||||
reloadHaproxy,
|
reloadHaproxy,
|
||||||
|
removeWwwRedirection,
|
||||||
|
setWwwRedirection,
|
||||||
startCoolifyProxy
|
startCoolifyProxy
|
||||||
} from '$lib/haproxy';
|
} from '$lib/haproxy';
|
||||||
import { letsEncrypt } from '$lib/letsencrypt';
|
import { letsEncrypt } from '$lib/letsencrypt';
|
||||||
@@ -45,9 +47,10 @@ export const del: RequestHandler = async (event) => {
|
|||||||
const { fqdn } = await event.request.json();
|
const { fqdn } = await event.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
await configureCoolifyProxyOff({ domain });
|
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||||
|
await configureCoolifyProxyOff(fqdn);
|
||||||
|
await removeWwwRedirection(domain);
|
||||||
return {
|
return {
|
||||||
status: 201
|
status: 201
|
||||||
};
|
};
|
||||||
@@ -77,9 +80,10 @@ export const post: RequestHandler = async (event) => {
|
|||||||
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
|
await db.prisma.setting.update({ where: { id }, data: { isRegistrationEnabled } });
|
||||||
}
|
}
|
||||||
if (oldFqdn && oldFqdn !== fqdn) {
|
if (oldFqdn && oldFqdn !== fqdn) {
|
||||||
const oldDomain = getDomain(oldFqdn);
|
|
||||||
if (oldFqdn) {
|
if (oldFqdn) {
|
||||||
await configureCoolifyProxyOff({ domain: oldDomain });
|
const oldDomain = getDomain(oldFqdn);
|
||||||
|
await configureCoolifyProxyOff(oldFqdn);
|
||||||
|
await removeWwwRedirection(oldDomain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fqdn) {
|
if (fqdn) {
|
||||||
@@ -88,7 +92,8 @@ export const post: RequestHandler = async (event) => {
|
|||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
const isHttps = fqdn.startsWith('https://');
|
const isHttps = fqdn.startsWith('https://');
|
||||||
if (domain) {
|
if (domain) {
|
||||||
await configureCoolifyProxyOn({ domain });
|
await configureCoolifyProxyOn(fqdn);
|
||||||
|
await setWwwRedirection(fqdn);
|
||||||
if (isHttps && !dev) {
|
if (isHttps && !dev) {
|
||||||
await letsEncrypt({ domain, isCoolify: true });
|
await letsEncrypt({ domain, isCoolify: true });
|
||||||
await forceSSLOnApplication({ domain });
|
await forceSSLOnApplication({ domain });
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
<div class="px-4 sm:px-6">
|
<div class="px-4 sm:px-6">
|
||||||
<div class="flex space-x-4 py-4 px-4">
|
<div class="flex space-x-4 py-4 px-4">
|
||||||
<p class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</p>
|
<p class="pt-2 text-base font-bold text-stone-100">Domain (FQDN)</p>
|
||||||
<div class="justify-center text-center">
|
<div class="justify-center">
|
||||||
<input
|
<input
|
||||||
bind:value={fqdn}
|
bind:value={fqdn}
|
||||||
readonly={!$session.isAdmin || isFqdnSet}
|
readonly={!$session.isAdmin || isFqdnSet}
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Explainer
|
<Explainer
|
||||||
text="Set the fully qualified domain name for your Coolify instance. <br>If you specify <span class='text-green-600 font-bold'>https</span>, it will be accessible only over https. <br>SSL certificate will be generated for you."
|
text="If you specify <span class='text-green-600 font-bold'>https</span>, Coolify will be accessible only over https. SSL certificate will be generated for you.<br>If you specify <span class='text-green-600 font-bold'>www</span>, Coolify will be redirected (302) from non-www and vice versa."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -71,8 +71,6 @@
|
|||||||
window.open(`${source.htmlUrl}/groups/${payload.groupName}/-/settings/applications`);
|
window.open(`${source.htmlUrl}/groups/${payload.groupName}/-/settings/applications`);
|
||||||
break;
|
break;
|
||||||
case 'instance':
|
case 'instance':
|
||||||
// TODO: This is not correct
|
|
||||||
// window.location.assign(`${source.htmlUrl}/-/profile/applications`);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ main,
|
|||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
|
@apply h-12 w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
@apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
|
@apply w-96 select-all rounded border border-transparent bg-transparent bg-coolgray-200 p-2 text-xs tracking-tight text-white placeholder-stone-600 outline-none transition duration-150 hover:bg-coolgray-500 focus:bg-coolgray-500 disabled:bg-transparent md:text-sm;
|
||||||
@@ -50,7 +50,7 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
@apply rounded bg-coolgray-200 p-1 px-4 py-2 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600 md:text-sm;
|
@apply rounded bg-coolgray-200 p-1 px-2 py-1 text-xs font-bold outline-none transition-all duration-100 hover:bg-coolgray-500 disabled:cursor-not-allowed disabled:bg-coolblack disabled:text-stone-600;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
Reference in New Issue
Block a user