269 lines
8 KiB
TypeScript
269 lines
8 KiB
TypeScript
import { Component, signal, output, input } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
|
|
interface NavItem {
|
|
id: string;
|
|
label: string;
|
|
icon: string;
|
|
route: string;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-sidebar',
|
|
standalone: true,
|
|
imports: [CommonModule],
|
|
template: `
|
|
<aside class="sidebar" [class.collapsed]="collapsed()">
|
|
<!-- Logo / Brand -->
|
|
<div class="sidebar-header">
|
|
<div class="logo">
|
|
<svg class="w-8 h-8 text-accent-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
|
|
</svg>
|
|
@if (!collapsed()) {
|
|
<span class="logo-text">Mining</span>
|
|
}
|
|
</div>
|
|
<button class="collapse-btn" (click)="toggleCollapse()">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
@if (collapsed()) {
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7"/>
|
|
} @else {
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7"/>
|
|
}
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<nav class="sidebar-nav">
|
|
@for (item of navItems; track item.id) {
|
|
<button
|
|
class="nav-item"
|
|
[class.active]="currentRoute() === item.route"
|
|
(click)="navigate(item.route)"
|
|
[title]="collapsed() ? item.label : ''">
|
|
<span class="nav-icon" [innerHTML]="item.icon"></span>
|
|
@if (!collapsed()) {
|
|
<span class="nav-label">{{ item.label }}</span>
|
|
}
|
|
</button>
|
|
}
|
|
</nav>
|
|
|
|
<!-- Footer with miner switcher placeholder -->
|
|
<div class="sidebar-footer">
|
|
@if (!collapsed()) {
|
|
<div class="miner-status">
|
|
<div class="status-indicator online"></div>
|
|
<span class="status-text">Mining Active</span>
|
|
</div>
|
|
} @else {
|
|
<div class="status-indicator online mx-auto"></div>
|
|
}
|
|
</div>
|
|
</aside>
|
|
`,
|
|
styles: [`
|
|
.sidebar {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: var(--spacing-sidebar-expanded, 200px);
|
|
height: 100vh;
|
|
background: var(--color-surface-200);
|
|
border-right: 1px solid rgb(37 37 66 / 0.2);
|
|
transition: width 0.2s ease;
|
|
}
|
|
|
|
.sidebar.collapsed {
|
|
width: var(--spacing-sidebar, 56px);
|
|
}
|
|
|
|
.sidebar-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 1rem;
|
|
border-bottom: 1px solid rgb(37 37 66 / 0.2);
|
|
}
|
|
|
|
.logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
.logo-text {
|
|
font-size: 1.125rem;
|
|
font-weight: 600;
|
|
color: white;
|
|
}
|
|
|
|
.collapse-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 28px;
|
|
height: 28px;
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 0.375rem;
|
|
color: #94a3b8;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.collapse-btn:hover {
|
|
background: rgb(37 37 66 / 0.5);
|
|
color: white;
|
|
}
|
|
|
|
.collapsed .collapse-btn {
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.sidebar-nav {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0.5rem;
|
|
gap: 0.25rem;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.625rem 0.75rem;
|
|
border-radius: 0.5rem;
|
|
border: none;
|
|
background: transparent;
|
|
color: #94a3b8;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
width: 100%;
|
|
text-align: left;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
color: white;
|
|
background: rgb(37 37 66 / 0.5);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: rgb(0 212 255 / 0.1);
|
|
color: var(--color-accent-400);
|
|
border-left: 2px solid var(--color-accent-500);
|
|
}
|
|
|
|
.collapsed .nav-item {
|
|
justify-content: center;
|
|
padding: 0.625rem;
|
|
}
|
|
|
|
.nav-icon {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 20px;
|
|
height: 20px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.nav-icon :deep(svg) {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.sidebar-footer {
|
|
padding: 1rem;
|
|
border-top: 1px solid rgb(37 37 66 / 0.2);
|
|
}
|
|
|
|
.miner-status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.status-indicator {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 9999px;
|
|
}
|
|
|
|
.status-indicator.online {
|
|
background: var(--color-success-500);
|
|
box-shadow: 0 0 8px var(--color-success-500);
|
|
}
|
|
|
|
.status-text {
|
|
font-size: 0.75rem;
|
|
color: #94a3b8;
|
|
}
|
|
`]
|
|
})
|
|
export class SidebarComponent {
|
|
collapsed = signal(false);
|
|
currentRoute = input<string>('workers');
|
|
routeChange = output<string>();
|
|
|
|
navItems: NavItem[] = [
|
|
{
|
|
id: 'workers',
|
|
label: 'Workers',
|
|
route: 'workers',
|
|
icon: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/></svg>'
|
|
},
|
|
{
|
|
id: 'graphs',
|
|
label: 'Graphs',
|
|
route: 'graphs',
|
|
icon: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>'
|
|
},
|
|
{
|
|
id: 'console',
|
|
label: 'Console',
|
|
route: 'console',
|
|
icon: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>'
|
|
},
|
|
{
|
|
id: 'pools',
|
|
label: 'Pools',
|
|
route: 'pools',
|
|
icon: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/></svg>'
|
|
},
|
|
{
|
|
id: 'profiles',
|
|
label: 'Profiles',
|
|
route: 'profiles',
|
|
icon: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>'
|
|
},
|
|
{
|
|
id: 'miners',
|
|
label: 'Miners',
|
|
route: 'miners',
|
|
icon: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/></svg>'
|
|
},
|
|
{
|
|
id: 'nodes',
|
|
label: 'Nodes',
|
|
route: 'nodes',
|
|
icon: '<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01M9 12h.01M12 12h.01M15 12h.01"/></svg>'
|
|
}
|
|
];
|
|
|
|
toggleCollapse() {
|
|
this.collapsed.update(v => !v);
|
|
}
|
|
|
|
navigate(route: string) {
|
|
this.routeChange.emit(route);
|
|
}
|
|
}
|