commit
310b099ecf
@ -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.15",
|
"version": "2.0.16",
|
||||||
"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",
|
||||||
|
19
prisma/migrations/20220219231255_prmr_secrets/migration.sql
Normal file
19
prisma/migrations/20220219231255_prmr_secrets/migration.sql
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
-- RedefineTables
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Secret" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" TEXT NOT NULL,
|
||||||
|
"isPRMRSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"isBuildSecret" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
"applicationId" TEXT NOT NULL,
|
||||||
|
CONSTRAINT "Secret_applicationId_fkey" FOREIGN KEY ("applicationId") REFERENCES "Application" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Secret" ("applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value") SELECT "applicationId", "createdAt", "id", "isBuildSecret", "name", "updatedAt", "value" FROM "Secret";
|
||||||
|
DROP TABLE "Secret";
|
||||||
|
ALTER TABLE "new_Secret" RENAME TO "Secret";
|
||||||
|
CREATE UNIQUE INDEX "Secret_name_applicationId_isPRMRSecret_key" ON "Secret"("name", "applicationId", "isPRMRSecret");
|
||||||
|
PRAGMA foreign_key_check;
|
||||||
|
PRAGMA foreign_keys=ON;
|
@ -109,13 +109,14 @@ model Secret {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String
|
name String
|
||||||
value String
|
value String
|
||||||
|
isPRMRSecret Boolean @default(false)
|
||||||
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])
|
@@unique([name, applicationId, isPRMRSecret])
|
||||||
}
|
}
|
||||||
|
|
||||||
model BuildLog {
|
model BuildLog {
|
||||||
|
@ -9,7 +9,8 @@ export default async function ({
|
|||||||
docker,
|
docker,
|
||||||
buildId,
|
buildId,
|
||||||
baseDirectory,
|
baseDirectory,
|
||||||
secrets
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
let file = `${workdir}/Dockerfile`;
|
let file = `${workdir}/Dockerfile`;
|
||||||
@ -24,8 +25,16 @@ export default async function ({
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${file}`, Dockerfile.join('\n'));
|
await fs.writeFile(`${file}`, Dockerfile.join('\n'));
|
||||||
|
@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
const {
|
||||||
data;
|
workdir,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||||
|
@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
const {
|
||||||
data;
|
workdir,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||||
|
@ -2,8 +2,16 @@ import { buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { workdir, port, installCommand, buildCommand, startCommand, baseDirectory, secrets } =
|
const {
|
||||||
data;
|
workdir,
|
||||||
|
port,
|
||||||
|
installCommand,
|
||||||
|
buildCommand,
|
||||||
|
startCommand,
|
||||||
|
baseDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
Dockerfile.push(`COPY ./${baseDirectory || ''}package*.json ./`);
|
||||||
|
@ -2,8 +2,16 @@ import { buildCacheImageWithNode, buildImage } from '$lib/docker';
|
|||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
|
|
||||||
const createDockerfile = async (data, image): Promise<void> => {
|
const createDockerfile = async (data, image): Promise<void> => {
|
||||||
const { applicationId, tag, workdir, buildCommand, baseDirectory, publishDirectory, secrets } =
|
const {
|
||||||
data;
|
applicationId,
|
||||||
|
tag,
|
||||||
|
workdir,
|
||||||
|
buildCommand,
|
||||||
|
baseDirectory,
|
||||||
|
publishDirectory,
|
||||||
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
|
|
||||||
Dockerfile.push(`FROM ${image}`);
|
Dockerfile.push(`FROM ${image}`);
|
||||||
@ -11,8 +19,16 @@ const createDockerfile = async (data, image): Promise<void> => {
|
|||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (buildCommand) {
|
if (buildCommand) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import { browser } from '$app/env';
|
import { browser } from '$app/env';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
export let value;
|
|
||||||
let showPassword = false;
|
let showPassword = false;
|
||||||
|
|
||||||
|
export let value;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let isPasswordField = false;
|
export let isPasswordField = false;
|
||||||
export let readonly = false;
|
export let readonly = false;
|
||||||
@ -14,7 +14,7 @@
|
|||||||
export let name;
|
export let name;
|
||||||
export let placeholder = '';
|
export let placeholder = '';
|
||||||
|
|
||||||
let disabledClass = 'bg-coolback disabled:bg-coolblack select-all';
|
let disabledClass = 'bg-coolback disabled:bg-coolblack';
|
||||||
let actionsShow = false;
|
let actionsShow = false;
|
||||||
let isHttps = browser && window.location.protocol === 'https:';
|
let isHttps = browser && window.location.protocol === 'https:';
|
||||||
|
|
||||||
@ -29,11 +29,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="relative">
|
||||||
class="relative"
|
|
||||||
on:mouseenter={() => showActions(true)}
|
|
||||||
on:mouseleave={() => showActions(false)}
|
|
||||||
>
|
|
||||||
{#if !isPasswordField || showPassword}
|
{#if !isPasswordField || showPassword}
|
||||||
{#if textarea}
|
{#if textarea}
|
||||||
<textarea
|
<textarea
|
||||||
@ -77,7 +73,6 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if actionsShow}
|
|
||||||
<div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
|
<div class="absolute top-0 right-0 m-3 cursor-pointer text-warmGray-600 hover:text-white">
|
||||||
<div class="flex space-x-2">
|
<div class="flex space-x-2">
|
||||||
{#if isPasswordField}
|
{#if isPasswordField}
|
||||||
@ -141,5 +136,4 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,7 +18,6 @@ export const buildPacks = [
|
|||||||
|
|
||||||
{
|
{
|
||||||
name: 'static',
|
name: 'static',
|
||||||
...defaultBuildAndDeploy,
|
|
||||||
publishDirectory: 'dist',
|
publishDirectory: 'dist',
|
||||||
port: 80,
|
port: 80,
|
||||||
fancyName: 'Static',
|
fancyName: 'Static',
|
||||||
|
@ -15,8 +15,8 @@ export async function isDockerNetworkExists({ network }) {
|
|||||||
return await prisma.destinationDocker.findFirst({ where: { network } });
|
return await prisma.destinationDocker.findFirst({ where: { network } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isSecretExists({ id, name }) {
|
export async function isSecretExists({ id, name, isPRMRSecret }) {
|
||||||
return await prisma.secret.findFirst({ where: { name, applicationId: id } });
|
return await prisma.secret.findFirst({ where: { name, applicationId: id, isPRMRSecret } });
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function isDomainConfigured({ id, fqdn }) {
|
export async function isDomainConfigured({ id, fqdn }) {
|
||||||
|
@ -1,19 +1,41 @@
|
|||||||
import { encrypt } from '$lib/crypto';
|
import { encrypt, decrypt } from '$lib/crypto';
|
||||||
import { prisma } from './common';
|
import { prisma } from './common';
|
||||||
|
|
||||||
export async function listSecrets({ applicationId }) {
|
export async function listSecrets(applicationId: string) {
|
||||||
return await prisma.secret.findMany({
|
let secrets = await prisma.secret.findMany({
|
||||||
where: { applicationId },
|
where: { applicationId },
|
||||||
orderBy: { createdAt: 'desc' },
|
orderBy: { createdAt: 'desc' }
|
||||||
select: { id: true, createdAt: true, name: true, isBuildSecret: true }
|
});
|
||||||
|
secrets = secrets.map((secret) => {
|
||||||
|
secret.value = decrypt(secret.value);
|
||||||
|
return secret;
|
||||||
|
});
|
||||||
|
|
||||||
|
return secrets;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||||
|
value = encrypt(value);
|
||||||
|
return await prisma.secret.create({
|
||||||
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createSecret({ id, name, value, isBuildSecret }) {
|
export async function updateSecret({ id, name, value, isBuildSecret, isPRMRSecret }) {
|
||||||
value = encrypt(value);
|
value = encrypt(value);
|
||||||
return await prisma.secret.create({
|
const found = await prisma.secret.findFirst({ where: { applicationId: id, name, isPRMRSecret } });
|
||||||
data: { name, value, isBuildSecret, application: { connect: { id } } }
|
console.log(found);
|
||||||
|
|
||||||
|
if (found) {
|
||||||
|
return await prisma.secret.updateMany({
|
||||||
|
where: { applicationId: id, name, isPRMRSecret },
|
||||||
|
data: { value, isBuildSecret, isPRMRSecret }
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return await prisma.secret.create({
|
||||||
|
data: { name, value, isBuildSecret, isPRMRSecret, application: { connect: { id } } }
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeSecret({ id, name }) {
|
export async function removeSecret({ id, name }) {
|
||||||
|
@ -13,16 +13,25 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
|
|||||||
installCommand,
|
installCommand,
|
||||||
buildCommand,
|
buildCommand,
|
||||||
debug,
|
debug,
|
||||||
secrets
|
secrets,
|
||||||
|
pullmergeRequestId
|
||||||
} = data;
|
} = data;
|
||||||
const Dockerfile: Array<string> = [];
|
const Dockerfile: Array<string> = [];
|
||||||
Dockerfile.push(`FROM ${imageForBuild}`);
|
Dockerfile.push(`FROM ${imageForBuild}`);
|
||||||
Dockerfile.push('WORKDIR /usr/src/app');
|
Dockerfile.push('WORKDIR /usr/src/app');
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
if (!secret.isBuildSecret) {
|
if (secret.isBuildSecret) {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
Dockerfile.push(`ARG ${secret.name} ${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// TODO: If build command defined, install command should be the default yarn install
|
// TODO: If build command defined, install command should be the default yarn install
|
||||||
|
@ -64,7 +64,6 @@ export default async function (job) {
|
|||||||
if (destinationDockerId) {
|
if (destinationDockerId) {
|
||||||
destinationType = 'docker';
|
destinationType = 'docker';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destinationType === 'docker') {
|
if (destinationType === 'docker') {
|
||||||
const docker = dockerInstance({ destinationDocker });
|
const docker = dockerInstance({ destinationDocker });
|
||||||
const host = getEngine(destinationDocker.engine);
|
const host = getEngine(destinationDocker.engine);
|
||||||
@ -205,7 +204,15 @@ export default async function (job) {
|
|||||||
const envs = [];
|
const envs = [];
|
||||||
if (secrets.length > 0) {
|
if (secrets.length > 0) {
|
||||||
secrets.forEach((secret) => {
|
secrets.forEach((secret) => {
|
||||||
|
if (pullmergeRequestId) {
|
||||||
|
if (secret.isPRMRSecret) {
|
||||||
envs.push(`${secret.name}=${secret.value}`);
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!secret.isPRMRSecret) {
|
||||||
|
envs.push(`${secret.name}=${secret.value}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
await fs.writeFile(`${workdir}/.env`, envs.join('\n'));
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
} else if (type === 'github') {
|
} else if (type === 'github') {
|
||||||
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
const files = await get(`${apiUrl}/repos/${repository}/contents?ref=${branch}`, {
|
||||||
Authorization: `Bearer ${$session.ghToken || ghToken}`,
|
Authorization: `Bearer ${$session.ghToken}`,
|
||||||
Accept: 'application/vnd.github.v2.json'
|
Accept: 'application/vnd.github.v2.json'
|
||||||
});
|
});
|
||||||
const packageJson = files.find(
|
const packageJson = files.find(
|
||||||
|
@ -197,7 +197,7 @@
|
|||||||
value={application.gitSource.name}
|
value={application.gitSource.name}
|
||||||
id="gitSource"
|
id="gitSource"
|
||||||
disabled
|
disabled
|
||||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -214,7 +214,7 @@
|
|||||||
value="{application.repository}/{application.branch}"
|
value="{application.repository}/{application.branch}"
|
||||||
id="repository"
|
id="repository"
|
||||||
disabled
|
disabled
|
||||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@ -232,7 +232,7 @@
|
|||||||
value={application.buildPack}
|
value={application.buildPack}
|
||||||
id="buildPack"
|
id="buildPack"
|
||||||
disabled
|
disabled
|
||||||
class="cursor-pointer bg-coolgray-200 hover:bg-coolgray-500"
|
class="cursor-pointer hover:bg-coolgray-500"
|
||||||
/></a
|
/></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,9 @@ export const get: RequestHandler = async (event) => {
|
|||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
try {
|
try {
|
||||||
|
const secrets = await db.listSecrets(id);
|
||||||
|
const applicationSecrets = secrets.filter((secret) => !secret.isPRMRSecret);
|
||||||
|
const PRMRSecrets = secrets.filter((secret) => secret.isPRMRSecret);
|
||||||
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
|
const destinationDocker = await db.getDestinationByApplicationId({ id, teamId });
|
||||||
const docker = dockerInstance({ destinationDocker });
|
const docker = dockerInstance({ destinationDocker });
|
||||||
const listContainers = await docker.engine.listContainers({
|
const listContainers = await docker.engine.listContainers({
|
||||||
@ -35,7 +38,13 @@ export const get: RequestHandler = async (event) => {
|
|||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
containers: jsonContainers
|
containers: jsonContainers,
|
||||||
|
applicationSecrets: applicationSecrets.sort((a, b) => {
|
||||||
|
return ('' + a.name).localeCompare(b.name);
|
||||||
|
}),
|
||||||
|
PRMRSecrets: PRMRSecrets.sort((a, b) => {
|
||||||
|
return ('' + a.name).localeCompare(b.name);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -22,8 +22,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let containers;
|
export let containers;
|
||||||
export let application;
|
export let application;
|
||||||
|
export let PRMRSecrets;
|
||||||
|
export let applicationSecrets;
|
||||||
import { getDomain } from '$lib/components/common';
|
import { getDomain } from '$lib/components/common';
|
||||||
|
import Secret from '../secrets/_Secret.svelte';
|
||||||
|
import { get } from '$lib/api';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import Explainer from '$lib/components/Explainer.svelte';
|
||||||
|
|
||||||
|
const { id } = $page.params;
|
||||||
|
async function refreshSecrets() {
|
||||||
|
const data = await get(`/applications/${id}/secrets.json`);
|
||||||
|
PRMRSecrets = [...data.secrets];
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex space-x-1 p-6 font-bold">
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
@ -32,7 +43,57 @@
|
|||||||
</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">
|
||||||
|
<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-white">Name</th
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
|
||||||
|
>Value</th
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
|
||||||
|
>Need during buildtime?</th
|
||||||
|
>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-bold uppercase tracking-wider text-white"
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="">
|
||||||
|
{#each applicationSecrets as secret}
|
||||||
|
{#key secret.id}
|
||||||
|
<tr class="h-20 transition duration-100 hover:bg-coolgray-400">
|
||||||
|
<Secret
|
||||||
|
PRMRSecret={PRMRSecrets.find((s) => s.name === secret.name)}
|
||||||
|
isPRMRSecret
|
||||||
|
name={secret.name}
|
||||||
|
value={secret.value}
|
||||||
|
isBuildSecret={secret.isBuildSecret}
|
||||||
|
on:refresh={refreshSecrets}
|
||||||
|
/>
|
||||||
|
</tr>
|
||||||
|
{/key}
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center py-4 text-center">
|
||||||
|
<Explainer
|
||||||
|
customClass="w-full"
|
||||||
|
text={applicationSecrets.length === 0
|
||||||
|
? "<span class='font-bold text-white text-xl'>Please add secrets to the application first.</span> <br><br>These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
||||||
|
: "These values overwrite application secrets in PR/MR deployments. Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mx-auto max-w-4xl py-10">
|
||||||
<div class="flex flex-wrap justify-center space-x-2">
|
<div class="flex flex-wrap justify-center space-x-2">
|
||||||
{#if containers.length > 0}
|
{#if containers.length > 0}
|
||||||
{#each containers as container}
|
{#each containers as container}
|
||||||
|
@ -3,14 +3,19 @@
|
|||||||
export let value = '';
|
export let value = '';
|
||||||
export let isBuildSecret = false;
|
export let isBuildSecret = false;
|
||||||
export let isNewSecret = false;
|
export let isNewSecret = false;
|
||||||
|
export let isPRMRSecret = false;
|
||||||
|
export let PRMRSecret = {};
|
||||||
|
|
||||||
|
if (isPRMRSecret) value = PRMRSecret.value;
|
||||||
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { del, post } from '$lib/api';
|
import { del, post } from '$lib/api';
|
||||||
|
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let nameEl;
|
|
||||||
let valueEl;
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
async function removeSecret() {
|
async function removeSecret() {
|
||||||
try {
|
try {
|
||||||
@ -25,24 +30,24 @@
|
|||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function saveSecret() {
|
async function saveSecret(isNew = false) {
|
||||||
const nameValid = nameEl.checkValidity();
|
if (!name) return errorNotification('Name is required.');
|
||||||
const valueValid = valueEl.checkValidity();
|
if (!value) return errorNotification('Value is required.');
|
||||||
if (!nameValid) {
|
|
||||||
return nameEl.reportValidity();
|
|
||||||
}
|
|
||||||
if (!valueValid) {
|
|
||||||
return valueEl.reportValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await post(`/applications/${id}/secrets.json`, { name, value, isBuildSecret });
|
await post(`/applications/${id}/secrets.json`, {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
isBuildSecret,
|
||||||
|
isPRMRSecret,
|
||||||
|
isNew
|
||||||
|
});
|
||||||
dispatch('refresh');
|
dispatch('refresh');
|
||||||
if (isNewSecret) {
|
if (isNewSecret) {
|
||||||
name = '';
|
name = '';
|
||||||
value = '';
|
value = '';
|
||||||
isBuildSecret = false;
|
isBuildSecret = false;
|
||||||
}
|
}
|
||||||
|
toast.push('Secret saved.');
|
||||||
} catch ({ error }) {
|
} catch ({ error }) {
|
||||||
return errorNotification(error);
|
return errorNotification(error);
|
||||||
}
|
}
|
||||||
@ -56,8 +61,7 @@
|
|||||||
|
|
||||||
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
||||||
<input
|
<input
|
||||||
id="secretName"
|
id={isNewSecret ? 'secretName' : 'secretNameNew'}
|
||||||
bind:this={nameEl}
|
|
||||||
bind:value={name}
|
bind:value={name}
|
||||||
required
|
required
|
||||||
placeholder="EXAMPLE_VARIABLE"
|
placeholder="EXAMPLE_VARIABLE"
|
||||||
@ -68,16 +72,13 @@
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
||||||
<input
|
<CopyPasswordField
|
||||||
id="secretValue"
|
id={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||||
|
name={isNewSecret ? 'secretValue' : 'secretValueNew'}
|
||||||
|
isPasswordField={true}
|
||||||
bind:value
|
bind:value
|
||||||
bind:this={valueEl}
|
|
||||||
required
|
required
|
||||||
placeholder="J$#@UIO%HO#$U%H"
|
placeholder="J$#@UIO%HO#$U%H"
|
||||||
class="-mx-2 w-64 border-2 border-transparent"
|
|
||||||
class:bg-transparent={!isNewSecret}
|
|
||||||
class:cursor-not-allowed={!isNewSecret}
|
|
||||||
readonly={!isNewSecret}
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
|
<td class="whitespace-nowrap px-6 py-2 text-center text-sm font-medium text-white">
|
||||||
@ -132,11 +133,20 @@
|
|||||||
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
<td class="whitespace-nowrap px-6 py-2 text-sm font-medium text-white">
|
||||||
{#if isNewSecret}
|
{#if isNewSecret}
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={saveSecret}>Add</button>
|
<button class="bg-green-600 hover:bg-green-500" on:click={() => saveSecret(true)}>Add</button>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
<div class="flex-col space-y-2">
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<button class="w-24 bg-green-600 hover:bg-green-500" on:click={() => saveSecret(false)}
|
||||||
|
>Set</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{#if !isPRMRSecret}
|
||||||
<div class="flex justify-center items-end">
|
<div class="flex justify-center items-end">
|
||||||
<button class="w-24 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>
|
||||||
|
{/if}
|
||||||
</td>
|
</td>
|
||||||
|
@ -7,8 +7,9 @@ export const get: RequestHandler = async (event) => {
|
|||||||
const { teamId, status, body } = await getUserDetails(event);
|
const { teamId, status, body } = await getUserDetails(event);
|
||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
|
const { id } = event.params;
|
||||||
try {
|
try {
|
||||||
const secrets = await db.listSecrets({ applicationId: event.params.id });
|
const secrets = await (await db.listSecrets(id)).filter((secret) => !secret.isPRMRSecret);
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
@ -27,16 +28,22 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { id } = event.params;
|
const { id } = event.params;
|
||||||
const { name, value, isBuildSecret } = await event.request.json();
|
const { name, value, isBuildSecret, isPRMRSecret, isNew } = await event.request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const found = await db.isSecretExists({ id, name });
|
if (isNew) {
|
||||||
|
const found = await db.isSecretExists({ id, name, isPRMRSecret });
|
||||||
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, isPRMRSecret });
|
||||||
|
return {
|
||||||
|
status: 201
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await db.updateSecret({ id, name, value, isBuildSecret, isPRMRSecret });
|
||||||
return {
|
return {
|
||||||
status: 201
|
status: 201
|
||||||
};
|
};
|
||||||
|
@ -67,10 +67,10 @@
|
|||||||
<tbody class="">
|
<tbody class="">
|
||||||
{#each secrets as secret}
|
{#each secrets as secret}
|
||||||
{#key secret.id}
|
{#key secret.id}
|
||||||
<tr class="hover:bg-coolgray-200">
|
<tr class="h-20 transition duration-100 hover:bg-coolgray-400">
|
||||||
<Secret
|
<Secret
|
||||||
name={secret.name}
|
name={secret.name}
|
||||||
value={secret.value ? secret.value : 'ENCRYPTED'}
|
value={secret.value}
|
||||||
isBuildSecret={secret.isBuildSecret}
|
isBuildSecret={secret.isBuildSecret}
|
||||||
on:refresh={refreshSecrets}
|
on:refresh={refreshSecrets}
|
||||||
/>
|
/>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">CouchDB</div>
|
<div class="title">CouchDB</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="defaultDatabase">Default Database</label>
|
<label for="defaultDatabase">Default Database</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">MongoDB</div>
|
<div class="title">MongoDB</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="rootUser">Root User</label>
|
<label for="rootUser">Root User</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">MySQL</div>
|
<div class="title">MySQL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=" px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="defaultDatabase">Default Database</label>
|
<label for="defaultDatabase">Default Database</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">PostgreSQL</div>
|
<div class="title">PostgreSQL</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="defaultDatabase">Default Database</label>
|
<label for="defaultDatabase">Default Database</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">Redis</div>
|
<div class="title">Redis</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="px-10">
|
<div class="space-y-2 px-10">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="dbUserPassword">Password</label>
|
<label for="dbUserPassword">Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<div class="flex space-x-1 py-5 font-bold">
|
<div class="flex space-x-1 py-5 font-bold">
|
||||||
<div class="title">MinIO Server</div>
|
<div class="title">MinIO Server</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="rootUser">Root User</label>
|
<label for="rootUser">Root User</label>
|
||||||
<input
|
<input
|
||||||
name="rootUser"
|
name="rootUser"
|
||||||
@ -18,7 +18,7 @@
|
|||||||
readonly
|
readonly
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="rootUserPassword">Root's Password</label>
|
<label for="rootUserPassword">Root's Password</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
id="rootUserPassword"
|
id="rootUserPassword"
|
||||||
@ -29,7 +29,7 @@
|
|||||||
value={service.minio.rootUserPassword}
|
value={service.minio.rootUserPassword}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center px-10">
|
||||||
<label for="publicPort">API Port</label>
|
<label for="publicPort">API Port</label>
|
||||||
<input
|
<input
|
||||||
name="publicPort"
|
name="publicPort"
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { dev } from '$app/env';
|
||||||
import { getDomain, getUserDetails } from '$lib/common';
|
import { getDomain, getUserDetails } from '$lib/common';
|
||||||
import * as db from '$lib/database';
|
import * as db from '$lib/database';
|
||||||
import { listSettings, ErrorHandler } from '$lib/database';
|
import { listSettings, ErrorHandler } from '$lib/database';
|
||||||
@ -43,7 +44,13 @@ export const del: RequestHandler = async (event) => {
|
|||||||
if (status === 401) return { status, body };
|
if (status === 401) return { status, body };
|
||||||
|
|
||||||
const { fqdn } = await event.request.json();
|
const { fqdn } = await event.request.json();
|
||||||
const ip = await dns.resolve(event.url.hostname);
|
let ip;
|
||||||
|
console.log(fqdn);
|
||||||
|
try {
|
||||||
|
ip = await dns.resolve(fqdn);
|
||||||
|
} catch (error) {
|
||||||
|
// Do not care.
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const domain = getDomain(fqdn);
|
const domain = getDomain(fqdn);
|
||||||
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
await db.prisma.setting.update({ where: { fqdn }, data: { fqdn: null } });
|
||||||
@ -53,7 +60,7 @@ export const del: RequestHandler = async (event) => {
|
|||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
message: 'Domain removed',
|
message: 'Domain removed',
|
||||||
redirect: `http://${ip[0]}:3000/settings`
|
redirect: ip ? `http://${ip[0]}:3000/settings` : undefined
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -159,7 +159,7 @@
|
|||||||
: browser && 'http://' + window.location.hostname + ':8404'
|
: browser && 'http://' + window.location.hostname + ':8404'
|
||||||
} target="_blank">stats</a> page.`}
|
} target="_blank">stats</a> page.`}
|
||||||
/>
|
/>
|
||||||
<div class="px-10 py-5">
|
<div class="space-y-2 px-10 py-5">
|
||||||
<div class="grid grid-cols-2 items-center">
|
<div class="grid grid-cols-2 items-center">
|
||||||
<label for="proxyUser">User</label>
|
<label for="proxyUser">User</label>
|
||||||
<CopyPasswordField
|
<CopyPasswordField
|
||||||
|
@ -35,10 +35,10 @@ .main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input {
|
input {
|
||||||
@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;
|
@apply h-12 w-96 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:border disabled:border-dashed disabled:border-coolgray-300 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 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:border disabled:border-dashed disabled:border-coolgray-300 disabled:bg-transparent md:text-sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
|
Loading…
Reference in New Issue
Block a user