feat: add host path to any container
This commit is contained in:
parent
3e81d7e9cb
commit
1c237affb4
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ApplicationPersistentStorage" ADD COLUMN "hostPath" TEXT;
|
@ -195,6 +195,7 @@ model ApplicationSettings {
|
|||||||
model ApplicationPersistentStorage {
|
model ApplicationPersistentStorage {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
applicationId String
|
applicationId String
|
||||||
|
hostPath String?
|
||||||
path String
|
path String
|
||||||
oldPath Boolean @default(false)
|
oldPath Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
@ -110,6 +110,9 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
.replace(/\//gi, '-')
|
.replace(/\//gi, '-')
|
||||||
.replace('-app', '')}:${storage.path}`;
|
.replace('-app', '')}:${storage.path}`;
|
||||||
}
|
}
|
||||||
|
if (storage.hostPath) {
|
||||||
|
return `${storage.hostPath}:${storage.path}`
|
||||||
|
}
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
@ -160,7 +163,11 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
port: exposePort ? `${exposePort}:${port}` : port
|
port: exposePort ? `${exposePort}:${port}` : port
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes.filter(v => {
|
||||||
|
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}).map((volume) => {
|
||||||
return {
|
return {
|
||||||
[`${volume.split(':')[0]}`]: {
|
[`${volume.split(':')[0]}`]: {
|
||||||
name: volume.split(':')[0]
|
name: volume.split(':')[0]
|
||||||
@ -381,6 +388,9 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
.replace(/\//gi, '-')
|
.replace(/\//gi, '-')
|
||||||
.replace('-app', '')}:${storage.path}`;
|
.replace('-app', '')}:${storage.path}`;
|
||||||
}
|
}
|
||||||
|
if (storage.hostPath) {
|
||||||
|
return `${storage.hostPath}:${storage.path}`
|
||||||
|
}
|
||||||
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
return `${applicationId}${storage.path.replace(/\//gi, '-')}:${storage.path}`;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
@ -691,7 +701,11 @@ import * as buildpacks from '../lib/buildPacks';
|
|||||||
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
await saveDockerRegistryCredentials({ url, username, password, workdir });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const composeVolumes = volumes.map((volume) => {
|
const composeVolumes = volumes.filter(v => {
|
||||||
|
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}).map((volume) => {
|
||||||
return {
|
return {
|
||||||
[`${volume.split(':')[0]}`]: {
|
[`${volume.split(':')[0]}`]: {
|
||||||
name: volume.split(':')[0]
|
name: volume.split(':')[0]
|
||||||
|
@ -36,12 +36,13 @@ export default async function (data) {
|
|||||||
if (volumes.length > 0) {
|
if (volumes.length > 0) {
|
||||||
for (const volume of volumes) {
|
for (const volume of volumes) {
|
||||||
let [v, path] = volume.split(':');
|
let [v, path] = volume.split(':');
|
||||||
|
if (!v.startsWith('.') && !v.startsWith('..') && !v.startsWith('/') && !v.startsWith('~')) {
|
||||||
composeVolumes[v] = {
|
composeVolumes[v] = {
|
||||||
name: v
|
name: v
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let networks = {};
|
let networks = {};
|
||||||
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
for (let [key, value] of Object.entries(dockerComposeYaml.services)) {
|
||||||
value['container_name'] = `${applicationId}-${key}`;
|
value['container_name'] = `${applicationId}-${key}`;
|
||||||
@ -78,6 +79,7 @@ export default async function (data) {
|
|||||||
if (value['volumes']?.length > 0) {
|
if (value['volumes']?.length > 0) {
|
||||||
value['volumes'] = value['volumes'].map((volume) => {
|
value['volumes'] = value['volumes'].map((volume) => {
|
||||||
let [v, path, permission] = volume.split(':');
|
let [v, path, permission] = volume.split(':');
|
||||||
|
console.log(v, path, permission)
|
||||||
if (
|
if (
|
||||||
v.startsWith('.') ||
|
v.startsWith('.') ||
|
||||||
v.startsWith('..') ||
|
v.startsWith('..') ||
|
||||||
@ -106,6 +108,7 @@ export default async function (data) {
|
|||||||
value['volumes'].push(volume);
|
value['volumes'].push(volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log({ volumes, composeVolumes })
|
||||||
if (dockerComposeConfiguration[key]?.port) {
|
if (dockerComposeConfiguration[key]?.port) {
|
||||||
value['expose'] = [dockerComposeConfiguration[key].port];
|
value['expose'] = [dockerComposeConfiguration[key].port];
|
||||||
}
|
}
|
||||||
|
@ -1633,6 +1633,9 @@ export function errorHandler({
|
|||||||
type?: string | null;
|
type?: string | null;
|
||||||
}) {
|
}) {
|
||||||
if (message.message) message = message.message;
|
if (message.message) message = message.message;
|
||||||
|
if (message.includes('Unique constraint failed')) {
|
||||||
|
message = 'This data is unique and already exists. Please try again with a different value.';
|
||||||
|
}
|
||||||
if (type === 'normal') {
|
if (type === 'normal') {
|
||||||
Sentry.captureException(message);
|
Sentry.captureException(message);
|
||||||
}
|
}
|
||||||
|
@ -1340,16 +1340,16 @@ export async function getStorages(request: FastifyRequest<OnlyId>) {
|
|||||||
export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) {
|
export async function saveStorage(request: FastifyRequest<SaveStorage>, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const { path, newStorage, storageId } = request.body;
|
const { hostPath, path, newStorage, storageId } = request.body;
|
||||||
|
|
||||||
if (newStorage) {
|
if (newStorage) {
|
||||||
await prisma.applicationPersistentStorage.create({
|
await prisma.applicationPersistentStorage.create({
|
||||||
data: { path, application: { connect: { id } } }
|
data: { hostPath, path, application: { connect: { id } } }
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await prisma.applicationPersistentStorage.update({
|
await prisma.applicationPersistentStorage.update({
|
||||||
where: { id: storageId },
|
where: { id: storageId },
|
||||||
data: { path }
|
data: { hostPath, path }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return reply.code(201).send();
|
return reply.code(201).send();
|
||||||
|
@ -96,6 +96,7 @@ export interface DeleteSecret extends OnlyId {
|
|||||||
}
|
}
|
||||||
export interface SaveStorage extends OnlyId {
|
export interface SaveStorage extends OnlyId {
|
||||||
Body: {
|
Body: {
|
||||||
|
hostPath?: string;
|
||||||
path: string;
|
path: string;
|
||||||
newStorage: boolean;
|
newStorage: boolean;
|
||||||
storageId: string;
|
storageId: string;
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
import { errorNotification } from '$lib/common';
|
import { errorNotification } from '$lib/common';
|
||||||
import { addToast } from '$lib/store';
|
import { addToast } from '$lib/store';
|
||||||
import CopyVolumeField from '$lib/components/CopyVolumeField.svelte';
|
import CopyVolumeField from '$lib/components/CopyVolumeField.svelte';
|
||||||
|
import SimpleExplainer from '$lib/components/SimpleExplainer.svelte';
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
let isHttps = browser && window.location.protocol === 'https:';
|
let isHttps = browser && window.location.protocol === 'https:';
|
||||||
export let value: string;
|
export let value: string;
|
||||||
@ -33,11 +34,13 @@
|
|||||||
storage.path.replace(/\/\//g, '/');
|
storage.path.replace(/\/\//g, '/');
|
||||||
await post(`/applications/${id}/storages`, {
|
await post(`/applications/${id}/storages`, {
|
||||||
path: storage.path,
|
path: storage.path,
|
||||||
|
hostPath: storage.hostPath,
|
||||||
storageId: storage.id,
|
storageId: storage.id,
|
||||||
newStorage
|
newStorage
|
||||||
});
|
});
|
||||||
dispatch('refresh');
|
dispatch('refresh');
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
|
storage.hostPath = null;
|
||||||
storage.path = null;
|
storage.path = null;
|
||||||
storage.id = null;
|
storage.id = null;
|
||||||
}
|
}
|
||||||
@ -80,27 +83,42 @@
|
|||||||
<div class="flex gap-4 pb-2" class:pt-8={isNew}>
|
<div class="flex gap-4 pb-2" class:pt-8={isNew}>
|
||||||
{#if storage.applicationId}
|
{#if storage.applicationId}
|
||||||
{#if storage.oldPath}
|
{#if storage.oldPath}
|
||||||
|
|
||||||
<CopyVolumeField
|
<CopyVolumeField
|
||||||
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else if !storage.hostPath}
|
||||||
|
|
||||||
<CopyVolumeField
|
<CopyVolumeField
|
||||||
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
value="{storage.applicationId}{storage.path.replace(/\//gi, '-').replace('-app', '')}"
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if isNew}
|
||||||
|
<div class="w-full">
|
||||||
|
<input
|
||||||
|
disabled={!isNew}
|
||||||
|
readonly={!isNew}
|
||||||
|
bind:value={storage.hostPath}
|
||||||
|
placeholder="Host path, example: ~/.directory"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SimpleExplainer
|
||||||
|
text="You can mount <span class='text-yellow-400 font-bold'>host paths</span> from the operating system.<br>Leave it empty to define a volume based volume."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{:else if storage.hostPath}
|
||||||
|
<input disabled readonly value={storage.hostPath} />
|
||||||
|
{/if}
|
||||||
<input
|
<input
|
||||||
disabled={!isNew}
|
disabled={!isNew}
|
||||||
readonly={!isNew}
|
readonly={!isNew}
|
||||||
class="w-full"
|
class="w-full"
|
||||||
bind:value={storage.path}
|
bind:value={storage.path}
|
||||||
required
|
required
|
||||||
placeholder="eg: /data"
|
placeholder="Mount point inside the container, example: /data"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-start justify-center">
|
||||||
{#if isNew}
|
{#if isNew}
|
||||||
<div class="w-full lg:w-64">
|
<div class="w-full lg:w-64">
|
||||||
<button class="btn btn-sm btn-primary w-full" on:click={() => saveStorage(true)}
|
<button class="btn btn-sm btn-primary w-full" on:click={() => saveStorage(true)}
|
||||||
|
Loading…
Reference in New Issue
Block a user