Scanner Verwaltung für Geräte-Agenten ergänzen
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE "instance_agents" ADD COLUMN "preferred_scanner_name" text;
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "instance_agents" ADD COLUMN "scan_defaults" jsonb DEFAULT '{"format":"pdf","resolution":300,"mode":"Color","source":null}'::jsonb NOT NULL;
|
||||||
@@ -29,6 +29,13 @@ export const instanceAgents = pgTable("instance_agents", {
|
|||||||
capabilities: jsonb("capabilities").notNull().default({ scan: true, print: false }),
|
capabilities: jsonb("capabilities").notNull().default({ scan: true, print: false }),
|
||||||
scannerNames: jsonb("scanner_names").notNull().default([]),
|
scannerNames: jsonb("scanner_names").notNull().default([]),
|
||||||
printerNames: jsonb("printer_names").notNull().default([]),
|
printerNames: jsonb("printer_names").notNull().default([]),
|
||||||
|
preferredScannerName: text("preferred_scanner_name"),
|
||||||
|
scanDefaults: jsonb("scan_defaults").notNull().default({
|
||||||
|
format: "pdf",
|
||||||
|
resolution: 300,
|
||||||
|
mode: "Color",
|
||||||
|
source: null,
|
||||||
|
}),
|
||||||
|
|
||||||
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }),
|
lastSeenAt: timestamp("last_seen_at", { withTimezone: true }),
|
||||||
lastDebugInfo: jsonb("last_debug_info"),
|
lastDebugInfo: jsonb("last_debug_info"),
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const updateAgentSchema = z.object({
|
|||||||
name: z.string().min(1).optional(),
|
name: z.string().min(1).optional(),
|
||||||
description: z.string().optional().nullable(),
|
description: z.string().optional().nullable(),
|
||||||
active: z.boolean().optional(),
|
active: z.boolean().optional(),
|
||||||
|
preferredScannerName: z.string().optional().nullable(),
|
||||||
|
scanDefaults: z.record(z.string(), z.any()).optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const createScanJobSchema = z.object({
|
const createScanJobSchema = z.object({
|
||||||
@@ -52,6 +54,8 @@ export default async function instanceAgentRoutes(server: FastifyInstance) {
|
|||||||
capabilities: instanceAgents.capabilities,
|
capabilities: instanceAgents.capabilities,
|
||||||
scannerNames: instanceAgents.scannerNames,
|
scannerNames: instanceAgents.scannerNames,
|
||||||
printerNames: instanceAgents.printerNames,
|
printerNames: instanceAgents.printerNames,
|
||||||
|
preferredScannerName: instanceAgents.preferredScannerName,
|
||||||
|
scanDefaults: instanceAgents.scanDefaults,
|
||||||
lastSeenAt: instanceAgents.lastSeenAt,
|
lastSeenAt: instanceAgents.lastSeenAt,
|
||||||
lastDebugInfo: instanceAgents.lastDebugInfo,
|
lastDebugInfo: instanceAgents.lastDebugInfo,
|
||||||
})
|
})
|
||||||
@@ -81,6 +85,8 @@ export default async function instanceAgentRoutes(server: FastifyInstance) {
|
|||||||
description: instanceAgents.description,
|
description: instanceAgents.description,
|
||||||
tokenPrefix: instanceAgents.tokenPrefix,
|
tokenPrefix: instanceAgents.tokenPrefix,
|
||||||
active: instanceAgents.active,
|
active: instanceAgents.active,
|
||||||
|
preferredScannerName: instanceAgents.preferredScannerName,
|
||||||
|
scanDefaults: instanceAgents.scanDefaults,
|
||||||
createdAt: instanceAgents.createdAt,
|
createdAt: instanceAgents.createdAt,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -106,6 +112,8 @@ export default async function instanceAgentRoutes(server: FastifyInstance) {
|
|||||||
name: instanceAgents.name,
|
name: instanceAgents.name,
|
||||||
description: instanceAgents.description,
|
description: instanceAgents.description,
|
||||||
active: instanceAgents.active,
|
active: instanceAgents.active,
|
||||||
|
preferredScannerName: instanceAgents.preferredScannerName,
|
||||||
|
scanDefaults: instanceAgents.scanDefaults,
|
||||||
updatedAt: instanceAgents.updatedAt,
|
updatedAt: instanceAgents.updatedAt,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -127,7 +135,12 @@ export default async function instanceAgentRoutes(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const [agent] = await server.db
|
const [agent] = await server.db
|
||||||
.select({ id: instanceAgents.id, active: instanceAgents.active })
|
.select({
|
||||||
|
id: instanceAgents.id,
|
||||||
|
active: instanceAgents.active,
|
||||||
|
preferredScannerName: instanceAgents.preferredScannerName,
|
||||||
|
scanDefaults: instanceAgents.scanDefaults,
|
||||||
|
})
|
||||||
.from(instanceAgents)
|
.from(instanceAgents)
|
||||||
.where(eq(instanceAgents.id, body.agentId))
|
.where(eq(instanceAgents.id, body.agentId))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
@@ -142,9 +155,12 @@ export default async function instanceAgentRoutes(server: FastifyInstance) {
|
|||||||
tenantId: requestedTenantId,
|
tenantId: requestedTenantId,
|
||||||
agentId: body.agentId,
|
agentId: body.agentId,
|
||||||
requestedBy: req.user?.user_id,
|
requestedBy: req.user?.user_id,
|
||||||
scannerName: body.scannerName,
|
scannerName: body.scannerName || agent.preferredScannerName,
|
||||||
requestedFilename: body.requestedFilename,
|
requestedFilename: body.requestedFilename,
|
||||||
settings: body.settings || {},
|
settings: {
|
||||||
|
...((agent.scanDefaults || {}) as Record<string, any>),
|
||||||
|
...(body.settings || {}),
|
||||||
|
},
|
||||||
target: body.target || {},
|
target: body.target || {},
|
||||||
})
|
})
|
||||||
.returning()
|
.returning()
|
||||||
|
|||||||
@@ -364,6 +364,11 @@ const links = computed(() => {
|
|||||||
to: "/administration/tenants",
|
to: "/administration/tenants",
|
||||||
icon: "i-heroicons-building-office-2",
|
icon: "i-heroicons-building-office-2",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Scanner",
|
||||||
|
to: "/administration/scanners",
|
||||||
|
icon: "i-heroicons-printer",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Systemstatus",
|
label: "Systemstatus",
|
||||||
to: "/administration/system",
|
to: "/administration/system",
|
||||||
|
|||||||
@@ -83,11 +83,39 @@ export type SystemStatus = {
|
|||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InstanceAgent = {
|
||||||
|
id: string
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
tokenPrefix: string
|
||||||
|
active: boolean
|
||||||
|
capabilities: Record<string, any>
|
||||||
|
scannerNames: string[]
|
||||||
|
printerNames: string[]
|
||||||
|
preferredScannerName?: string | null
|
||||||
|
scanDefaults: {
|
||||||
|
format?: string
|
||||||
|
resolution?: number
|
||||||
|
mode?: string
|
||||||
|
source?: string | null
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
lastSeenAt?: string | null
|
||||||
|
lastDebugInfo?: Record<string, any> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CreateInstanceAgentResult = {
|
||||||
|
agent: InstanceAgent
|
||||||
|
token: string
|
||||||
|
}
|
||||||
|
|
||||||
export const useAdmin = () => {
|
export const useAdmin = () => {
|
||||||
const { $api } = useNuxtApp()
|
const { $api } = useNuxtApp()
|
||||||
|
|
||||||
const getOverview = async (): Promise<AdminOverview> => {
|
const getOverview = async (): Promise<AdminOverview> => {
|
||||||
const response = await $api("/api/admin/overview")
|
const response = await $api("/api/admin/overview") as any
|
||||||
|
|
||||||
return {
|
return {
|
||||||
users: response?.users || [],
|
users: response?.users || [],
|
||||||
@@ -162,9 +190,31 @@ export const useAdmin = () => {
|
|||||||
return await $api("/api/admin/system-status")
|
return await $api("/api/admin/system-status")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getInstanceAgents = async (): Promise<{ agents: InstanceAgent[] }> => {
|
||||||
|
const response = await $api("/api/instance-agents") as any
|
||||||
|
return { agents: response?.agents || [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const createInstanceAgent = async (body: Record<string, any>): Promise<CreateInstanceAgentResult> => {
|
||||||
|
return await $api("/api/instance-agents", {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateInstanceAgent = async (id: string, body: Record<string, any>): Promise<{ agent: InstanceAgent }> => {
|
||||||
|
return await $api(`/api/instance-agents/${id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getOverview,
|
getOverview,
|
||||||
getSystemStatus,
|
getSystemStatus,
|
||||||
|
getInstanceAgents,
|
||||||
|
createInstanceAgent,
|
||||||
|
updateInstanceAgent,
|
||||||
createUser,
|
createUser,
|
||||||
createUserForProfile,
|
createUserForProfile,
|
||||||
updateUser,
|
updateUser,
|
||||||
|
|||||||
406
frontend/pages/administration/scanners.vue
Normal file
406
frontend/pages/administration/scanners.vue
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { InstanceAgent } from "~/composables/useAdmin"
|
||||||
|
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
const admin = useAdmin()
|
||||||
|
|
||||||
|
const loading = ref(true)
|
||||||
|
const saving = ref(false)
|
||||||
|
const creating = ref(false)
|
||||||
|
const agents = ref<InstanceAgent[]>([])
|
||||||
|
const selectedAgentId = ref<string | null>(null)
|
||||||
|
const generatedToken = ref("")
|
||||||
|
|
||||||
|
const newAgent = reactive({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const editState = reactive({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
active: true,
|
||||||
|
preferredScannerName: "",
|
||||||
|
format: "pdf",
|
||||||
|
resolution: 300,
|
||||||
|
mode: "Color",
|
||||||
|
source: "",
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedAgent = computed(() =>
|
||||||
|
agents.value.find((agent) => agent.id === selectedAgentId.value) || null
|
||||||
|
)
|
||||||
|
|
||||||
|
const scannerOptions = computed(() => {
|
||||||
|
const names = selectedAgent.value?.scannerNames || []
|
||||||
|
return names.map((scanner) => ({
|
||||||
|
label: scanner,
|
||||||
|
value: scanner,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
const isOnline = (agent: InstanceAgent) => {
|
||||||
|
if (!agent.lastSeenAt) return false
|
||||||
|
return Date.now() - new Date(agent.lastSeenAt).getTime() < 2 * 60 * 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDate = (value?: string | null) => {
|
||||||
|
if (!value) return "-"
|
||||||
|
return new Date(value).toLocaleString("de-DE")
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyAgentToForm = (agent: InstanceAgent | null) => {
|
||||||
|
if (!agent) return
|
||||||
|
|
||||||
|
editState.name = agent.name || ""
|
||||||
|
editState.description = agent.description || ""
|
||||||
|
editState.active = Boolean(agent.active)
|
||||||
|
editState.preferredScannerName = agent.preferredScannerName || ""
|
||||||
|
editState.format = agent.scanDefaults?.format || "pdf"
|
||||||
|
editState.resolution = Number(agent.scanDefaults?.resolution || 300)
|
||||||
|
editState.mode = agent.scanDefaults?.mode || "Color"
|
||||||
|
editState.source = agent.scanDefaults?.source || ""
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(selectedAgent, (agent) => applyAgentToForm(agent), { immediate: true })
|
||||||
|
|
||||||
|
const loadAgents = async () => {
|
||||||
|
loading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await admin.getInstanceAgents()
|
||||||
|
agents.value = response.agents
|
||||||
|
|
||||||
|
if (!selectedAgentId.value && agents.value[0]) {
|
||||||
|
selectedAgentId.value = agents.value[0].id
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedAgent.value) applyAgentToForm(selectedAgent.value)
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("[administration/scanners]", err)
|
||||||
|
toast.add({
|
||||||
|
title: "Scanner konnten nicht geladen werden",
|
||||||
|
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
||||||
|
color: "error",
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createAgent = async () => {
|
||||||
|
if (!newAgent.name.trim()) return
|
||||||
|
|
||||||
|
creating.value = true
|
||||||
|
generatedToken.value = ""
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await admin.createInstanceAgent({
|
||||||
|
name: newAgent.name.trim(),
|
||||||
|
description: newAgent.description.trim() || null,
|
||||||
|
})
|
||||||
|
|
||||||
|
generatedToken.value = response.token
|
||||||
|
newAgent.name = ""
|
||||||
|
newAgent.description = ""
|
||||||
|
selectedAgentId.value = response.agent.id
|
||||||
|
await loadAgents()
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
title: "Agent angelegt",
|
||||||
|
description: "Der Token wird nur jetzt vollständig angezeigt.",
|
||||||
|
color: "success",
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.add({
|
||||||
|
title: "Agent konnte nicht angelegt werden",
|
||||||
|
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
||||||
|
color: "error",
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
creating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveAgent = async () => {
|
||||||
|
if (!selectedAgent.value) return
|
||||||
|
|
||||||
|
saving.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await admin.updateInstanceAgent(selectedAgent.value.id, {
|
||||||
|
name: editState.name.trim(),
|
||||||
|
description: editState.description.trim() || null,
|
||||||
|
active: editState.active,
|
||||||
|
preferredScannerName: editState.preferredScannerName || null,
|
||||||
|
scanDefaults: {
|
||||||
|
format: editState.format,
|
||||||
|
resolution: Number(editState.resolution || 300),
|
||||||
|
mode: editState.mode,
|
||||||
|
source: editState.source || null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await loadAgents()
|
||||||
|
toast.add({
|
||||||
|
title: "Scanner-Einstellungen gespeichert",
|
||||||
|
color: "success",
|
||||||
|
})
|
||||||
|
} catch (err: any) {
|
||||||
|
toast.add({
|
||||||
|
title: "Scanner-Einstellungen konnten nicht gespeichert werden",
|
||||||
|
description: err?.data?.error || err?.message || "Unbekannter Fehler",
|
||||||
|
color: "error",
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!auth.user?.is_admin) {
|
||||||
|
await router.push("/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await loadAgents()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<UDashboardNavbar title="Administration: Scanner">
|
||||||
|
<template #right>
|
||||||
|
<UButton
|
||||||
|
icon="i-heroicons-arrow-path"
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
:loading="loading"
|
||||||
|
@click="loadAgents"
|
||||||
|
>
|
||||||
|
Aktualisieren
|
||||||
|
</UButton>
|
||||||
|
</template>
|
||||||
|
</UDashboardNavbar>
|
||||||
|
|
||||||
|
<UDashboardPanelContent>
|
||||||
|
<div class="grid min-h-0 gap-6 xl:grid-cols-[360px_minmax(0,1fr)]">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<UCard :ui="{ root: 'rounded-lg' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-heroicons-plus-circle" class="size-5 text-primary" />
|
||||||
|
<h2 class="text-base font-semibold text-highlighted">Agent anlegen</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<form class="space-y-3" @submit.prevent="createAgent">
|
||||||
|
<UFormField label="Name">
|
||||||
|
<UInput v-model="newAgent.name" placeholder="MacBook Büro" />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField label="Beschreibung">
|
||||||
|
<UTextarea v-model="newAgent.description" :rows="2" placeholder="Arbeitsplatz oder Standort" />
|
||||||
|
</UFormField>
|
||||||
|
<UButton
|
||||||
|
type="submit"
|
||||||
|
icon="i-heroicons-plus"
|
||||||
|
:loading="creating"
|
||||||
|
:disabled="!newAgent.name.trim()"
|
||||||
|
>
|
||||||
|
Agent anlegen
|
||||||
|
</UButton>
|
||||||
|
</form>
|
||||||
|
</UCard>
|
||||||
|
|
||||||
|
<UAlert
|
||||||
|
v-if="generatedToken"
|
||||||
|
color="warning"
|
||||||
|
variant="soft"
|
||||||
|
icon="i-heroicons-key"
|
||||||
|
title="Agent-Token"
|
||||||
|
description="Diesen Token jetzt in die .env des lokalen Agenten übernehmen. Später wird er nicht erneut angezeigt."
|
||||||
|
>
|
||||||
|
<template #description>
|
||||||
|
<code class="mt-2 block break-all rounded bg-muted px-2 py-1 text-xs">{{ generatedToken }}</code>
|
||||||
|
</template>
|
||||||
|
</UAlert>
|
||||||
|
|
||||||
|
<UCard :ui="{ root: 'rounded-lg' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between gap-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-heroicons-computer-desktop" class="size-5 text-primary" />
|
||||||
|
<h2 class="text-base font-semibold text-highlighted">Agenten</h2>
|
||||||
|
</div>
|
||||||
|
<UBadge variant="soft">{{ agents.length }}</UBadge>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<button
|
||||||
|
v-for="agent in agents"
|
||||||
|
:key="agent.id"
|
||||||
|
type="button"
|
||||||
|
class="flex w-full items-center gap-3 rounded-lg border border-default px-3 py-2 text-left transition hover:bg-muted"
|
||||||
|
:class="selectedAgentId === agent.id ? 'border-primary bg-primary/5' : ''"
|
||||||
|
@click="selectedAgentId = agent.id"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="size-2 rounded-full"
|
||||||
|
:class="isOnline(agent) ? 'bg-success' : 'bg-muted'"
|
||||||
|
/>
|
||||||
|
<span class="min-w-0 flex-1">
|
||||||
|
<span class="block truncate text-sm font-medium text-highlighted">{{ agent.name }}</span>
|
||||||
|
<span class="block truncate text-xs text-muted">{{ agent.scannerNames?.length || 0 }} Scanner · {{ formatDate(agent.lastSeenAt) }}</span>
|
||||||
|
</span>
|
||||||
|
<UBadge :color="agent.active ? 'success' : 'neutral'" variant="soft" size="xs">
|
||||||
|
{{ agent.active ? "Aktiv" : "Inaktiv" }}
|
||||||
|
</UBadge>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="selectedAgent" class="space-y-4">
|
||||||
|
<UCard :ui="{ root: 'rounded-lg' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-base font-semibold text-highlighted">{{ selectedAgent.name }}</h2>
|
||||||
|
<p class="text-xs text-muted">Token-Präfix {{ selectedAgent.tokenPrefix }} · zuletzt gesehen {{ formatDate(selectedAgent.lastSeenAt) }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UBadge :color="isOnline(selectedAgent) ? 'success' : 'neutral'" variant="soft">
|
||||||
|
{{ isOnline(selectedAgent) ? "Online" : "Offline" }}
|
||||||
|
</UBadge>
|
||||||
|
<UBadge :color="selectedAgent.active ? 'success' : 'neutral'" variant="soft">
|
||||||
|
{{ selectedAgent.active ? "Aktiv" : "Inaktiv" }}
|
||||||
|
</UBadge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="grid gap-6 lg:grid-cols-2">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<UFormField label="Name">
|
||||||
|
<UInput v-model="editState.name" />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField label="Beschreibung">
|
||||||
|
<UTextarea v-model="editState.description" :rows="3" />
|
||||||
|
</UFormField>
|
||||||
|
<UCheckbox v-model="editState.active" label="Agent aktiv" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<UFormField label="Standard-Scanner">
|
||||||
|
<USelectMenu
|
||||||
|
v-if="scannerOptions.length"
|
||||||
|
v-model="editState.preferredScannerName"
|
||||||
|
:items="scannerOptions"
|
||||||
|
value-key="value"
|
||||||
|
label-key="label"
|
||||||
|
placeholder="Scanner wählen"
|
||||||
|
/>
|
||||||
|
<UInput
|
||||||
|
v-else
|
||||||
|
v-model="editState.preferredScannerName"
|
||||||
|
placeholder="Scannername"
|
||||||
|
/>
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
|
<UFormField label="Format">
|
||||||
|
<USelectMenu
|
||||||
|
v-model="editState.format"
|
||||||
|
:items="[
|
||||||
|
{ label: 'PDF', value: 'pdf' },
|
||||||
|
{ label: 'PNG', value: 'png' },
|
||||||
|
{ label: 'TIFF', value: 'tiff' },
|
||||||
|
]"
|
||||||
|
value-key="value"
|
||||||
|
label-key="label"
|
||||||
|
/>
|
||||||
|
</UFormField>
|
||||||
|
<UFormField label="Auflösung">
|
||||||
|
<UInput v-model.number="editState.resolution" type="number" min="75" step="25" />
|
||||||
|
</UFormField>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-3 sm:grid-cols-2">
|
||||||
|
<UFormField label="Modus">
|
||||||
|
<UInput v-model="editState.mode" placeholder="Color" />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField label="Quelle">
|
||||||
|
<UInput v-model="editState.source" placeholder="ADF Duplex" />
|
||||||
|
</UFormField>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<UButton icon="i-heroicons-check" :loading="saving" @click="saveAgent">
|
||||||
|
Speichern
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
|
||||||
|
<div class="grid gap-4 lg:grid-cols-2">
|
||||||
|
<UCard :ui="{ root: 'rounded-lg' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-heroicons-queue-list" class="size-5 text-primary" />
|
||||||
|
<h2 class="text-base font-semibold text-highlighted">Erkannte Scanner</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-if="selectedAgent.scannerNames?.length" class="space-y-2">
|
||||||
|
<div
|
||||||
|
v-for="scanner in selectedAgent.scannerNames"
|
||||||
|
:key="scanner"
|
||||||
|
class="rounded border border-default px-3 py-2 text-sm text-highlighted"
|
||||||
|
>
|
||||||
|
{{ scanner }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p v-else class="text-sm text-muted">Noch keine Scanner vom Agenten gemeldet.</p>
|
||||||
|
</UCard>
|
||||||
|
|
||||||
|
<UCard :ui="{ root: 'rounded-lg' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<UIcon name="i-heroicons-information-circle" class="size-5 text-primary" />
|
||||||
|
<h2 class="text-base font-semibold text-highlighted">Agent-Info</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<dl class="space-y-3 text-sm">
|
||||||
|
<div class="flex justify-between gap-3">
|
||||||
|
<dt class="text-muted">Plattform</dt>
|
||||||
|
<dd class="text-highlighted">{{ selectedAgent.capabilities?.platform || "-" }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between gap-3">
|
||||||
|
<dt class="text-muted">Scan</dt>
|
||||||
|
<dd class="text-highlighted">{{ selectedAgent.capabilities?.scan ? "Ja" : "Nein" }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between gap-3">
|
||||||
|
<dt class="text-muted">Druck</dt>
|
||||||
|
<dd class="text-highlighted">{{ selectedAgent.capabilities?.print ? "Ja" : "Nein" }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between gap-3">
|
||||||
|
<dt class="text-muted">Hostname</dt>
|
||||||
|
<dd class="truncate text-highlighted">{{ selectedAgent.lastDebugInfo?.hostname || "-" }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<UCard v-else :ui="{ root: 'rounded-lg' }">
|
||||||
|
<div class="py-10 text-center text-muted">
|
||||||
|
Noch kein Scanner-Agent angelegt.
|
||||||
|
</div>
|
||||||
|
</UCard>
|
||||||
|
</div>
|
||||||
|
</UDashboardPanelContent>
|
||||||
|
</template>
|
||||||
Reference in New Issue
Block a user