Merge pull request #611 from coollabsio/next

v3.10.1
This commit is contained in:
Andras Bacsai 2022-09-10 10:47:47 +02:00 committed by GitHub
commit 0a68a48fc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 159 additions and 74 deletions

View File

@ -261,7 +261,10 @@ import * as buildpacks from '../lib/buildPacks';
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
envs.push(`${secret.name}=${isSecretFound[0].value}`);
} else {
envs.push(`${secret.name}=${secret.value}`);
}
} else {

View File

@ -556,7 +556,6 @@ export function checkPnpm(installCommand = null, buildCommand = null, startComma
);
}
export async function buildImage({
applicationId,
tag,
@ -677,8 +676,6 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
secrets,
pullmergeRequestId
} = data;
const isPnpm = checkPnpm(installCommand, buildCommand);
const Dockerfile: Array<string> = [];
Dockerfile.push(`FROM ${imageForBuild}`);
@ -688,7 +685,10 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {
@ -707,6 +707,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) {
Dockerfile.push(`RUN ${installCommand}`);
}
Dockerfile.push(`RUN ${buildCommand}`);
console.log(Dockerfile.join('\n'))
await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n'));
await buildImage({ ...data, isCache: true });
}
@ -722,7 +723,10 @@ export async function buildCacheImageForLaravel(data, imageForBuild) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -27,7 +27,10 @@ const createDockerfile = async (data, image): Promise<void> => {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -28,6 +28,7 @@ export default async function (data) {
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
// TODO: fix secrets
if (
(pullmergeRequestId && secret.isPRMRSecret) ||
(!pullmergeRequestId && !secret.isPRMRSecret)

View File

@ -27,7 +27,10 @@ const createDockerfile = async (data, image): Promise<void> => {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -23,7 +23,10 @@ const createDockerfile = async (data, image): Promise<void> => {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -27,7 +27,10 @@ const createDockerfile = async (data, image): Promise<void> => {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -16,7 +16,10 @@ const createDockerfile = async (data, image, htaccessFound): Promise<void> => {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -21,7 +21,10 @@ const createDockerfile = async (data, image): Promise<void> => {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -24,7 +24,10 @@ const createDockerfile = async (data, image): Promise<void> => {
secrets.forEach((secret) => {
if (secret.isBuildSecret) {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
Dockerfile.push(`ARG ${secret.name}=${isSecretFound[0].value}`);
} else {
Dockerfile.push(`ARG ${secret.name}=${secret.value}`);
}
} else {

View File

@ -21,7 +21,7 @@ import { scheduler } from './scheduler';
import { supportedServiceTypesAndVersions } from './services/supportedVersions';
import { includeServices } from './services/common';
export const version = '3.10.0';
export const version = '3.10.1';
export const isDev = process.env.NODE_ENV === 'development';
const algorithm = 'aes-256-ctr';
@ -537,7 +537,7 @@ export async function executeDockerCmd({ debug, buildId, applicationId, dockerId
}
export async function startTraefikProxy(id: string): Promise<void> {
const { engine, network, remoteEngine, remoteIpAddress } = await prisma.destinationDocker.findUnique({ where: { id } })
const found = await checkContainer({ dockerId: id, container: 'coolify-proxy', remove: true });
const { found } = await checkContainer({ dockerId: id, container: 'coolify-proxy', remove: true });
const { id: settingsId, ipv4, ipv6 } = await listSettings();
if (!found) {
@ -621,7 +621,7 @@ export async function configureNetworkTraefikProxy(destination: any): Promise<vo
export async function stopTraefikProxy(
id: string
): Promise<{ stdout: string; stderr: string } | Error> {
const found = await checkContainer({ dockerId: id, container: 'coolify-proxy' });
const { found } = await checkContainer({ dockerId: id, container: 'coolify-proxy' });
await prisma.destinationDocker.update({
where: { id },
data: { isCoolifyProxyUsed: false }
@ -1062,7 +1062,7 @@ export async function stopTcpHttpProxy(
const { id: dockerId } = destinationDocker;
let container = `${id}-${publicPort}`;
if (forceName) container = forceName;
const found = await checkContainer({ dockerId, container });
const { found } = await checkContainer({ dockerId, container });
try {
if (found) {
return await executeDockerCmd({
@ -1284,7 +1284,7 @@ export async function startTraefikTCPProxy(
): Promise<{ stdout: string; stderr: string } | Error> {
const { network, id: dockerId, remoteEngine } = destinationDocker;
const container = `${id}-${publicPort}`;
const found = await checkContainer({ dockerId, container, remove: true });
const { found } = await checkContainer({ dockerId, container, remove: true });
const { ipv4, ipv6 } = await listSettings();
let dependentId = id;

View File

@ -13,7 +13,7 @@ export function formatLabelsOnDocker(data) {
return container
})
}
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<boolean> {
export async function checkContainer({ dockerId, container, remove = false }: { dockerId: string, container: string, remove?: boolean }): Promise<{ found: boolean, status?: { isExited: boolean, isRunning: boolean, isRestarting: boolean } }> {
let containerFound = false;
try {
const { stdout } = await executeDockerCmd({
@ -21,10 +21,12 @@ export async function checkContainer({ dockerId, container, remove = false }: {
command:
`docker inspect --format '{{json .State}}' ${container}`
});
containerFound = true
const parsedStdout = JSON.parse(stdout);
const status = parsedStdout.Status;
const isRunning = status === 'running';
const isRestarting = status === 'restarting'
const isExited = status === 'exited'
if (status === 'created') {
await executeDockerCmd({
dockerId,
@ -39,13 +41,23 @@ export async function checkContainer({ dockerId, container, remove = false }: {
`docker rm ${container}`
});
}
if (isRunning) {
containerFound = true;
}
return {
found: containerFound,
status: {
isRunning,
isRestarting,
isExited
}
};
} catch (err) {
// Container not found
}
return containerFound;
return {
found: false
};
}
export async function isContainerExited(dockerId: string, containerName: string): Promise<boolean> {

View File

@ -10,7 +10,8 @@ export default async function ({
branch,
buildId,
privateSshKey,
customPort
customPort,
forPublic
}: {
applicationId: string;
workdir: string;
@ -21,11 +22,15 @@ export default async function ({
repodir: string;
privateSshKey: string;
customPort: number;
forPublic: boolean;
}): Promise<string> {
const url = htmlUrl.replace('https://', '').replace('http://', '').replace(/\/$/, '');
await saveBuildLog({ line: 'GitLab importer started.', buildId, applicationId });
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
if (!forPublic) {
await asyncExecShell(`echo '${privateSshKey}' > ${repodir}/id.rsa`);
await asyncExecShell(`chmod 600 ${repodir}/id.rsa`);
}
await saveBuildLog({
line: `Cloning ${repository}:${branch} branch.`,
@ -33,9 +38,16 @@ export default async function ({
applicationId
});
await asyncExecShell(
`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 submodule update --init --recursive && git lfs pull && cd .. `
);
if (forPublic) {
await asyncExecShell(
`git clone -q -b ${branch} git@${url}:${repository}.git --config core.sshCommand="ssh -p ${customPort} -q -o StrictHostKeyChecking=no" ${workdir}/ && cd ${workdir}/ && git submodule update --init --recursive && git lfs pull && cd .. `
);
} else {
await asyncExecShell(
`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 submodule update --init --recursive && git lfs pull && cd .. `
);
}
const { stdout: commit } = await asyncExecShell(`cd ${workdir}/ && git rev-parse HEAD`);
return commit.replace('\n', '');
}

View File

@ -655,7 +655,7 @@ async function startLanguageToolService(request: FastifyRequest<ServiceStartStop
image: config.languagetool.image,
environment: config.languagetool.environmentVariables,
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
volumes: config.languagetool,
volumes: config.languagetool.volumes,
labels: makeLabelForServices('languagetool'),
...defaultComposeConfiguration(network),
}
@ -710,7 +710,7 @@ async function startN8nService(request: FastifyRequest<ServiceStartStop>) {
[id]: {
container_name: id,
image: config.n8n.image,
volumes: config.n8n,
volumes: config.n8n.volumes,
environment: config.n8n.environmentVariables,
labels: makeLabelForServices('n8n'),
...(exposePort ? { ports: [`${exposePort}:${port}`] } : {}),
@ -1116,7 +1116,6 @@ async function startUmamiService(request: FastifyRequest<ServiceStartStop>) {
},
volumes: volumeMounts
};
console.log(composeFile)
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
await startServiceContainers(destinationDocker.id, composeFileDestination)

View File

@ -74,14 +74,21 @@ export async function getApplicationStatus(request: FastifyRequest<OnlyId>) {
const { teamId } = request.user
let isRunning = false;
let isExited = false;
let isRestarting = false;
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
isRunning = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
isExited = await isContainerExited(application.destinationDocker.id, id);
const status = await checkContainer({ dockerId: application.destinationDocker.id, container: id });
if (status?.found) {
isRunning = status.status.isRunning;
isExited = status.status.isExited;
isRestarting = status.status.isRestarting
}
// isExited = await isContainerExited(application.destinationDocker.id, id);
}
return {
isRunning,
isRestarting,
isExited,
};
} catch ({ status, message }) {
@ -339,7 +346,7 @@ export async function stopPreviewApplication(request: FastifyRequest<StopPreview
if (application?.destinationDockerId) {
const container = `${id}-${pullmergeRequestId}`
const { id: dockerId } = application.destinationDocker;
const found = await checkContainer({ dockerId, container });
const { found } = await checkContainer({ dockerId, container });
if (found) {
await removeContainer({ id: container, dockerId: application.destinationDocker.id });
}
@ -366,7 +373,10 @@ export async function restartApplication(request: FastifyRequest<OnlyId>, reply:
if (secrets.length > 0) {
secrets.forEach((secret) => {
if (pullmergeRequestId) {
if (secret.isPRMRSecret) {
const isSecretFound = secrets.filter(s => s.name === secret.name && s.isPRMRSecret)
if (isSecretFound.length > 0) {
envs.push(`${secret.name}=${isSecretFound[0].value}`);
} else {
envs.push(`${secret.name}=${secret.value}`);
}
} else {
@ -463,7 +473,7 @@ export async function stopApplication(request: FastifyRequest<OnlyId>, reply: Fa
const application: any = await getApplicationFromDB(id, teamId);
if (application?.destinationDockerId) {
const { id: dockerId } = application.destinationDocker;
const found = await checkContainer({ dockerId, container: id });
const { found } = await checkContainer({ dockerId, container: id });
if (found) {
await removeContainer({ id, dockerId: application.destinationDocker.id });
}

View File

@ -229,7 +229,7 @@ export async function getDestinationStatus(request: FastifyRequest<OnlyId>) {
try {
const { id } = request.params
const destination = await prisma.destinationDocker.findUnique({ where: { id } })
const isRunning = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
const { found: isRunning } = await checkContainer({ dockerId: destination.id, container: 'coolify-proxy', remove: true })
return {
isRunning
}

View File

@ -26,7 +26,7 @@ export async function cleanupManually(request: FastifyRequest) {
}
export async function checkUpdate(request: FastifyRequest) {
try {
const isStaging = request.hostname === 'staging.coolify.io'
const isStaging = request.hostname === 'staging.coolify.io' || request.hostname === 'arm.coolify.io'
const currentVersion = version;
const { data: versions } = await axios.get(
`https://get.coollabs.io/versions.json?appId=${process.env['COOLIFY_APP_ID']}&version=${currentVersion}`

View File

@ -43,13 +43,17 @@ export async function getServiceStatus(request: FastifyRequest<OnlyId>) {
let isRunning = false;
let isExited = false
let isRestarting = false;
const service = await getServiceFromDB({ id, teamId });
const { destinationDockerId, settings } = service;
if (destinationDockerId) {
isRunning = await checkContainer({ dockerId: service.destinationDocker.id, container: id });
isExited = await isContainerExited(service.destinationDocker.id, id);
const status = await checkContainer({ dockerId: service.destinationDocker.id, container: id });
if (status?.found) {
isRunning = status.status.isRunning;
isExited = status.status.isExited;
isRestarting = status.status.isRestarting
}
}
return {
isRunning,
@ -554,7 +558,7 @@ export async function activateWordpressFtp(request: FastifyRequest<ActivateWordp
});
try {
const isRunning = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
const { found: isRunning } = await checkContainer({ dockerId: destinationDocker.id, container: `${id}-ftp` });
if (isRunning) {
await executeDockerCmd({
dockerId: destinationDocker.id,

View File

@ -154,7 +154,7 @@ export async function gitHubEvents(request: FastifyRequest<GitHubEvents>): Promi
if (application.settings.previews) {
if (application.destinationDockerId) {
const isRunning = await checkContainer(
const { found: isRunning } = await checkContainer(
{
dockerId: application.destinationDocker.id,
container: application.id

View File

@ -107,7 +107,7 @@ export async function gitLabEvents(request: FastifyRequest<GitLabEvents>) {
const buildId = cuid();
if (application.settings.previews) {
if (application.destinationDockerId) {
const isRunning = await checkContainer(
const { found: isRunning } = await checkContainer(
{
dockerId: application.destinationDocker.id,
container: application.id

View File

@ -73,6 +73,7 @@ export const status: Writable<any> = writable({
application: {
isRunning: false,
isExited: false,
isRestarting: false,
loading: false,
initialLoading: true
},

View File

@ -107,7 +107,7 @@
$status.application.initialLoading = true;
try {
await del(`/applications/${id}`, { id, force });
return await goto(`/`, { replaceState: true });
return await window.location.assign(`/`);
} catch (error) {
if (error.message.startsWith(`Command failed: SSH_AUTH_SOCK=/tmp/coolify-ssh-agent.pid`)) {
forceDelete = true;
@ -154,6 +154,7 @@
const data = await get(`/applications/${id}/status`);
$status.application.isRunning = data.isRunning;
$status.application.isExited = data.isExited;
$status.application.isRestarting = data.isRestarting;
$status.application.loading = false;
$status.application.initialLoading = false;
}
@ -162,6 +163,7 @@
$status.application.initialLoading = true;
$status.application.isRunning = false;
$status.application.isExited = false;
$status.application.isRestarting = false;
$status.application.loading = false;
$location = null;
$isDeploymentEnabled = false;
@ -171,6 +173,7 @@
setLocation(application, settings);
$status.application.isRunning = false;
$status.application.isExited = false;
$status.application.isRestarting = false;
$status.application.loading = false;
if (
application.gitSourceId &&
@ -215,7 +218,7 @@
<div class="border border-coolgray-500 h-8" />
{/if}
{#if $status.application.isExited}
{#if $status.application.isExited || $status.application.isRestarting}
<a
id="applicationerror"
href={$isDeploymentEnabled ? `/applications/${id}/logs` : null}
@ -240,7 +243,30 @@
<line x1="12" y1="16" x2="12.01" y2="16" />
</svg>
</a>
<Tooltip triggeredBy="#applicationerror">Application exited with an error!</Tooltip>
<Tooltip triggeredBy="#applicationerror">Application exited or restarting!</Tooltip>
<button
id="stop"
on:click={stopApplication}
type="submit"
disabled={!$isDeploymentEnabled}
class="icons bg-transparent text-sm flex items-center space-x-2 text-error"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="w-6 h-6"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<rect x="6" y="5" width="4" height="14" rx="1" />
<rect x="14" y="5" width="4" height="14" rx="1" />
</svg>
</button>
<Tooltip triggeredBy="#stop">Stop</Tooltip>
{/if}
{#if $status.application.initialLoading}
<button

View File

@ -220,7 +220,6 @@
loading = true;
try {
nonWWWDomain = application.fqdn && getDomain(application.fqdn).replace(/^www\./, '');
console.log({debug: nonWWWDomain})
if (application.deploymentType)
application.deploymentType = application.deploymentType.toLowerCase();
!isBot &&

View File

@ -103,7 +103,7 @@
<button
id="follow"
on:click={followBuild}
class="bg-transparent btn btn-sm btn-linkhover:text-green-500 hover:bg-coolgray-500"
class="bg-transparent btn btn-sm btn-link hover:text-green-500 hover:bg-coolgray-500"
class:text-green-500={followingBuild}
>
<svg

View File

@ -6,7 +6,8 @@
import LoadingLogs from '$lib/components/LoadingLogs.svelte';
import { onMount, onDestroy } from 'svelte';
import Tooltip from '$lib/components/Tooltip.svelte';
import { status } from '$lib/store';
import { goto } from '$app/navigation';
let application: any = {};
let logsLoading = false;
let loadLogsInterval: any = null;
@ -16,7 +17,13 @@
let followingLogs: any;
let logsEl: any;
let position = 0;
if (
!$status.application.isExited &&
!$status.application.isRestarting &&
!$status.application.isRunning
) {
goto(`/applications/${$page.params.id}/`, { replaceState: true });
}
const { id } = $page.params;
onMount(async () => {
const response = await get(`/applications/${id}`);

View File

@ -76,7 +76,7 @@
$status.database.initialLoading = true;
try {
await del(`/databases/${database.id}`, { id: database.id, force });
return await goto('/', { replaceState: true });
return await window.location.assign('/');
} catch (error) {
return errorNotification(error);
} finally {

View File

@ -112,7 +112,7 @@
<button
id="follow"
on:click={followBuild}
class="bg-transparent btn btn-sm"
class="bg-transparent btn btn-sm btn-link"
class:text-green-500={followingLogs}
>
<svg

View File

@ -188,6 +188,7 @@
return (
(service.name && service.name.toLowerCase().includes($search.toLowerCase())) ||
(service.type && service.type.toLowerCase().includes($search.toLowerCase())) ||
(service.fqdn && service.fqdn.toLowerCase().includes($search.toLowerCase())) ||
(service.version && service.version.toLowerCase().includes($search.toLowerCase())) ||
(service.destinationDockerId &&
service.destinationDocker.name.toLowerCase().includes($search.toLowerCase()))

View File

@ -85,7 +85,7 @@
if (service.type && $status.service.isRunning)
await post(`/services/${service.id}/${service.type}/stop`, {});
await del(`/services/${service.id}`, { id: service.id });
return await goto(`/`, { replaceState: true });
return await window.location.assign(`/`);
} catch (error) {
return errorNotification(error);
} finally {

View File

@ -128,7 +128,7 @@
<button
id="follow"
on:click={followBuild}
class="bg-transparent btn btn-sm"
class="bg-transparent btn btn-sm btn-link"
class:text-green-500={followingLogs}
>
<svg

View File

@ -200,21 +200,6 @@
<input name="name" id="name" required bind:value={source.name} />
</div>
</div>
{#if source.gitlabApp.groupName}
<div class="grid grid-cols-2 items-center">
<label for="groupName" class="text-base font-bold text-stone-100"
>{$t('source.group_name')}</label
>
<input
name="groupName"
id="groupName"
disabled={source.gitlabAppId}
readonly={source.gitlabAppId}
required
bind:value={source.gitlabApp.groupName}
/>
</div>
{/if}
<div class="grid grid-cols-2 items-center">
<label for="htmlUrl" class="text-base font-bold text-stone-100">HTML URL</label>
<input

View File

@ -1,7 +1,7 @@
{
"name": "coolify",
"description": "An open-source & self-hostable Heroku / Netlify alternative.",
"version": "3.10.0",
"version": "3.10.1",
"license": "Apache-2.0",
"repository": "github:coollabsio/coolify",
"scripts": {