commit
58af09114b
10
.github/workflows/pocketbase-release.yml
vendored
10
.github/workflows/pocketbase-release.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
context: others/pocketbase/
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: coollabsio/pocketbase:0.10.2-arm64
|
||||
tags: coollabsio/pocketbase:0.11.0-arm64
|
||||
amd64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
context: others/pocketbase/
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: coollabsio/pocketbase:0.10.2-amd64
|
||||
tags: coollabsio/pocketbase:0.11.0-amd64
|
||||
aarch64:
|
||||
runs-on: [self-hosted, arm64]
|
||||
steps:
|
||||
@ -69,7 +69,7 @@ jobs:
|
||||
context: others/pocketbase/
|
||||
platforms: linux/aarch64
|
||||
push: true
|
||||
tags: coollabsio/pocketbase:0.10.2-aarch64
|
||||
tags: coollabsio/pocketbase:0.11.0-aarch64
|
||||
merge-manifest:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [amd64, arm64, aarch64]
|
||||
@ -87,5 +87,5 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Create & publish manifest
|
||||
run: |
|
||||
docker manifest create coollabsio/pocketbase:0.10.2 --amend coollabsio/pocketbase:0.10.2-amd64 --amend coollabsio/pocketbase:0.10.2-arm64 --amend coollabsio/pocketbase:0.10.2-aarch64
|
||||
docker manifest push coollabsio/pocketbase:0.10.2
|
||||
docker manifest create coollabsio/pocketbase:0.11.0 --amend coollabsio/pocketbase:0.11.0-amd64 --amend coollabsio/pocketbase:0.11.0-arm64 --amend coollabsio/pocketbase:0.11.0-aarch64
|
||||
docker manifest push coollabsio/pocketbase:0.11.0
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,6 +12,7 @@ dist
|
||||
apps/api/db/*.db
|
||||
apps/api/db/migration.db-journal
|
||||
apps/api/core*
|
||||
apps/server/build
|
||||
apps/backup/backups/*
|
||||
!apps/backup/backups/.gitkeep
|
||||
/logs
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,3 +1,227 @@
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: "9.22"
|
||||
documentation: https://docs.directus.io/getting-started/introduction.html
|
||||
type: directus-postgresql
|
||||
name: Directus
|
||||
subname: (PostgreSQL)
|
||||
description: >-
|
||||
Directus is a free and open-source headless CMS framework for managing custom SQL-based databases.
|
||||
labels:
|
||||
- CMS
|
||||
- headless
|
||||
services:
|
||||
$$id:
|
||||
name: Directus
|
||||
depends_on:
|
||||
- $$id-postgresql
|
||||
- $$id-redis
|
||||
image: directus/directus:$$core_version
|
||||
volumes:
|
||||
- $$id-uploads:/directus/uploads
|
||||
- $$id-database:/directus/database
|
||||
- $$id-extensions:/directus/extensions
|
||||
environment:
|
||||
- KEY=$$secret_key
|
||||
- SECRET=$$secret_secret
|
||||
- DB_CLIENT=pg
|
||||
- DB_CONNECTION_STRING=$$secret_db_connection_string
|
||||
- CACHE_ENABLED=true
|
||||
- CACHE_STORE=redis
|
||||
- CACHE_REDIS=$$secret_cache_redis
|
||||
- ADMIN_EMAIL=$$config_admin_email
|
||||
- ADMIN_PASSWORD=$$secret_admin_password
|
||||
- CACHE_AUTO_PURGE=true
|
||||
- PUBLIC_URL=$$config_public_url
|
||||
ports:
|
||||
- "8055"
|
||||
$$id-postgresql:
|
||||
name: Directus PostgreSQL
|
||||
depends_on: []
|
||||
image: postgres:14-alpine
|
||||
volumes:
|
||||
- $$id-postgresql-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=$$config_postgres_user
|
||||
- POSTGRES_PASSWORD=$$secret_postgres_password
|
||||
- POSTGRES_DB=$$config_postgres_db
|
||||
ports: []
|
||||
$$id-redis:
|
||||
name: Directus Redis
|
||||
depends_on: []
|
||||
image: redis:7.0.4-alpine
|
||||
command: "--maxmemory 512mb --maxmemory-policy allkeys-lru --maxmemory-samples 5"
|
||||
volumes:
|
||||
- "$$id-redis:/data"
|
||||
environment: []
|
||||
variables:
|
||||
- id: $$config_public_url
|
||||
name: PUBLIC_URL
|
||||
label: Public URL
|
||||
defaultValue: $$generate_fqdn
|
||||
description: ""
|
||||
- id: $$secret_db_connection_string
|
||||
name: DB_CONNECTION_STRING
|
||||
label: Directus Database Url
|
||||
defaultValue: postgresql://$$config_postgres_user:$$secret_postgres_password@$$id-postgresql:5432/$$config_postgres_db
|
||||
description: ""
|
||||
- id: $$config_postgres_db
|
||||
main: $$id-postgresql
|
||||
name: POSTGRES_DB
|
||||
label: Database
|
||||
defaultValue: directus
|
||||
description: ""
|
||||
- id: $$config_postgres_user
|
||||
main: $$id-postgresql
|
||||
name: POSTGRES_USER
|
||||
label: User
|
||||
defaultValue: $$generate_username
|
||||
description: ""
|
||||
- id: $$secret_postgres_password
|
||||
main: $$id-postgresql
|
||||
name: POSTGRES_PASSWORD
|
||||
label: Password
|
||||
defaultValue: $$generate_password
|
||||
description: ""
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_cache_redis
|
||||
name: CACHE_REDIS
|
||||
label: Redis Url
|
||||
defaultValue: redis://$$id-redis:6379
|
||||
description: ""
|
||||
- id: $$config_admin_email
|
||||
name: ADMIN_EMAIL
|
||||
label: Initial Admin Email
|
||||
defaultValue: "admin@example.com"
|
||||
description: "The email address of the first user that is automatically created. You can change it later in Directus."
|
||||
- id: $$secret_admin_password
|
||||
name: ADMIN_PASSWORD
|
||||
label: Initial Admin Password
|
||||
defaultValue: $$generate_password
|
||||
description: "The password of the first user that is automatically created."
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_key
|
||||
name: KEY
|
||||
label: Key
|
||||
defaultValue: $$generate_password
|
||||
description: "Unique identifier for the project."
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_secret
|
||||
name: SECRET
|
||||
label: Secret
|
||||
defaultValue: $$generate_password
|
||||
description: "Secret string for the project."
|
||||
showOnConfiguration: true
|
||||
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: v1.3.8
|
||||
documentation: https://github.com/LibreTranslate/LibreTranslate
|
||||
description: Free and Open Source Machine Translation API. 100% self-hosted, offline capable and easy to setup.
|
||||
type: libretranslate
|
||||
name: Libretranslate
|
||||
labels:
|
||||
- translator
|
||||
- argos
|
||||
- python
|
||||
- libretranslate
|
||||
services:
|
||||
$$id:
|
||||
name: Libretranslate
|
||||
image: libretranslate/libretranslate:$$core_version
|
||||
environment:
|
||||
- LT_HOST=0.0.0.0
|
||||
- LT_SUGGESTIONS=true
|
||||
- LT_CHAR_LIMIT=$$config_lt_char_limit
|
||||
- LT_REQ_LIMIT=$$config_lt_req_limit
|
||||
- LT_BATCH_LIMIT=$$config_lt_batch_limit
|
||||
- LT_GA_ID=$$config_lt_ga_id
|
||||
- LT_DISABLE_WEB_UI=$$config_lt_web_ui
|
||||
volumes:
|
||||
- $$id-libretranslate:/libretranslate
|
||||
ports:
|
||||
- "5000"
|
||||
variables:
|
||||
- id: $$config_lt_char_limit
|
||||
name: LT_CHAR_LIMIT
|
||||
label: Char limit
|
||||
defaultValue: "5000"
|
||||
description: "Set character limit."
|
||||
- id: $$config_lt_req_limit
|
||||
name: LT_REQ_LIMIT
|
||||
label: Request limit
|
||||
defaultValue: "5000"
|
||||
description: "Set maximum number of requests per minute per client."
|
||||
- id: $$config_lt_batch_limit
|
||||
name: LT_BATCH_LIMIT
|
||||
label: Batch Limit
|
||||
defaultValue: "5000"
|
||||
description: "Set maximum number of texts to translate in a batch request."
|
||||
- id: $$config_lt_ga_id
|
||||
name: LT_GA_ID
|
||||
label: Google Analytics ID
|
||||
defaultValue: ""
|
||||
description: "Enable Google Analytics on the API client page by providing an ID"
|
||||
- id: $$config_lt_web_ui
|
||||
name: LT_DISABLE_WEB_UI
|
||||
label: Web UI
|
||||
defaultValue: "false"
|
||||
description: "Disable or enable web ui. True or false."
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 0.8.1
|
||||
documentation: https://github.com/benbusby/whoogle-search
|
||||
type: whoogle
|
||||
name: Whoogle Search
|
||||
description: A self-hosted, ad-free, privacy-respecting metasearch engine
|
||||
labels:
|
||||
- search
|
||||
- google
|
||||
services:
|
||||
$$id:
|
||||
name: Whoogle Search
|
||||
documentation: https://github.com/benbusby/whoogle-search
|
||||
depends_on: []
|
||||
image: benbusby/whoogle-search:$$core_version
|
||||
cap_drop:
|
||||
- ALL
|
||||
environment:
|
||||
- WHOOGLE_USER=$$config_whoogle_username
|
||||
- WHOOGLE_PASS=$$secret_whoogle_password
|
||||
- WHOOGLE_CONFIG_PREFERENCES_KEY=$$secret_whoogle_preferences_key
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
ports:
|
||||
- "5000"
|
||||
variables:
|
||||
- id: $$config_whoogle_username
|
||||
name: WHOOGLE_USER
|
||||
label: Whoogle User
|
||||
defaultValue: $$generate_username
|
||||
description: "Username to log into Whoogle"
|
||||
- id: $$secret_whoogle_password
|
||||
name: WHOOGLE_PASSWORD
|
||||
label: Whoogle Password
|
||||
defaultValue: $$generate_password
|
||||
description: "Password to log into Whoogle"
|
||||
showOnConfiguration: true
|
||||
- id: $$secret_whoogle_preferences_key
|
||||
name: WHOOGLE_CONFIG_PREFERENCES_KEY
|
||||
label: Whoogle preferences key
|
||||
defaultValue: $$generate_password
|
||||
description: "password to encrypt preferences"
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: 1.1.3
|
||||
documentation: https://docs.openblocks.dev/
|
||||
type: openblocks
|
||||
name: Openblocks
|
||||
description: The Open Source Retool Alternative
|
||||
services:
|
||||
$$id:
|
||||
image: openblocksdev/openblocks-ce:$$core_version
|
||||
volumes:
|
||||
- $$id-stacks-data:/openblocks-stacks
|
||||
ports:
|
||||
- "3000"
|
||||
- templateVersion: 1.0.0
|
||||
defaultVersion: "0.10.2"
|
||||
documentation: https://pocketbase.io/docs/
|
||||
@ -124,12 +348,12 @@
|
||||
description: ""
|
||||
- id: $$config_disable_auth
|
||||
name: DISABLE_AUTH
|
||||
label: Disable Authentication
|
||||
label: Authentication
|
||||
defaultValue: "false"
|
||||
description: ""
|
||||
- id: $$config_disable_registration
|
||||
name: DISABLE_REGISTRATION
|
||||
label: Disable Registration
|
||||
label: Registration
|
||||
defaultValue: "true"
|
||||
description: ""
|
||||
- id: $$config_postgres_user
|
||||
@ -332,12 +556,12 @@
|
||||
volumes:
|
||||
- $$id-lavalink:/lavalink
|
||||
ports:
|
||||
- "2333"
|
||||
- $$config_port
|
||||
files:
|
||||
- location: /opt/Lavalink/application.yml
|
||||
content: >-
|
||||
server:
|
||||
port: $$config_port
|
||||
port: 2333
|
||||
address: 0.0.0.0
|
||||
lavalink:
|
||||
server:
|
||||
@ -364,11 +588,6 @@
|
||||
max-file-size: 1GB
|
||||
max-history: 30
|
||||
variables:
|
||||
- id: $$config_port
|
||||
name: PORT
|
||||
label: Port
|
||||
defaultValue: "2333"
|
||||
required: true
|
||||
- id: $$secret_password
|
||||
name: PASSWORD
|
||||
label: Password
|
||||
@ -3250,12 +3469,12 @@
|
||||
description: ""
|
||||
- id: $$config_disable_auth
|
||||
name: DISABLE_AUTH
|
||||
label: Disable Authentication
|
||||
label: Authentication
|
||||
defaultValue: "false"
|
||||
description: ""
|
||||
- id: $$config_disable_registration
|
||||
name: DISABLE_REGISTRATION
|
||||
label: Disable Registration
|
||||
label: Registration
|
||||
defaultValue: "true"
|
||||
description: ""
|
||||
- id: $$config_postgresql_username
|
||||
|
@ -48,7 +48,7 @@
|
||||
"is-ip": "5.0.0",
|
||||
"is-port-reachable": "4.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"minimist": "^1.2.7",
|
||||
"node-forge": "1.3.1",
|
||||
"node-os-utils": "1.3.7",
|
||||
|
@ -0,0 +1,27 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_GitSource" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"name" TEXT NOT NULL,
|
||||
"forPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"type" TEXT,
|
||||
"apiUrl" TEXT,
|
||||
"htmlUrl" TEXT,
|
||||
"customPort" INTEGER NOT NULL DEFAULT 22,
|
||||
"customUser" TEXT NOT NULL DEFAULT 'git',
|
||||
"organization" TEXT,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"githubAppId" TEXT,
|
||||
"gitlabAppId" TEXT,
|
||||
"isSystemWide" BOOLEAN NOT NULL DEFAULT false,
|
||||
CONSTRAINT "GitSource_gitlabAppId_fkey" FOREIGN KEY ("gitlabAppId") REFERENCES "GitlabApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "GitSource_githubAppId_fkey" FOREIGN KEY ("githubAppId") REFERENCES "GithubApp" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_GitSource" ("apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt") SELECT "apiUrl", "createdAt", "customPort", "forPublic", "githubAppId", "gitlabAppId", "htmlUrl", "id", "isSystemWide", "name", "organization", "type", "updatedAt" FROM "GitSource";
|
||||
DROP TABLE "GitSource";
|
||||
ALTER TABLE "new_GitSource" RENAME TO "GitSource";
|
||||
CREATE UNIQUE INDEX "GitSource_githubAppId_key" ON "GitSource"("githubAppId");
|
||||
CREATE UNIQUE INDEX "GitSource_gitlabAppId_key" ON "GitSource"("gitlabAppId");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
@ -325,6 +325,7 @@ model GitSource {
|
||||
apiUrl String?
|
||||
htmlUrl String?
|
||||
customPort Int @default(22)
|
||||
customUser String @default("git")
|
||||
organization String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
@ -156,7 +156,7 @@ const host = '0.0.0.0';
|
||||
graceful.listen();
|
||||
|
||||
setInterval(async () => {
|
||||
if (!scheduler.workers.has('deployApplication')) {
|
||||
if (!scheduler.workers.has('deployApplication')) {
|
||||
scheduler.run('deployApplication');
|
||||
}
|
||||
}, 2000);
|
||||
|
@ -419,6 +419,7 @@ import * as buildpacks from '../lib/buildPacks';
|
||||
githubAppId: gitSource.githubApp?.id,
|
||||
gitlabAppId: gitSource.gitlabApp?.id,
|
||||
customPort: gitSource.customPort,
|
||||
customUser: gitSource.customUser,
|
||||
gitCommitHash,
|
||||
configuration,
|
||||
repository,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { generateSecrets } from '../common';
|
||||
import { buildCacheImageForLaravel, buildImage } from './common';
|
||||
|
||||
const createDockerfile = async (data, image): Promise<void> => {
|
||||
@ -7,6 +8,11 @@ const createDockerfile = async (data, image): Promise<void> => {
|
||||
|
||||
Dockerfile.push(`FROM ${image}`);
|
||||
Dockerfile.push(`LABEL coolify.buildId=${buildId}`);
|
||||
if (secrets.length > 0) {
|
||||
generateSecrets(secrets, pullmergeRequestId, true).forEach((env) => {
|
||||
Dockerfile.push(env);
|
||||
});
|
||||
}
|
||||
Dockerfile.push('WORKDIR /app');
|
||||
Dockerfile.push(`ENV WEB_DOCUMENT_ROOT /app/public`);
|
||||
Dockerfile.push(`COPY --chown=application:application composer.* ./`);
|
||||
|
@ -19,7 +19,7 @@ import { saveBuildLog, saveDockerRegistryCredentials } from './buildPacks/common
|
||||
import { scheduler } from './scheduler';
|
||||
import type { ExecaChildProcess } from 'execa';
|
||||
|
||||
export const version = '3.12.8';
|
||||
export const version = '3.12.9';
|
||||
export const isDev = process.env.NODE_ENV === 'development';
|
||||
export const sentryDSN =
|
||||
'https://409f09bcb7af47928d3e0f46b78987f3@o1082494.ingest.sentry.io/4504236622217216';
|
||||
@ -1884,6 +1884,30 @@ export async function pushToRegistry(
|
||||
});
|
||||
}
|
||||
|
||||
function parseSecret(secret, isBuild) {
|
||||
if (secret.value.includes('$')) {
|
||||
secret.value = secret.value.replaceAll('$', '$$$$');
|
||||
}
|
||||
if (secret.value.includes('\\n')) {
|
||||
if (isBuild) {
|
||||
return `ARG ${secret.name}=${secret.value}`;
|
||||
} else {
|
||||
return `${secret.name}=${secret.value}`;
|
||||
}
|
||||
} else if (secret.value.includes(' ')) {
|
||||
if (isBuild) {
|
||||
return `ARG ${secret.name}='${secret.value}'`;
|
||||
} else {
|
||||
return `${secret.name}='${secret.value}'`;
|
||||
}
|
||||
} else {
|
||||
if (isBuild) {
|
||||
return `ARG ${secret.name}=${secret.value}`;
|
||||
} else {
|
||||
return `${secret.name}=${secret.value}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
export function generateSecrets(
|
||||
secrets: Array<any>,
|
||||
pullmergeRequestId: string,
|
||||
@ -1899,15 +1923,7 @@ export function generateSecrets(
|
||||
return;
|
||||
}
|
||||
const build = isBuild && secret.isBuildSecret;
|
||||
if (build) {
|
||||
if (secret.value.includes(' ') || secret.value.includes('\\n')) {
|
||||
envs.push(`ARG ${secret.name}='${secret.value}'`);
|
||||
} else {
|
||||
envs.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
envs.push(parseSecret(secret, build));
|
||||
});
|
||||
}
|
||||
if (!pullmergeRequestId && normalSecrets.length > 0) {
|
||||
@ -1916,15 +1932,7 @@ export function generateSecrets(
|
||||
return;
|
||||
}
|
||||
const build = isBuild && secret.isBuildSecret;
|
||||
if (build) {
|
||||
if (secret.value.includes(' ') || secret.value.includes('\\n')) {
|
||||
envs.push(`ARG ${secret.name}='${secret.value}'`);
|
||||
} else {
|
||||
envs.push(`ARG ${secret.name}=${secret.value}`);
|
||||
}
|
||||
} else {
|
||||
envs.push(`${secret.name}=${secret.value}`);
|
||||
}
|
||||
envs.push(parseSecret(secret, build));
|
||||
});
|
||||
}
|
||||
const portFound = envs.filter((env) => env.startsWith('PORT'));
|
||||
|
@ -12,7 +12,8 @@ export default async function ({
|
||||
buildId,
|
||||
privateSshKey,
|
||||
customPort,
|
||||
forPublic
|
||||
forPublic,
|
||||
customUser,
|
||||
}: {
|
||||
applicationId: string;
|
||||
workdir: string;
|
||||
@ -25,6 +26,7 @@ export default async function ({
|
||||
privateSshKey: string;
|
||||
customPort: number;
|
||||
forPublic: boolean;
|
||||
customUser: string;
|
||||
}): Promise<string> {
|
||||
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
|
||||
if (!forPublic) {
|
||||
@ -53,7 +55,7 @@ export default async function ({
|
||||
} else {
|
||||
await executeCommand({
|
||||
command:
|
||||
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||
`git clone -q -b ${branch} ${customUser}@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -i ${repodir}id.rsa -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git checkout ${gitCommitHash || ""} && git submodule update --init --recursive && git lfs pull && cd .. `, shell: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -764,7 +764,9 @@ export async function checkDomain(request: FastifyRequest<CheckDomain>) {
|
||||
fqdn,
|
||||
settings: { dualCerts }
|
||||
} = await prisma.application.findUnique({ where: { id }, include: { settings: true } });
|
||||
return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
|
||||
// TODO: Disabled this because it is having problems with remote docker engines.
|
||||
// return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
}
|
||||
@ -805,11 +807,12 @@ export async function checkDNS(request: FastifyRequest<CheckDNS>) {
|
||||
remoteEngine,
|
||||
remoteIpAddress
|
||||
});
|
||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||
let hostname = request.hostname.split(':')[0];
|
||||
if (remoteEngine) hostname = remoteIpAddress;
|
||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
}
|
||||
// TODO: Disabled this because it is having problems with remote docker engines.
|
||||
// if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||
// let hostname = request.hostname.split(':')[0];
|
||||
// if (remoteEngine) hostname = remoteIpAddress;
|
||||
// return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
// }
|
||||
return {};
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message });
|
||||
@ -842,15 +845,16 @@ export async function getDockerImages(request) {
|
||||
try {
|
||||
const { stdout } = await executeCommand({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}' | grep -i ${id} | grep -v cache`,
|
||||
shell: true
|
||||
command: `docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'`
|
||||
});
|
||||
const { stdout: runningImage } = await executeCommand({
|
||||
dockerId: application.destinationDocker.id,
|
||||
command: `docker ps -a --filter 'label=com.docker.compose.service=${id}' --format {{.Image}}`
|
||||
});
|
||||
const images = stdout.trim().split('\n');
|
||||
|
||||
const images = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter((image) => image.includes(id) && !image.includes('-cache'));
|
||||
for (const image of images) {
|
||||
const [repository, tag, createdAt] = image.split('#');
|
||||
if (tag.includes('-')) {
|
||||
@ -871,6 +875,7 @@ export async function getDockerImages(request) {
|
||||
runningImage
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
imagesAvailables
|
||||
};
|
||||
|
@ -263,14 +263,14 @@ export async function parseAndFindServiceTemplates(service: any, workdir?: strin
|
||||
for (const secret of service.serviceSecret) {
|
||||
let { name, value } = secret
|
||||
name = name.toLowerCase()
|
||||
const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}\"`, 'gi')
|
||||
const regex = new RegExp(`\\$\\$secret_${name}\"`, 'gi')
|
||||
const regexHashed = new RegExp(`\\$\\$hashed\\$\\$secret_${name}`, 'gi')
|
||||
const regex = new RegExp(`\\$\\$secret_${name}`, 'gi')
|
||||
if (value) {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10) + '"')
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\"") + '"')
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, bcrypt.hashSync(value.replaceAll("\"", "\\\""), 10))
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, value.replaceAll("\"", "\\\""))
|
||||
} else {
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '' + '"')
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, '' + '"')
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regexHashed, '')
|
||||
strParsedTemplate = strParsedTemplate.replaceAll(regex, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -504,7 +504,9 @@ export async function checkServiceDomain(request: FastifyRequest<CheckServiceDom
|
||||
const { id } = request.params
|
||||
const { domain } = request.query
|
||||
const { fqdn, dualCerts } = await prisma.service.findUnique({ where: { id } })
|
||||
return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
|
||||
// TODO: Disabled this because it is having problems with remote docker engines.
|
||||
// return await checkDomainsIsValidInDNS({ hostname: domain, fqdn, dualCerts });
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
}
|
||||
@ -530,11 +532,12 @@ export async function checkService(request: FastifyRequest<CheckService>) {
|
||||
throw { status: 500, message: `Domain ${getDomain(fqdn).replace('www.', '')} is already in use!` }
|
||||
}
|
||||
if (exposePort) await checkExposedPort({ id, configuredPort, exposePort, engine, remoteEngine, remoteIpAddress })
|
||||
if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||
let hostname = request.hostname.split(':')[0];
|
||||
if (remoteEngine) hostname = remoteIpAddress;
|
||||
return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
}
|
||||
// TODO: Disabled this because it is having problems with remote docker engines.
|
||||
// if (isDNSCheckEnabled && !isDev && !forceSave) {
|
||||
// let hostname = request.hostname.split(':')[0];
|
||||
// if (remoteEngine) hostname = remoteIpAddress;
|
||||
// return await checkDomainsIsValidInDNS({ hostname, fqdn, dualCerts });
|
||||
// }
|
||||
return {}
|
||||
} catch ({ status, message }) {
|
||||
return errorHandler({ status, message })
|
||||
|
@ -22,11 +22,11 @@ export async function listSources(request: FastifyRequest) {
|
||||
export async function saveSource(request, reply) {
|
||||
try {
|
||||
const { id } = request.params
|
||||
let { name, htmlUrl, apiUrl, customPort, isSystemWide } = request.body
|
||||
let { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide } = request.body
|
||||
if (customPort) customPort = Number(customPort)
|
||||
await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name, htmlUrl, apiUrl, customPort, isSystemWide }
|
||||
data: { name, htmlUrl, apiUrl, customPort, customUser, isSystemWide }
|
||||
});
|
||||
return reply.code(201).send()
|
||||
} catch ({ status, message }) {
|
||||
@ -48,6 +48,7 @@ export async function getSource(request: FastifyRequest<OnlyId>) {
|
||||
apiUrl: null,
|
||||
organization: null,
|
||||
customPort: 22,
|
||||
customUser: 'git',
|
||||
},
|
||||
settings
|
||||
}
|
||||
@ -133,7 +134,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
||||
try {
|
||||
const { id } = request.params
|
||||
const { teamId } = request.user
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort } =
|
||||
let { type, name, htmlUrl, apiUrl, oauthId, appId, appSecret, groupName, customPort, customUser } =
|
||||
request.body
|
||||
|
||||
if (oauthId) oauthId = Number(oauthId);
|
||||
@ -142,7 +143,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
||||
|
||||
if (id === 'new') {
|
||||
const newId = cuid()
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitSource.create({ data: { id: newId, type, apiUrl, htmlUrl, name, customPort, customUser, teams: { connect: { id: teamId } } } });
|
||||
await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
@ -158,7 +159,7 @@ export async function saveGitLabSource(request: FastifyRequest<SaveGitLabSource>
|
||||
id: newId
|
||||
}
|
||||
} else {
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort } });
|
||||
await prisma.gitSource.update({ where: { id }, data: { type, apiUrl, htmlUrl, name, customPort, customUser } });
|
||||
await prisma.gitlabApp.update({
|
||||
where: { id },
|
||||
data: {
|
||||
|
@ -21,6 +21,7 @@ export interface SaveGitLabSource extends OnlyId {
|
||||
appSecret: string,
|
||||
groupName: string,
|
||||
customPort: number,
|
||||
customUser: string,
|
||||
}
|
||||
}
|
||||
export interface CheckGitLabOAuthId extends OnlyId {
|
||||
|
1014
apps/api/tags.json
1014
apps/api/tags.json
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -352,11 +352,12 @@
|
||||
async function reloadCompose() {
|
||||
if (loading.reloadCompose) return;
|
||||
loading.reloadCompose = true;
|
||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||
? application.dockerComposeFileLocation
|
||||
: `/${application.dockerComposeFileLocation}`;
|
||||
try {
|
||||
if (application.gitSource.type === 'github') {
|
||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||
? application.dockerComposeFileLocation
|
||||
: `/${application.dockerComposeFileLocation}`;
|
||||
|
||||
const headers = isPublicRepository
|
||||
? {}
|
||||
: {
|
||||
@ -383,6 +384,17 @@
|
||||
if (!$appSession.tokens.gitlab) {
|
||||
await getGitlabToken();
|
||||
}
|
||||
|
||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||
? application.dockerComposeFileLocation.substring(1) // Remove the '/' from the start
|
||||
: application.dockerComposeFileLocation;
|
||||
|
||||
// If the file is in a subdirectory, lastIndex will be > 0
|
||||
// Otherwise it will be -1 and path will be an empty string
|
||||
const lastIndex = composeLocation.lastIndexOf('/') + 1
|
||||
const path = composeLocation.substring(0, lastIndex)
|
||||
const fileName = composeLocation.substring(lastIndex)
|
||||
|
||||
const headers = isPublicRepository
|
||||
? {}
|
||||
: {
|
||||
@ -390,13 +402,13 @@
|
||||
};
|
||||
const url = isPublicRepository
|
||||
? ``
|
||||
: `/v4/projects/${application.projectId}/repository/tree`;
|
||||
: `/v4/projects/${application.projectId}/repository/tree?path=${path}`; // Use path param to find file in a subdirectory
|
||||
const files = await get(`${apiUrl}${url}`, {
|
||||
...headers
|
||||
});
|
||||
const dockerComposeFileYml = files.find(
|
||||
(file: { name: string; type: string }) =>
|
||||
file.name === composeLocation && file.type === 'blob'
|
||||
file.name === fileName && file.type === 'blob'
|
||||
);
|
||||
const id = dockerComposeFileYml.id;
|
||||
|
||||
|
7
apps/server/nodemon.json
Normal file
7
apps/server/nodemon.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"watch": ["src"],
|
||||
"ignore": ["src/**/*.test.ts"],
|
||||
"ext": "ts,mjs,json,graphql",
|
||||
"exec": "rimraf build && esbuild `find src \\( -name '*.ts' -o -name '*.js' \\)` --platform=node --outdir=build --format=cjs && node build",
|
||||
"legacyWatch": true
|
||||
}
|
@ -4,7 +4,8 @@
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"build": "rimraf ../../build && tsc --outDir ../../build",
|
||||
"dev": "tsx watch --clear-screen=false src",
|
||||
"dev-old": "tsx watch --clear-screen=false src",
|
||||
"dev": "nodemon",
|
||||
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
||||
"format": "prettier --plugin-search-dir . --write .",
|
||||
"test-dev": "start-server-and-test 'tsx src/server' http-get://localhost:2022 'tsx src/client'",
|
||||
@ -43,7 +44,7 @@
|
||||
"is-ip": "5.0.0",
|
||||
"is-port-reachable": "4.0.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jsonwebtoken": "9.0.0",
|
||||
"node-fetch": "3.3.0",
|
||||
"p-all": "4.0.0",
|
||||
"p-throttle": "5.0.0",
|
||||
@ -65,6 +66,8 @@
|
||||
"@types/node-fetch": "2.6.2",
|
||||
"@types/shell-quote": "^1.7.1",
|
||||
"@types/ws": "8.5.3",
|
||||
"esbuild": "0.15.15",
|
||||
"nodemon": "2.0.20",
|
||||
"npm-run-all": "4.1.5",
|
||||
"rimraf": "3.0.2",
|
||||
"start-server-and-test": "1.14.0",
|
||||
|
@ -325,6 +325,7 @@ model GitSource {
|
||||
apiUrl String?
|
||||
htmlUrl String?
|
||||
customPort Int @default(22)
|
||||
customUser String @default("git")
|
||||
organization String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Bree from 'bree';
|
||||
import path from 'path';
|
||||
import Cabin from 'cabin';
|
||||
// import Cabin from 'cabin';
|
||||
import TSBree from '@breejs/ts-worker';
|
||||
|
||||
export const isDev = process.env['NODE_ENV'] === 'development';
|
||||
@ -9,7 +9,7 @@ Bree.extend(TSBree);
|
||||
|
||||
const options: any = {
|
||||
defaultExtension: 'js',
|
||||
logger: new Cabin(),
|
||||
logger: false,
|
||||
// logger: false,
|
||||
// workerMessageHandler: async ({ name, message }) => {
|
||||
// if (name === 'deployApplication' && message?.deploying) {
|
||||
@ -18,9 +18,8 @@ const options: any = {
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// jobs: [{ name: 'deployApplication' }]
|
||||
jobs: [{ name: 'worker' }]
|
||||
jobs: [{ name: 'deployApplication' }, { name: 'worker' }]
|
||||
};
|
||||
if (isDev) options.root = path.join(__dirname, '../jobs');
|
||||
if (isDev) options.root = path.join(__dirname, './jobs');
|
||||
|
||||
export const scheduler = new Bree(options);
|
||||
|
@ -5,6 +5,7 @@
|
||||
const handleError = (ev: { target: { src: string } }) => (ev.target.src = fallback);
|
||||
let extension = 'png';
|
||||
let svgs = [
|
||||
'directus',
|
||||
'pocketbase',
|
||||
'gitea',
|
||||
'languagetool',
|
||||
|
@ -58,11 +58,10 @@ export const appSession: Writable<AppSession> = writable({
|
||||
});
|
||||
export const disabledButton: Writable<boolean> = writable(false);
|
||||
export const isDeploymentEnabled: Writable<boolean> = writable(false);
|
||||
export function checkIfDeploymentEnabledApplications(isAdmin: boolean, application: any) {
|
||||
export function checkIfDeploymentEnabledApplications(application: any) {
|
||||
return !!(
|
||||
isAdmin &&
|
||||
(application.buildPack === 'compose') ||
|
||||
(application.fqdn || application.settings.isBot) &&
|
||||
(application.fqdn || application.settings?.isBot) &&
|
||||
((application.gitSource &&
|
||||
application.repository &&
|
||||
application.buildPack) || application.simpleDockerfile) &&
|
||||
@ -70,9 +69,8 @@ export function checkIfDeploymentEnabledApplications(isAdmin: boolean, applicati
|
||||
|
||||
);
|
||||
}
|
||||
export function checkIfDeploymentEnabledServices(isAdmin: boolean, service: any) {
|
||||
export function checkIfDeploymentEnabledServices( service: any) {
|
||||
return (
|
||||
isAdmin &&
|
||||
service.fqdn &&
|
||||
service.destinationDocker &&
|
||||
service.version &&
|
||||
|
@ -162,12 +162,12 @@
|
||||
<input id="main-drawer" type="checkbox" class="drawer-toggle" bind:this={sidedrawerToggler} />
|
||||
<div class="drawer-content">
|
||||
{#if $appSession.userId}
|
||||
<Tooltip triggeredBy="#dashboard" placement="right" color="bg-pink-500">Dashboard</Tooltip>
|
||||
<Tooltip triggeredBy="#servers" placement="right" color="bg-sky-500">Servers</Tooltip>
|
||||
<Tooltip triggeredBy="#iam" placement="right" color="bg-iam">IAM</Tooltip>
|
||||
<Tooltip triggeredBy="#settings" placement="right" color="bg-settings text-black"
|
||||
>Settings</Tooltip
|
||||
>
|
||||
<Tooltip triggeredBy="#documentation" placement="right" color="bg-info">Documentation</Tooltip
|
||||
>
|
||||
<Tooltip triggeredBy="#logout" placement="right" color="bg-red-600">Logout</Tooltip>
|
||||
<nav class="nav-main hidden lg:block z-20">
|
||||
<div class="flex h-screen w-full flex-col items-center transition-all duration-100">
|
||||
@ -183,7 +183,6 @@
|
||||
<div class="flex flex-col space-y-2 py-2" class:mt-2={$appSession.whiteLabeled}>
|
||||
<a
|
||||
id="dashboard"
|
||||
sveltekit:prefetch
|
||||
href="/"
|
||||
class="icons hover:text-pink-500"
|
||||
class:text-pink-500={$page.url.pathname === '/'}
|
||||
@ -210,7 +209,6 @@
|
||||
{#if $appSession.teamId === '0'}
|
||||
<a
|
||||
id="servers"
|
||||
sveltekit:prefetch
|
||||
href="/servers"
|
||||
class="icons hover:text-sky-500"
|
||||
class:text-sky-500={$page.url.pathname === '/servers'}
|
||||
@ -236,8 +234,6 @@
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
<Tooltip triggeredBy="#dashboard" placement="right">Dashboard</Tooltip>
|
||||
<Tooltip triggeredBy="#servers" placement="right">Servers</Tooltip>
|
||||
<div class="flex-1" />
|
||||
<div class="lg:block hidden">
|
||||
<UpdateAvailable />
|
||||
@ -245,7 +241,6 @@
|
||||
<div class="flex flex-col space-y-2 py-2">
|
||||
<a
|
||||
id="iam"
|
||||
sveltekit:prefetch
|
||||
href={$appSession.pendingInvitations.length > 0 ? '/iam/pending' : '/iam'}
|
||||
class="icons hover:text-iam indicator"
|
||||
class:text-iam={$page.url.pathname.startsWith('/iam')}
|
||||
@ -274,7 +269,6 @@
|
||||
</a>
|
||||
<a
|
||||
id="settings"
|
||||
sveltekit:prefetch
|
||||
href={$appSession.teamId === '0' ? '/settings/coolify' : '/settings/docker'}
|
||||
class="icons hover:text-settings"
|
||||
class:text-settings={$page.url.pathname.startsWith('/settings')}
|
||||
@ -299,10 +293,9 @@
|
||||
</a>
|
||||
<a
|
||||
id="documentation"
|
||||
sveltekit:prefetch
|
||||
href="https://docs.coollabs.io/coolify/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
rel="noreferrer external"
|
||||
class="icons hover:text-info"
|
||||
>
|
||||
<svg
|
||||
@ -395,7 +388,6 @@
|
||||
<li>
|
||||
<a
|
||||
class="no-underline icons hover:text-white hover:bg-pink-500"
|
||||
sveltekit:prefetch
|
||||
href="/"
|
||||
class:bg-pink-500={$page.url.pathname === '/'}
|
||||
on:click={closeDrawer}
|
||||
@ -424,7 +416,6 @@
|
||||
<a
|
||||
id="servers"
|
||||
class="no-underline icons hover:text-white hover:bg-sky-500"
|
||||
sveltekit:prefetch
|
||||
href="/servers"
|
||||
class:bg-sky-500={$page.url.pathname.startsWith('/servers')}
|
||||
on:click={closeDrawer}
|
||||
@ -504,6 +495,30 @@
|
||||
Settings
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
class="no-underline icons hover:text-white hover:bg-info"
|
||||
href="https://docs.coollabs.io/coolify/"
|
||||
target="_blank"
|
||||
rel="noreferrer external"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-8 h-8"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"
|
||||
/>
|
||||
</svg>
|
||||
Documentation
|
||||
</a>
|
||||
</li>
|
||||
<li class="flex-1 bg-transparent" />
|
||||
<div class="block lg:hidden">
|
||||
<UpdateAvailable />
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
export let application: any;
|
||||
import { status } from '$lib/store';
|
||||
import { appSession, status } from '$lib/store';
|
||||
import { page } from '$app/stores';
|
||||
</script>
|
||||
|
||||
@ -220,7 +220,7 @@
|
||||
<li class="menu-title">
|
||||
<span>Advanced</span>
|
||||
</li>
|
||||
{#if application.gitSourceId}
|
||||
{#if application.gitSourceId && $appSession.isAdmin}
|
||||
<li
|
||||
class="rounded"
|
||||
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/revert`}
|
||||
@ -295,6 +295,7 @@
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
{#if $appSession.isAdmin}
|
||||
<li
|
||||
class="rounded"
|
||||
class:bg-coollabs={$page.url.pathname === `/applications/${$page.params.id}/danger`}
|
||||
@ -318,4 +319,5 @@
|
||||
</svg>Danger Zone</a
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
|
@ -9,7 +9,7 @@
|
||||
import { del, post, put } from '$lib/api';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import { addToast } from '$lib/store';
|
||||
import { addToast, appSession } from '$lib/store';
|
||||
import { t } from '$lib/translations';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
@ -120,6 +120,7 @@
|
||||
<label for="name" class="pb-5 uppercase lg:block hidden font-bold" />
|
||||
{/if}
|
||||
|
||||
{#if $appSession.isAdmin}
|
||||
<div class="flex justify-center h-full items-center pt-3">
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
@ -127,5 +128,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,7 +10,7 @@
|
||||
import { del, post, put } from '$lib/api';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import { addToast } from '$lib/store';
|
||||
import { addToast, appSession } from '$lib/store';
|
||||
import { t } from '$lib/translations';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
@ -124,8 +124,9 @@
|
||||
<div class="flex justify-center h-full items-center pt-0 lg:pt-0 pl-4 lg:pl-0">
|
||||
<button
|
||||
on:click={() => updateSecret({ changeIsBuildSecret: true })}
|
||||
disabled={!$appSession.isAdmin}
|
||||
aria-pressed="false"
|
||||
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-stone-700={!isBuildSecret}
|
||||
>
|
||||
@ -177,7 +178,7 @@
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="btn btn-sm btn-primary" on:click={addNewSecret}>Add</button>
|
||||
</div>
|
||||
{:else}
|
||||
{:else if $appSession.isAdmin}
|
||||
<div class="flex flex-row justify-center space-x-2">
|
||||
<div class="flex items-center justify-center">
|
||||
<button class="btn btn-sm btn-primary" on:click={() => updateSecret()}>Set</button>
|
||||
|
@ -83,7 +83,7 @@
|
||||
let forceDelete = false;
|
||||
let stopping = false;
|
||||
const { id } = $page.params;
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||
|
||||
async function deleteApplication(name: string, force: boolean) {
|
||||
const sure = confirm($t('application.confirm_to_delete', { name }));
|
||||
@ -292,7 +292,6 @@
|
||||
<a
|
||||
href={$isDeploymentEnabled ? `/applications/${id}/logs` : null}
|
||||
class="btn btn-sm text-sm gap-2"
|
||||
sveltekit:prefetch
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -363,7 +362,7 @@
|
||||
<button
|
||||
on:click={restartApplication}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
class="btn btn-sm gap-2"
|
||||
>
|
||||
<svg
|
||||
@ -383,7 +382,7 @@
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
class="btn btn-sm gap-2"
|
||||
on:click={() => handleDeploySubmit(true)}
|
||||
>
|
||||
@ -409,7 +408,7 @@
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
class="btn btn-sm gap-2"
|
||||
>
|
||||
<svg
|
||||
@ -432,7 +431,7 @@
|
||||
<button
|
||||
on:click={stopApplication}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
class="btn btn-sm gap-2"
|
||||
>
|
||||
<svg
|
||||
@ -453,7 +452,7 @@
|
||||
{/if}
|
||||
<button
|
||||
class="btn btn-sm gap-2"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
on:click={() => handleDeploySubmit(true)}
|
||||
>
|
||||
{#if $status.application.overallStatus !== 'degraded'}
|
||||
|
@ -72,7 +72,6 @@
|
||||
<div class="flex justify-center">
|
||||
<a
|
||||
href={`/destinations/new?from=/applications/${id}/configuration/destination`}
|
||||
sveltekit:prefetch
|
||||
class="add-icon bg-sky-600 hover:bg-sky-500"
|
||||
>
|
||||
<svg
|
||||
|
@ -255,7 +255,7 @@
|
||||
{/if}
|
||||
<div class="flex flex-row items-center">
|
||||
<div class="title py-4 pr-4">Public Repository from Git</div>
|
||||
<DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository" />
|
||||
<DocLink url="https://docs.coollabs.io/coolify/applications/#public-repository-from-git" />
|
||||
</div>
|
||||
<PublicRepository />
|
||||
<div class="flex flex-row items-center pt-10">
|
||||
|
@ -51,6 +51,7 @@
|
||||
let isDBBranching = application.settings.isDBBranching;
|
||||
|
||||
async function changeSettings(name: any) {
|
||||
if (!$appSession.isAdmin) return
|
||||
if (name === 'previews') {
|
||||
previews = !previews;
|
||||
}
|
||||
@ -102,7 +103,7 @@
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -119,6 +120,7 @@
|
||||
id="autodeploy"
|
||||
isCenter={false}
|
||||
bind:setting={autodeploy}
|
||||
disabled={!$appSession.isAdmin}
|
||||
on:click={() => changeSettings('autodeploy')}
|
||||
title={$t('application.enable_automatic_deployment')}
|
||||
description={$t('application.enable_auto_deploy_webhooks')}
|
||||
@ -130,6 +132,7 @@
|
||||
id="previews"
|
||||
isCenter={false}
|
||||
bind:setting={previews}
|
||||
disabled={!$appSession.isAdmin}
|
||||
on:click={() => changeSettings('previews')}
|
||||
title={$t('application.enable_mr_pr_previews')}
|
||||
description={$t('application.enable_preview_deploy_mr_pr_requests')}
|
||||
|
@ -58,7 +58,7 @@
|
||||
$status.application.overallStatus === 'degraded' ||
|
||||
$status.application.overallStatus === 'healthy' ||
|
||||
$status.application.initialLoading;
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||
let statues: any = {};
|
||||
let loading = {
|
||||
save: false,
|
||||
@ -235,7 +235,7 @@
|
||||
}
|
||||
return errorNotification(error);
|
||||
} finally {
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||
}
|
||||
}
|
||||
async function handleSubmit(toast: boolean = true) {
|
||||
@ -269,7 +269,7 @@
|
||||
}
|
||||
await saveForm(id, application, baseDatabaseBranch, dockerComposeConfiguration);
|
||||
setLocation(application, settings);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications($appSession.isAdmin, application);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledApplications(application);
|
||||
|
||||
forceSave = false;
|
||||
if (toast) {
|
||||
@ -366,11 +366,12 @@
|
||||
async function reloadCompose() {
|
||||
if (loading.reloadCompose) return;
|
||||
loading.reloadCompose = true;
|
||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||
? application.dockerComposeFileLocation
|
||||
: `/${application.dockerComposeFileLocation}`;
|
||||
try {
|
||||
if (application.gitSource.type === 'github') {
|
||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||
? application.dockerComposeFileLocation
|
||||
: `/${application.dockerComposeFileLocation}`;
|
||||
|
||||
const headers = isPublicRepository
|
||||
? {}
|
||||
: {
|
||||
@ -397,6 +398,17 @@
|
||||
if (!$appSession.tokens.gitlab) {
|
||||
await getGitlabToken();
|
||||
}
|
||||
|
||||
const composeLocation = application.dockerComposeFileLocation.startsWith('/')
|
||||
? application.dockerComposeFileLocation.substring(1) // Remove the '/' from the start
|
||||
: application.dockerComposeFileLocation;
|
||||
|
||||
// If the file is in a subdirectory, lastIndex will be > 0
|
||||
// Otherwise it will be -1 and path will be an empty string
|
||||
const lastIndex = composeLocation.lastIndexOf('/') + 1
|
||||
const path = composeLocation.substring(0, lastIndex)
|
||||
const fileName = composeLocation.substring(lastIndex)
|
||||
|
||||
const headers = isPublicRepository
|
||||
? {}
|
||||
: {
|
||||
@ -404,13 +416,13 @@
|
||||
};
|
||||
const url = isPublicRepository
|
||||
? ``
|
||||
: `/v4/projects/${application.projectId}/repository/tree`;
|
||||
: `/v4/projects/${application.projectId}/repository/tree?path=${path}`;
|
||||
const files = await get(`${apiUrl}${url}`, {
|
||||
...headers
|
||||
});
|
||||
const dockerComposeFileYml = files.find(
|
||||
(file: { name: string; type: string }) =>
|
||||
file.name === composeLocation && file.type === 'blob'
|
||||
file.name === fileName && file.type === 'blob'
|
||||
);
|
||||
const id = dockerComposeFileYml.id;
|
||||
|
||||
@ -490,7 +502,7 @@
|
||||
<div class="grid grid-flow-row gap-2 px-4">
|
||||
<div class="mt-2 grid grid-cols-2 items-center">
|
||||
<label for="name">{$t('forms.name')}</label>
|
||||
<input name="name" id="name" class="w-full" bind:value={application.name} required />
|
||||
<input name="name" id="name" class="w-full" bind:value={application.name} disabled={!$appSession.isAdmin} required />
|
||||
</div>
|
||||
{#if !isSimpleDockerfile}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
|
@ -23,7 +23,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { asyncSleep, errorNotification, getRndInteger } from '$lib/common';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { addToast } from '$lib/store';
|
||||
import { addToast, appSession } from '$lib/store';
|
||||
import Tooltip from '$lib/components/Tooltip.svelte';
|
||||
import DeleteIcon from '$lib/components/DeleteIcon.svelte';
|
||||
|
||||
@ -264,6 +264,7 @@
|
||||
{:else}
|
||||
<button
|
||||
id="restart"
|
||||
disabled={!$appSession.isAdmin}
|
||||
on:click={() => restartPreview(preview)}
|
||||
type="submit"
|
||||
class="icons bg-transparent text-sm flex items-center space-x-2"
|
||||
@ -286,7 +287,12 @@
|
||||
{/if}
|
||||
|
||||
<Tooltip triggeredBy="#restart">Restart (useful to change secrets)</Tooltip>
|
||||
<button id="forceredeploypreview" class="icons" on:click={() => redeploy(preview)}>
|
||||
<button
|
||||
id="forceredeploypreview"
|
||||
class="icons"
|
||||
disabled={!$appSession.isAdmin}
|
||||
on:click={() => redeploy(preview)}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-6 h-6"
|
||||
@ -310,7 +316,7 @@
|
||||
id="deletepreview"
|
||||
class="icons"
|
||||
class:hover:text-error={!loading.removing}
|
||||
disabled={loading.removing}
|
||||
disabled={loading.removing || !$appSession.isAdmin}
|
||||
on:click={() => removeApplication(preview)}
|
||||
><DeleteIcon />
|
||||
</button>
|
||||
|
@ -25,7 +25,7 @@
|
||||
import pLimit from 'p-limit';
|
||||
import { page } from '$app/stores';
|
||||
import { get, post, put } from '$lib/api';
|
||||
import { addToast } from '$lib/store';
|
||||
import { addToast, appSession } from '$lib/store';
|
||||
import Secret from './_Secret.svelte';
|
||||
import PreviewSecret from './_PreviewSecret.svelte';
|
||||
import { errorNotification } from '$lib/common';
|
||||
@ -106,46 +106,50 @@
|
||||
/>
|
||||
{/key}
|
||||
{/each}
|
||||
<div class="lg:pt-0 pt-10">
|
||||
<Secret on:refresh={refreshSecrets} length={secrets.length} isNewSecret />
|
||||
</div>
|
||||
{#if !application.settings.isBot && !application.simpleDockerfile}
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||
<div class="title font-bold pb-3 pt-8">
|
||||
Preview Secrets <Explainer
|
||||
explanation="These values overwrite application secrets in PR/MR deployments. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
||||
/>
|
||||
{#if $appSession.isAdmin}
|
||||
<div class="lg:pt-0 pt-10">
|
||||
<Secret on:refresh={refreshSecrets} length={secrets.length} isNewSecret />
|
||||
</div>
|
||||
</div>
|
||||
{#if previewSecrets.length !== 0}
|
||||
{#each previewSecrets as secret, index}
|
||||
{#key index}
|
||||
<PreviewSecret
|
||||
{index}
|
||||
length={secrets.length}
|
||||
name={secret.name}
|
||||
value={secret.value}
|
||||
isBuildSecret={secret.isBuildSecret}
|
||||
on:refresh={refreshSecrets}
|
||||
/>
|
||||
{/key}
|
||||
{/each}
|
||||
{:else}
|
||||
Add secrets first to see Preview Secrets.
|
||||
{/if}
|
||||
{#if !application.settings.isBot && !application.simpleDockerfile}
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2">
|
||||
<div class="title font-bold pb-3 pt-8">
|
||||
Preview Secrets <Explainer
|
||||
explanation="These values overwrite application secrets in PR/MR deployments. <br>Useful for creating <span class='text-green-500 font-bold'>staging</span> environments."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{#if previewSecrets.length !== 0}
|
||||
{#each previewSecrets as secret, index}
|
||||
{#key index}
|
||||
<PreviewSecret
|
||||
{index}
|
||||
length={secrets.length}
|
||||
name={secret.name}
|
||||
value={secret.value}
|
||||
isBuildSecret={secret.isBuildSecret}
|
||||
on:refresh={refreshSecrets}
|
||||
/>
|
||||
{/key}
|
||||
{/each}
|
||||
{:else}
|
||||
Add secrets first to see Preview Secrets.
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2 pt-10">
|
||||
<div class="flex flex-row space-x-2">
|
||||
<div class="title font-bold pb-3 ">Paste <code>.env</code> file</div>
|
||||
<button type="submit" class="btn btn-sm bg-primary">Add Secrets in Batch</button>
|
||||
{#if $appSession.isAdmin}
|
||||
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2 pt-10">
|
||||
<div class="flex flex-row space-x-2">
|
||||
<div class="title font-bold pb-3 ">Paste <code>.env</code> file</div>
|
||||
<button type="submit" class="btn btn-sm bg-primary">Add Secrets in Batch</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
placeholder={`PORT=1337\nPASSWORD=supersecret`}
|
||||
bind:value={batchSecrets}
|
||||
class="mb-2 min-h-[200px] w-full"
|
||||
/>
|
||||
</form>
|
||||
<textarea
|
||||
placeholder={`PORT=1337\nPASSWORD=supersecret`}
|
||||
bind:value={batchSecrets}
|
||||
class="mb-2 min-h-[200px] w-full"
|
||||
/>
|
||||
</form>
|
||||
{/if}
|
||||
|
@ -27,6 +27,7 @@
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { appSession } from '$lib/store';
|
||||
|
||||
let composeJson = JSON.parse(application?.dockerComposeFile || '{}');
|
||||
let predefinedVolumes: any[] = [];
|
||||
@ -86,12 +87,15 @@
|
||||
<Storage on:refresh={refreshStorage} {storage} />
|
||||
{/key}
|
||||
{/each}
|
||||
<div class="Preview Secrets" class:pt-10={predefinedVolumes.length > 0}>
|
||||
{#if $appSession.isAdmin}
|
||||
<div class:pt-10={predefinedVolumes.length > 0}>
|
||||
Add New Volume <Explainer
|
||||
position="dropdown-bottom"
|
||||
explanation={$t('application.storage.persistent_storage_explainer')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Storage on:refresh={refreshStorage} isNew />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -176,7 +176,6 @@
|
||||
id="exited"
|
||||
href={!$status.database.isRunning ? `/databases/${id}/logs` : null}
|
||||
class="icons bg-transparent text-red-500 tooltip-error"
|
||||
sveltekit:prefetch
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -271,7 +270,6 @@
|
||||
<a
|
||||
id="configuration"
|
||||
href="/databases/{id}"
|
||||
sveltekit:prefetch
|
||||
class="hover:text-yellow-500 rounded"
|
||||
class:text-yellow-500={$page.url.pathname === `/databases/${id}`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}`}
|
||||
@ -305,7 +303,6 @@
|
||||
<a
|
||||
id="databaselogs"
|
||||
href={$status.database.isRunning ? `/databases/${id}/logs` : null}
|
||||
sveltekit:prefetch
|
||||
class="hover:text-pink-500 rounded"
|
||||
class:text-pink-500={$page.url.pathname === `/databases/${id}/logs`}
|
||||
class:bg-coolgray-500={$page.url.pathname === `/databases/${id}/logs`}
|
||||
|
@ -60,7 +60,7 @@
|
||||
{$t('application.configuration.no_configurable_destination')}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<a href="/destinations/new" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||
<a href="/destinations/new" class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -57,11 +57,7 @@
|
||||
<div class="flex-col">
|
||||
<div class="pb-2 text-center font-bold">No SSH key found</div>
|
||||
<div class="flex justify-center">
|
||||
<a
|
||||
href="/settings/ssh"
|
||||
sveltekit:prefetch
|
||||
class="add-icon bg-sky-600 hover:bg-sky-500"
|
||||
>
|
||||
<a href="/settings/ssh" class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -669,7 +669,7 @@
|
||||
<button class="btn btn-sm btn-primary" on:click={refreshStatusApplications}
|
||||
>{noInitialStatus.applications ? 'Load Status' : 'Refresh Status'}</button
|
||||
>
|
||||
{#if foundUnconfiguredApplication}
|
||||
{#if foundUnconfiguredApplication && $appSession.isAdmin}
|
||||
<button
|
||||
class="btn btn-sm"
|
||||
class:loading={loading.applications}
|
||||
@ -783,11 +783,13 @@
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="icons hover:bg-green-500"
|
||||
on:click|stopPropagation|preventDefault={() =>
|
||||
deleteApplication(application.id)}><DeleteIcon /></button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -896,11 +898,13 @@
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="icons hover:bg-green-500"
|
||||
on:click|stopPropagation|preventDefault={() =>
|
||||
deleteApplication(application.id)}><DeleteIcon /></button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -991,11 +995,13 @@
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="icons hover:bg-pink-500"
|
||||
on:click|stopPropagation|preventDefault={() => deleteService(service.id)}
|
||||
><DeleteIcon /></button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1077,11 +1083,13 @@
|
||||
</svg>
|
||||
</a>
|
||||
{/if}
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="icons hover:bg-pink-500"
|
||||
on:click|stopPropagation|preventDefault={() => deleteService(service.id)}
|
||||
><DeleteIcon /></button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1173,11 +1181,13 @@
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="icons hover:bg-databases-100"
|
||||
on:click|stopPropagation|preventDefault={() => deleteDatabase(database.id)}
|
||||
><DeleteIcon /></button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1259,11 +1269,13 @@
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{#if $appSession.isAdmin}
|
||||
<button
|
||||
class="icons hover:bg-databases"
|
||||
on:click|stopPropagation|preventDefault={() => deleteDatabase(database.id)}
|
||||
><DeleteIcon /></button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,6 +2,7 @@
|
||||
export let service: any;
|
||||
export let template: any;
|
||||
import { page } from '$app/stores';
|
||||
import { appSession } from '$lib/store';
|
||||
import ServiceLinks from './_ServiceLinks.svelte';
|
||||
</script>
|
||||
|
||||
@ -106,6 +107,7 @@
|
||||
</svg>Service</a
|
||||
>
|
||||
</li>
|
||||
{#if $appSession.isAdmin}
|
||||
<li class="menu-title">
|
||||
<span>Advanced</span>
|
||||
</li>
|
||||
@ -132,4 +134,5 @@
|
||||
</svg>Danger Zone</a
|
||||
>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
|
@ -8,7 +8,7 @@
|
||||
import { del, post } from '$lib/api';
|
||||
import { errorNotification } from '$lib/common';
|
||||
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
|
||||
import { addToast } from '$lib/store';
|
||||
import { addToast, appSession } from '$lib/store';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
@ -77,6 +77,7 @@
|
||||
/>
|
||||
</td>
|
||||
|
||||
{#if $appSession.isAdmin}
|
||||
<td>
|
||||
{#if isNewSecret}
|
||||
<div class="flex items-center justify-center">
|
||||
@ -93,3 +94,4 @@
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
{/if}
|
||||
|
@ -76,10 +76,9 @@
|
||||
import { dev } from '$app/env';
|
||||
const { id } = $page.params;
|
||||
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledServices($appSession.isAdmin, service);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledServices(service);
|
||||
|
||||
let statusInterval: any;
|
||||
|
||||
|
||||
async function deleteService() {
|
||||
const sure = confirm($t('application.confirm_to_delete', { name: service.name }));
|
||||
@ -291,7 +290,7 @@
|
||||
</button>
|
||||
{:else if $status.service.overallStatus === 'healthy'}
|
||||
<button
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
class="btn btn-sm gap-2"
|
||||
on:click={() => restartService()}
|
||||
>
|
||||
@ -317,7 +316,7 @@
|
||||
<button
|
||||
on:click={() => stopService(false)}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
class="btn btn-sm gap-2"
|
||||
>
|
||||
<svg
|
||||
@ -338,10 +337,10 @@
|
||||
</button>
|
||||
{:else if $status.service.overallStatus === 'degraded'}
|
||||
<button
|
||||
on:click={stopService}
|
||||
on:click={() => stopService()}
|
||||
type="submit"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
class="btn btn-sm gap-2"
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
class="btn btn-sm gap-2"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -362,7 +361,7 @@
|
||||
{#if $status.service.overallStatus === 'degraded'}
|
||||
<button
|
||||
class="btn btn-sm gap-2"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
on:click={() => restartService()}
|
||||
>
|
||||
<svg
|
||||
@ -386,7 +385,7 @@
|
||||
{:else if $status.service.overallStatus === 'stopped'}
|
||||
<button
|
||||
class="btn btn-sm gap-2"
|
||||
disabled={!$isDeploymentEnabled}
|
||||
disabled={!$isDeploymentEnabled || !$appSession.isAdmin}
|
||||
on:click={() => startService()}
|
||||
>
|
||||
<svg
|
||||
|
@ -59,7 +59,7 @@
|
||||
{$t('application.configuration.no_configurable_destination')}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<a href="/new/destination" sveltekit:prefetch class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||
<a href="/new/destination" class="add-icon bg-sky-600 hover:bg-sky-500">
|
||||
<svg
|
||||
class="w-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -110,7 +110,7 @@
|
||||
if (formData) service = await saveForm(formData, service);
|
||||
setLocation(service);
|
||||
forceSave = false;
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledServices($appSession.isAdmin, service);
|
||||
$isDeploymentEnabled = checkIfDeploymentEnabledServices(service);
|
||||
return addToast({
|
||||
message: 'Configuration saved.',
|
||||
type: 'success'
|
||||
@ -165,6 +165,7 @@
|
||||
}
|
||||
}
|
||||
async function changeSettings(name: any) {
|
||||
if (!$appSession.isAdmin) return;
|
||||
try {
|
||||
if (name === 'dualCerts') {
|
||||
dualCerts = !dualCerts;
|
||||
@ -277,7 +278,14 @@
|
||||
<div class="grid grid-flow-row gap-2 px-4">
|
||||
<div class="mt-2 grid grid-cols-2 items-center">
|
||||
<label for="name">{$t('forms.name')}</label>
|
||||
<input name="name" id="name" class="w-full" bind:value={service.name} required />
|
||||
<input
|
||||
name="name"
|
||||
id="name"
|
||||
class="w-full"
|
||||
disabled={!$appSession.isAdmin}
|
||||
bind:value={service.name}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="version">Version / Tag</label>
|
||||
@ -386,7 +394,7 @@
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<Setting
|
||||
id="dualCerts"
|
||||
disabled={$status.service.isRunning}
|
||||
disabled={$status.service.isRunning || !$appSession.isAdmin}
|
||||
dataTooltip={$t('forms.must_be_stopped_to_modify')}
|
||||
bind:setting={dualCerts}
|
||||
title={$t('application.ssl_www_and_non_www')}
|
||||
@ -482,7 +490,7 @@
|
||||
required={variable?.required}
|
||||
/>
|
||||
{:else if variable.defaultValue === 'true' || variable.defaultValue === 'false'}
|
||||
{#if variable.value === 'true' || variable.value === 'false'}
|
||||
{#if variable.value === 'true' || variable.value === 'false' || variable.value === 'invite_only'}
|
||||
<select
|
||||
class="w-full font-normal"
|
||||
readonly={isDisabled}
|
||||
@ -496,6 +504,9 @@
|
||||
>
|
||||
<option value="true">enabled</option>
|
||||
<option value="false">disabled</option>
|
||||
{#if service.type.startsWith('plausibleanalytics') && variable.id == 'config_disable_registration'}
|
||||
<option value="invite_only">invite_only</option>
|
||||
{/if}
|
||||
</select>
|
||||
{:else}
|
||||
<select
|
||||
@ -510,7 +521,7 @@
|
||||
required={variable?.required}
|
||||
>
|
||||
<option value="true">true</option>
|
||||
<option value="false"> false</option>
|
||||
<option value="false">false</option>
|
||||
</select>
|
||||
{/if}
|
||||
{:else if variable.defaultValue === '$$generate_password'}
|
||||
|
@ -25,7 +25,7 @@
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import pLimit from 'p-limit';
|
||||
import { addToast } from '$lib/store';
|
||||
import { addToast, appSession } from '$lib/store';
|
||||
import { saveSecret } from './utils';
|
||||
const limit = pLimit(1);
|
||||
|
||||
@ -83,7 +83,12 @@
|
||||
{#each secrets as secret}
|
||||
{#key secret.id}
|
||||
<tr>
|
||||
<Secret name={secret.name} value={secret.value} readonly={secret.readOnly} on:refresh={refreshSecrets} />
|
||||
<Secret
|
||||
name={secret.name}
|
||||
value={secret.value}
|
||||
readonly={secret.readOnly}
|
||||
on:refresh={refreshSecrets}
|
||||
/>
|
||||
</tr>
|
||||
{/key}
|
||||
{/each}
|
||||
@ -93,18 +98,20 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2 pt-10">
|
||||
<div class="flex flex-row space-x-2">
|
||||
<div class="title font-bold pb-3 ">Paste <code>.env</code> file</div>
|
||||
<button type="submit" class="btn btn-sm bg-primary">Add Secrets in Batch</button>
|
||||
{#if $appSession.isAdmin}
|
||||
<form on:submit|preventDefault={getValues} class="mb-12 w-full">
|
||||
<div class="flex flex-row border-b border-coolgray-500 mb-6 space-x-2 pt-10">
|
||||
<div class="flex flex-row space-x-2">
|
||||
<div class="title font-bold pb-3 ">Paste <code>.env</code> file</div>
|
||||
<button type="submit" class="btn btn-sm bg-primary">Add Secrets in Batch</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<textarea
|
||||
placeholder={`PORT=1337\nPASSWORD=supersecret`}
|
||||
bind:value={batchSecrets}
|
||||
class="mb-2 min-h-[200px] w-full"
|
||||
/>
|
||||
</form>
|
||||
<textarea
|
||||
placeholder={`PORT=1337\nPASSWORD=supersecret`}
|
||||
bind:value={batchSecrets}
|
||||
class="mb-2 min-h-[200px] w-full"
|
||||
/>
|
||||
</form>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -26,6 +26,7 @@
|
||||
import { get } from '$lib/api';
|
||||
import { t } from '$lib/translations';
|
||||
import Explainer from '$lib/components/Explainer.svelte';
|
||||
import { appSession } from '$lib/store';
|
||||
|
||||
const { id } = $page.params;
|
||||
async function refreshStorage() {
|
||||
@ -81,10 +82,11 @@
|
||||
{/key}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
<div class="title" class:pt-10={persistentStorages.filter((s) => s.predefined).length > 0}>
|
||||
Add New Volume
|
||||
</div>
|
||||
<Storage on:refresh={refreshStorage} isNew {services} />
|
||||
{#if $appSession.isAdmin}
|
||||
<div class="title" class:pt-10={persistentStorages.filter((s) => s.predefined).length > 0}>
|
||||
Add New Volume
|
||||
</div>
|
||||
<Storage on:refresh={refreshStorage} isNew {services} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -190,7 +190,7 @@
|
||||
id="name"
|
||||
required
|
||||
bind:value={source.name}
|
||||
disabled={$appSession.teamId !== '0'}
|
||||
disabled={!$appSession.isAdmin}
|
||||
/>
|
||||
<label for="htmlUrl">HTML URL</label>
|
||||
<input
|
||||
@ -236,16 +236,18 @@
|
||||
placeholder="eg: coollabsio"
|
||||
bind:value={source.organization}
|
||||
/>
|
||||
<Setting
|
||||
customClass="pt-4"
|
||||
id="autodeploy"
|
||||
isCenter={false}
|
||||
disabled={$appSession.teamId !== '0'}
|
||||
bind:setting={source.isSystemWide}
|
||||
on:click={() => changeSettings('isSystemWide', true)}
|
||||
title="System Wide Git Source"
|
||||
description="System Wide Git Sources are available to all the users in your Coolify instance. <br><br> <span class='font-bold text-warning'>Use with caution, as it can be a security risk.</span>"
|
||||
/>
|
||||
{#if $appSession.isAdmin}
|
||||
<Setting
|
||||
customClass="pt-4"
|
||||
id="autodeploy"
|
||||
isCenter={false}
|
||||
disabled={$appSession.teamId !== '0'}
|
||||
bind:setting={source.isSystemWide}
|
||||
on:click={() => changeSettings('isSystemWide', true)}
|
||||
title="System Wide Git Source"
|
||||
description="System Wide Git Sources are available to all the users in your Coolify instance. <br><br> <span class='font-bold text-warning'>Use with caution, as it can be a security risk.</span>"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</form>
|
||||
{:else}
|
||||
|
@ -51,7 +51,8 @@
|
||||
appId: source.gitlabApp.appId,
|
||||
appSecret: source.gitlabApp.appSecret,
|
||||
groupName: source.gitlabApp.groupName,
|
||||
customPort: source.customPort
|
||||
customPort: source.customPort,
|
||||
customUser: source.customUser,
|
||||
});
|
||||
const from = $page.url.searchParams.get('from');
|
||||
if (from) {
|
||||
@ -70,7 +71,8 @@
|
||||
name: source.name,
|
||||
htmlUrl: source.htmlUrl.replace(/\/$/, ''),
|
||||
apiUrl: source.apiUrl.replace(/\/$/, ''),
|
||||
customPort: source.customPort
|
||||
customPort: source.customPort,
|
||||
customUser: source.customUser
|
||||
});
|
||||
return addToast({
|
||||
message: 'Configuration saved.',
|
||||
@ -244,6 +246,22 @@
|
||||
/>
|
||||
</div>
|
||||
{#if selfHosted}
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="customPort" class="text-base font-bold text-stone-100"
|
||||
>Custom SSH User <Explainer
|
||||
explanation={'If you use a self-hosted version of Git, you can provide a custom SSH user for all the Git related actions.'}
|
||||
/></label
|
||||
>
|
||||
<input
|
||||
class="w-full"
|
||||
name="customUser"
|
||||
id="customUser"
|
||||
disabled={!selfHosted}
|
||||
readonly={!selfHosted}
|
||||
required
|
||||
bind:value={source.customUser}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center">
|
||||
<label for="customPort" class="text-base font-bold text-stone-100"
|
||||
>Custom SSH Port <Explainer
|
||||
|
4
apps/ui/static/icons/directus.svg
Normal file
4
apps/ui/static/icons/directus.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 200 200" fill="none">
|
||||
<rect width="200" height="200" rx="30" fill="#4422dd" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M148.883 112.106C148.124 111.916 147.491 111.726 146.921 111.473C146.5 111.286 146.148 111.064 145.839 110.807C145.635 110.637 145.547 110.369 145.572 110.104C145.877 106.923 145.541 104.118 145.845 100.964C147.111 88.1771 155.15 92.2285 162.367 90.1395C166.511 88.9722 170.655 86.6747 172.169 82.1555C172.417 81.4157 172.199 80.6114 171.684 80.0255C166.956 74.6505 161.723 69.9012 156.037 65.831C136.973 52.2582 112.197 46.6221 89.4444 49.9189C88.5825 50.0438 88.1307 50.9987 88.6016 51.7314C91.4833 56.2156 95.2849 59.8853 99.6447 62.5864C100.437 63.0773 100.119 64.1376 99.2092 63.9337C97.0711 63.4543 94.3238 62.5194 91.7401 60.6961C91.4911 60.5204 91.1704 60.4762 90.8873 60.5891C89.7323 61.0497 88.0643 61.7139 86.6859 62.3089C85.8925 62.6514 85.731 63.6747 86.3841 64.2407C97.8122 74.1467 114.437 75.6526 127.455 67.7254C128.248 67.2425 129.518 68.2348 129.262 69.1275C128.853 70.5574 128.375 72.523 127.867 75.1999C124.638 91.5322 115.332 90.2661 103.811 86.1514C80.7898 77.8076 67.7294 84.9328 56.1177 70.8281C55.3109 69.8481 53.8925 69.5083 52.9328 70.3392C50.5358 72.4145 49.1172 75.4437 49.1172 78.6816C49.1172 82.5166 51.0968 85.8018 54.0311 87.7682C54.3981 88.0142 54.8858 87.9102 55.1598 87.5636C55.8748 86.6586 56.4597 86.0587 57.1881 85.6794C57.9852 85.2641 58.374 86.4045 57.701 87.0003C55.2349 89.1839 54.527 91.7851 52.9154 96.913C50.3832 104.952 51.4594 113.182 39.6217 115.334C33.3546 115.651 33.4812 119.892 31.2023 126.222C28.5575 133.863 25.0942 137.247 18.6844 143.924C17.8078 144.837 17.7326 146.297 18.696 147.118C21.2564 149.301 23.8969 149.421 26.5812 148.315C33.228 145.53 38.3556 136.921 43.1666 131.35C48.5474 125.146 61.4613 127.805 71.21 121.728C76.4677 118.504 79.6266 114.386 78.6164 108.217C78.4537 107.224 79.5906 106.626 80.0029 107.545C80.7856 109.289 81.2988 111.149 81.5121 113.071C81.5681 113.575 82.0184 113.947 82.5245 113.919C93.0718 113.326 106.711 124.959 119.458 128.107C120.233 128.299 120.784 127.403 120.346 126.736C119.539 125.507 118.854 124.233 118.308 122.931C117.744 121.578 117.317 120.265 117.015 118.998C116.778 118.007 118.226 117.741 118.721 118.632C121.99 124.524 128.523 130.057 137.615 130.717C140.717 130.97 144.136 130.59 147.681 129.514C151.922 128.248 155.847 126.602 160.531 127.488C164.013 128.122 167.241 129.894 169.267 132.869C172.11 137.015 178.11 138.113 181.308 133.781C181.743 133.191 181.78 132.403 181.492 131.729C174.45 115.223 156.569 114.089 148.883 112.106Z" fill="white" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
BIN
apps/ui/static/icons/libretranslate.png
Normal file
BIN
apps/ui/static/icons/libretranslate.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
apps/ui/static/icons/openblocks.png
Normal file
BIN
apps/ui/static/icons/openblocks.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
BIN
apps/ui/static/icons/whoogle.png
Normal file
BIN
apps/ui/static/icons/whoogle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -1,6 +1,6 @@
|
||||
FROM alpine:3.17
|
||||
ARG BUILDARCH
|
||||
ARG PB_VERSION=0.10.2
|
||||
ARG PB_VERSION=0.11.0
|
||||
RUN apk add --no-cache \
|
||||
unzip \
|
||||
ca-certificates
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "coolify",
|
||||
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
|
||||
"version": "3.12.8",
|
||||
"version": "3.12.9",
|
||||
"license": "Apache-2.0",
|
||||
"repository": "github:coollabsio/coolify",
|
||||
"scripts": {
|
||||
|
@ -55,7 +55,7 @@ importers:
|
||||
is-ip: 5.0.0
|
||||
is-port-reachable: 4.0.0
|
||||
js-yaml: 4.1.0
|
||||
jsonwebtoken: 8.5.1
|
||||
jsonwebtoken: 9.0.0
|
||||
minimist: ^1.2.7
|
||||
node-forge: 1.3.1
|
||||
node-os-utils: 1.3.7
|
||||
@ -109,7 +109,7 @@ importers:
|
||||
is-ip: 5.0.0
|
||||
is-port-reachable: 4.0.0
|
||||
js-yaml: 4.1.0
|
||||
jsonwebtoken: 8.5.1
|
||||
jsonwebtoken: 9.0.0
|
||||
minimist: 1.2.7
|
||||
node-forge: 1.3.1
|
||||
node-os-utils: 1.3.7
|
||||
@ -265,6 +265,7 @@ importers:
|
||||
cuid: 2.1.8
|
||||
dayjs: 1.11.6
|
||||
dotenv: ^16.0.3
|
||||
esbuild: 0.15.15
|
||||
execa: 6.1.0
|
||||
fastify: 4.10.2
|
||||
fastify-plugin: 4.4.0
|
||||
@ -272,8 +273,9 @@ importers:
|
||||
is-ip: 5.0.0
|
||||
is-port-reachable: 4.0.0
|
||||
js-yaml: 4.1.0
|
||||
jsonwebtoken: 8.5.1
|
||||
jsonwebtoken: 9.0.0
|
||||
node-fetch: 3.3.0
|
||||
nodemon: 2.0.20
|
||||
npm-run-all: 4.1.5
|
||||
p-all: 4.0.0
|
||||
p-throttle: 5.0.0
|
||||
@ -319,7 +321,7 @@ importers:
|
||||
is-ip: 5.0.0
|
||||
is-port-reachable: 4.0.0
|
||||
js-yaml: 4.1.0
|
||||
jsonwebtoken: 8.5.1
|
||||
jsonwebtoken: 9.0.0
|
||||
node-fetch: 3.3.0
|
||||
p-all: 4.0.0
|
||||
p-throttle: 5.0.0
|
||||
@ -340,6 +342,8 @@ importers:
|
||||
'@types/node-fetch': 2.6.2
|
||||
'@types/shell-quote': 1.7.1
|
||||
'@types/ws': 8.5.3
|
||||
esbuild: 0.15.15
|
||||
nodemon: 2.0.20
|
||||
npm-run-all: 4.1.5
|
||||
rimraf: 3.0.2
|
||||
start-server-and-test: 1.14.0
|
||||
@ -1449,6 +1453,7 @@ packages:
|
||||
engines: {node: '>= 12.11'}
|
||||
peerDependencies:
|
||||
bree: '>=9.0.0'
|
||||
tsconfig-paths: '>= 4'
|
||||
dependencies:
|
||||
bree: 9.1.2
|
||||
ts-node: 10.8.2_wup25etrarvlqkprac7h35hj7u
|
||||
@ -5733,20 +5738,14 @@ packages:
|
||||
graceful-fs: 4.2.10
|
||||
dev: false
|
||||
|
||||
/jsonwebtoken/8.5.1:
|
||||
resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==}
|
||||
engines: {node: '>=4', npm: '>=1.4.28'}
|
||||
/jsonwebtoken/9.0.0:
|
||||
resolution: {integrity: sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==}
|
||||
engines: {node: '>=12', npm: '>=6'}
|
||||
dependencies:
|
||||
jws: 3.2.2
|
||||
lodash.includes: 4.3.0
|
||||
lodash.isboolean: 3.0.3
|
||||
lodash.isinteger: 4.0.4
|
||||
lodash.isnumber: 3.0.3
|
||||
lodash.isplainobject: 4.0.6
|
||||
lodash.isstring: 4.0.1
|
||||
lodash.once: 4.1.1
|
||||
lodash: 4.17.21
|
||||
ms: 2.1.3
|
||||
semver: 5.7.1
|
||||
semver: 7.3.8
|
||||
dev: false
|
||||
|
||||
/jwa/1.4.1:
|
||||
@ -5902,37 +5901,13 @@ packages:
|
||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||
dev: false
|
||||
|
||||
/lodash.includes/4.3.0:
|
||||
resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
|
||||
dev: false
|
||||
|
||||
/lodash.isboolean/3.0.3:
|
||||
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
|
||||
dev: false
|
||||
|
||||
/lodash.isinteger/4.0.4:
|
||||
resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isnumber/3.0.3:
|
||||
resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
|
||||
dev: false
|
||||
|
||||
/lodash.isplainobject/4.0.6:
|
||||
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
|
||||
dev: false
|
||||
|
||||
/lodash.isstring/4.0.1:
|
||||
resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge/4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
/lodash.once/4.1.1:
|
||||
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
|
||||
dev: false
|
||||
|
||||
/lodash.uniqby/4.5.0:
|
||||
resolution: {integrity: sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==}
|
||||
dependencies:
|
||||
|
Loading…
Reference in New Issue
Block a user