mirror of
https://github.com/Cleanuparr/Cleanuparr.git
synced 2026-06-12 07:45:49 -04:00
This commit is contained in:
@@ -10,5 +10,5 @@ import { MainLayoutComponent } from './layout/main-layout/main-layout.component'
|
||||
styleUrl: './app.component.scss'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'CleanupErr';
|
||||
title = 'Cleanuparr';
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import Aura from '@primeng/themes/aura';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideServiceWorker } from '@angular/service-worker';
|
||||
import Noir from './app.preset';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
@@ -16,7 +17,7 @@ export const appConfig: ApplicationConfig = {
|
||||
provideAnimationsAsync(),
|
||||
providePrimeNG({
|
||||
theme: {
|
||||
preset: Aura
|
||||
preset: Noir
|
||||
}
|
||||
}),
|
||||
provideServiceWorker('ngsw-worker.js', {
|
||||
|
||||
53
code/UI/src/app/app.preset.ts
Normal file
53
code/UI/src/app/app.preset.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { definePreset } from '@primeng/themes';
|
||||
import Aura from '@primeng/themes/aura';
|
||||
|
||||
const Noir = definePreset(Aura, {
|
||||
semantic: {
|
||||
// primary: {
|
||||
// 50: '{violet.50}',
|
||||
// 100: '{violet.100}',
|
||||
// 200: '{violet.200}',
|
||||
// 300: '{violet.300}',
|
||||
// 400: '{violet.400}',
|
||||
// 500: '{violet.500}',
|
||||
// 600: '{violet.600}',
|
||||
// 700: '{violet.700}',
|
||||
// 800: '{violet.800}',
|
||||
// 900: '{violet.900}',
|
||||
// 950: '{violet.950}'
|
||||
// },
|
||||
// colorScheme: {
|
||||
// light: {
|
||||
// primary: {
|
||||
// color: '{violet.950}',
|
||||
// inverseColor: '#ffffff',
|
||||
// hoverColor: '{violet.900}',
|
||||
// activeColor: '{violet.800}'
|
||||
// },
|
||||
// highlight: {
|
||||
// background: '{violet.950}',
|
||||
// focusBackground: '{violet.700}',
|
||||
// color: '#ffffff',
|
||||
// focusColor: '#ffffff'
|
||||
// }
|
||||
// },
|
||||
// dark: {
|
||||
// primary: {
|
||||
// color: '{violet.50}',
|
||||
// inverseColor: '{violet.950}',
|
||||
// hoverColor: '{violet.100}',
|
||||
// activeColor: '{violet.200}'
|
||||
// },
|
||||
// highlight: {
|
||||
// background: 'rgba(250, 250, 250, .16)',
|
||||
// focusBackground: 'rgba(250, 250, 250, .24)',
|
||||
// color: 'rgba(255,255,255,.87)',
|
||||
// focusColor: 'rgba(255,255,255,.87)'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default Noir;
|
||||
@@ -1,33 +1,190 @@
|
||||
<div class="dashboard-container">
|
||||
<h1>Dashboard</h1>
|
||||
<div class="dashboard-container content-section">
|
||||
<div class="flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="m-0">Dashboard</h1>
|
||||
<p-button icon="pi pi-sync" styleClass="p-button-text" label="Refresh" (onClick)="refreshDashboard()"></p-button>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Status Overview -->
|
||||
<div class="grid grid-container">
|
||||
<div class="col-12 md:col-6 xl:col-3">
|
||||
<div class="overview-box bg-primary">
|
||||
<div class="overview-icon">
|
||||
<i class="pi pi-bolt"></i>
|
||||
</div>
|
||||
<div class="overview-data">
|
||||
<div class="overview-value">System Status</div>
|
||||
<div class="overview-label">Operational</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 xl:col-3">
|
||||
<div class="overview-box bg-success">
|
||||
<div class="overview-icon">
|
||||
<i class="pi pi-server"></i>
|
||||
</div>
|
||||
<div class="overview-data">
|
||||
<div class="overview-value">Services</div>
|
||||
<div class="overview-label">All Running</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 xl:col-3">
|
||||
<div class="overview-box bg-warning">
|
||||
<div class="overview-icon">
|
||||
<i class="pi pi-exclamation-triangle"></i>
|
||||
</div>
|
||||
<div class="overview-data">
|
||||
<div class="overview-value">Warnings</div>
|
||||
<div class="overview-label">0 Warnings</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 xl:col-3">
|
||||
<div class="overview-box bg-danger">
|
||||
<div class="overview-icon">
|
||||
<i class="pi pi-times-circle"></i>
|
||||
</div>
|
||||
<div class="overview-data">
|
||||
<div class="overview-value">Errors</div>
|
||||
<div class="overview-label">0 Errors</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Cards -->
|
||||
<div class="grid grid-container">
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<p-card header="Logs Summary" subheader="Recent logging activity">
|
||||
<p-card styleClass="dashboard-card">
|
||||
<ng-template pTemplate="header">
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div>
|
||||
<h2 class="card-title m-0">Logs Summary</h2>
|
||||
<span class="card-subtitle">Recent logging activity</span>
|
||||
</div>
|
||||
<i class="pi pi-list text-xl"></i>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="card-content">
|
||||
<p>Monitor your application logs and error statuses.</p>
|
||||
<div class="mb-3">
|
||||
<div class="flex align-items-center mb-2">
|
||||
<i class="pi pi-check-circle text-success mr-2"></i>
|
||||
<span class="font-medium">Info Logs</span>
|
||||
<span class="ml-auto font-medium">24</span>
|
||||
</div>
|
||||
<p-progressBar [value]="30" styleClass="mb-2" [style]="{'height': '8px'}"></p-progressBar>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="flex align-items-center mb-2">
|
||||
<i class="pi pi-exclamation-triangle text-warning mr-2"></i>
|
||||
<span class="font-medium">Warning Logs</span>
|
||||
<span class="ml-auto font-medium">10</span>
|
||||
</div>
|
||||
<p-progressBar [value]="12" styleClass="mb-2" [style]="{'height': '8px'}" [styleClass]="'warning-progress'"></p-progressBar>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="flex align-items-center mb-2">
|
||||
<i class="pi pi-times-circle text-danger mr-2"></i>
|
||||
<span class="font-medium">Error Logs</span>
|
||||
<span class="ml-auto font-medium">3</span>
|
||||
</div>
|
||||
<p-progressBar [value]="5" styleClass="mb-2" [style]="{'height': '8px'}" [styleClass]="'danger-progress'"></p-progressBar>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<button pButton label="View Logs" icon="pi pi-list" routerLink="/logs"></button>
|
||||
<button pButton label="View All Logs" icon="pi pi-arrow-right" routerLink="/logs" iconPos="right"></button>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<p-card header="System Status" subheader="Current health status">
|
||||
<p-card styleClass="dashboard-card">
|
||||
<ng-template pTemplate="header">
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div>
|
||||
<h2 class="card-title m-0">System Status</h2>
|
||||
<span class="card-subtitle">Current health status</span>
|
||||
</div>
|
||||
<i class="pi pi-shield text-xl"></i>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="card-content">
|
||||
<p>All systems operational</p>
|
||||
<div class="status-item mb-3 flex align-items-center">
|
||||
<span class="status-indicator active mr-2"></span>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">API Service</div>
|
||||
<div class="text-sm text-color-secondary">Running since 2 days</div>
|
||||
</div>
|
||||
<p-tag severity="success" value="Active"></p-tag>
|
||||
</div>
|
||||
|
||||
<div class="status-item mb-3 flex align-items-center">
|
||||
<span class="status-indicator active mr-2"></span>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">Log Hub</div>
|
||||
<div class="text-sm text-color-secondary">Connected</div>
|
||||
</div>
|
||||
<p-tag severity="success" value="Active"></p-tag>
|
||||
</div>
|
||||
|
||||
<div class="status-item mb-3 flex align-items-center">
|
||||
<span class="status-indicator active mr-2"></span>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">Database</div>
|
||||
<div class="text-sm text-color-secondary">Operational</div>
|
||||
</div>
|
||||
<p-tag severity="success" value="Active"></p-tag>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<button pButton label="Details" icon="pi pi-server" class="p-button-outlined"></button>
|
||||
<button pButton label="System Details" icon="pi pi-server" class="p-button-outlined"></button>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
<div class="col-12 md:col-6 lg:col-4">
|
||||
<p-card header="Available Updates" subheader="System updates">
|
||||
<p-card styleClass="dashboard-card">
|
||||
<ng-template pTemplate="header">
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div>
|
||||
<h2 class="card-title m-0">Updates & Info</h2>
|
||||
<span class="card-subtitle">System updates</span>
|
||||
</div>
|
||||
<i class="pi pi-info-circle text-xl"></i>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="card-content">
|
||||
<p>Your application is up to date.</p>
|
||||
<div class="update-item mb-3 p-2 border-round surface-hover flex align-items-center">
|
||||
<i class="pi pi-check-circle text-success mr-2"></i>
|
||||
<div>
|
||||
<div class="font-medium">System up to date</div>
|
||||
<div class="text-sm text-color-secondary">Latest version installed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-item mb-3 p-2 border-round surface-hover flex align-items-center">
|
||||
<i class="pi pi-calendar mr-2"></i>
|
||||
<div>
|
||||
<div class="font-medium">Last scan</div>
|
||||
<div class="text-sm text-color-secondary">Today at 10:30 AM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="update-item mb-3 p-2 border-round surface-hover flex align-items-center">
|
||||
<i class="pi pi-history mr-2"></i>
|
||||
<div>
|
||||
<div class="font-medium">Next scan scheduled</div>
|
||||
<div class="text-sm text-color-secondary">Tomorrow at 02:00 AM</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-footer">
|
||||
<button pButton label="Check for Updates" icon="pi pi-refresh" class="p-button-secondary"></button>
|
||||
</div>
|
||||
@@ -35,4 +192,28 @@
|
||||
</p-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity Section -->
|
||||
<div class="grid">
|
||||
<div class="col-12">
|
||||
<p-card styleClass="dashboard-card" header="Recent Activity">
|
||||
<p-timeline [value]="activityItems" styleClass="activity-timeline">
|
||||
<ng-template pTemplate="content" let-item>
|
||||
<div class="activity-item p-2">
|
||||
<div class="font-medium">{{item.title}}</div>
|
||||
<div class="text-sm text-color-secondary">{{item.description}}</div>
|
||||
<div class="text-xs mt-2">{{item.time}}</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="opposite" let-item>
|
||||
<div class="activity-icon-container">
|
||||
<span class="activity-icon" [ngClass]="item.iconClass">
|
||||
<i [class]="item.icon"></i>
|
||||
</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-timeline>
|
||||
</p-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,37 +1,259 @@
|
||||
.dashboard-container {
|
||||
padding: 1.5rem;
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 600;
|
||||
padding: 0;
|
||||
|
||||
.grid-container {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Overview Boxes */
|
||||
.overview-box {
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: white;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
height: 100%;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.overview-icon {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.overview-data {
|
||||
flex: 1;
|
||||
|
||||
.overview-value {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.overview-label {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
&.bg-primary {
|
||||
background-color: var(--primary-color);
|
||||
background-image: linear-gradient(30deg, var(--primary-600), var(--primary-400));
|
||||
}
|
||||
|
||||
&.bg-success {
|
||||
background-color: var(--green-500);
|
||||
background-image: linear-gradient(30deg, var(--green-600), var(--green-400));
|
||||
}
|
||||
|
||||
&.bg-warning {
|
||||
background-color: var(--yellow-500);
|
||||
background-image: linear-gradient(30deg, var(--yellow-600), var(--yellow-400));
|
||||
color: var(--yellow-900);
|
||||
}
|
||||
|
||||
&.bg-danger {
|
||||
background-color: var(--red-500);
|
||||
background-image: linear-gradient(30deg, var(--red-600), var(--red-400));
|
||||
}
|
||||
}
|
||||
|
||||
/* Card styling */
|
||||
::ng-deep {
|
||||
.dashboard-card {
|
||||
height: 100%;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 8px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.p-card-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
color: var(--text-color-secondary);
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.p-card-body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.p-card-content {
|
||||
flex-grow: 1;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Progress bar styling */
|
||||
.p-progressbar {
|
||||
height: 8px;
|
||||
background-color: var(--surface-200);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
|
||||
.p-progressbar-value {
|
||||
background-color: var(--primary-color);
|
||||
transition: width 0.5s ease;
|
||||
}
|
||||
|
||||
&.warning-progress .p-progressbar-value {
|
||||
background-color: var(--yellow-500);
|
||||
}
|
||||
|
||||
&.danger-progress .p-progressbar-value {
|
||||
background-color: var(--red-500);
|
||||
}
|
||||
}
|
||||
|
||||
/* Timeline customizations */
|
||||
.activity-timeline {
|
||||
.p-timeline-event-opposite {
|
||||
flex: 0;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.p-timeline-event-content {
|
||||
padding: 0 1rem 1.5rem 1rem;
|
||||
}
|
||||
|
||||
.activity-icon-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
.activity-icon {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
|
||||
&.bg-primary {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&.bg-success {
|
||||
background-color: var(--green-500);
|
||||
}
|
||||
|
||||
&.bg-info {
|
||||
background-color: var(--blue-500);
|
||||
}
|
||||
|
||||
&.bg-warning {
|
||||
background-color: var(--yellow-500);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
background-color: var(--surface-card);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateX(3px);
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Status indicators */
|
||||
.status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
|
||||
&.active {
|
||||
background-color: var(--green-500);
|
||||
box-shadow: 0 0 0 3px rgba(80, 200, 120, 0.2);
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
background-color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: var(--yellow-500);
|
||||
box-shadow: 0 0 0 3px rgba(255, 213, 79, 0.2);
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: var(--red-500);
|
||||
box-shadow: 0 0 0 3px rgba(255, 86, 48, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Card content styling */
|
||||
.card-content {
|
||||
min-height: 100px;
|
||||
min-height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
margin-top: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
::ng-deep .p-card {
|
||||
height: 100%;
|
||||
|
||||
.p-card-body {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Responsive adjustments */
|
||||
@media screen and (max-width: 768px) {
|
||||
.overview-box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.p-card-content {
|
||||
flex-grow: 1;
|
||||
padding-bottom: 0;
|
||||
::ng-deep .activity-timeline {
|
||||
.p-timeline-event {
|
||||
flex-direction: column !important;
|
||||
|
||||
.p-timeline-event-opposite {
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.p-timeline-event-content {
|
||||
padding: 0 0 1.5rem 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,86 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule, NgClass } from '@angular/common';
|
||||
import { RouterLink } from '@angular/router';
|
||||
|
||||
// PrimeNG Components
|
||||
import { CardModule } from 'primeng/card';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { ProgressBarModule } from 'primeng/progressbar';
|
||||
import { TagModule } from 'primeng/tag';
|
||||
import { TimelineModule } from 'primeng/timeline';
|
||||
|
||||
// Models
|
||||
interface ActivityItem {
|
||||
title: string;
|
||||
description: string;
|
||||
time: string;
|
||||
icon: string;
|
||||
iconClass: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard-page',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgClass,
|
||||
RouterLink,
|
||||
CardModule,
|
||||
ButtonModule
|
||||
ButtonModule,
|
||||
ProgressBarModule,
|
||||
TagModule,
|
||||
TimelineModule
|
||||
],
|
||||
templateUrl: './dashboard-page.component.html',
|
||||
styleUrl: './dashboard-page.component.scss'
|
||||
})
|
||||
export class DashboardPageComponent {
|
||||
export class DashboardPageComponent implements OnInit {
|
||||
// Sample activity items for the timeline
|
||||
activityItems: ActivityItem[] = [];
|
||||
|
||||
ngOnInit() {
|
||||
// Initialize dashboard data
|
||||
this.initializeActivityData();
|
||||
}
|
||||
|
||||
refreshDashboard() {
|
||||
console.log('Refreshing dashboard data...');
|
||||
// Here you would normally fetch new data
|
||||
// For now, we'll just reinitialize the demo data
|
||||
this.initializeActivityData();
|
||||
}
|
||||
|
||||
private initializeActivityData() {
|
||||
// Sample activity data
|
||||
this.activityItems = [
|
||||
{
|
||||
title: 'System started',
|
||||
description: 'Application services initialized successfully',
|
||||
time: '10 minutes ago',
|
||||
icon: 'pi pi-power-off',
|
||||
iconClass: 'bg-primary'
|
||||
},
|
||||
{
|
||||
title: 'Database backup completed',
|
||||
description: 'Automatic backup task executed successfully',
|
||||
time: '2 hours ago',
|
||||
icon: 'pi pi-database',
|
||||
iconClass: 'bg-success'
|
||||
},
|
||||
{
|
||||
title: 'Configuration updated',
|
||||
description: 'System configuration changes applied',
|
||||
time: 'Yesterday, 14:23',
|
||||
icon: 'pi pi-cog',
|
||||
iconClass: 'bg-info'
|
||||
},
|
||||
{
|
||||
title: 'System update available',
|
||||
description: 'New version 1.2.5 is available for installation',
|
||||
time: '2 days ago',
|
||||
icon: 'pi pi-download',
|
||||
iconClass: 'bg-warning'
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
aria-label="Toggle menu">
|
||||
</button>
|
||||
<div class="logo-container">
|
||||
<span class="app-name">CleanupErr</span>
|
||||
<span class="app-name">Cleanuparr</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-toolbar-group-end">
|
||||
@@ -25,7 +25,7 @@
|
||||
<p-sidebar [visible]="mobileSidebarVisible()" (visibleChange)="mobileSidebarVisible.set($event)" position="left" styleClass="mobile-sidebar">
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-header">
|
||||
<h3>CleanupErr</h3>
|
||||
<h3>Cleanuparr</h3>
|
||||
</div>
|
||||
<div class="sidebar-menu">
|
||||
<ul class="menu-list">
|
||||
@@ -47,7 +47,7 @@
|
||||
<div class="sidebar">
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-logo">
|
||||
<span class="app-logo">CE</span>
|
||||
<span class="app-logo">Logo</span>
|
||||
</div>
|
||||
<ul class="menu-list">
|
||||
<li *ngFor="let item of menuItems" class="menu-item">
|
||||
|
||||
@@ -1,31 +1,40 @@
|
||||
<div class="logs-container">
|
||||
<p-card *ngIf="!isConnected()" styleClass="mb-3">
|
||||
<!-- Connection Status Card - only shown when disconnected -->
|
||||
<p-card *ngIf="!isConnected()" styleClass="mb-3 connection-status-card">
|
||||
<div class="flex flex-column align-items-center gap-3 py-5">
|
||||
<p-progressSpinner styleClass="w-4rem h-4rem" strokeWidth="4" fill="var(--surface-ground)" animationDuration=".5s"></p-progressSpinner>
|
||||
<span class="text-xl font-medium">Connecting to server...</span>
|
||||
</div>
|
||||
</p-card>
|
||||
<p-card>
|
||||
|
||||
<!-- Main Logs Card -->
|
||||
<p-card styleClass="logs-card">
|
||||
<!-- Card Header -->
|
||||
<ng-template pTemplate="header">
|
||||
<div class="flex align-items-center justify-content-between p-3 border-bottom-1 surface-border">
|
||||
<div class="flex align-items-center">
|
||||
<h2>Application Logs</h2>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<h2 class="m-0">Application Logs</h2>
|
||||
<p-tag [severity]="isConnected() ? 'success' : 'danger'"
|
||||
[value]="isConnected() ? 'Connected' : 'Disconnected'"></p-tag>
|
||||
[value]="isConnected() ? 'Connected' : 'Disconnected'"
|
||||
[pTooltip]="isConnected() ? 'Connected to log hub' : 'Attempting to reconnect...'"
|
||||
tooltipPosition="right"></p-tag>
|
||||
</div>
|
||||
<button pButton icon="pi pi-refresh" class="p-button-rounded p-button-text"
|
||||
(click)="refresh()" pTooltip="Refresh logs"></button>
|
||||
(click)="refresh()" pTooltip="Refresh logs"
|
||||
[loading]="false"></button>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div class="filter-container mb-3 flex align-items-center justify-content-between flex-wrap gap-2">
|
||||
<div class="flex align-items-center gap-2">
|
||||
<!-- Filters Section -->
|
||||
<div class="filter-container flex align-items-center justify-content-between flex-wrap gap-3">
|
||||
<div class="flex align-items-center gap-2 flex-wrap">
|
||||
<!-- Level Filter -->
|
||||
<p-dropdown [options]="levels()" placeholder="Filter by level"
|
||||
[showClear]="true" (onChange)="onLevelFilterChange($event.value)">
|
||||
[showClear]="true" (onChange)="onLevelFilterChange($event.value)"
|
||||
styleClass="level-dropdown" [disabled]="!isConnected()">
|
||||
<ng-template pTemplate="selectedItem">
|
||||
<div class="flex align-items-center gap-2" *ngIf="levelFilter()">
|
||||
<p-tag [severity]="getSeverity(levelFilter() || '')" [value]="levelFilter() || ''">
|
||||
</p-tag>
|
||||
<p-tag [severity]="getSeverity(levelFilter() || '')" [value]="levelFilter() || ''"></p-tag>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template let-level pTemplate="item">
|
||||
@@ -33,36 +42,45 @@
|
||||
</ng-template>
|
||||
</p-dropdown>
|
||||
|
||||
<!-- Category Filter -->
|
||||
<p-dropdown [options]="categories()" placeholder="Filter by category"
|
||||
[showClear]="true" (onChange)="onCategoryFilterChange($event.value)">
|
||||
[showClear]="true" (onChange)="onCategoryFilterChange($event.value)"
|
||||
styleClass="category-dropdown" [disabled]="!isConnected()">
|
||||
</p-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="flex align-items-center gap-2">
|
||||
<div class="flex align-items-center gap-2 flex-wrap">
|
||||
<!-- Search Filter -->
|
||||
<span class="p-input-icon-left">
|
||||
<i class="pi pi-search"></i>
|
||||
<input type="text" pInputText [(ngModel)]="searchFilter"
|
||||
(input)="onSearchChange($event)"
|
||||
placeholder="Search logs"/>
|
||||
placeholder="Search logs" [disabled]="!isConnected()"/>
|
||||
</span>
|
||||
|
||||
<!-- Clear Filters Button -->
|
||||
<button pButton icon="pi pi-filter-slash"
|
||||
label="Clear Filters"
|
||||
class="p-button-outlined"
|
||||
(click)="clearFilters()"></button>
|
||||
(click)="clearFilters()"
|
||||
[disabled]="!isConnected() || (!levelFilter() && !categoryFilter() && !searchFilter)"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs Table -->
|
||||
<p-table [value]="filteredLogs()"
|
||||
styleClass="p-datatable-sm"
|
||||
styleClass="p-datatable-sm logs-table"
|
||||
[scrollable]="true"
|
||||
scrollHeight="70vh"
|
||||
scrollHeight="calc(100vh - 260px)"
|
||||
[paginator]="true"
|
||||
[rows]="25"
|
||||
[showCurrentPageReport]="true"
|
||||
[rowsPerPageOptions]="[10, 25, 50, 100]"
|
||||
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} logs">
|
||||
currentPageReportTemplate="Showing {first} to {last} of {totalRecords} logs"
|
||||
[rowHover]="true"
|
||||
[loading]="!isConnected()">
|
||||
|
||||
<!-- Table Header -->
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th style="width: 180px">Timestamp</th>
|
||||
@@ -74,17 +92,35 @@
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<!-- Table Body -->
|
||||
<ng-template pTemplate="body" let-log>
|
||||
<tr>
|
||||
<td>{{ log.timestamp | date: 'medium' }}</td>
|
||||
<!-- Log Entry Row -->
|
||||
<tr [ngClass]="{
|
||||
'error-row': log.level === 'Error' || log.level === 'Fatal' || log.level === 'Critical',
|
||||
'warning-row': log.level === 'Warning'
|
||||
}">
|
||||
<td>
|
||||
<span class="font-medium">{{ log.timestamp | date: 'yyyy-MM-dd' }}</span>
|
||||
<div class="text-sm text-color-secondary">{{ log.timestamp | date: 'HH:mm:ss' }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<p-tag [severity]="getSeverity(log.level)" [value]="log.level"></p-tag>
|
||||
</td>
|
||||
<td>{{ log.category }}</td>
|
||||
<td>{{ log.message }}</td>
|
||||
<td *ngIf="hasJobInfo()">{{ log.jobName }}</td>
|
||||
<td *ngIf="hasInstanceInfo()">{{ log.instanceName }}</td>
|
||||
<td>
|
||||
<span class="log-category">{{ log.category }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span [pTooltip]="log.exception ? 'Click to view stack trace' : undefined"
|
||||
tooltipPosition="top"
|
||||
[tooltipStyleClass]="log.exception ? 'visible' : 'invisible'">
|
||||
{{ log.message }}
|
||||
</span>
|
||||
</td>
|
||||
<td *ngIf="hasJobInfo()" class="font-medium">{{ log.jobName }}</td>
|
||||
<td *ngIf="hasInstanceInfo()" class="text-sm">{{ log.instanceName }}</td>
|
||||
</tr>
|
||||
|
||||
<!-- Exception Row -->
|
||||
<tr *ngIf="log.exception" class="exception-row">
|
||||
<td [attr.colspan]="hasJobInfo() && hasInstanceInfo() ? 6 : (hasJobInfo() || hasInstanceInfo() ? 5 : 4)" class="exception-cell">
|
||||
<div class="exception-content">
|
||||
@@ -94,15 +130,20 @@
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<!-- Empty State -->
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td [attr.colspan]="hasJobInfo() && hasInstanceInfo() ? 6 : (hasJobInfo() || hasInstanceInfo() ? 5 : 4)" class="text-center">
|
||||
<div class="p-5 text-center">
|
||||
<i class="pi pi-inbox text-5xl mb-3" style="color: var(--text-color-secondary)"></i>
|
||||
<p class="text-xl" *ngIf="isConnected(); else disconnectedMessage">No logs found. Waiting for new logs...</p>
|
||||
<td [attr.colspan]="hasJobInfo() && hasInstanceInfo() ? 6 : (hasJobInfo() || hasInstanceInfo() ? 5 : 4)">
|
||||
<div class="empty-message">
|
||||
<i class="pi pi-inbox"></i>
|
||||
<div class="empty-text" *ngIf="isConnected(); else disconnectedMessage">
|
||||
No logs found
|
||||
</div>
|
||||
<p *ngIf="isConnected()">Waiting for new logs or try adjusting your filters</p>
|
||||
<ng-template #disconnectedMessage>
|
||||
<div class="flex flex-column align-items-center gap-3">
|
||||
<p class="text-xl">Not connected to log hub. Reconnecting...</p>
|
||||
<div class="empty-text">Not connected to log hub</div>
|
||||
<p>Attempting to reconnect to the server...</p>
|
||||
<p-progressSpinner styleClass="w-3rem h-3rem" strokeWidth="4" fill="var(--surface-ground)" animationDuration=".5s"></p-progressSpinner>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -1,24 +1,48 @@
|
||||
.logs-container {
|
||||
min-height: 85vh;
|
||||
margin-bottom: 2rem;
|
||||
background-color: var(--surface-card);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--card-shadow);
|
||||
padding: 1.5rem;
|
||||
height: calc(100vh - 8rem); /* Adjust based on your layout */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
/* Filter section styling */
|
||||
.filter-container {
|
||||
padding-bottom: 1rem;
|
||||
padding: 1rem 0;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
transition: background-color var(--app-transition-speed);
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
// Table overrides
|
||||
/* Card styling - add subtle animation on hover */
|
||||
.p-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
.p-card-content {
|
||||
flex: 1;
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table styling - improved for readability */
|
||||
.p-datatable {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.p-datatable-wrapper {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.p-datatable-header {
|
||||
background-color: var(--surface-section);
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-top-right-radius: var(--border-radius);
|
||||
background-color: var(--surface-card);
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.p-datatable-thead > tr > th {
|
||||
@@ -27,31 +51,61 @@
|
||||
border-color: var(--surface-border);
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 600;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-tbody > tr {
|
||||
&:nth-child(even) {
|
||||
background-color: var(--surface-ground);
|
||||
}
|
||||
.p-datatable-tbody {
|
||||
> tr {
|
||||
transition: background-color 0.2s;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
> td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-color: var(--surface-border);
|
||||
&:nth-child(even) {
|
||||
background-color: var(--surface-ground);
|
||||
}
|
||||
|
||||
> td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-color: var(--surface-border);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
/* Highlight rows with errors */
|
||||
&.error-row > td:first-child {
|
||||
border-left: 4px solid var(--red-500);
|
||||
}
|
||||
|
||||
&.warning-row > td:first-child {
|
||||
border-left: 4px solid var(--yellow-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-paginator {
|
||||
background-color: var(--surface-section);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
border-color: var(--surface-border);
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
// Exception handling
|
||||
/* Exception handling with improved styling */
|
||||
.exception-row {
|
||||
background-color: var(--surface-hover) !important;
|
||||
margin: 0;
|
||||
transition: max-height 0.3s;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.exception-cell {
|
||||
@@ -65,15 +119,25 @@
|
||||
white-space: pre-wrap;
|
||||
font-size: 0.85rem;
|
||||
overflow: auto;
|
||||
max-height: 200px;
|
||||
max-height: 300px;
|
||||
color: var(--text-color);
|
||||
border-top: 1px dashed var(--surface-border);
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
// Tag styling
|
||||
/* Tag styling with improved visibility */
|
||||
.p-tag {
|
||||
font-weight: 600;
|
||||
border-radius: var(--border-radius);
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&.p-tag-danger {
|
||||
background-color: var(--red-500);
|
||||
@@ -96,10 +160,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Dropdowns and inputs
|
||||
/* Form controls styling */
|
||||
.p-dropdown {
|
||||
min-width: 180px;
|
||||
border-radius: var(--border-radius);
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
|
||||
&:hover, &.p-focus {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&.p-focus {
|
||||
box-shadow: 0 0 0 1px var(--primary-100);
|
||||
}
|
||||
|
||||
.p-dropdown-label {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.p-input-icon-left {
|
||||
@@ -109,12 +186,37 @@
|
||||
input {
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
transition: box-shadow 0.2s, border-color 0.2s;
|
||||
padding: 0.5rem 0.75rem 0.5rem 2rem;
|
||||
|
||||
&:hover, &:focus {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px var(--primary-100);
|
||||
}
|
||||
}
|
||||
|
||||
i {
|
||||
left: 0.75rem;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
// Button styling
|
||||
/* Button styling with micro-interactions */
|
||||
.p-button {
|
||||
border-radius: var(--border-radius);
|
||||
transition: background-color 0.2s, color 0.2s, border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
&.p-button-outlined {
|
||||
background-color: transparent;
|
||||
@@ -125,13 +227,75 @@
|
||||
background-color: var(--primary-50);
|
||||
}
|
||||
}
|
||||
|
||||
&.p-button-text {
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.p-button-rounded {
|
||||
&:hover {
|
||||
transform: translateY(-1px) rotate(15deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh button container
|
||||
.refresh-container {
|
||||
margin-bottom: 1rem;
|
||||
/* Empty state styling */
|
||||
.empty-message {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--text-color-secondary);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 300px;
|
||||
|
||||
i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media screen and (max-width: 768px) {
|
||||
.logs-container {
|
||||
height: calc(100vh - 7rem);
|
||||
|
||||
.filter-container {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
|
||||
.p-dropdown, .p-input-icon-left {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep {
|
||||
.p-datatable {
|
||||
.p-datatable-thead > tr > th {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.p-datatable-tbody > tr > td {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy, signal, computed, inject } from '@angular/core';
|
||||
import { DatePipe, NgIf } from '@angular/common';
|
||||
import { DatePipe, NgIf, NgClass } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@@ -23,6 +23,7 @@ import { LogEntry } from '../../core/models/signalr.models';
|
||||
standalone: true,
|
||||
imports: [
|
||||
NgIf,
|
||||
NgClass,
|
||||
DatePipe,
|
||||
FormsModule,
|
||||
TableModule,
|
||||
|
||||
@@ -1,41 +1,148 @@
|
||||
<div class="settings-container">
|
||||
<h1>Settings</h1>
|
||||
<div class="settings-container content-section">
|
||||
<div class="flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="m-0">Settings</h1>
|
||||
<div>
|
||||
<button pButton label="Save Changes" icon="pi pi-save" class="p-button-success mr-2" (click)="saveSettings()"></button>
|
||||
<button pButton label="Reset to Defaults" icon="pi pi-refresh" class="p-button-secondary p-button-outlined" (click)="resetToDefaults()"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-accordion [multiple]="true" [activeIndex]="[0]">
|
||||
<p-accordionTab header="API Connection Settings">
|
||||
<div class="field-row">
|
||||
<label for="apiUrl">API URL</label>
|
||||
<input id="apiUrl" type="text" pInputText [(ngModel)]="apiUrl" />
|
||||
</div>
|
||||
</p-accordionTab>
|
||||
|
||||
<p-accordionTab header="Logging Settings">
|
||||
<div class="field-row">
|
||||
<label for="enableLogs">Enable Logging</label>
|
||||
<p-inputSwitch id="enableLogs" [(ngModel)]="enableLogs"></p-inputSwitch>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="col-12 md:col-6">
|
||||
<p-card header="General Settings" styleClass="settings-card mb-4">
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">API Connection</h3>
|
||||
|
||||
<div class="field-row">
|
||||
<label for="apiUrl">API URL</label>
|
||||
<div class="field-input">
|
||||
<span class="p-input-icon-left w-full">
|
||||
<i class="pi pi-link"></i>
|
||||
<input id="apiUrl" type="text" pInputText [(ngModel)]="apiUrl" class="w-full" placeholder="https://api.example.com" />
|
||||
</span>
|
||||
<small class="form-helper-text">The base URL of the Cleanuparr API service</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<label for="apiKey">API Key</label>
|
||||
<div class="field-input">
|
||||
<div class="p-inputgroup">
|
||||
<input id="apiKey" type="password" pInputText [(ngModel)]="apiKey" class="w-full" placeholder="Enter API key" />
|
||||
<button type="button" pButton icon="pi pi-eye" class="p-button-outlined"></button>
|
||||
</div>
|
||||
<small class="form-helper-text">Required for authenticated API requests</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<label for="apiTimeout">Timeout (seconds)</label>
|
||||
<div class="field-input">
|
||||
<p-inputNumber id="apiTimeout" [(ngModel)]="apiTimeout" [min]="5" [max]="120" [showButtons]="true" suffix=" sec" buttonLayout="horizontal" spinnerMode="horizontal"
|
||||
inputStyleClass="text-right" [step]="5" decrementButtonClass="p-button-secondary" incrementButtonClass="p-button-secondary"></p-inputNumber>
|
||||
<small class="form-helper-text">Maximum time to wait for API responses</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
|
||||
<div class="field-row">
|
||||
<label for="enableNotifications">Show Log Notifications</label>
|
||||
<p-inputSwitch id="enableNotifications" [(ngModel)]="enableNotifications"></p-inputSwitch>
|
||||
</div>
|
||||
</p-accordionTab>
|
||||
<p-card header="UI Preferences" styleClass="settings-card">
|
||||
<div class="settings-section">
|
||||
<div class="field-row align-items-center">
|
||||
<label for="theme">Theme</label>
|
||||
<div class="field-input">
|
||||
<div class="flex gap-3 align-items-center">
|
||||
<p-radioButton id="themeLight" name="theme" value="light" [(ngModel)]="theme" inputId="light"></p-radioButton>
|
||||
<label for="light" class="mr-5">Light</label>
|
||||
|
||||
<p-radioButton id="themeDark" name="theme" value="dark" [(ngModel)]="theme" inputId="dark"></p-radioButton>
|
||||
<label for="dark">Dark</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row align-items-center">
|
||||
<label for="fontSize">UI Font Size</label>
|
||||
<div class="field-input">
|
||||
<p-slider [(ngModel)]="fontSize" [min]="12" [max]="18" class="w-full mb-3"></p-slider>
|
||||
<div class="text-center font-medium">{{ fontSize }}px</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
<p-accordionTab header="UI Settings">
|
||||
<div class="field-row">
|
||||
<label for="autoRefresh">Auto-refresh Logs</label>
|
||||
<p-inputSwitch id="autoRefresh" [(ngModel)]="autoRefresh"></p-inputSwitch>
|
||||
</div>
|
||||
<div class="col-12 md:col-6">
|
||||
<p-card header="Logging Configuration" styleClass="settings-card mb-4">
|
||||
<div class="settings-section">
|
||||
<div class="field-row">
|
||||
<label for="enableLogs">Enable Logging</label>
|
||||
<div class="field-input">
|
||||
<p-inputSwitch id="enableLogs" [(ngModel)]="enableLogs"></p-inputSwitch>
|
||||
<small class="form-helper-text">When enabled, application events will be logged</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row" [class.field-disabled]="!enableLogs">
|
||||
<label for="logLevel">Log Level</label>
|
||||
<div class="field-input">
|
||||
<p-dropdown id="logLevel" [options]="logLevels" [(ngModel)]="logLevel" optionLabel="label" [disabled]="!enableLogs"
|
||||
placeholder="Select log level" [showClear]="false" styleClass="w-full">
|
||||
<ng-template pTemplate="selectedItem">
|
||||
<div class="flex align-items-center gap-2" *ngIf="logLevel">
|
||||
<p-tag [severity]="getSeverity(logLevel.value)" [value]="logLevel.label"></p-tag>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template let-level pTemplate="item">
|
||||
<p-tag [severity]="getSeverity(level.value)" [value]="level.label"></p-tag>
|
||||
</ng-template>
|
||||
</p-dropdown>
|
||||
<small class="form-helper-text">Minimum level of logs to capture</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row" [class.field-disabled]="!enableLogs">
|
||||
<label for="enableNotifications">Show Notifications</label>
|
||||
<div class="field-input">
|
||||
<p-inputSwitch id="enableNotifications" [(ngModel)]="enableNotifications" [disabled]="!enableLogs"></p-inputSwitch>
|
||||
<small class="form-helper-text">Show desktop notifications for important logs</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
|
||||
<div class="field-row" *ngIf="autoRefresh">
|
||||
<label for="refreshInterval">Refresh Interval (seconds)</label>
|
||||
<input id="refreshInterval" type="number" pInputText [(ngModel)]="refreshInterval" min="5" max="300" />
|
||||
</div>
|
||||
</p-accordionTab>
|
||||
</p-accordion>
|
||||
<p-card header="Log Viewer Settings" styleClass="settings-card">
|
||||
<div class="settings-section">
|
||||
<div class="field-row">
|
||||
<label for="autoRefresh">Auto-refresh Logs</label>
|
||||
<div class="field-input">
|
||||
<p-inputSwitch id="autoRefresh" [(ngModel)]="autoRefresh"></p-inputSwitch>
|
||||
<small class="form-helper-text">Automatically refresh logs at the specified interval</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row" [class.field-disabled]="!autoRefresh">
|
||||
<label for="refreshInterval">Refresh Interval</label>
|
||||
<div class="field-input">
|
||||
<p-slider [(ngModel)]="refreshInterval" [min]="5" [max]="60" [disabled]="!autoRefresh" class="w-full mb-3"></p-slider>
|
||||
<div class="text-center font-medium">{{ refreshInterval }} seconds</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-row">
|
||||
<label for="maxLogEntries">Max Log Entries</label>
|
||||
<div class="field-input">
|
||||
<p-dropdown id="maxLogEntries" [options]="maxLogOptions" [(ngModel)]="maxLogEntries"
|
||||
optionLabel="label" optionValue="value" styleClass="w-full"></p-dropdown>
|
||||
<small class="form-helper-text">Maximum number of log entries to display at once</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button pButton label="Save Changes" icon="pi pi-save" class="p-button-success"></button>
|
||||
<button pButton label="Reset to Defaults" icon="pi pi-refresh" class="p-button-secondary p-button-outlined"></button>
|
||||
<div class="fixed bottom-0 right-0 p-3" *ngIf="showSaveNotification">
|
||||
<p-toast position="bottom-right" key="settings"></p-toast>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +1,160 @@
|
||||
.settings-container {
|
||||
padding: 1.5rem;
|
||||
padding: 0;
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
.settings-section {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-section-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 0;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
label {
|
||||
font-weight: 500;
|
||||
margin-right: 1rem;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 300px;
|
||||
&.field-disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
label {
|
||||
width: 30%;
|
||||
min-width: 150px;
|
||||
font-weight: 500;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
width: 70%;
|
||||
|
||||
.form-helper-text {
|
||||
display: block;
|
||||
color: var(--text-color-secondary);
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
input, .p-dropdown, .p-inputnumber {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin-top: 1.5rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: flex-end;
|
||||
/* Card styling */
|
||||
::ng-deep {
|
||||
.settings-card {
|
||||
height: 100%;
|
||||
transition: box-shadow 0.3s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.p-card-header {
|
||||
padding: 1rem 1.25rem;
|
||||
background-color: var(--surface-section);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.p-card-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Input styling */
|
||||
.p-inputtext, .p-dropdown, .p-inputnumber {
|
||||
&:hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&:focus, &.p-focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 1px var(--primary-100);
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown {
|
||||
.p-dropdown-label {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Input Group */
|
||||
.p-inputgroup {
|
||||
.p-button {
|
||||
background-color: var(--surface-200);
|
||||
color: var(--text-color-secondary);
|
||||
border-color: var(--surface-border);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-300);
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Slider styling */
|
||||
.p-slider {
|
||||
background-color: var(--surface-200);
|
||||
|
||||
.p-slider-handle {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-600);
|
||||
border-color: var(--primary-600);
|
||||
}
|
||||
}
|
||||
|
||||
.p-slider-range {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
/* Radio Button */
|
||||
.p-radiobutton {
|
||||
.p-radiobutton-box {
|
||||
&.p-highlight {
|
||||
border-color: var(--primary-color);
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
&:not(.p-disabled):hover {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media screen and (max-width: 768px) {
|
||||
.field-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
label {
|
||||
width: 100%;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
@@ -7,7 +7,24 @@ import { CardModule } from 'primeng/card';
|
||||
import { InputTextModule } from 'primeng/inputtext';
|
||||
import { InputSwitchModule } from 'primeng/inputswitch';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { AccordionModule } from 'primeng/accordion';
|
||||
import { DropdownModule } from 'primeng/dropdown';
|
||||
import { SliderModule } from 'primeng/slider';
|
||||
import { RadioButtonModule } from 'primeng/radiobutton';
|
||||
import { InputNumberModule } from 'primeng/inputnumber';
|
||||
import { TagModule } from 'primeng/tag';
|
||||
import { ToastModule } from 'primeng/toast';
|
||||
import { MessageService } from 'primeng/api';
|
||||
|
||||
// Define interfaces for our settings
|
||||
interface LogLevel {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface MaxLogOption {
|
||||
label: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings-page',
|
||||
@@ -19,16 +36,148 @@ import { AccordionModule } from 'primeng/accordion';
|
||||
InputTextModule,
|
||||
InputSwitchModule,
|
||||
ButtonModule,
|
||||
AccordionModule
|
||||
DropdownModule,
|
||||
SliderModule,
|
||||
RadioButtonModule,
|
||||
InputNumberModule,
|
||||
TagModule,
|
||||
ToastModule
|
||||
],
|
||||
providers: [MessageService],
|
||||
templateUrl: './settings-page.component.html',
|
||||
styleUrl: './settings-page.component.scss'
|
||||
})
|
||||
export class SettingsPageComponent {
|
||||
// Sample settings
|
||||
export class SettingsPageComponent implements OnInit {
|
||||
// API Settings
|
||||
apiUrl = 'http://localhost:5000';
|
||||
apiKey = '';
|
||||
apiTimeout = 30;
|
||||
|
||||
// UI Settings
|
||||
theme = 'light';
|
||||
fontSize = 14;
|
||||
|
||||
// Logging Settings
|
||||
enableLogs = true;
|
||||
logLevel: LogLevel = { label: 'Information', value: 'Information' };
|
||||
enableNotifications = true;
|
||||
|
||||
// Log Viewer Settings
|
||||
autoRefresh = false;
|
||||
refreshInterval = 30;
|
||||
maxLogEntries: number = 100;
|
||||
|
||||
// Available options for select lists
|
||||
logLevels: LogLevel[] = [
|
||||
{ label: 'Debug', value: 'Debug' },
|
||||
{ label: 'Information', value: 'Information' },
|
||||
{ label: 'Warning', value: 'Warning' },
|
||||
{ label: 'Error', value: 'Error' },
|
||||
{ label: 'Critical', value: 'Critical' }
|
||||
];
|
||||
|
||||
maxLogOptions: MaxLogOption[] = [
|
||||
{ label: '50 entries', value: 50 },
|
||||
{ label: '100 entries', value: 100 },
|
||||
{ label: '250 entries', value: 250 },
|
||||
{ label: '500 entries', value: 500 },
|
||||
{ label: '1000 entries', value: 1000 }
|
||||
];
|
||||
|
||||
// UI state
|
||||
showSaveNotification = false;
|
||||
|
||||
constructor(private messageService: MessageService) {}
|
||||
|
||||
ngOnInit() {
|
||||
// Load saved settings if available
|
||||
this.loadSettings();
|
||||
}
|
||||
|
||||
getSeverity(level: string): string {
|
||||
const normalizedLevel = level?.toLowerCase() || '';
|
||||
|
||||
switch (normalizedLevel) {
|
||||
case 'error':
|
||||
case 'critical':
|
||||
case 'fatal':
|
||||
return 'danger';
|
||||
case 'warning':
|
||||
return 'warning';
|
||||
case 'information':
|
||||
case 'info':
|
||||
return 'info';
|
||||
case 'debug':
|
||||
case 'trace':
|
||||
return 'success';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
}
|
||||
|
||||
saveSettings() {
|
||||
// Here we would normally save the settings to local storage or a backend API
|
||||
console.log('Saving settings:', {
|
||||
apiUrl: this.apiUrl,
|
||||
apiKey: this.apiKey ? '******' : null, // Don't log actual API key for security
|
||||
apiTimeout: this.apiTimeout,
|
||||
theme: this.theme,
|
||||
fontSize: this.fontSize,
|
||||
enableLogs: this.enableLogs,
|
||||
logLevel: this.logLevel,
|
||||
enableNotifications: this.enableNotifications,
|
||||
autoRefresh: this.autoRefresh,
|
||||
refreshInterval: this.refreshInterval,
|
||||
maxLogEntries: this.maxLogEntries
|
||||
});
|
||||
|
||||
// Show success message
|
||||
this.messageService.add({
|
||||
key: 'settings',
|
||||
severity: 'success',
|
||||
summary: 'Settings Saved',
|
||||
detail: 'Your settings have been successfully saved.',
|
||||
life: 3000
|
||||
});
|
||||
|
||||
this.showSaveNotification = true;
|
||||
setTimeout(() => {
|
||||
this.showSaveNotification = false;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
resetToDefaults() {
|
||||
// Reset to default values
|
||||
this.apiUrl = 'http://localhost:5000';
|
||||
this.apiKey = '';
|
||||
this.apiTimeout = 30;
|
||||
this.theme = 'light';
|
||||
this.fontSize = 14;
|
||||
this.enableLogs = true;
|
||||
this.logLevel = { label: 'Information', value: 'Information' };
|
||||
this.enableNotifications = true;
|
||||
this.autoRefresh = false;
|
||||
this.refreshInterval = 30;
|
||||
this.maxLogEntries = 100;
|
||||
|
||||
// Show info message
|
||||
this.messageService.add({
|
||||
key: 'settings',
|
||||
severity: 'info',
|
||||
summary: 'Settings Reset',
|
||||
detail: 'All settings have been reset to their default values.',
|
||||
life: 3000
|
||||
});
|
||||
|
||||
this.showSaveNotification = true;
|
||||
setTimeout(() => {
|
||||
this.showSaveNotification = false;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
private loadSettings() {
|
||||
// In a real application, we would load settings from local storage or an API
|
||||
// For now, we'll just use the default values set in the class
|
||||
console.log('Loading settings from storage...');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,249 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
/* Global styles and PrimeNG theme setup */
|
||||
|
||||
/* Import PrimeNG Theme (Lara Light by default) */
|
||||
@import "primeicons/primeicons.css";
|
||||
@import "primeflex/primeflex.css";
|
||||
|
||||
/* Global Variables */
|
||||
:root {
|
||||
--app-font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--app-primary: var(--primary-color);
|
||||
--app-card-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.06);
|
||||
--app-content-padding: 1.5rem;
|
||||
--app-border-radius: 6px;
|
||||
--app-transition-speed: 0.3s;
|
||||
}
|
||||
|
||||
/* Base Styles */
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--app-font-family);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--surface-ground);
|
||||
color: var(--text-color);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* Layout Helpers */
|
||||
.content-section {
|
||||
padding: var(--app-content-padding);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--surface-card);
|
||||
box-shadow: var(--app-card-shadow);
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* PrimeNG Component Customizations */
|
||||
|
||||
/* Card styles */
|
||||
.p-card {
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--app-card-shadow) !important;
|
||||
transition: transform var(--app-transition-speed), box-shadow var(--app-transition-speed);
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.p-card-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.p-card-subtitle {
|
||||
font-weight: 400;
|
||||
color: var(--text-color-secondary);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.p-card-content {
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.p-card-footer {
|
||||
padding-top: 1rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table styles */
|
||||
.p-datatable {
|
||||
.p-datatable-header {
|
||||
background-color: var(--surface-section);
|
||||
border-top-left-radius: var(--border-radius);
|
||||
border-top-right-radius: var(--border-radius);
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.p-datatable-thead > tr > th {
|
||||
background-color: var(--surface-section);
|
||||
color: var(--text-color);
|
||||
border-color: var(--surface-border);
|
||||
padding: 0.75rem 1rem;
|
||||
font-weight: 600;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.p-datatable-tbody > tr {
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
}
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: var(--surface-ground);
|
||||
}
|
||||
|
||||
> td {
|
||||
padding: 0.75rem 1rem;
|
||||
border-color: var(--surface-border);
|
||||
}
|
||||
}
|
||||
|
||||
.p-paginator {
|
||||
background-color: var(--surface-section);
|
||||
border-bottom-left-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
border-color: var(--surface-border);
|
||||
}
|
||||
}
|
||||
|
||||
/* Button styling */
|
||||
.p-button {
|
||||
border-radius: var(--border-radius);
|
||||
transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px var(--surface-ground), 0 0 0 4px var(--primary-color), 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&.p-button-outlined {
|
||||
background-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-50);
|
||||
}
|
||||
}
|
||||
|
||||
&.p-button-text {
|
||||
background-color: transparent;
|
||||
color: var(--primary-color);
|
||||
border-color: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--surface-hover);
|
||||
color: var(--primary-600);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Form elements */
|
||||
.form-field {
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.field-row {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
label {
|
||||
min-width: 200px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media screen and (max-width: 768px) {
|
||||
html {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.p-card .p-card-content {
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
label {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.dark-mode {
|
||||
/* Dark mode specific adjustments */
|
||||
.p-button {
|
||||
&.p-button-outlined {
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-section {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.p-card {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2) !important;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user