mirror of
https://github.com/unraid/api.git
synced 2026-01-22 00:19:56 -06:00
feat: create base Detail component and placeholder tab components
This commit is contained in:
60
web/components/Docker/Console.vue
Normal file
60
web/components/Docker/Console.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { UButton } from '#components';
|
||||
|
||||
interface Props {
|
||||
item: {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
badge?: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const command = ref('');
|
||||
const output = ref<string[]>([
|
||||
`root@${props.item.id}:/# echo "Welcome to ${props.item.label}"`,
|
||||
`Welcome to ${props.item.label}`,
|
||||
`root@${props.item.id}:/#`,
|
||||
]);
|
||||
|
||||
const executeCommand = () => {
|
||||
if (command.value.trim()) {
|
||||
output.value.push(`root@${props.item.id}:/# ${command.value}`);
|
||||
output.value.push(`${command.value}: command executed`);
|
||||
output.value.push(`root@${props.item.id}:/#`);
|
||||
command.value = '';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-medium">Terminal</h3>
|
||||
<div class="flex gap-2">
|
||||
<UButton size="sm" color="primary" variant="outline" icon="i-lucide-maximize-2">
|
||||
Fullscreen
|
||||
</UButton>
|
||||
<UButton size="sm" color="primary" variant="outline" icon="i-lucide-refresh-cw">
|
||||
Restart
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-black text-green-400 p-4 rounded-lg font-mono text-sm h-96 overflow-y-auto">
|
||||
<div v-for="(line, index) in output" :key="index">
|
||||
{{ line }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span>root@{{ item.id }}:/# </span>
|
||||
<input
|
||||
v-model="command"
|
||||
class="bg-transparent outline-none flex-1 ml-1"
|
||||
type="text"
|
||||
@keyup.enter="executeCommand"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
127
web/components/Docker/Edit.vue
Normal file
127
web/components/Docker/Edit.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { UButton, UCard, UInput, USelectMenu, UIcon, UFormField } from '#components';
|
||||
|
||||
interface Props {
|
||||
item: {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
badge?: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const config = ref({
|
||||
name: props.item.label,
|
||||
image: 'ghcr.io/imagegenius/immich:latest',
|
||||
network: 'bridge',
|
||||
restartPolicy: 'unless-stopped',
|
||||
cpuLimit: '',
|
||||
memoryLimit: '',
|
||||
ports: [{ container: '7878', host: '7878', protocol: 'tcp' }],
|
||||
volumes: [
|
||||
{ container: '/config', host: '/mnt/user/appdata/immich' },
|
||||
{ container: '/media', host: '/mnt/user/media' },
|
||||
],
|
||||
environment: [
|
||||
{ key: 'PUID', value: '99' },
|
||||
{ key: 'PGID', value: '100' },
|
||||
],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-medium">Container Configuration</h3>
|
||||
<div class="flex gap-2">
|
||||
<UButton color="primary" variant="outline">Cancel</UButton>
|
||||
<UButton color="primary">Save Changes</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h4 class="font-medium">Basic Settings</h4>
|
||||
</template>
|
||||
<div class="space-y-4">
|
||||
<UFormField label="Container Name">
|
||||
<UInput v-model="config.name" />
|
||||
</UFormField>
|
||||
<UFormField label="Image">
|
||||
<UInput v-model="config.image" />
|
||||
</UFormField>
|
||||
<UFormField label="Network Mode">
|
||||
<USelectMenu v-model="config.network" :options="['bridge', 'host', 'none', 'custom']" />
|
||||
</UFormField>
|
||||
<UFormField label="Restart Policy">
|
||||
<USelectMenu
|
||||
v-model="config.restartPolicy"
|
||||
:options="['no', 'always', 'unless-stopped', 'on-failure']"
|
||||
/>
|
||||
</UFormField>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h4 class="font-medium">Resource Limits</h4>
|
||||
</template>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<UFormField label="CPU Limit">
|
||||
<UInput v-model="config.cpuLimit" placeholder="e.g., 0.5 or 2" />
|
||||
</UFormField>
|
||||
<UFormField label="Memory Limit">
|
||||
<UInput v-model="config.memoryLimit" placeholder="e.g., 512m or 2g" />
|
||||
</UFormField>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h4 class="font-medium">Port Mappings</h4>
|
||||
</template>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(port, index) in config.ports" :key="index" class="flex gap-2 items-center">
|
||||
<UInput v-model="port.host" placeholder="Host Port" class="flex-1" />
|
||||
<UIcon name="i-lucide-arrow-right" class="text-gray-400" />
|
||||
<UInput v-model="port.container" placeholder="Container Port" class="flex-1" />
|
||||
<USelectMenu v-model="port.protocol" :options="['tcp', 'udp']" class="w-24" />
|
||||
<UButton icon="i-lucide-trash-2" color="primary" variant="ghost" size="sm" />
|
||||
</div>
|
||||
<UButton icon="i-lucide-plus" size="sm" variant="outline">Add Port</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h4 class="font-medium">Volume Mappings</h4>
|
||||
</template>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(volume, index) in config.volumes" :key="index" class="flex gap-2 items-center">
|
||||
<UInput v-model="volume.host" placeholder="Host Path" class="flex-1" />
|
||||
<UIcon name="i-lucide-arrow-right" class="text-gray-400" />
|
||||
<UInput v-model="volume.container" placeholder="Container Path" class="flex-1" />
|
||||
<UButton icon="i-lucide-trash-2" color="primary" variant="ghost" size="sm" />
|
||||
</div>
|
||||
<UButton icon="i-lucide-plus" size="sm" variant="outline">Add Volume</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
|
||||
<UCard>
|
||||
<template #header>
|
||||
<h4 class="font-medium">Environment Variables</h4>
|
||||
</template>
|
||||
<div class="space-y-2">
|
||||
<div v-for="(env, index) in config.environment" :key="index" class="flex gap-2 items-center">
|
||||
<UInput v-model="env.key" placeholder="Variable Name" class="flex-1" />
|
||||
<UInput v-model="env.value" placeholder="Value" class="flex-1" />
|
||||
<UButton icon="i-lucide-trash-2" color="primary" variant="ghost" size="sm" />
|
||||
</div>
|
||||
<UButton icon="i-lucide-plus" size="sm" variant="outline">Add Variable</UButton>
|
||||
</div>
|
||||
</UCard>
|
||||
</div>
|
||||
</template>
|
||||
36
web/components/Docker/Logs.vue
Normal file
36
web/components/Docker/Logs.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { UButton } from '#components';
|
||||
|
||||
interface Props {
|
||||
item: {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
badge?: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const sampleLogs = [
|
||||
{ timestamp: '2024-01-22 10:15:23', message: `Starting ${props.item.label}...` },
|
||||
{ timestamp: '2024-01-22 10:15:24', message: 'Container initialized successfully' },
|
||||
{ timestamp: '2024-01-22 10:15:25', message: 'Listening on configured port' },
|
||||
{ timestamp: '2024-01-22 10:15:26', message: 'Health check passed' },
|
||||
{ timestamp: '2024-01-22 10:15:27', message: 'Ready to accept connections' },
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-medium">Container Logs</h3>
|
||||
<UButton size="sm" color="primary" variant="outline" icon="i-lucide-download"> Export </UButton>
|
||||
</div>
|
||||
<div class="bg-gray-900 text-gray-100 p-4 rounded-lg font-mono text-sm overflow-x-auto">
|
||||
<div v-for="(log, index) in sampleLogs" :key="index" class="whitespace-nowrap">
|
||||
<span class="text-gray-500">[{{ log.timestamp }}]</span> {{ log.message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
66
web/components/Docker/Overview.vue
Normal file
66
web/components/Docker/Overview.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<script setup lang="ts">
|
||||
interface ContainerDetails {
|
||||
network: string;
|
||||
lanIpPort: string;
|
||||
containerIp: string;
|
||||
uptime: string;
|
||||
containerPort: string;
|
||||
creationDate: string;
|
||||
containerId: string;
|
||||
maintainer: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
item: {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
badge?: string | number;
|
||||
};
|
||||
details?: ContainerDetails;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="details" class="space-y-4">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Network:</p>
|
||||
<p class="mt-1">{{ details.network }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">LAN IP:Port</p>
|
||||
<p class="mt-1">{{ details.lanIpPort }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Container IP:</p>
|
||||
<p class="mt-1">{{ details.containerIp }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Uptime:</p>
|
||||
<p class="mt-1">{{ details.uptime }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Container Port:</p>
|
||||
<p class="mt-1">{{ details.containerPort }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Creation Date:</p>
|
||||
<p class="mt-1">{{ details.creationDate }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Container Id:</p>
|
||||
<p class="mt-1 font-mono text-sm">{{ details.containerId }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Maintainer:</p>
|
||||
<p class="mt-1 text-sm">{{ details.maintainer }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-gray-500 dark:text-gray-400">
|
||||
No details available for {{ item.label }}
|
||||
</div>
|
||||
</template>
|
||||
56
web/components/Docker/Preview.vue
Normal file
56
web/components/Docker/Preview.vue
Normal file
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { UButton, UIcon } from '#components';
|
||||
|
||||
interface Props {
|
||||
item: {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
badge?: string | number;
|
||||
};
|
||||
port?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const previewUrl = props.port ? `http://localhost:${props.port}` : null;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-lg font-medium">Web Preview</h3>
|
||||
<div class="flex gap-2">
|
||||
<UButton
|
||||
v-if="previewUrl"
|
||||
size="sm"
|
||||
color="primary"
|
||||
variant="outline"
|
||||
icon="i-lucide-external-link"
|
||||
:to="previewUrl"
|
||||
target="_blank"
|
||||
>
|
||||
Open in new tab
|
||||
</UButton>
|
||||
<UButton size="sm" color="primary" variant="outline" icon="i-lucide-refresh-cw"> Refresh </UButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
<div v-if="previewUrl" class="bg-gray-100 dark:bg-gray-800 px-4 py-2 flex items-center gap-2">
|
||||
<UIcon name="i-lucide-lock" class="w-4 h-4 text-gray-500" />
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">{{ previewUrl }}</span>
|
||||
</div>
|
||||
<div class="p-8 text-center h-96 flex items-center justify-center">
|
||||
<div v-if="previewUrl" class="text-gray-500 dark:text-gray-400">
|
||||
<UIcon name="i-lucide-globe" class="w-16 h-16 mx-auto mb-4" />
|
||||
<p>Web interface preview for {{ item.label }}</p>
|
||||
<p class="text-sm mt-2">Container must be running and accessible on port {{ port }}</p>
|
||||
</div>
|
||||
<div v-else class="text-gray-500 dark:text-gray-400">
|
||||
<UIcon name="i-lucide-alert-circle" class="w-16 h-16 mx-auto mb-4" />
|
||||
<p>No web interface available for {{ item.label }}</p>
|
||||
<p class="text-sm mt-2">This container does not expose a web interface</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
114
web/components/LayoutViews/Detail.vue
Normal file
114
web/components/LayoutViews/Detail.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { UIcon, UBadge, UTabs } from '#components';
|
||||
|
||||
interface NavigationItem {
|
||||
id: string;
|
||||
label: string;
|
||||
icon?: string;
|
||||
badge?: string | number;
|
||||
}
|
||||
|
||||
interface TabItem {
|
||||
key: string;
|
||||
label: string;
|
||||
component?: Component;
|
||||
props?: Record<string, unknown>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
navigationItems?: NavigationItem[];
|
||||
tabs?: TabItem[];
|
||||
defaultNavigationId?: string;
|
||||
defaultTabKey?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
navigationItems: () => [],
|
||||
tabs: () => [],
|
||||
defaultNavigationId: undefined,
|
||||
defaultTabKey: undefined,
|
||||
});
|
||||
|
||||
const selectedNavigationId = ref(props.defaultNavigationId || props.navigationItems[0]?.id || '');
|
||||
const selectedTab = ref(props.defaultTabKey || '0');
|
||||
|
||||
const selectedNavigationItem = computed(() =>
|
||||
props.navigationItems.find((item) => item.id === selectedNavigationId.value)
|
||||
);
|
||||
|
||||
const tabItems = computed(() =>
|
||||
props.tabs.map((tab) => ({
|
||||
label: tab.label,
|
||||
key: tab.key,
|
||||
disabled: tab.disabled,
|
||||
}))
|
||||
);
|
||||
|
||||
const selectNavigationItem = (id: string) => {
|
||||
selectedNavigationId.value = id;
|
||||
selectedTab.value = '0'; // Reset to first tab index
|
||||
};
|
||||
|
||||
// UTabs uses index, so convert to tab key
|
||||
const getCurrentTabComponent = () => {
|
||||
const tabIndex = parseInt(selectedTab.value);
|
||||
|
||||
return props.tabs[tabIndex]?.component;
|
||||
};
|
||||
|
||||
const getCurrentTabProps = () => {
|
||||
const tabIndex = parseInt(selectedTab.value);
|
||||
const currentTab = props.tabs[tabIndex];
|
||||
|
||||
return {
|
||||
item: selectedNavigationItem.value,
|
||||
...currentTab?.props,
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex h-full gap-6">
|
||||
<!-- Left Navigation Section -->
|
||||
<div class="w-64 flex-shrink-0">
|
||||
<nav class="space-y-1">
|
||||
<button
|
||||
v-for="item in navigationItems"
|
||||
:key="item.id"
|
||||
:class="[
|
||||
'group flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors',
|
||||
selectedNavigationId === item.id
|
||||
? 'bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-white'
|
||||
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white',
|
||||
]"
|
||||
@click="selectNavigationItem(item.id)"
|
||||
>
|
||||
<UIcon v-if="item.icon" :name="item.icon" class="h-5 w-5 flex-shrink-0" />
|
||||
<span class="truncate">{{ item.label }}</span>
|
||||
<UBadge v-if="item.badge" size="xs" :label="String(item.badge)" class="ml-auto" />
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- Right Content Section -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<UTabs v-model="selectedTab" :items="tabItems" class="w-full" />
|
||||
|
||||
<!-- Tab Content -->
|
||||
<div class="mt-6">
|
||||
<component
|
||||
:is="getCurrentTabComponent()"
|
||||
v-if="getCurrentTabComponent() && selectedNavigationItem"
|
||||
v-bind="getCurrentTabProps()"
|
||||
/>
|
||||
<div v-else-if="!selectedNavigationItem" class="text-gray-500 dark:text-gray-400">
|
||||
No item selected
|
||||
</div>
|
||||
<div v-else class="text-gray-500 dark:text-gray-400">No content available</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,67 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { UNavigationMenu, UPage } from '#components';
|
||||
|
||||
const items = ref([
|
||||
{
|
||||
label: 'Guide',
|
||||
icon: 'i-lucide-book-open',
|
||||
to: '/getting-started',
|
||||
children: [
|
||||
{
|
||||
label: 'Introduction',
|
||||
description: 'Fully styled and customizable components for Nuxt.',
|
||||
icon: 'i-lucide-house',
|
||||
},
|
||||
{
|
||||
label: 'Installation',
|
||||
description: 'Learn how to install and configure Nuxt UI in your application.',
|
||||
icon: 'i-lucide-cloud-download',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Components',
|
||||
icon: 'i-lucide-box',
|
||||
to: '/components',
|
||||
active: true,
|
||||
children: [
|
||||
{
|
||||
label: 'Link',
|
||||
icon: 'i-lucide-file-text',
|
||||
description: 'Use NuxtLink with superpowers.',
|
||||
to: '/components/link',
|
||||
},
|
||||
{
|
||||
label: 'Modal',
|
||||
icon: 'i-lucide-file-text',
|
||||
description: 'Display a modal within your application.',
|
||||
to: '/components/modal',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'GitHub',
|
||||
icon: 'i-simple-icons-github',
|
||||
badge: '3.8k',
|
||||
to: 'https://github.com/nuxt/ui',
|
||||
target: '_blank',
|
||||
},
|
||||
{
|
||||
label: 'Help',
|
||||
icon: 'i-lucide-circle-help',
|
||||
disabled: true,
|
||||
},
|
||||
]);
|
||||
import { UPage } from '#components';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UPage>
|
||||
<template #left>
|
||||
<UNavigationMenu collapsed orientation="vertical" :items="items" />
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
</UPage>
|
||||
</template>
|
||||
|
||||
@@ -1,22 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import { UButton, UPageHeader } from '#components';
|
||||
import { definePageMeta } from '#imports';
|
||||
|
||||
import Console from '../components/Docker/Console.vue';
|
||||
import Edit from '../components/Docker/Edit.vue';
|
||||
import Logs from '../components/Docker/Logs.vue';
|
||||
import Overview from '../components/Docker/Overview.vue';
|
||||
import Preview from '../components/Docker/Preview.vue';
|
||||
import Detail from '../components/LayoutViews/Detail.vue';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'unraid-next',
|
||||
});
|
||||
|
||||
const test = 'Unraid Next Page';
|
||||
const description = 'This is a sample page using the Unraid Next layout.';
|
||||
interface ContainerDetails {
|
||||
network: string;
|
||||
lanIpPort: string;
|
||||
containerIp: string;
|
||||
uptime: string;
|
||||
containerPort: string;
|
||||
creationDate: string;
|
||||
containerId: string;
|
||||
maintainer: string;
|
||||
}
|
||||
|
||||
const dockerContainers = [
|
||||
{
|
||||
id: 'immich',
|
||||
label: 'immich',
|
||||
icon: 'i-lucide-play-circle',
|
||||
},
|
||||
{
|
||||
id: 'organizrv2',
|
||||
label: 'organizrv2',
|
||||
icon: 'i-lucide-layers',
|
||||
},
|
||||
{
|
||||
id: 'jellyfin',
|
||||
label: 'Jellyfin',
|
||||
icon: 'i-lucide-film',
|
||||
},
|
||||
{
|
||||
id: 'mongodb',
|
||||
label: 'MongoDB',
|
||||
icon: 'i-lucide-database',
|
||||
badge: 'DB',
|
||||
},
|
||||
{
|
||||
id: 'postgres17',
|
||||
label: 'postgres17',
|
||||
icon: 'i-lucide-database',
|
||||
badge: 'DB',
|
||||
},
|
||||
{
|
||||
id: 'redis',
|
||||
label: 'Redis',
|
||||
icon: 'i-lucide-database',
|
||||
badge: 'DB',
|
||||
},
|
||||
];
|
||||
|
||||
const containerDetails: Record<string, ContainerDetails> = {
|
||||
immich: {
|
||||
network: 'Bridge',
|
||||
lanIpPort: '7878',
|
||||
containerIp: '172.17.0.4',
|
||||
uptime: '13 hours',
|
||||
containerPort: '9696:TCP',
|
||||
creationDate: '2 weeks ago',
|
||||
containerId: '472b4c2442b9',
|
||||
maintainer: 'ghcr.io/imagegenius/immich',
|
||||
},
|
||||
};
|
||||
|
||||
const getTabsWithProps = (containerId: string) => [
|
||||
{
|
||||
key: 'overview',
|
||||
label: 'Overview',
|
||||
component: Overview,
|
||||
props: { details: containerDetails[containerId] },
|
||||
},
|
||||
{
|
||||
key: 'logs',
|
||||
label: 'Logs',
|
||||
component: Logs,
|
||||
},
|
||||
{
|
||||
key: 'console',
|
||||
label: 'Console',
|
||||
component: Console,
|
||||
},
|
||||
{
|
||||
key: 'preview',
|
||||
label: 'Preview',
|
||||
component: Preview,
|
||||
props: { port: containerDetails[containerId]?.lanIpPort || '8080' },
|
||||
},
|
||||
{
|
||||
key: 'edit',
|
||||
label: 'Edit',
|
||||
component: Edit,
|
||||
},
|
||||
];
|
||||
|
||||
const tabs = getTabsWithProps('immich');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<UPageHeader :title="test" :description="description" />
|
||||
|
||||
<p class="mb-4">This is a sample page using the Unraid Next layout.</p>
|
||||
<p class="mb-4">You can customize this page as needed.</p>
|
||||
|
||||
<UButton color="primary" />
|
||||
<div class="h-full">
|
||||
<Detail :navigation-items="dockerContainers" :tabs="tabs" default-navigation-id="immich" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user