@design.estate/dees-catalog 3.36.0 → 3.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,12 +30,30 @@ export interface ICpuCore {
30
30
  label?: string;
31
31
  }
32
32
 
33
+ export interface IPartitionData {
34
+ used: number; // bytes
35
+ total: number; // bytes
36
+ filesystem: string; // e.g., 'ext4', 'NTFS', 'btrfs'
37
+ mountPoint?: string; // e.g., '/', '/home', 'C:'
38
+ }
39
+
40
+ export interface IDiskData {
41
+ capacity: number; // bytes
42
+ model?: string; // e.g., 'Samsung 970 EVO Plus'
43
+ type?: 'ssd' | 'hdd' | 'nvme';
44
+ iops?: {
45
+ read: number;
46
+ write: number;
47
+ };
48
+ health?: number; // 0-100 (100 = new, 0 = end of life)
49
+ }
50
+
33
51
  export interface IStatsTile {
34
52
  id: string;
35
53
  title: string;
36
54
  value: number | string;
37
55
  unit?: string;
38
- type: 'number' | 'gauge' | 'percentage' | 'trend' | 'text' | 'multiPercentage' | 'cpuCores';
56
+ type: 'number' | 'gauge' | 'percentage' | 'trend' | 'text' | 'multiPercentage' | 'cpuCores' | 'partition' | 'disk';
39
57
 
40
58
  // Layout options
41
59
  columnSpan?: number; // Number of columns to span (default: 1)
@@ -60,6 +78,12 @@ export interface IStatsTile {
60
78
  // For cpuCores type
61
79
  coresData?: ICpuCore[];
62
80
 
81
+ // For partition type
82
+ partitionData?: IPartitionData;
83
+
84
+ // For disk type
85
+ diskData?: IDiskData;
86
+
63
87
  // Visual customization
64
88
  color?: string;
65
89
  icon?: string;
@@ -485,6 +509,219 @@ export class DeesStatsGrid extends DeesElement {
485
509
  max-width: 100%;
486
510
  }
487
511
 
512
+ /* Partition Styles */
513
+ .partition-wrapper {
514
+ width: 100%;
515
+ display: flex;
516
+ flex-direction: column;
517
+ flex: 1;
518
+ gap: 8px;
519
+ }
520
+
521
+ .partition-header {
522
+ display: flex;
523
+ align-items: baseline;
524
+ gap: 8px;
525
+ }
526
+
527
+ .partition-percentage {
528
+ font-size: var(--value-font-size);
529
+ font-weight: 600;
530
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
531
+ line-height: 1.1;
532
+ letter-spacing: -0.025em;
533
+ }
534
+
535
+ .partition-bar {
536
+ width: 100%;
537
+ height: 6px;
538
+ background: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
539
+ border-radius: 3px;
540
+ overflow: hidden;
541
+ }
542
+
543
+ .partition-bar-fill {
544
+ height: 100%;
545
+ background: ${cssManager.bdTheme('#333333', '#e0e0e0')};
546
+ transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
547
+ border-radius: 3px;
548
+ }
549
+
550
+ .partition-bar-fill.warning {
551
+ background: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
552
+ }
553
+
554
+ .partition-bar-fill.critical {
555
+ background: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 84.2% 60.2%)')};
556
+ }
557
+
558
+ .partition-stats {
559
+ display: flex;
560
+ justify-content: space-between;
561
+ align-items: center;
562
+ margin-top: auto;
563
+ }
564
+
565
+ .partition-stat {
566
+ display: flex;
567
+ flex-direction: column;
568
+ gap: 2px;
569
+ }
570
+
571
+ .partition-stat-label {
572
+ font-size: 10px;
573
+ font-weight: 500;
574
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
575
+ text-transform: uppercase;
576
+ letter-spacing: 0.02em;
577
+ }
578
+
579
+ .partition-stat-value {
580
+ font-size: 13px;
581
+ font-weight: 600;
582
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
583
+ letter-spacing: -0.01em;
584
+ }
585
+
586
+ .partition-meta {
587
+ display: flex;
588
+ align-items: center;
589
+ gap: 6px;
590
+ margin-top: 4px;
591
+ }
592
+
593
+ .partition-filesystem {
594
+ font-size: 11px;
595
+ font-weight: 500;
596
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
597
+ background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
598
+ padding: 2px 6px;
599
+ border-radius: 3px;
600
+ }
601
+
602
+ .partition-mountpoint {
603
+ font-size: 11px;
604
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
605
+ }
606
+
607
+ /* Disk Styles */
608
+ .disk-wrapper {
609
+ width: 100%;
610
+ display: flex;
611
+ flex-direction: column;
612
+ flex: 1;
613
+ gap: 8px;
614
+ }
615
+
616
+ .disk-capacity {
617
+ font-size: var(--value-font-size);
618
+ font-weight: 600;
619
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
620
+ line-height: 1.1;
621
+ letter-spacing: -0.025em;
622
+ }
623
+
624
+ .disk-model {
625
+ font-size: 12px;
626
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
627
+ display: flex;
628
+ align-items: center;
629
+ gap: 6px;
630
+ }
631
+
632
+ .disk-type-badge {
633
+ font-size: 10px;
634
+ font-weight: 600;
635
+ text-transform: uppercase;
636
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
637
+ background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(215 20.2% 16.8%)')};
638
+ padding: 2px 6px;
639
+ border-radius: 3px;
640
+ }
641
+
642
+ .disk-metrics {
643
+ display: flex;
644
+ flex-direction: column;
645
+ gap: 8px;
646
+ margin-top: auto;
647
+ }
648
+
649
+ .disk-iops {
650
+ display: flex;
651
+ align-items: center;
652
+ gap: 12px;
653
+ }
654
+
655
+ .disk-iops-item {
656
+ display: flex;
657
+ align-items: baseline;
658
+ gap: 4px;
659
+ }
660
+
661
+ .disk-iops-label {
662
+ font-size: 10px;
663
+ font-weight: 500;
664
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
665
+ text-transform: uppercase;
666
+ }
667
+
668
+ .disk-iops-value {
669
+ font-size: 13px;
670
+ font-weight: 600;
671
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
672
+ }
673
+
674
+ .disk-health {
675
+ display: flex;
676
+ flex-direction: column;
677
+ gap: 4px;
678
+ }
679
+
680
+ .disk-health-header {
681
+ display: flex;
682
+ justify-content: space-between;
683
+ align-items: baseline;
684
+ }
685
+
686
+ .disk-health-label {
687
+ font-size: 10px;
688
+ font-weight: 500;
689
+ color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
690
+ text-transform: uppercase;
691
+ }
692
+
693
+ .disk-health-value {
694
+ font-size: 12px;
695
+ font-weight: 600;
696
+ color: ${cssManager.bdTheme('hsl(215.3 25% 8.8%)', 'hsl(210 40% 98%)')};
697
+ }
698
+
699
+ .disk-health-bar {
700
+ width: 100%;
701
+ height: 4px;
702
+ background: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
703
+ border-radius: 2px;
704
+ overflow: hidden;
705
+ }
706
+
707
+ .disk-health-fill {
708
+ height: 100%;
709
+ transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
710
+ border-radius: 2px;
711
+ }
712
+
713
+ .disk-health-fill.good {
714
+ background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
715
+ }
716
+
717
+ .disk-health-fill.warning {
718
+ background: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
719
+ }
720
+
721
+ .disk-health-fill.critical {
722
+ background: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 84.2% 60.2%)')};
723
+ }
724
+
488
725
  /* Trend Styles */
489
726
  .trend-container {
490
727
  width: 100%;
@@ -659,6 +896,12 @@ export class DeesStatsGrid extends DeesElement {
659
896
  case 'cpuCores':
660
897
  return this.renderCpuCores(tile);
661
898
 
899
+ case 'partition':
900
+ return this.renderPartition(tile);
901
+
902
+ case 'disk':
903
+ return this.renderDisk(tile);
904
+
662
905
  case 'text':
663
906
  return html`
664
907
  <div class="text-value" style="${tile.color ? `color: ${tile.color}` : ''}">
@@ -876,6 +1119,114 @@ export class DeesStatsGrid extends DeesElement {
876
1119
  `;
877
1120
  }
878
1121
 
1122
+ private formatBytes(bytes: number): string {
1123
+ if (bytes === 0) return '0 B';
1124
+ const k = 1024;
1125
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
1126
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1127
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
1128
+ }
1129
+
1130
+ private renderPartition(tile: IStatsTile): TemplateResult {
1131
+ if (!tile.partitionData) {
1132
+ return html`<div class="tile-value">${tile.value}</div>`;
1133
+ }
1134
+
1135
+ const { used, total, filesystem, mountPoint } = tile.partitionData;
1136
+ const percentage = Math.min(100, Math.max(0, (used / total) * 100));
1137
+ const free = total - used;
1138
+
1139
+ // Determine color class based on usage
1140
+ const getColorClass = (): string => {
1141
+ if (percentage >= 90) return 'critical';
1142
+ if (percentage >= 75) return 'warning';
1143
+ return '';
1144
+ };
1145
+
1146
+ return html`
1147
+ <div class="partition-wrapper">
1148
+ <div class="partition-header">
1149
+ <span class="partition-percentage">${Math.round(percentage)}%</span>
1150
+ </div>
1151
+ <div class="partition-bar">
1152
+ <div
1153
+ class="partition-bar-fill ${getColorClass()}"
1154
+ style="width: ${percentage}%"
1155
+ ></div>
1156
+ </div>
1157
+ <div class="partition-stats">
1158
+ <div class="partition-stat">
1159
+ <span class="partition-stat-label">Used</span>
1160
+ <span class="partition-stat-value">${this.formatBytes(used)}</span>
1161
+ </div>
1162
+ <div class="partition-stat">
1163
+ <span class="partition-stat-label">Free</span>
1164
+ <span class="partition-stat-value">${this.formatBytes(free)}</span>
1165
+ </div>
1166
+ </div>
1167
+ <div class="partition-meta">
1168
+ <span class="partition-filesystem">${filesystem}</span>
1169
+ ${mountPoint ? html`<span class="partition-mountpoint">${mountPoint}</span>` : ''}
1170
+ </div>
1171
+ </div>
1172
+ `;
1173
+ }
1174
+
1175
+ private renderDisk(tile: IStatsTile): TemplateResult {
1176
+ if (!tile.diskData) {
1177
+ return html`<div class="tile-value">${tile.value}</div>`;
1178
+ }
1179
+
1180
+ const { capacity, model, type, iops, health } = tile.diskData;
1181
+
1182
+ // Determine health color class (inverted - high is good)
1183
+ const getHealthClass = (value: number): string => {
1184
+ if (value >= 70) return 'good';
1185
+ if (value >= 30) return 'warning';
1186
+ return 'critical';
1187
+ };
1188
+
1189
+ return html`
1190
+ <div class="disk-wrapper">
1191
+ <div class="disk-capacity">${this.formatBytes(capacity)}</div>
1192
+ ${model || type ? html`
1193
+ <div class="disk-model">
1194
+ ${model ? html`<span>${model}</span>` : ''}
1195
+ ${type ? html`<span class="disk-type-badge">${type}</span>` : ''}
1196
+ </div>
1197
+ ` : ''}
1198
+ <div class="disk-metrics">
1199
+ ${iops ? html`
1200
+ <div class="disk-iops">
1201
+ <div class="disk-iops-item">
1202
+ <span class="disk-iops-label">Read</span>
1203
+ <span class="disk-iops-value">${iops.read.toLocaleString()}</span>
1204
+ </div>
1205
+ <div class="disk-iops-item">
1206
+ <span class="disk-iops-label">Write</span>
1207
+ <span class="disk-iops-value">${iops.write.toLocaleString()}</span>
1208
+ </div>
1209
+ </div>
1210
+ ` : ''}
1211
+ ${health !== undefined ? html`
1212
+ <div class="disk-health">
1213
+ <div class="disk-health-header">
1214
+ <span class="disk-health-label">Health</span>
1215
+ <span class="disk-health-value">${health}%</span>
1216
+ </div>
1217
+ <div class="disk-health-bar">
1218
+ <div
1219
+ class="disk-health-fill ${getHealthClass(health)}"
1220
+ style="width: ${health}%"
1221
+ ></div>
1222
+ </div>
1223
+ </div>
1224
+ ` : ''}
1225
+ </div>
1226
+ </div>
1227
+ `;
1228
+ }
1229
+
879
1230
  private async handleGridAction(action: plugins.tsclass.website.IMenuItem) {
880
1231
  if (action.action) {
881
1232
  await action.action();