WIP - Persistent storage
This commit is contained in:
		
							parent
							
								
									1281a0f7e4
								
							
						
					
					
						commit
						2320ab0dfc
					
				| @ -86,6 +86,7 @@ model Application { | |||||||
|   startCommand        String? |   startCommand        String? | ||||||
|   baseDirectory       String? |   baseDirectory       String? | ||||||
|   publishDirectory    String? |   publishDirectory    String? | ||||||
|  |   phpModules          String? | ||||||
|   createdAt           DateTime                       @default(now()) |   createdAt           DateTime                       @default(now()) | ||||||
|   updatedAt           DateTime                       @updatedAt |   updatedAt           DateTime                       @updatedAt | ||||||
|   settings            ApplicationSettings? |   settings            ApplicationSettings? | ||||||
| @ -95,7 +96,7 @@ model Application { | |||||||
|   gitSourceId         String? |   gitSourceId         String? | ||||||
|   gitSource           GitSource?                     @relation(fields: [gitSourceId], references: [id]) |   gitSource           GitSource?                     @relation(fields: [gitSourceId], references: [id]) | ||||||
|   secrets             Secret[] |   secrets             Secret[] | ||||||
|   phpModules          String? |   persistentStorage   ApplicationPersistentStorage[] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| model ApplicationSettings { | model ApplicationSettings { | ||||||
| @ -110,6 +111,17 @@ model ApplicationSettings { | |||||||
|   updatedAt     DateTime    @updatedAt |   updatedAt     DateTime    @updatedAt | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | model ApplicationPersistentStorage { | ||||||
|  |   id            String      @id @default(cuid()) | ||||||
|  |   application   Application @relation(fields: [applicationId], references: [id]) | ||||||
|  |   applicationId String      @unique | ||||||
|  |   path          String      @unique | ||||||
|  |   createdAt     DateTime    @default(now()) | ||||||
|  |   updatedAt     DateTime    @updatedAt | ||||||
|  | 
 | ||||||
|  |   @@unique([applicationId, path]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| model Secret { | model Secret { | ||||||
|   id            String      @id @default(cuid()) |   id            String      @id @default(cuid()) | ||||||
|   name          String |   name          String | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								src/app.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -7,7 +7,9 @@ declare namespace App { | |||||||
| 	} | 	} | ||||||
| 	interface Platform {} | 	interface Platform {} | ||||||
| 	interface Session extends SessionData {} | 	interface Session extends SessionData {} | ||||||
| 	interface Stuff {} | 	interface Stuff { | ||||||
|  | 		application: any; | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface SessionData { | interface SessionData { | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ const createDockerfile = async (data, imageforBuild): Promise<void> => { | |||||||
| 	Dockerfile.push(`FROM ${imageforBuild}`); | 	Dockerfile.push(`FROM ${imageforBuild}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); | ||||||
| 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | ||||||
| 	Dockerfile.push(`EXPOSE 80`); | 	Dockerfile.push(`EXPOSE 80`); | ||||||
| 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | ||||||
|  | |||||||
| @ -7,15 +7,13 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 	const isPnpm = startCommand.includes('pnpm'); | 	const isPnpm = startCommand.includes('pnpm'); | ||||||
| 
 | 
 | ||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	if (isPnpm) { | 	if (isPnpm) { | ||||||
| 		Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); | 		Dockerfile.push('RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm'); | ||||||
| 		Dockerfile.push('RUN pnpm add -g pnpm'); | 		Dockerfile.push('RUN pnpm add -g pnpm'); | ||||||
| 	} | 	} | ||||||
| 	Dockerfile.push( | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${baseDirectory || ''} ./`); | ||||||
| 		`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${baseDirectory || ''} ./` |  | ||||||
| 	); |  | ||||||
| 
 | 
 | ||||||
| 	Dockerfile.push(`EXPOSE ${port}`); | 	Dockerfile.push(`EXPOSE ${port}`); | ||||||
| 	Dockerfile.push(`CMD ${startCommand}`); | 	Dockerfile.push(`CMD ${startCommand}`); | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 	const Dockerfile: Array<string> = []; | 	const Dockerfile: Array<string> = []; | ||||||
| 	const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); | 	const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); | ||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	if (secrets.length > 0) { | 	if (secrets.length > 0) { | ||||||
| 		secrets.forEach((secret) => { | 		secrets.forEach((secret) => { | ||||||
|  | |||||||
| @ -17,7 +17,7 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 	const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); | 	const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); | ||||||
| 
 | 
 | ||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	if (secrets.length > 0) { | 	if (secrets.length > 0) { | ||||||
| 		secrets.forEach((secret) => { | 		secrets.forEach((secret) => { | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 	const Dockerfile: Array<string> = []; | 	const Dockerfile: Array<string> = []; | ||||||
| 	const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); | 	const isPnpm = checkPnpm(installCommand, buildCommand, startCommand); | ||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	if (secrets.length > 0) { | 	if (secrets.length > 0) { | ||||||
| 		secrets.forEach((secret) => { | 		secrets.forEach((secret) => { | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | ||||||
| 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); | ||||||
| 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | ||||||
| 	Dockerfile.push(`EXPOSE 80`); | 	Dockerfile.push(`EXPOSE 80`); | ||||||
| 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | ||||||
|  | |||||||
| @ -7,23 +7,21 @@ const createDockerfile = async (data, image, name): Promise<void> => { | |||||||
| 	const { workdir, port, applicationId, tag } = data; | 	const { workdir, port, applicationId, tag } = data; | ||||||
| 	const Dockerfile: Array<string> = []; | 	const Dockerfile: Array<string> = []; | ||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target target`); | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target target`); | ||||||
| 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`); | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/local/cargo /usr/local/cargo`); | ||||||
| 	Dockerfile.push(`COPY . .`); | 	Dockerfile.push(`COPY . .`); | ||||||
| 	Dockerfile.push(`RUN cargo build --release --bin ${name}`); | 	Dockerfile.push(`RUN cargo build --release --bin ${name}`); | ||||||
| 	Dockerfile.push('FROM debian:buster-slim'); | 	Dockerfile.push('FROM debian:buster-slim'); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push( | 	Dockerfile.push( | ||||||
| 		`RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*` | 		`RUN apt-get update -y && apt-get install -y --no-install-recommends openssl libcurl4 ca-certificates && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/*` | ||||||
| 	); | 	); | ||||||
| 	Dockerfile.push(`RUN update-ca-certificates`); | 	Dockerfile.push(`RUN update-ca-certificates`); | ||||||
| 	Dockerfile.push( | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/target/release/${name} ${name}`); | ||||||
| 		`COPY --from=${applicationId}:${tag}-cache /usr/src/app/target/release/${name} ${name}` |  | ||||||
| 	); |  | ||||||
| 	Dockerfile.push(`EXPOSE ${port}`); | 	Dockerfile.push(`EXPOSE ${port}`); | ||||||
| 	Dockerfile.push(`CMD ["/usr/src/app/${name}"]`); | 	Dockerfile.push(`CMD ["/app/${name}"]`); | ||||||
| 	await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); | 	await fs.writeFile(`${workdir}/Dockerfile`, Dockerfile.join('\n')); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -33,9 +33,7 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 	if (buildCommand) { | 	if (buildCommand) { | ||||||
| 		Dockerfile.push( | 		Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); | ||||||
| 			`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./` |  | ||||||
| 		); |  | ||||||
| 	} else { | 	} else { | ||||||
| 		Dockerfile.push(`COPY .${baseDirectory || ''} ./`); | 		Dockerfile.push(`COPY .${baseDirectory || ''} ./`); | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); | ||||||
| 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | ||||||
| 	Dockerfile.push(`EXPOSE 80`); | 	Dockerfile.push(`EXPOSE 80`); | ||||||
| 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ const createDockerfile = async (data, image): Promise<void> => { | |||||||
| 	Dockerfile.push(`FROM ${image}`); | 	Dockerfile.push(`FROM ${image}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | 	Dockerfile.push('WORKDIR /usr/share/nginx/html'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /usr/src/app/${publishDirectory} ./`); | 	Dockerfile.push(`COPY --from=${applicationId}:${tag}-cache /app/${publishDirectory} ./`); | ||||||
| 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | 	Dockerfile.push(`COPY /nginx.conf /etc/nginx/nginx.conf`); | ||||||
| 	Dockerfile.push(`EXPOSE 80`); | 	Dockerfile.push(`EXPOSE 80`); | ||||||
| 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | 	Dockerfile.push('CMD ["nginx", "-g", "daemon off;"]'); | ||||||
|  | |||||||
| @ -134,7 +134,8 @@ export async function getApplication({ id, teamId }) { | |||||||
| 			destinationDocker: true, | 			destinationDocker: true, | ||||||
| 			settings: true, | 			settings: true, | ||||||
| 			gitSource: { include: { githubApp: true, gitlabApp: true } }, | 			gitSource: { include: { githubApp: true, gitlabApp: true } }, | ||||||
| 			secrets: true | 			secrets: true, | ||||||
|  | 			persistentStorage: true | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| @ -268,3 +269,7 @@ export async function createBuild({ | |||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | export async function getPersistentStorage(id) { | ||||||
|  | 	return await prisma.applicationPersistentStorage.findMany({ where: { applicationId: id } }); | ||||||
|  | } | ||||||
|  | |||||||
| @ -20,7 +20,7 @@ export async function buildCacheImageWithNode(data, imageForBuild) { | |||||||
| 	const isPnpm = checkPnpm(installCommand, buildCommand); | 	const isPnpm = checkPnpm(installCommand, buildCommand); | ||||||
| 	const Dockerfile: Array<string> = []; | 	const Dockerfile: Array<string> = []; | ||||||
| 	Dockerfile.push(`FROM ${imageForBuild}`); | 	Dockerfile.push(`FROM ${imageForBuild}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push(`LABEL coolify.image=true`); | 	Dockerfile.push(`LABEL coolify.image=true`); | ||||||
| 	if (secrets.length > 0) { | 	if (secrets.length > 0) { | ||||||
| 		secrets.forEach((secret) => { | 		secrets.forEach((secret) => { | ||||||
| @ -65,14 +65,14 @@ export async function buildCacheImageWithCargo(data, imageForBuild) { | |||||||
| 	} = data; | 	} = data; | ||||||
| 	const Dockerfile: Array<string> = []; | 	const Dockerfile: Array<string> = []; | ||||||
| 	Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); | 	Dockerfile.push(`FROM ${imageForBuild} as planner-${applicationId}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push('RUN cargo install cargo-chef'); | 	Dockerfile.push('RUN cargo install cargo-chef'); | ||||||
| 	Dockerfile.push('COPY . .'); | 	Dockerfile.push('COPY . .'); | ||||||
| 	Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json'); | 	Dockerfile.push('RUN cargo chef prepare --recipe-path recipe.json'); | ||||||
| 	Dockerfile.push(`FROM ${imageForBuild}`); | 	Dockerfile.push(`FROM ${imageForBuild}`); | ||||||
| 	Dockerfile.push('WORKDIR /usr/src/app'); | 	Dockerfile.push('WORKDIR /app'); | ||||||
| 	Dockerfile.push('RUN cargo install cargo-chef'); | 	Dockerfile.push('RUN cargo install cargo-chef'); | ||||||
| 	Dockerfile.push(`COPY --from=planner-${applicationId} /usr/src/app/recipe.json recipe.json`); | 	Dockerfile.push(`COPY --from=planner-${applicationId} /app/recipe.json recipe.json`); | ||||||
| 	Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); | 	Dockerfile.push('RUN cargo chef cook --release --recipe-path recipe.json'); | ||||||
| 	await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); | 	await fs.writeFile(`${workdir}/Dockerfile-cache`, Dockerfile.join('\n')); | ||||||
| 	await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); | 	await buildImage({ applicationId, tag, workdir, docker, buildId, isCache: true, debug }); | ||||||
|  | |||||||
| @ -49,8 +49,10 @@ export default async function (job) { | |||||||
| 		type, | 		type, | ||||||
| 		pullmergeRequestId = null, | 		pullmergeRequestId = null, | ||||||
| 		sourceBranch = null, | 		sourceBranch = null, | ||||||
| 		settings | 		settings, | ||||||
|  | 		persistentStorage | ||||||
| 	} = job.data; | 	} = job.data; | ||||||
|  | 	console.log(persistentStorage); | ||||||
| 	const { debug } = settings; | 	const { debug } = settings; | ||||||
| 
 | 
 | ||||||
| 	await asyncSleep(1000); | 	await asyncSleep(1000); | ||||||
| @ -68,6 +70,10 @@ export default async function (job) { | |||||||
| 	let domain = getDomain(fqdn); | 	let domain = getDomain(fqdn); | ||||||
| 	const isHttps = fqdn.startsWith('https://'); | 	const isHttps = fqdn.startsWith('https://'); | ||||||
| 
 | 
 | ||||||
|  | 	let volumes = | ||||||
|  | 		persistentStorage?.map((storage) => { | ||||||
|  | 			return `${applicationId}-${storage.id}:${storage.path}`; | ||||||
|  | 		}) || []; | ||||||
| 	// Previews, we need to get the source branch and set subdomain
 | 	// Previews, we need to get the source branch and set subdomain
 | ||||||
| 	if (pullmergeRequestId) { | 	if (pullmergeRequestId) { | ||||||
| 		branch = sourceBranch; | 		branch = sourceBranch; | ||||||
| @ -252,12 +258,22 @@ export default async function (job) { | |||||||
| 		} | 		} | ||||||
| 		try { | 		try { | ||||||
| 			saveBuildLog({ line: 'Deployment started.', buildId, applicationId }); | 			saveBuildLog({ line: 'Deployment started.', buildId, applicationId }); | ||||||
|  | 			for await (const volume of volumes) { | ||||||
|  | 				const id = volume.split(':')[0]; | ||||||
|  | 				try { | ||||||
|  | 					await asyncExecShell(`DOCKER_HOST=${host} docker volume inspect ${id}`); | ||||||
|  | 				} catch (error) { | ||||||
|  | 					await asyncExecShell(`DOCKER_HOST=${host} docker volume create ${id}`); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			volumes = volumes.map((volume) => `-v ${volume} `).join(); | ||||||
|  | 			console.log(volumes); | ||||||
| 			const { stderr } = await asyncExecShell( | 			const { stderr } = await asyncExecShell( | ||||||
| 				`DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join( | 				`DOCKER_HOST=${host} docker run ${envFound && `--env-file=${workdir}/.env`} ${labels.join( | ||||||
| 					' ' | 					' ' | ||||||
| 				)} --name ${imageId} --network ${ | 				)} --name ${imageId} --network ${docker.network} --restart always ${ | ||||||
| 					docker.network | 					volumes.length > 0 && volumes | ||||||
| 				} --restart always -d ${applicationId}:${tag}` | 				} -d ${applicationId}:${tag}` | ||||||
| 			); | 			); | ||||||
| 			if (stderr) console.log(stderr); | 			if (stderr) console.log(stderr); | ||||||
| 			saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); | 			saveBuildLog({ line: 'Deployment successful!', buildId, applicationId }); | ||||||
|  | |||||||
| @ -271,6 +271,35 @@ | |||||||
| 					</svg></button | 					</svg></button | ||||||
| 				></a | 				></a | ||||||
| 			> | 			> | ||||||
|  | 			<a | ||||||
|  | 				href="/applications/{id}/storage" | ||||||
|  | 				sveltekit:prefetch | ||||||
|  | 				class="hover:text-pink-500 rounded" | ||||||
|  | 				class:text-pink-500={$page.url.pathname === `/applications/${id}/storage`} | ||||||
|  | 				class:bg-coolgray-500={$page.url.pathname === `/applications/${id}/storage`} | ||||||
|  | 			> | ||||||
|  | 				<button | ||||||
|  | 					title="Persistent Storage" | ||||||
|  | 					class="icons bg-transparent tooltip-bottom text-sm disabled:text-red-500" | ||||||
|  | 					data-tooltip="Persistent Storage" | ||||||
|  | 				> | ||||||
|  | 					<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" /> | ||||||
|  | 						<ellipse cx="12" cy="6" rx="8" ry="3" /> | ||||||
|  | 						<path d="M4 6v6a8 3 0 0 0 16 0v-6" /> | ||||||
|  | 						<path d="M4 12v6a8 3 0 0 0 16 0v-6" /> | ||||||
|  | 					</svg> | ||||||
|  | 				</button></a | ||||||
|  | 			> | ||||||
| 			<a | 			<a | ||||||
| 				href="/applications/{id}/previews" | 				href="/applications/{id}/previews" | ||||||
| 				sveltekit:prefetch | 				sveltekit:prefetch | ||||||
|  | |||||||
							
								
								
									
										46
									
								
								src/routes/applications/[id]/storage/_Storage.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/routes/applications/[id]/storage/_Storage.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | <script lang="ts"> | ||||||
|  | 	export let isNew = false; | ||||||
|  | 	export let storage = { | ||||||
|  | 		id: null, | ||||||
|  | 		path: null | ||||||
|  | 	}; | ||||||
|  | 	import { del, post } from '$lib/api'; | ||||||
|  | 	import { page } from '$app/stores'; | ||||||
|  | 	import { errorNotification } from '$lib/form'; | ||||||
|  | 	const { id } = $page.params; | ||||||
|  | 
 | ||||||
|  | 	async function saveStorage() { | ||||||
|  | 		try { | ||||||
|  | 			await post(`/applications/${id}/storage.json`, { | ||||||
|  | 				path: storage.path | ||||||
|  | 			}); | ||||||
|  | 		} catch ({ error }) { | ||||||
|  | 			return errorNotification(error); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	async function removeStorage() { | ||||||
|  | 		try { | ||||||
|  | 			await del(`/applications/${id}/storage.json`, { path: storage.path }); | ||||||
|  | 		} catch ({ error }) { | ||||||
|  | 			return errorNotification(error); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <td> | ||||||
|  | 	<input | ||||||
|  | 		readonly={!isNew} | ||||||
|  | 		bind:value={storage.path} | ||||||
|  | 		required | ||||||
|  | 		placeholder="eg: /sqlite.db" | ||||||
|  | 		class=" border border-dashed border-coolgray-300" | ||||||
|  | 	/> | ||||||
|  | </td> | ||||||
|  | <td> | ||||||
|  | 	<div class="flex items-center justify-center px-2"> | ||||||
|  | 		<button class="bg-green-600 hover:bg-green-500" on:click={saveStorage}>Add</button> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="flex items-center justify-center px-2"> | ||||||
|  | 		<button class="bg-green-600 hover:bg-green-500" on:click={removeStorage}>Remove</button> | ||||||
|  | 	</div> | ||||||
|  | </td> | ||||||
							
								
								
									
										58
									
								
								src/routes/applications/[id]/storage/index.json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/routes/applications/[id]/storage/index.json.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | |||||||
|  | import { getTeam, getUserDetails } from '$lib/common'; | ||||||
|  | import * as db from '$lib/database'; | ||||||
|  | import { ErrorHandler } from '$lib/database'; | ||||||
|  | import { dockerInstance } from '$lib/docker'; | ||||||
|  | import type { RequestHandler } from '@sveltejs/kit'; | ||||||
|  | import jsonwebtoken from 'jsonwebtoken'; | ||||||
|  | 
 | ||||||
|  | export const get: RequestHandler = async (event) => { | ||||||
|  | 	const { status, body, teamId } = await getUserDetails(event, false); | ||||||
|  | 	if (status === 401) return { status, body }; | ||||||
|  | 
 | ||||||
|  | 	const { id } = event.params; | ||||||
|  | 	try { | ||||||
|  | 		const persistentStorages = await db.getPersistentStorage(id); | ||||||
|  | 		return { | ||||||
|  | 			body: { | ||||||
|  | 				persistentStorages | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 	} catch (error) { | ||||||
|  | 		return ErrorHandler(error); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const post: RequestHandler = async (event) => { | ||||||
|  | 	const { teamId, status, body } = await getUserDetails(event); | ||||||
|  | 	if (status === 401) return { status, body }; | ||||||
|  | 
 | ||||||
|  | 	const { id } = event.params; | ||||||
|  | 	const { path } = await event.request.json(); | ||||||
|  | 	try { | ||||||
|  | 		await db.prisma.applicationPersistentStorage.create({ | ||||||
|  | 			data: { path, application: { connect: { id } } } | ||||||
|  | 		}); | ||||||
|  | 		return { | ||||||
|  | 			status: 201 | ||||||
|  | 		}; | ||||||
|  | 	} catch (error) { | ||||||
|  | 		return ErrorHandler(error); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const del: RequestHandler = async (event) => { | ||||||
|  | 	const { teamId, status, body } = await getUserDetails(event); | ||||||
|  | 	if (status === 401) return { status, body }; | ||||||
|  | 
 | ||||||
|  | 	const { id } = event.params; | ||||||
|  | 	const { path } = await event.request.json(); | ||||||
|  | 
 | ||||||
|  | 	try { | ||||||
|  | 		await db.prisma.applicationPersistentStorage.deleteMany({ where: { applicationId: id, path } }); | ||||||
|  | 		return { | ||||||
|  | 			status: 200 | ||||||
|  | 		}; | ||||||
|  | 	} catch (error) { | ||||||
|  | 		return ErrorHandler(error); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
							
								
								
									
										61
									
								
								src/routes/applications/[id]/storage/index.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/routes/applications/[id]/storage/index.svelte
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | |||||||
|  | <script context="module" lang="ts"> | ||||||
|  | 	import type { Load } from '@sveltejs/kit'; | ||||||
|  | 	export const load: Load = async ({ fetch, params, stuff }) => { | ||||||
|  | 		let endpoint = `/applications/${params.id}/storage.json`; | ||||||
|  | 		const res = await fetch(endpoint); | ||||||
|  | 		if (res.ok) { | ||||||
|  | 			return { | ||||||
|  | 				props: { | ||||||
|  | 					application: stuff.application, | ||||||
|  | 					...(await res.json()) | ||||||
|  | 				} | ||||||
|  | 			}; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return { | ||||||
|  | 			status: res.status, | ||||||
|  | 			error: new Error(`Could not load ${endpoint}`) | ||||||
|  | 		}; | ||||||
|  | 	}; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <script lang="ts"> | ||||||
|  | 	export let application; | ||||||
|  | 
 | ||||||
|  | 	export let persistentStorages; | ||||||
|  | 	import { getDomain } from '$lib/components/common'; | ||||||
|  | 	import { page } from '$app/stores'; | ||||||
|  | 	import Storage from './_Storage.svelte'; | ||||||
|  | 
 | ||||||
|  | 	const { id } = $page.params; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <div class="flex space-x-1 p-6 font-bold"> | ||||||
|  | 	<div class="mr-4 text-2xl tracking-tight"> | ||||||
|  | 		Persistent storage for <a href={application.fqdn} target="_blank" | ||||||
|  | 			>{getDomain(application.fqdn)}</a | ||||||
|  | 		> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | <div class="mx-auto max-w-6xl rounded-xl px-6 pt-4"> | ||||||
|  | 	<table class="mx-auto border-separate text-left"> | ||||||
|  | 		<thead> | ||||||
|  | 			<tr class="h-12"> | ||||||
|  | 				<th scope="col">Path</th> | ||||||
|  | 			</tr> | ||||||
|  | 		</thead> | ||||||
|  | 		<tbody> | ||||||
|  | 			{#each persistentStorages as storage} | ||||||
|  | 				{#key storage.id} | ||||||
|  | 					<tr> | ||||||
|  | 						<Storage {storage} /> | ||||||
|  | 					</tr> | ||||||
|  | 				{/key} | ||||||
|  | 			{/each} | ||||||
|  | 			<tr> | ||||||
|  | 				<Storage isNew /> | ||||||
|  | 			</tr> | ||||||
|  | 		</tbody> | ||||||
|  | 	</table> | ||||||
|  | </div> | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user