refactor(tools): improve UI components across multiple pages with improved styling, layout adjustments, and responsive design updates

This commit is contained in:
Ivan committed 2026-04-12 18:59:17 -05:00
1 parent 3956986202
commit 3052de5d75
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>