mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-10-23 00:21:18 +00:00
refactor(tools): improve UI components across multiple pages with improved styling, layout adjustments, and responsive design updates
This commit is contained in:
6 files changed
+256
-243
No files matched your search
@@ -1,99 +1,93 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-1 overflow-hidden min-w-0 bg-gradient-to-br from-slate-50 via-slate-100 to-white dark:from-zinc-950 dark:via-zinc-900 dark:to-zinc-900"
|
||||
>
|
||||
<div class="flex-1 overflow-y-auto w-full px-4 md:px-8 py-6">
|
||||
<div class="space-y-4 w-full max-w-4xl mx-auto">
|
||||
<div class="glass-card space-y-5">
|
||||
<div class="space-y-2">
|
||||
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
{{ $t("bots.bot_framework") }}
|
||||
</div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-white">{{ $t("bots.title") }}</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-300">
|
||||
{{ $t("bots.description") }}
|
||||
<div class="flex flex-col flex-1 overflow-hidden min-w-0 bg-white dark:bg-zinc-950">
|
||||
<div class="flex-1 overflow-y-auto w-full px-3 sm:px-4 md:px-8 py-4 sm:py-6">
|
||||
<div class="space-y-8 w-full max-w-4xl mx-auto">
|
||||
<div class="space-y-2 border-b border-gray-200 dark:border-zinc-800 pb-6">
|
||||
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
{{ $t("bots.bot_framework") }}
|
||||
</div>
|
||||
<div class="text-2xl font-semibold text-gray-900 dark:text-white">{{ $t("bots.title") }}</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-300">
|
||||
{{ $t("bots.description") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Create New Bot -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ $t("bots.create_new_bot") }}
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
|
||||
<div
|
||||
v-for="template in templates"
|
||||
:key="template.id"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-4 hover:border-blue-400 dark:hover:border-blue-600 transition cursor-pointer flex flex-col justify-between min-h-[140px]"
|
||||
@click="selectTemplate(template)"
|
||||
>
|
||||
<div class="min-w-0">
|
||||
<div class="font-bold text-gray-900 dark:text-white">{{ template.name }}</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
{{ template.description }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="primary-chip w-full mt-4 py-2"
|
||||
@click.stop="selectTemplate(template)"
|
||||
>
|
||||
{{ $t("bots.select") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="rounded-lg border border-dashed border-gray-300 dark:border-zinc-700 bg-gray-50/50 dark:bg-zinc-900/50 p-4 flex flex-col items-center justify-center min-h-[140px] opacity-70"
|
||||
>
|
||||
<div class="p-2 bg-gray-100 dark:bg-zinc-800 rounded-lg mb-2">
|
||||
<MaterialDesignIcon icon-name="plus" class="size-6 text-gray-400 dark:text-gray-500" />
|
||||
</div>
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400 text-center">
|
||||
{{ $t("bots.more_bots_coming") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Create New Bot -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ $t("bots.create_new_bot") }}
|
||||
</h3>
|
||||
<div class="grid sm:grid-cols-2 gap-4">
|
||||
<div
|
||||
v-for="template in templates"
|
||||
:key="template.id"
|
||||
class="glass-card !p-4 hover:border-blue-400 transition cursor-pointer flex flex-col justify-between"
|
||||
@click="selectTemplate(template)"
|
||||
>
|
||||
<div>
|
||||
<div class="font-bold text-gray-900 dark:text-white">{{ template.name }}</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
{{ template.description }}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="primary-chip w-full mt-4 py-2"
|
||||
@click.stop="selectTemplate(template)"
|
||||
>
|
||||
{{ $t("bots.select") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- More bots coming soon -->
|
||||
<div
|
||||
class="glass-card !p-4 border-dashed border-2 border-gray-200 dark:border-zinc-800 flex flex-col items-center justify-center opacity-60"
|
||||
>
|
||||
<div class="p-2 bg-gray-100 dark:bg-zinc-800 rounded-lg mb-2">
|
||||
<!-- Saved Bots -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ $t("bots.saved_bots") }}
|
||||
</h3>
|
||||
<div v-if="bots.length === 0" class="text-sm text-gray-500 italic">
|
||||
{{ $t("bots.no_bots_running") }}
|
||||
</div>
|
||||
<div v-else class="space-y-2 sm:space-y-3">
|
||||
<div
|
||||
v-for="bot in bots"
|
||||
:key="bot.id"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-3 sm:p-4 flex flex-col sm:flex-row sm:items-center gap-3 sm:justify-between"
|
||||
>
|
||||
<div class="flex items-start gap-3 min-w-0">
|
||||
<div class="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg shrink-0">
|
||||
<MaterialDesignIcon
|
||||
icon-name="plus"
|
||||
class="size-6 text-gray-400 dark:text-gray-500"
|
||||
icon-name="robot"
|
||||
class="size-6 text-blue-600 dark:text-blue-400"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-sm font-medium text-gray-500 dark:text-gray-400 text-center">
|
||||
{{ $t("bots.more_bots_coming") }}
|
||||
<div class="min-w-0">
|
||||
<div class="font-bold text-gray-900 dark:text-white truncate">{{ bot.name }}</div>
|
||||
<div class="text-xs font-mono text-gray-500 break-all">
|
||||
{{ bot.address || runningMap[bot.id]?.address || "Not running" }}
|
||||
</div>
|
||||
<div class="text-[10px] text-gray-400">
|
||||
{{ bot.template_id || bot.template }}
|
||||
</div>
|
||||
<div v-if="bot.storage_dir" class="text-[10px] text-gray-400 break-all">
|
||||
{{ bot.storage_dir }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Saved Bots -->
|
||||
<div class="space-y-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{{ $t("bots.saved_bots") }}
|
||||
</h3>
|
||||
<div v-if="bots.length === 0" class="text-sm text-gray-500 italic">
|
||||
{{ $t("bots.no_bots_running") }}
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="bot in bots"
|
||||
:key="bot.id"
|
||||
class="glass-card !p-4 flex items-center justify-between"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 bg-blue-100 dark:bg-blue-900/30 rounded-lg">
|
||||
<MaterialDesignIcon
|
||||
icon-name="robot"
|
||||
class="size-6 text-blue-600 dark:text-blue-400"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-gray-900 dark:text-white">{{ bot.name }}</div>
|
||||
<div class="text-xs font-mono text-gray-500">
|
||||
{{ bot.address || runningMap[bot.id]?.address || "Not running" }}
|
||||
</div>
|
||||
<div class="text-[10px] text-gray-400">
|
||||
{{ bot.template_id || bot.template }}
|
||||
</div>
|
||||
<div v-if="bot.storage_dir" class="text-[10px] text-gray-400">
|
||||
{{ bot.storage_dir }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex flex-wrap items-center gap-1.5 sm:gap-2 justify-end sm:shrink-0 pt-1 sm:pt-0 border-t border-gray-100 dark:border-zinc-800 sm:border-0">
|
||||
<template v-if="runningMap[bot.id]">
|
||||
<button
|
||||
class="p-2 text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors"
|
||||
@@ -129,7 +123,6 @@
|
||||
>
|
||||
<MaterialDesignIcon icon-name="delete" class="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -141,12 +134,14 @@
|
||||
<!-- Start Bot Modal -->
|
||||
<div
|
||||
v-if="selectedTemplate"
|
||||
class="fixed inset-0 z-[100] flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm"
|
||||
class="fixed inset-0 z-[100] flex items-end sm:items-center justify-center p-0 sm:p-4 bg-black/50"
|
||||
@click.self="selectedTemplate = null"
|
||||
>
|
||||
<div class="glass-card max-w-md w-full space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<h3 class="text-xl font-bold text-gray-900 dark:text-white">
|
||||
<div
|
||||
class="w-full sm:max-w-md rounded-t-2xl sm:rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-4 sm:p-6 space-y-4 max-h-[90vh] overflow-y-auto"
|
||||
>
|
||||
<div class="flex justify-between items-start gap-2">
|
||||
<h3 class="text-lg sm:text-xl font-bold text-gray-900 dark:text-white pr-2">
|
||||
{{ $t("bots.start_bot") }}: {{ selectedTemplate.name }}
|
||||
</h3>
|
||||
<button
|
||||
@@ -172,11 +167,11 @@
|
||||
{{ selectedTemplate.description }}
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 justify-end pt-2">
|
||||
<button class="secondary-chip px-6 py-2" @click="selectedTemplate = null">
|
||||
<div class="flex flex-col-reverse sm:flex-row gap-2 sm:gap-3 sm:justify-end pt-2">
|
||||
<button type="button" class="secondary-chip px-6 py-2 w-full sm:w-auto" @click="selectedTemplate = null">
|
||||
{{ $t("bots.cancel") }}
|
||||
</button>
|
||||
<button class="primary-chip px-6 py-2" :disabled="isStarting" @click="startBot">
|
||||
<button type="button" class="primary-chip px-6 py-2 w-full sm:w-auto" :disabled="isStarting" @click="startBot">
|
||||
<span
|
||||
v-if="isStarting"
|
||||
class="inline-block w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2"
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-1 overflow-hidden min-w-0 bg-gradient-to-br from-slate-50 via-slate-100 to-white dark:from-zinc-950 dark:via-zinc-900 dark:to-zinc-900"
|
||||
>
|
||||
<div class="flex flex-col flex-1 overflow-hidden min-w-0 bg-white dark:bg-zinc-950">
|
||||
<div class="flex-1 overflow-y-auto w-full">
|
||||
<div class="p-3 md:p-4 max-w-5xl mx-auto w-full">
|
||||
<div class="p-3 sm:p-4 max-w-5xl mx-auto w-full space-y-4">
|
||||
<!-- header -->
|
||||
<div class="glass-card mb-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded-xl">
|
||||
<div class="border-b border-gray-200 dark:border-zinc-800 pb-4">
|
||||
<div class="flex items-start gap-3 min-w-0">
|
||||
<div class="p-2 bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400 rounded-lg shrink-0">
|
||||
<MaterialDesignIcon icon-name="qrcode" class="size-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-xl font-bold text-gray-900 dark:text-white tracking-tight">
|
||||
<div class="min-w-0">
|
||||
<h2 class="text-lg sm:text-xl font-bold text-gray-900 dark:text-white tracking-tight">
|
||||
Paper Message Generator
|
||||
</h2>
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">
|
||||
<p class="text-xs sm:text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
Generate signed LXMF messages for physical delivery or offline transfer.
|
||||
</p>
|
||||
</div>
|
||||
@@ -23,15 +21,15 @@
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<!-- composer -->
|
||||
<div class="space-y-4">
|
||||
<section class="glass-card">
|
||||
<div class="glass-card__header">
|
||||
<h2 class="flex items-center gap-2">
|
||||
<MaterialDesignIcon icon-name="pencil-outline" class="size-5 text-gray-400" />
|
||||
<div class="space-y-4 min-w-0">
|
||||
<section class="rounded-lg border border-gray-200 dark:border-zinc-800 overflow-hidden bg-white dark:bg-zinc-950">
|
||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-zinc-800 bg-gray-50/80 dark:bg-zinc-900/50">
|
||||
<h2 class="flex items-center gap-2 text-base font-semibold text-gray-900 dark:text-white">
|
||||
<MaterialDesignIcon icon-name="pencil-outline" class="size-5 text-gray-400 shrink-0" />
|
||||
Compose Message
|
||||
</h2>
|
||||
</div>
|
||||
<div class="glass-card__body space-y-3">
|
||||
<div class="px-4 py-4 space-y-3 text-gray-900 dark:text-gray-100">
|
||||
<div>
|
||||
<label
|
||||
class="block text-[10px] font-bold text-gray-400 dark:text-zinc-500 uppercase tracking-widest mb-1.5"
|
||||
@@ -93,31 +91,32 @@
|
||||
</section>
|
||||
|
||||
<!-- read / ingest section -->
|
||||
<section class="glass-card">
|
||||
<div class="glass-card__header">
|
||||
<h2 class="flex items-center gap-2">
|
||||
<MaterialDesignIcon icon-name="qrcode-scan" class="size-5 text-gray-400" />
|
||||
<section class="rounded-lg border border-gray-200 dark:border-zinc-800 overflow-hidden bg-white dark:bg-zinc-950">
|
||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-zinc-800 bg-gray-50/80 dark:bg-zinc-900/50">
|
||||
<h2 class="flex items-center gap-2 text-base font-semibold text-gray-900 dark:text-white">
|
||||
<MaterialDesignIcon icon-name="qrcode-scan" class="size-5 text-gray-400 shrink-0" />
|
||||
Ingest Paper Message
|
||||
</h2>
|
||||
</div>
|
||||
<div class="glass-card__body space-y-3">
|
||||
<div class="px-4 py-4 space-y-3 text-gray-900 dark:text-gray-100">
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">
|
||||
Paste an LXMF URI to decode and add it to your conversations.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
v-model="ingestUri"
|
||||
type="text"
|
||||
placeholder="lxmf://..."
|
||||
class="input-field flex-1 font-mono text-sm"
|
||||
class="input-field flex-1 min-w-0 font-mono text-sm"
|
||||
@keydown.enter="ingestPaperMessage"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="px-3 py-2 bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-zinc-300 rounded-xl hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors"
|
||||
class="inline-flex items-center justify-center gap-2 px-3 py-2.5 sm:py-2 bg-gray-100 dark:bg-zinc-800 text-gray-700 dark:text-zinc-300 rounded-lg hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors shrink-0"
|
||||
@click="pasteFromClipboard"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="content-paste" class="size-5" />
|
||||
<span class="sm:hidden text-sm font-medium">Paste</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -133,12 +132,15 @@
|
||||
</div>
|
||||
|
||||
<!-- preview / result -->
|
||||
<div class="space-y-4">
|
||||
<section v-if="generatedUri" class="glass-card overflow-hidden">
|
||||
<div class="glass-card__header bg-blue-50/50 dark:bg-blue-900/10">
|
||||
<h2 class="text-blue-600 dark:text-blue-400">Generated QR Code</h2>
|
||||
<div class="space-y-4 min-w-0">
|
||||
<section
|
||||
v-if="generatedUri"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 overflow-hidden bg-white dark:bg-zinc-950"
|
||||
>
|
||||
<div class="px-4 py-3 border-b border-gray-200 dark:border-zinc-800 bg-blue-50/80 dark:bg-blue-900/20">
|
||||
<h2 class="text-base font-semibold text-blue-600 dark:text-blue-400">Generated QR Code</h2>
|
||||
</div>
|
||||
<div class="glass-card__body flex flex-col items-center p-4 sm:p-6">
|
||||
<div class="px-4 py-4 sm:p-6 flex flex-col items-center text-gray-900 dark:text-gray-100">
|
||||
<div class="p-3 bg-white rounded-2xl shadow-inner border border-gray-100 mb-6">
|
||||
<div class="size-40 sm:size-48 flex items-center justify-center overflow-hidden">
|
||||
<canvas ref="qrcode"></canvas>
|
||||
@@ -171,10 +173,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 pt-1">
|
||||
<div class="flex flex-col sm:flex-row gap-2 pt-1">
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 flex items-center justify-center gap-2 py-2.5 px-4 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-bold shadow-lg shadow-blue-500/20 transition-all active:scale-[0.98] text-sm"
|
||||
class="flex-1 flex items-center justify-center gap-2 py-3 sm:py-2.5 px-4 bg-blue-600 hover:bg-blue-700 text-white rounded-lg font-bold transition-all active:scale-[0.98] text-sm min-h-[44px]"
|
||||
@click="printQRCode"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="printer" class="size-4" />
|
||||
@@ -182,7 +184,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 flex items-center justify-center gap-2 py-2.5 px-4 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl font-bold shadow-lg shadow-emerald-500/20 transition-all active:scale-[0.98] text-sm"
|
||||
class="flex-1 flex items-center justify-center gap-2 py-3 sm:py-2.5 px-4 bg-emerald-600 hover:bg-emerald-700 text-white rounded-lg font-bold transition-all active:scale-[0.98] text-sm min-h-[44px]"
|
||||
:disabled="isSending"
|
||||
@click="sendPaperMessage"
|
||||
>
|
||||
@@ -204,7 +206,7 @@
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="glass-card flex flex-col items-center justify-center p-8 text-center h-[320px] border-dashed"
|
||||
class="rounded-lg border border-dashed border-gray-300 dark:border-zinc-700 bg-gray-50/50 dark:bg-zinc-900/30 flex flex-col items-center justify-center p-6 sm:p-8 text-center min-h-[240px] sm:min-h-[280px] sm:h-[320px]"
|
||||
>
|
||||
<div class="p-3 bg-gray-100 dark:bg-zinc-800 text-gray-400 rounded-full mb-3">
|
||||
<MaterialDesignIcon icon-name="qrcode" class="size-10" />
|
||||
@@ -435,18 +437,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.glass-card {
|
||||
@apply bg-white/90 dark:bg-zinc-900/80 backdrop-blur border border-gray-200 dark:border-zinc-800 rounded-3xl shadow-lg flex flex-col;
|
||||
}
|
||||
.glass-card__header {
|
||||
@apply flex items-center justify-between gap-3 px-6 py-4 border-b border-gray-100/70 dark:border-zinc-800/80;
|
||||
}
|
||||
.glass-card__header h2 {
|
||||
@apply text-lg font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
.glass-card__body {
|
||||
@apply px-6 py-6 text-gray-900 dark:text-gray-100;
|
||||
}
|
||||
.input-field {
|
||||
@apply bg-gray-50/90 dark:bg-zinc-800/80 border border-gray-200 dark:border-zinc-700 text-sm rounded-2xl focus:ring-2 focus:ring-blue-400 focus:border-blue-400 dark:focus:ring-blue-50 dark:focus:border-blue-500 block w-full p-2.5 text-gray-900 dark:text-gray-100 transition;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 h-full overflow-hidden bg-slate-50 dark:bg-zinc-950">
|
||||
<div class="flex flex-col flex-1 h-full overflow-hidden bg-white dark:bg-zinc-950">
|
||||
<!-- header -->
|
||||
<div
|
||||
class="flex items-center px-4 py-4 bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 shadow-sm"
|
||||
class="flex flex-wrap items-center gap-2 px-3 sm:px-4 py-3 sm:py-4 bg-white dark:bg-zinc-950 border-b border-gray-200 dark:border-zinc-800"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 bg-indigo-100 dark:bg-indigo-900/30 rounded-lg">
|
||||
<MaterialDesignIcon icon-name="route" class="size-6 text-indigo-600 dark:text-indigo-400" />
|
||||
<div class="flex items-center gap-2 sm:gap-3 min-w-0 flex-1">
|
||||
<div class="p-2 bg-indigo-100 dark:bg-indigo-900/30 rounded-lg shrink-0">
|
||||
<MaterialDesignIcon icon-name="route" class="size-5 sm:size-6 text-indigo-600 dark:text-indigo-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-xl font-bold text-gray-900 dark:text-white">RNPath</h1>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Reticulum Path Management Utility</p>
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-lg sm:text-xl font-bold text-gray-900 dark:text-white truncate">RNPath</h1>
|
||||
<p class="text-xs sm:text-sm text-gray-500 dark:text-gray-400 line-clamp-2 sm:line-clamp-none">
|
||||
Reticulum Path Management Utility
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<div class="ml-auto flex items-center gap-2 shrink-0">
|
||||
<button
|
||||
class="p-2 text-gray-500 hover:text-indigo-500 dark:text-gray-400 dark:hover:text-indigo-400 transition-colors"
|
||||
title="Refresh"
|
||||
@@ -29,28 +31,33 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto p-4 md:p-6 space-y-6">
|
||||
<div class="flex-1 overflow-y-auto p-3 sm:p-4 md:p-6 space-y-4 sm:space-y-6">
|
||||
<!-- tabs -->
|
||||
<div class="flex border-b border-gray-200 dark:border-zinc-800">
|
||||
<button
|
||||
v-for="t in ['table', 'rates', 'actions']"
|
||||
:key="t"
|
||||
class="px-6 py-3 text-sm font-semibold transition-colors border-b-2 -mb-px"
|
||||
:class="[
|
||||
tab === t
|
||||
? 'text-indigo-600 border-indigo-500 dark:text-indigo-400 dark:border-indigo-400'
|
||||
: 'text-gray-500 border-transparent hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200',
|
||||
]"
|
||||
@click="tab = t"
|
||||
>
|
||||
{{ t.charAt(0).toUpperCase() + t.slice(1) }}
|
||||
</button>
|
||||
<div class="-mx-3 sm:mx-0 overflow-x-auto border-b border-gray-200 dark:border-zinc-800">
|
||||
<div class="flex min-w-0 px-3 sm:px-0">
|
||||
<button
|
||||
v-for="t in ['table', 'rates', 'actions']"
|
||||
:key="t"
|
||||
type="button"
|
||||
class="shrink-0 px-4 sm:px-6 py-3 text-sm font-semibold transition-colors border-b-2 -mb-px"
|
||||
:class="[
|
||||
tab === t
|
||||
? 'text-indigo-600 border-indigo-500 dark:text-indigo-400 dark:border-indigo-400'
|
||||
: 'text-gray-500 border-transparent hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200',
|
||||
]"
|
||||
@click="tab = t"
|
||||
>
|
||||
{{ t.charAt(0).toUpperCase() + t.slice(1) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- path table -->
|
||||
<div v-if="tab === 'table'" class="space-y-4">
|
||||
<!-- filters -->
|
||||
<div class="glass-card p-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-3 sm:p-4 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4"
|
||||
>
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
@@ -82,18 +89,20 @@
|
||||
class="input-field"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<div class="flex flex-col items-end">
|
||||
<div
|
||||
class="flex flex-wrap items-center justify-start sm:justify-end gap-x-4 gap-y-2 sm:flex-nowrap lg:col-span-1"
|
||||
>
|
||||
<div class="flex flex-col items-start sm:items-end">
|
||||
<span class="text-[10px] font-bold text-gray-400 uppercase">Total</span>
|
||||
<span class="text-sm font-bold">{{ totalItems }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-end">
|
||||
<div class="flex flex-col items-start sm:items-end">
|
||||
<span class="text-[10px] font-bold text-green-500 uppercase">Responsive</span>
|
||||
<span class="text-sm font-bold text-green-600 dark:text-green-400">{{
|
||||
responsiveItems
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-end">
|
||||
<div class="flex flex-col items-start sm:items-end">
|
||||
<span class="text-[10px] font-bold text-red-500 uppercase">Unresponsive</span>
|
||||
<span class="text-sm font-bold text-red-600 dark:text-red-400">{{
|
||||
unresponsiveItems
|
||||
@@ -102,14 +111,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="pathTable.length === 0" class="glass-card p-12 text-center text-gray-500">
|
||||
<div
|
||||
v-if="pathTable.length === 0"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-8 sm:p-12 text-center text-gray-500"
|
||||
>
|
||||
No paths found matching your criteria.
|
||||
</div>
|
||||
<div v-else class="grid gap-4">
|
||||
<div
|
||||
v-for="path in pathTable"
|
||||
:key="path.hash"
|
||||
class="glass-card p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-l-4"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-3 sm:p-4 flex flex-col sm:flex-row sm:items-center justify-between gap-4 border-l-4"
|
||||
:class="[
|
||||
path.state === 2
|
||||
? 'border-l-green-500'
|
||||
@@ -161,7 +173,10 @@
|
||||
</div>
|
||||
|
||||
<!-- pagination -->
|
||||
<div v-if="totalPages > 1" class="flex items-center justify-between glass-card p-4">
|
||||
<div
|
||||
v-if="totalPages > 1"
|
||||
class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-3 sm:p-4"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-zinc-800 disabled:opacity-50 transition-colors"
|
||||
@@ -179,7 +194,7 @@
|
||||
<MaterialDesignIcon icon-name="chevron-right" class="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center gap-2 justify-between sm:justify-end w-full sm:w-auto">
|
||||
<span class="text-xs text-gray-500 uppercase font-semibold">Show:</span>
|
||||
<select
|
||||
v-model="itemsPerPage"
|
||||
@@ -196,11 +211,18 @@
|
||||
|
||||
<!-- announce rates -->
|
||||
<div v-if="tab === 'rates'" class="space-y-4">
|
||||
<div v-if="rateTable.length === 0" class="glass-card p-12 text-center text-gray-500">
|
||||
<div
|
||||
v-if="rateTable.length === 0"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-8 sm:p-12 text-center text-gray-500"
|
||||
>
|
||||
No announce rate data available.
|
||||
</div>
|
||||
<div v-else class="grid gap-4">
|
||||
<div v-for="rate in rateTable" :key="rate.hash" class="glass-card p-4">
|
||||
<div
|
||||
v-for="rate in rateTable"
|
||||
:key="rate.hash"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-3 sm:p-4"
|
||||
>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-2 mb-3">
|
||||
<span class="font-mono text-sm font-bold text-indigo-600 dark:text-indigo-400 truncate">
|
||||
{{ rate.hash }}
|
||||
@@ -242,18 +264,19 @@
|
||||
<!-- manual actions -->
|
||||
<div v-if="tab === 'actions'" class="max-w-2xl mx-auto space-y-6">
|
||||
<!-- request path -->
|
||||
<section class="glass-card p-6 space-y-4">
|
||||
<section class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-4 sm:p-6 space-y-4">
|
||||
<h2 class="text-lg font-bold">Request Path</h2>
|
||||
<p class="text-sm text-gray-500">Broadcast a path request for a destination hash.</p>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
v-model="requestHash"
|
||||
type="text"
|
||||
placeholder="Destination Hash (32 hex chars)"
|
||||
class="input-field flex-1 font-mono"
|
||||
class="input-field flex-1 min-w-0 font-mono"
|
||||
/>
|
||||
<button
|
||||
class="px-4 py-2 bg-indigo-600 text-white rounded-xl font-semibold hover:bg-indigo-500 transition shadow-lg shadow-indigo-500/20 active:scale-95 disabled:opacity-50"
|
||||
type="button"
|
||||
class="px-4 py-2.5 sm:py-2 bg-indigo-600 text-white rounded-lg font-semibold hover:bg-indigo-500 transition active:scale-95 disabled:opacity-50 shrink-0"
|
||||
:disabled="requestHash.length !== 32"
|
||||
@click="requestPath"
|
||||
>
|
||||
@@ -263,20 +286,21 @@
|
||||
</section>
|
||||
|
||||
<!-- drop all via -->
|
||||
<section class="glass-card p-6 space-y-4">
|
||||
<section class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-4 sm:p-6 space-y-4">
|
||||
<h2 class="text-lg font-bold">Drop All Via</h2>
|
||||
<p class="text-sm text-gray-500">
|
||||
Remove all known paths routed through a specific transport instance.
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex flex-col sm:flex-row gap-2">
|
||||
<input
|
||||
v-model="dropViaHash"
|
||||
type="text"
|
||||
placeholder="Transport Instance Hash"
|
||||
class="input-field flex-1 font-mono"
|
||||
class="input-field flex-1 min-w-0 font-mono"
|
||||
/>
|
||||
<button
|
||||
class="px-4 py-2 bg-red-600 text-white rounded-xl font-semibold hover:bg-red-500 transition shadow-lg shadow-red-500/20 active:scale-95 disabled:opacity-50"
|
||||
type="button"
|
||||
class="px-4 py-2.5 sm:py-2 bg-red-600 text-white rounded-lg font-semibold hover:bg-red-500 transition active:scale-95 disabled:opacity-50 shrink-0"
|
||||
:disabled="dropViaHash.length !== 32"
|
||||
@click="dropAllVia"
|
||||
>
|
||||
@@ -286,13 +310,14 @@
|
||||
</section>
|
||||
|
||||
<!-- drop queues -->
|
||||
<section class="glass-card p-6 space-y-4">
|
||||
<section class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-4 sm:p-6 space-y-4">
|
||||
<h2 class="text-lg font-bold">Drop Announce Queues</h2>
|
||||
<p class="text-sm text-gray-500">
|
||||
Clear all outbound announce packets currently queued on all interfaces.
|
||||
</p>
|
||||
<button
|
||||
class="w-full px-4 py-3 bg-zinc-800 text-white rounded-xl font-semibold hover:bg-zinc-700 transition active:scale-95"
|
||||
type="button"
|
||||
class="w-full px-4 py-3 bg-zinc-800 text-white rounded-lg font-semibold hover:bg-zinc-700 transition active:scale-95"
|
||||
@click="dropAnnounceQueues"
|
||||
>
|
||||
Purge All Queues
|
||||
@@ -500,9 +525,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.glass-card {
|
||||
@apply bg-white/90 dark:bg-zinc-900/80 backdrop-blur border border-gray-200 dark:border-zinc-800 rounded-2xl shadow-lg;
|
||||
}
|
||||
.input-field {
|
||||
@apply bg-gray-50/90 dark:bg-zinc-800/80 border border-gray-200 dark:border-zinc-700 text-sm rounded-xl focus:ring-2 focus:ring-indigo-400 focus:border-indigo-400 dark:focus:ring-indigo-500 dark:focus:border-indigo-500 block w-full p-3 text-gray-900 dark:text-gray-100 transition;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col flex-1 h-full overflow-hidden bg-slate-50 dark:bg-zinc-950">
|
||||
<div class="flex flex-col flex-1 h-full overflow-hidden bg-white dark:bg-zinc-950">
|
||||
<!-- header -->
|
||||
<div class="bg-white dark:bg-zinc-900 border-b border-gray-200 dark:border-zinc-800 shadow-sm z-10">
|
||||
<div class="px-4 py-3 md:px-6 md:py-4 flex items-center justify-between gap-4">
|
||||
@@ -34,26 +34,27 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="p-4 md:p-6 lg:p-8 max-w-5xl mx-auto space-y-6">
|
||||
<div class="p-3 sm:p-4 md:p-6 max-w-5xl mx-auto space-y-4 sm:space-y-6">
|
||||
<!-- main input card -->
|
||||
<div class="glass-card p-4 md:p-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative flex-1">
|
||||
<div class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-3 sm:p-4 md:p-6">
|
||||
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
|
||||
<div class="relative flex-1 min-w-0">
|
||||
<input
|
||||
v-model="destinationHash"
|
||||
type="text"
|
||||
placeholder="input destination hash"
|
||||
class="w-full pl-4 pr-12 py-3 bg-gray-50 dark:bg-zinc-800 border border-gray-200 dark:border-zinc-700 rounded-2xl text-sm md:text-base font-mono focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all dark:text-white shadow-inner"
|
||||
class="w-full pl-4 pr-12 py-3 bg-gray-50 dark:bg-zinc-800 border border-gray-200 dark:border-zinc-700 rounded-lg text-sm md:text-base font-mono focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all dark:text-white"
|
||||
@keyup.enter="runTrace"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-4 pointer-events-none text-gray-400 dark:text-zinc-500"
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-gray-400 dark:text-zinc-500"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="identifier" class="size-5" />
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="size-12 md:size-14 bg-indigo-600 hover:bg-indigo-500 text-white rounded-2xl flex items-center justify-center transition shadow-lg shadow-indigo-500/20 active:scale-95 disabled:opacity-50 shrink-0"
|
||||
type="button"
|
||||
class="w-full sm:w-auto sm:min-w-[3rem] h-12 sm:h-14 px-4 sm:px-0 bg-indigo-600 hover:bg-indigo-500 text-white rounded-lg flex items-center justify-center gap-2 transition active:scale-95 disabled:opacity-50 shrink-0"
|
||||
:disabled="!isValidHash || isLoading"
|
||||
title="Trace Path"
|
||||
@click="runTrace"
|
||||
@@ -61,9 +62,10 @@
|
||||
<MaterialDesignIcon
|
||||
v-if="!isLoading"
|
||||
icon-name="keyboard-return"
|
||||
class="size-6 md:size-7"
|
||||
class="size-6 sm:size-7"
|
||||
/>
|
||||
<MaterialDesignIcon v-else icon-name="loading" class="size-6 animate-spin" />
|
||||
<span class="sm:hidden font-semibold">{{ $t("tools.rnpath_trace.trace") }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +73,10 @@
|
||||
<!-- results area -->
|
||||
<div v-if="traceResult || isLoading || error" class="space-y-6">
|
||||
<!-- loading state -->
|
||||
<div v-if="isLoading" class="glass-card p-12 flex flex-col items-center justify-center gap-4">
|
||||
<div
|
||||
v-if="isLoading"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-8 sm:p-12 flex flex-col items-center justify-center gap-4"
|
||||
>
|
||||
<div class="relative">
|
||||
<div
|
||||
class="w-12 h-12 border-4 border-indigo-200 dark:border-indigo-900/30 border-t-indigo-600 rounded-full animate-spin"
|
||||
@@ -85,7 +90,7 @@
|
||||
<!-- error state -->
|
||||
<div
|
||||
v-else-if="error"
|
||||
class="glass-card p-6 border-l-4 border-l-red-500 bg-red-50/50 dark:bg-red-900/10"
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 border-l-4 border-l-red-500 p-4 sm:p-6 bg-red-50/50 dark:bg-red-900/10"
|
||||
>
|
||||
<div class="flex items-start gap-3 text-red-600 dark:text-red-400">
|
||||
<MaterialDesignIcon icon-name="alert-circle" class="size-5 md:size-6 shrink-0 mt-0.5" />
|
||||
@@ -104,7 +109,7 @@
|
||||
class="space-y-6 animate-in fade-in slide-in-from-bottom-4 duration-500"
|
||||
>
|
||||
<!-- compact summary bar -->
|
||||
<div class="glass-card p-1 overflow-hidden">
|
||||
<div class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-1 overflow-hidden">
|
||||
<div class="flex flex-wrap items-center divide-x divide-gray-100 dark:divide-zinc-800">
|
||||
<div class="flex-1 min-w-[120px] p-3 md:p-4 flex flex-col items-center text-center">
|
||||
<div class="text-[10px] font-bold text-gray-400 uppercase tracking-widest mb-1">
|
||||
@@ -140,7 +145,7 @@
|
||||
</div>
|
||||
|
||||
<!-- path visualization -->
|
||||
<div class="glass-card p-6 md:p-10 lg:p-16">
|
||||
<div class="rounded-lg border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 p-4 sm:p-6 md:p-10 lg:p-16">
|
||||
<!-- Desktop View (Horizontal) -->
|
||||
<div class="hidden md:flex items-start justify-center min-w-fit py-4">
|
||||
<template v-for="(node, idx) in traceResult.path" :key="'d-' + idx">
|
||||
@@ -370,8 +375,3 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.glass-card {
|
||||
@apply bg-white/90 dark:bg-zinc-900/80 backdrop-blur border border-gray-200 dark:border-zinc-800 rounded-3xl shadow-xl shadow-slate-200/20 dark:shadow-none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,17 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full w-full bg-white dark:bg-zinc-950 overflow-hidden">
|
||||
<div class="flex flex-col h-full w-full bg-white dark:bg-zinc-950 overflow-hidden min-w-0">
|
||||
<!-- header -->
|
||||
<div
|
||||
class="flex items-center px-4 py-2 border-b border-gray-200 dark:border-zinc-800 bg-white/60 dark:bg-zinc-900/50 backdrop-blur-lg z-10 relative"
|
||||
class="flex flex-wrap items-center gap-2 px-3 sm:px-4 py-2 border-b border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 z-10 relative"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<v-icon icon="mdi-chip" color="purple" size="24"></v-icon>
|
||||
<h1 class="text-xl font-black text-gray-900 dark:text-white">{{ $t("tools.rnode_flasher.title") }}</h1>
|
||||
<div class="flex items-center gap-2 min-w-0 flex-1">
|
||||
<v-icon icon="mdi-chip" color="purple" size="24" class="shrink-0"></v-icon>
|
||||
<h1 class="text-lg sm:text-xl font-black text-gray-900 dark:text-white truncate">
|
||||
{{ $t("tools.rnode_flasher.title") }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex items-center space-x-2">
|
||||
<div class="ml-auto flex flex-wrap items-center justify-end gap-1 sm:gap-2 shrink-0">
|
||||
<button
|
||||
class="p-2 text-gray-500 hover:bg-gray-100 dark:hover:bg-zinc-800 rounded-lg transition-colors flex items-center gap-2 text-sm font-medium"
|
||||
@click="showAdvanced = !showAdvanced"
|
||||
@@ -36,11 +38,9 @@
|
||||
</div>
|
||||
|
||||
<!-- content -->
|
||||
<div class="flex-1 min-h-0 overflow-y-auto p-4 sm:p-6 space-y-6">
|
||||
<div class="flex-1 min-h-0 overflow-y-auto p-3 sm:p-6 space-y-4 sm:space-y-6">
|
||||
<!-- setup card: Select device & Firmware -->
|
||||
<div
|
||||
class="border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 rounded-2xl shadow-xl overflow-hidden"
|
||||
>
|
||||
<div class="border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 rounded-lg overflow-hidden">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
||||
<!-- Left: Device Selection -->
|
||||
<div class="p-6 border-b md:border-b-0 md:border-r border-gray-100 dark:border-zinc-800 space-y-4">
|
||||
@@ -245,7 +245,7 @@
|
||||
<!-- actions & tools card -->
|
||||
<div
|
||||
v-if="showAdvanced || isProvisioning || isSettingFirmwareHash"
|
||||
class="border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 rounded-2xl shadow-xl p-6 space-y-6"
|
||||
class="border border-gray-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 rounded-lg p-4 sm:p-6 space-y-6"
|
||||
>
|
||||
<!-- Provision & Finalize -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col flex-1 overflow-hidden min-w-0 bg-gradient-to-br from-slate-50 via-slate-100 to-white dark:from-zinc-950 dark:via-zinc-900 dark:to-zinc-900"
|
||||
>
|
||||
<div class="flex flex-col flex-1 overflow-hidden min-w-0 bg-white dark:bg-zinc-950">
|
||||
<div class="flex-1 overflow-y-auto w-full">
|
||||
<div class="space-y-4 p-4 md:p-6 lg:p-8 w-full">
|
||||
<div class="glass-card flex flex-col md:flex-row md:items-center justify-between gap-6 p-6 md:p-8">
|
||||
<div class="space-y-3 flex-1 min-w-0">
|
||||
<div class="border-b border-gray-200 dark:border-zinc-800 px-4 py-4 md:px-6 md:py-5">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between lg:gap-6">
|
||||
<div class="space-y-2 min-w-0 flex-1">
|
||||
<div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400">
|
||||
{{ $t("tools.utilities") }}
|
||||
</div>
|
||||
<div class="text-3xl font-black text-gray-900 dark:text-white tracking-tight">
|
||||
<div class="text-2xl md:text-3xl font-black text-gray-900 dark:text-white tracking-tight">
|
||||
{{ $t("tools.power_tools") }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 dark:text-gray-300 leading-relaxed max-w-xl">
|
||||
@@ -17,9 +15,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="w-full md:w-80 shrink-0">
|
||||
<div class="w-full lg:max-w-sm shrink-0">
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<MaterialDesignIcon
|
||||
icon-name="magnify"
|
||||
class="size-5 text-gray-400 group-focus-within:text-blue-500 transition-colors"
|
||||
@@ -29,11 +27,12 @@
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
:placeholder="$t('common.search')"
|
||||
class="w-full pl-12 pr-10 py-3.5 bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded-2xl focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 shadow-sm transition-all text-sm"
|
||||
class="w-full pl-10 pr-10 py-3 bg-gray-50 dark:bg-zinc-900 border border-gray-200 dark:border-zinc-800 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500/40 focus:border-blue-500 text-gray-900 dark:text-white placeholder-gray-400 dark:placeholder-gray-500 text-sm"
|
||||
/>
|
||||
<button
|
||||
v-if="searchQuery"
|
||||
class="absolute inset-y-0 right-0 pr-4 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
|
||||
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-200"
|
||||
type="button"
|
||||
@click="searchQuery = ''"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="close-circle" class="size-5" />
|
||||
@@ -41,16 +40,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
<div class="p-4 md:p-6 w-full max-w-6xl mx-auto">
|
||||
<div
|
||||
class="rounded-lg border border-gray-200 dark:border-zinc-800 divide-y divide-gray-200 dark:divide-zinc-800 overflow-hidden bg-white dark:bg-zinc-950"
|
||||
>
|
||||
<RouterLink
|
||||
v-for="tool in filteredTools"
|
||||
:key="tool.name"
|
||||
:to="tool.comingSoon ? '' : tool.route"
|
||||
:class="
|
||||
[
|
||||
'tool-card',
|
||||
'glass-card',
|
||||
'tool-row',
|
||||
tool.customClass,
|
||||
tool.comingSoon ? 'opacity-60 grayscale-[0.5] cursor-default' : '',
|
||||
].filter(Boolean)
|
||||
@@ -66,12 +68,12 @@
|
||||
:alt="tool.imageAlt"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<div class="tool-card__title">{{ tool.title }}</div>
|
||||
<span
|
||||
v-if="tool.comingSoon"
|
||||
class="px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider bg-gray-100 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 rounded-md border border-gray-200 dark:border-zinc-700"
|
||||
class="px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider bg-gray-100 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 rounded border border-gray-200 dark:border-zinc-700"
|
||||
>
|
||||
Soon
|
||||
</span>
|
||||
@@ -80,7 +82,7 @@
|
||||
{{ tool.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!tool.comingSoon">
|
||||
<div v-if="!tool.comingSoon" class="shrink-0 flex items-center gap-1">
|
||||
<div v-if="tool.extraAction" class="flex items-center gap-2">
|
||||
<a
|
||||
:href="tool.extraAction.href"
|
||||
@@ -97,7 +99,10 @@
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<div v-if="filteredTools.length === 0" class="glass-card text-center py-12">
|
||||
<div
|
||||
v-if="filteredTools.length === 0"
|
||||
class="mt-6 rounded-lg border border-dashed border-gray-300 dark:border-zinc-700 px-4 py-12 text-center"
|
||||
>
|
||||
<MaterialDesignIcon icon-name="magnify" class="w-12 h-12 mx-auto mb-4 text-gray-400" />
|
||||
<div class="text-gray-600 dark:text-gray-400">{{ $t("common.no_results") }}</div>
|
||||
</div>
|
||||
@@ -263,7 +268,7 @@ export default {
|
||||
title: "Debug Logs",
|
||||
descriptionKey: null,
|
||||
description: "View and export internal system logs for troubleshooting.",
|
||||
customClass: "border-dashed border-2",
|
||||
customClass: "bg-amber-50/40 dark:bg-amber-950/20",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -294,19 +299,20 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tool-card {
|
||||
@apply flex items-center gap-4 hover:border-blue-400 dark:hover:border-blue-500 transition cursor-pointer;
|
||||
.tool-row {
|
||||
@apply flex items-start sm:items-center gap-3 sm:gap-4 px-4 py-3.5 min-h-[4.25rem] transition-colors;
|
||||
@apply hover:bg-gray-50 dark:hover:bg-zinc-900/80 active:bg-gray-100 dark:active:bg-zinc-800/80;
|
||||
}
|
||||
.tool-card__icon {
|
||||
@apply w-12 h-12 rounded-2xl flex items-center justify-center;
|
||||
@apply w-11 h-11 sm:w-12 sm:h-12 rounded-xl flex items-center justify-center shrink-0;
|
||||
}
|
||||
.tool-card__title {
|
||||
@apply text-lg font-semibold text-gray-900 dark:text-white;
|
||||
@apply text-base sm:text-lg font-semibold text-gray-900 dark:text-white;
|
||||
}
|
||||
.tool-card__description {
|
||||
@apply text-sm text-gray-600 dark:text-gray-300;
|
||||
@apply text-sm text-gray-600 dark:text-gray-300 mt-0.5 line-clamp-2 sm:line-clamp-none;
|
||||
}
|
||||
.tool-card__chevron {
|
||||
@apply w-5 h-5 text-gray-400;
|
||||
@apply w-5 h-5 text-gray-400 shrink-0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user