Apply dark theme to all UI pages

- Convert registry list, docker detail, package detail, maven detail pages to dark theme
- Use layout_dark instead of layout for all pages
- Update colors: bg-[#1e293b] cards, slate-700 borders, slate-200/400 text
- Mark unused light theme functions with #[allow(dead_code)]
This commit is contained in:
2026-01-26 18:43:11 +00:00
parent d2fec9ad15
commit 411bc75e5e
5 changed files with 178 additions and 139 deletions

6
Cargo.lock generated
View File

@@ -1185,7 +1185,7 @@ checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
[[package]]
name = "nora-cli"
version = "0.2.7"
version = "0.2.9"
dependencies = [
"clap",
"flate2",
@@ -1199,7 +1199,7 @@ dependencies = [
[[package]]
name = "nora-registry"
version = "0.2.7"
version = "0.2.9"
dependencies = [
"async-trait",
"axum",
@@ -1234,7 +1234,7 @@ dependencies = [
[[package]]
name = "nora-storage"
version = "0.2.7"
version = "0.2.9"
dependencies = [
"axum",
"base64",

83
deploy/demo-traffic.sh Normal file
View File

@@ -0,0 +1,83 @@
#!/bin/bash
# Demo traffic simulator for NORA registry
# Generates random registry activity for dashboard demo
REGISTRY="http://localhost:4000"
LOG_FILE="/var/log/nora-demo-traffic.log"
# Sample packages to fetch
NPM_PACKAGES=("lodash" "express" "react" "axios" "moment" "underscore" "chalk" "debug")
MAVEN_ARTIFACTS=(
"org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.pom"
"com/google/guava/guava/31.1-jre/guava-31.1-jre.pom"
"org/slf4j/slf4j-api/2.0.0/slf4j-api-2.0.0.pom"
)
DOCKER_IMAGES=("alpine" "busybox" "hello-world")
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}
# Random sleep between min and max seconds
random_sleep() {
local min=$1
local max=$2
local delay=$((RANDOM % (max - min + 1) + min))
sleep $delay
}
# Fetch random npm package
fetch_npm() {
local pkg=${NPM_PACKAGES[$RANDOM % ${#NPM_PACKAGES[@]}]}
log "NPM: fetching $pkg"
curl -s "$REGISTRY/npm/$pkg" > /dev/null 2>&1
}
# Fetch random maven artifact
fetch_maven() {
local artifact=${MAVEN_ARTIFACTS[$RANDOM % ${#MAVEN_ARTIFACTS[@]}]}
log "MAVEN: fetching $artifact"
curl -s "$REGISTRY/maven2/$artifact" > /dev/null 2>&1
}
# Docker push/pull cycle
docker_cycle() {
local img=${DOCKER_IMAGES[$RANDOM % ${#DOCKER_IMAGES[@]}]}
local tag="demo-$(date +%s)"
log "DOCKER: push/pull cycle for $img"
# Tag and push
docker tag "$img:latest" "localhost:4000/demo/$img:$tag" 2>/dev/null
docker push "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
# Pull back
docker rmi "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
docker pull "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
# Cleanup
docker rmi "localhost:4000/demo/$img:$tag" > /dev/null 2>&1
}
# Main loop
log "Starting demo traffic simulator"
while true; do
# Random operation
op=$((RANDOM % 10))
case $op in
0|1|2|3) # 40% npm
fetch_npm
;;
4|5|6) # 30% maven
fetch_maven
;;
7|8|9) # 30% docker
docker_cycle
;;
esac
# Random delay: 30-120 seconds
random_sleep 30 120
done

View File

@@ -0,0 +1,15 @@
[Unit]
Description=NORA Demo Traffic Simulator
After=docker.service
Requires=docker.service
[Service]
Type=simple
ExecStart=/opt/nora/demo-traffic.sh
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target

View File

@@ -1,68 +1,6 @@
/// Application version from Cargo.toml
const VERSION: &str = env!("CARGO_PKG_VERSION");
/// Main layout wrapper with header and sidebar
pub fn layout(title: &str, content: &str, active_page: Option<&str>) -> String {
format!(
r##"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{} - Nora</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
[x-cloak] {{ display: none !important; }}
.sidebar-open {{ overflow: hidden; }}
</style>
</head>
<body class="bg-slate-100 min-h-screen">
<div class="flex h-screen overflow-hidden">
<!-- Mobile sidebar overlay -->
<div id="sidebar-overlay" class="fixed inset-0 bg-black/50 z-40 hidden md:hidden" onclick="toggleSidebar()"></div>
<!-- Sidebar -->
{}
<!-- Main content -->
<div class="flex-1 flex flex-col overflow-hidden min-w-0">
<!-- Header -->
{}
<!-- Content -->
<main class="flex-1 overflow-y-auto p-4 md:p-6">
{}
</main>
</div>
</div>
<script>
function toggleSidebar() {{
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebar-overlay');
const isOpen = !sidebar.classList.contains('-translate-x-full');
if (isOpen) {{
sidebar.classList.add('-translate-x-full');
overlay.classList.add('hidden');
document.body.classList.remove('sidebar-open');
}} else {{
sidebar.classList.remove('-translate-x-full');
overlay.classList.remove('hidden');
document.body.classList.add('sidebar-open');
}}
}}
</script>
</body>
</html>"##,
html_escape(title),
sidebar(active_page),
header(),
content
)
}
/// Dark theme layout wrapper for dashboard
pub fn layout_dark(
title: &str,
@@ -485,7 +423,8 @@ pub fn render_polling_script() -> String {
"##.to_string()
}
/// Sidebar navigation component
/// Sidebar navigation component (light theme, unused)
#[allow(dead_code)]
fn sidebar(active_page: Option<&str>) -> String {
let active = active_page.unwrap_or("");
@@ -578,7 +517,8 @@ fn sidebar(active_page: Option<&str>) -> String {
)
}
/// Header component
/// Header component (light theme, unused)
#[allow(dead_code)]
fn header() -> String {
r##"
<header class="h-16 bg-white border-b border-slate-200 flex items-center justify-between px-4 md:px-6">

View File

@@ -155,12 +155,12 @@ pub fn render_registry_list(registry_type: &str, title: &str, repos: &[RepoInfo]
format!("/ui/{}/{}", registry_type, encode_uri_component(&repo.name));
format!(
r##"
<tr class="hover:bg-slate-50 cursor-pointer" onclick="window.location='{}'">
<tr class="hover:bg-slate-700 cursor-pointer" onclick="window.location='{}'">
<td class="px-6 py-4">
<a href="{}" class="text-blue-600 hover:text-blue-800 font-medium">{}</a>
<a href="{}" class="text-blue-400 hover:text-blue-300 font-medium">{}</a>
</td>
<td class="px-6 py-4 text-slate-600">{}</td>
<td class="px-6 py-4 text-slate-600">{}</td>
<td class="px-6 py-4 text-slate-400">{}</td>
<td class="px-6 py-4 text-slate-400">{}</td>
<td class="px-6 py-4 text-slate-500 text-sm">{}</td>
</tr>
"##,
@@ -186,9 +186,9 @@ pub fn render_registry_list(registry_type: &str, title: &str, repos: &[RepoInfo]
r##"
<div class="mb-6 flex items-center justify-between">
<div class="flex items-center">
<svg class="w-10 h-10 mr-3 text-slate-600" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<svg class="w-10 h-10 mr-3 text-slate-400" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<div>
<h1 class="text-2xl font-bold text-slate-800">{}</h1>
<h1 class="text-2xl font-bold text-slate-200">{}</h1>
<p class="text-slate-500">{} repositories</p>
</div>
</div>
@@ -196,29 +196,29 @@ pub fn render_registry_list(registry_type: &str, title: &str, repos: &[RepoInfo]
<div class="relative">
<input type="text"
placeholder="Search repositories..."
class="pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
class="pl-10 pr-4 py-2 bg-slate-800 border border-slate-600 text-slate-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-slate-500"
hx-get="/api/ui/{}/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#repo-table-body"
name="q">
<svg class="absolute left-3 top-2.5 h-5 w-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg class="absolute left-3 top-2.5 h-5 w-5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden">
<div class="bg-[#1e293b] rounded-lg shadow-sm border border-slate-700 overflow-hidden">
<table class="w-full">
<thead class="bg-slate-50 border-b border-slate-200">
<thead class="bg-slate-800 border-b border-slate-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Name</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">{}</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Updated</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Name</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">{}</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Updated</th>
</tr>
</thead>
<tbody id="repo-table-body" class="divide-y divide-slate-200">
<tbody id="repo-table-body" class="divide-y divide-slate-700">
{}
</tbody>
</table>
@@ -232,7 +232,7 @@ pub fn render_registry_list(registry_type: &str, title: &str, repos: &[RepoInfo]
table_rows
);
layout(title, &content, Some(registry_type))
layout_dark(title, &content, Some(registry_type), "")
}
/// Renders Docker image detail page
@@ -246,11 +246,11 @@ pub fn render_docker_detail(name: &str, detail: &DockerDetail) -> String {
.map(|tag| {
format!(
r##"
<tr class="hover:bg-slate-50">
<tr class="hover:bg-slate-700">
<td class="px-6 py-4">
<span class="font-mono text-sm bg-slate-100 px-2 py-1 rounded">{}</span>
<span class="font-mono text-sm bg-slate-700 text-slate-200 px-2 py-1 rounded">{}</span>
</td>
<td class="px-6 py-4 text-slate-600">{}</td>
<td class="px-6 py-4 text-slate-400">{}</td>
<td class="px-6 py-4 text-slate-500 text-sm">{}</td>
</tr>
"##,
@@ -269,18 +269,18 @@ pub fn render_docker_detail(name: &str, detail: &DockerDetail) -> String {
r##"
<div class="mb-6">
<div class="flex items-center mb-2">
<a href="/ui/docker" class="text-blue-600 hover:text-blue-800">Docker Registry</a>
<span class="mx-2 text-slate-400">/</span>
<span class="text-slate-800 font-medium">{}</span>
<a href="/ui/docker" class="text-blue-400 hover:text-blue-300">Docker Registry</a>
<span class="mx-2 text-slate-500">/</span>
<span class="text-slate-200 font-medium">{}</span>
</div>
<div class="flex items-center">
<svg class="w-10 h-10 mr-3 text-slate-600" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<h1 class="text-2xl font-bold text-slate-800">{}</h1>
<svg class="w-10 h-10 mr-3 text-slate-400" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<h1 class="text-2xl font-bold text-slate-200">{}</h1>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-slate-200 p-6 mb-6">
<h2 class="text-lg font-semibold text-slate-800 mb-3">Pull Command</h2>
<div class="bg-[#1e293b] rounded-lg shadow-sm border border-slate-700 p-6 mb-6">
<h2 class="text-lg font-semibold text-slate-200 mb-3">Pull Command</h2>
<div class="flex items-center bg-slate-900 text-green-400 rounded-lg p-4 font-mono text-sm">
<code class="flex-1">{}</code>
<button onclick="navigator.clipboard.writeText('{}')" class="ml-4 text-slate-400 hover:text-white transition-colors" title="Copy to clipboard">
@@ -291,19 +291,19 @@ pub fn render_docker_detail(name: &str, detail: &DockerDetail) -> String {
</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-200">
<h2 class="text-lg font-semibold text-slate-800">Tags ({} total)</h2>
<div class="bg-[#1e293b] rounded-lg shadow-sm border border-slate-700 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-700">
<h2 class="text-lg font-semibold text-slate-200">Tags ({} total)</h2>
</div>
<table class="w-full">
<thead class="bg-slate-50 border-b border-slate-200">
<thead class="bg-slate-800 border-b border-slate-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Tag</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Created</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Tag</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Created</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200">
<tbody class="divide-y divide-slate-700">
{}
</tbody>
</table>
@@ -318,7 +318,7 @@ pub fn render_docker_detail(name: &str, detail: &DockerDetail) -> String {
tags_rows
);
layout(&format!("{} - Docker", name), &content, Some("docker"))
layout_dark(&format!("{} - Docker", name), &content, Some("docker"), "")
}
/// Renders package detail page (npm, cargo, pypi)
@@ -335,11 +335,11 @@ pub fn render_package_detail(registry_type: &str, name: &str, detail: &PackageDe
.map(|v| {
format!(
r##"
<tr class="hover:bg-slate-50">
<tr class="hover:bg-slate-700">
<td class="px-6 py-4">
<span class="font-mono text-sm bg-slate-100 px-2 py-1 rounded">{}</span>
<span class="font-mono text-sm bg-slate-700 text-slate-200 px-2 py-1 rounded">{}</span>
</td>
<td class="px-6 py-4 text-slate-600">{}</td>
<td class="px-6 py-4 text-slate-400">{}</td>
<td class="px-6 py-4 text-slate-500 text-sm">{}</td>
</tr>
"##,
@@ -366,18 +366,18 @@ pub fn render_package_detail(registry_type: &str, name: &str, detail: &PackageDe
r##"
<div class="mb-6">
<div class="flex items-center mb-2">
<a href="/ui/{}" class="text-blue-600 hover:text-blue-800">{}</a>
<span class="mx-2 text-slate-400">/</span>
<span class="text-slate-800 font-medium">{}</span>
<a href="/ui/{}" class="text-blue-400 hover:text-blue-300">{}</a>
<span class="mx-2 text-slate-500">/</span>
<span class="text-slate-200 font-medium">{}</span>
</div>
<div class="flex items-center">
<svg class="w-10 h-10 mr-3 text-slate-600" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<h1 class="text-2xl font-bold text-slate-800">{}</h1>
<svg class="w-10 h-10 mr-3 text-slate-400" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<h1 class="text-2xl font-bold text-slate-200">{}</h1>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-slate-200 p-6 mb-6">
<h2 class="text-lg font-semibold text-slate-800 mb-3">Install Command</h2>
<div class="bg-[#1e293b] rounded-lg shadow-sm border border-slate-700 p-6 mb-6">
<h2 class="text-lg font-semibold text-slate-200 mb-3">Install Command</h2>
<div class="flex items-center bg-slate-900 text-green-400 rounded-lg p-4 font-mono text-sm">
<code class="flex-1">{}</code>
<button onclick="navigator.clipboard.writeText('{}')" class="ml-4 text-slate-400 hover:text-white transition-colors" title="Copy to clipboard">
@@ -388,19 +388,19 @@ pub fn render_package_detail(registry_type: &str, name: &str, detail: &PackageDe
</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-200">
<h2 class="text-lg font-semibold text-slate-800">Versions ({} total)</h2>
<div class="bg-[#1e293b] rounded-lg shadow-sm border border-slate-700 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-700">
<h2 class="text-lg font-semibold text-slate-200">Versions ({} total)</h2>
</div>
<table class="w-full">
<thead class="bg-slate-50 border-b border-slate-200">
<thead class="bg-slate-800 border-b border-slate-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Version</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Published</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Version</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Published</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200">
<tbody class="divide-y divide-slate-700">
{}
</tbody>
</table>
@@ -417,10 +417,11 @@ pub fn render_package_detail(registry_type: &str, name: &str, detail: &PackageDe
versions_rows
);
layout(
layout_dark(
&format!("{} - {}", name, registry_title),
&content,
Some(registry_type),
"",
)
}
@@ -432,11 +433,11 @@ pub fn render_maven_detail(path: &str, detail: &MavenDetail) -> String {
detail.artifacts.iter().map(|a| {
let download_url = format!("/maven2/{}/{}", path, a.filename);
format!(r##"
<tr class="hover:bg-slate-50">
<tr class="hover:bg-slate-700">
<td class="px-6 py-4">
<a href="{}" class="text-blue-600 hover:text-blue-800 font-mono text-sm">{}</a>
<a href="{}" class="text-blue-400 hover:text-blue-300 font-mono text-sm">{}</a>
</td>
<td class="px-6 py-4 text-slate-600">{}</td>
<td class="px-6 py-4 text-slate-400">{}</td>
</tr>
"##, download_url, html_escape(&a.filename), format_size(a.size))
}).collect::<Vec<_>>().join("")
@@ -465,33 +466,33 @@ pub fn render_maven_detail(path: &str, detail: &MavenDetail) -> String {
r##"
<div class="mb-6">
<div class="flex items-center mb-2">
<a href="/ui/maven" class="text-blue-600 hover:text-blue-800">Maven Repository</a>
<span class="mx-2 text-slate-400">/</span>
<span class="text-slate-800 font-medium">{}</span>
<a href="/ui/maven" class="text-blue-400 hover:text-blue-300">Maven Repository</a>
<span class="mx-2 text-slate-500">/</span>
<span class="text-slate-200 font-medium">{}</span>
</div>
<div class="flex items-center">
<svg class="w-10 h-10 mr-3 text-slate-600" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<h1 class="text-2xl font-bold text-slate-800">{}</h1>
<svg class="w-10 h-10 mr-3 text-slate-400" fill="currentColor" viewBox="0 0 24 24">{}</svg>
<h1 class="text-2xl font-bold text-slate-200">{}</h1>
</div>
</div>
<div class="bg-white rounded-lg shadow-sm border border-slate-200 p-6 mb-6">
<h2 class="text-lg font-semibold text-slate-800 mb-3">Maven Dependency</h2>
<div class="bg-[#1e293b] rounded-lg shadow-sm border border-slate-700 p-6 mb-6">
<h2 class="text-lg font-semibold text-slate-200 mb-3">Maven Dependency</h2>
<pre class="bg-slate-900 text-green-400 rounded-lg p-4 font-mono text-sm overflow-x-auto">{}</pre>
</div>
<div class="bg-white rounded-lg shadow-sm border border-slate-200 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-200">
<h2 class="text-lg font-semibold text-slate-800">Artifacts ({} files)</h2>
<div class="bg-[#1e293b] rounded-lg shadow-sm border border-slate-700 overflow-hidden">
<div class="px-6 py-4 border-b border-slate-700">
<h2 class="text-lg font-semibold text-slate-200">Artifacts ({} files)</h2>
</div>
<table class="w-full">
<thead class="bg-slate-50 border-b border-slate-200">
<thead class="bg-slate-800 border-b border-slate-700">
<tr>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Filename</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-600 uppercase tracking-wider">Size</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Filename</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-slate-400 uppercase tracking-wider">Size</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200">
<tbody class="divide-y divide-slate-700">
{}
</tbody>
</table>
@@ -505,7 +506,7 @@ pub fn render_maven_detail(path: &str, detail: &MavenDetail) -> String {
artifact_rows
);
layout(&format!("{} - Maven", path), &content, Some("maven"))
layout_dark(&format!("{} - Maven", path), &content, Some("maven"), "")
}
/// Returns SVG icon path for the registry type