-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathakrites-triage-tab.component.html
More file actions
152 lines (142 loc) · 7.12 KB
/
Copy pathakrites-triage-tab.component.html
File metadata and controls
152 lines (142 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<!-- Copyright The Linux Foundation and each contributor to LFX. -->
<!-- SPDX-License-Identifier: MIT -->
<!-- Loading skeleton -->
@if (loading()) {
<div class="flex gap-3.5 overflow-x-auto pb-2" data-testid="akrites-triage-loading">
@for (col of TRIAGE_COLUMNS; track col.status) {
<div class="flex-1 min-w-[240px] bg-gray-50 border border-gray-200 rounded-[14px] p-3 flex flex-col gap-2.5">
<div class="flex items-center justify-between py-1 px-0.5 pb-1.5">
<div class="flex items-center gap-2.5">
<div class="w-6 h-6 rounded-full bg-gray-200 animate-pulse"></div>
<div class="w-24 h-3.5 rounded bg-gray-200 animate-pulse"></div>
</div>
<div class="w-4 h-3 rounded bg-gray-200 animate-pulse"></div>
</div>
@for (i of [1, 2, 3]; track i) {
<div class="bg-white border border-gray-200 rounded-[11px] p-3 flex flex-col gap-2">
<div class="w-20 h-3 rounded bg-gray-200 animate-pulse"></div>
<div class="w-32 h-2.5 rounded bg-gray-100 animate-pulse"></div>
<div class="flex gap-1.5 mt-1">
<div class="w-20 h-5 rounded-full bg-gray-100 animate-pulse"></div>
<div class="w-16 h-5 rounded-full bg-gray-100 animate-pulse"></div>
</div>
<div class="w-28 h-7 rounded-full bg-gray-100 animate-pulse mt-1"></div>
</div>
}
</div>
}
</div>
}
<!-- Board -->
@if (!loading() && boardData() !== undefined) {
<div class="flex gap-3.5 overflow-x-auto pb-2" data-testid="akrites-triage-board">
@for (col of TRIAGE_COLUMNS; track col.status) {
@let state = columnStates()[col.status];
<div
class="flex-1 min-w-[240px] max-w-[320px] border border-gray-200 rounded-[14px] p-3 flex flex-col gap-2.5"
[style.background]="'linear-gradient(180deg, ' + col.color + '12 0%, #fff 38%)'"
[attr.data-testid]="'akrites-triage-col-' + col.status">
<!-- Column header -->
<div class="flex items-center justify-between py-1 px-0.5 pb-1.5">
<span class="flex items-center gap-2.5 text-[13.5px] font-semibold text-slate-900">
<span class="w-6 h-6 rounded-full flex items-center justify-center text-white flex-shrink-0" [style.background]="col.color">
<i [class]="col.iconClass" aria-hidden="true"></i>
</span>
{{ col.label }}
</span>
<span class="text-[11px] font-bold text-gray-400">{{ state.total }}</span>
</div>
<!-- Error state -->
@if (state.error) {
<div class="flex flex-col items-center py-6 text-center" data-testid="akrites-triage-col-error">
<i class="fa-light fa-triangle-exclamation text-lg text-gray-400 mb-1" aria-hidden="true"></i>
<p class="text-xs text-gray-500">Failed to load</p>
</div>
}
<!-- Empty state -->
@if (!state.error && state.total === 0) {
<div class="flex flex-col items-center justify-center py-8 text-center" [attr.data-testid]="'akrites-triage-col-empty-' + col.status">
<i class="fa-light fa-circle-check text-xl text-gray-300 mb-1.5" aria-hidden="true"></i>
<p class="text-[11px] text-gray-400">No packages</p>
</div>
}
<!-- Package cards -->
@for (pkg of state.packages; track pkg.id) {
<div
class="bg-white border border-gray-200 rounded-[11px] p-[11px] cursor-pointer transition-all duration-100 hover:border-gray-300 hover:shadow-[0_3px_10px_rgba(2,39,65,0.08)]"
role="button"
tabindex="0"
[attr.aria-label]="'Open package details for ' + pkg.name"
(click)="onCardClick(pkg)"
(keydown.enter)="onCardClick(pkg)"
(keydown.space)="$event.preventDefault(); onCardClick(pkg)"
[attr.data-testid]="'akrites-triage-card-' + pkg.id">
<!-- Name + purl -->
<div class="text-[13px] font-semibold text-slate-900 truncate">{{ pkg.name }}</div>
<div class="text-[10.5px] text-gray-400 font-mono mt-px truncate">{{ pkg.purl }}</div>
<!-- Chips -->
<div class="flex flex-wrap gap-1.5 mt-2.5">
@if (pkg.healthScore !== null) {
<span
class="inline-flex items-center gap-1.5 bg-white border border-gray-200 rounded-full py-0.5 px-2.5 text-[11px] font-semibold text-gray-700">
<span class="w-1.5 h-1.5 rounded-full flex-shrink-0" [style.background]="pkg.healthColor"></span>
{{ pkg.healthScore | number: '1.0-0' }} {{ pkg.healthLabel }}
</span>
}
@if (pkg.impactScore !== null) {
<span
class="inline-flex items-center gap-1.5 bg-white border border-gray-200 rounded-full py-0.5 px-2.5 text-[11px] font-semibold text-gray-700">
Impact {{ pkg.impactScore | number: '1.0-0' }}
</span>
}
<span class="inline-flex items-center gap-1.5 bg-white border border-gray-200 rounded-full py-0.5 px-2.5 text-[11px] font-semibold text-gray-700">
<span class="w-1.5 h-1.5 rounded-full flex-shrink-0" [style.background]="pkg.vulnColor"></span>
@if (pkg.vulnCount > 0 && pkg.vulnSeverity) {
{{ pkg.vulnCount }} {{ pkg.vulnSeverity | titlecase }}
} @else {
{{ pkg.vulnCount }} vulns
}
</span>
</div>
<!-- Steward -->
@if (pkg.stewards.length > 0) {
<div class="flex items-center gap-2 mt-2.5 text-[11px] text-gray-500">
<i class="fa-light fa-user text-xs text-gray-400 flex-shrink-0" aria-hidden="true"></i>
@if (pkg.stewards[0].name) {
<span class="truncate">{{ pkg.stewards[0].name }}</span>
} @else {
<span class="text-gray-400">Steward assigned</span>
}
</div>
}
<!-- Action button — writer-only -->
@if (canWrite()) {
<div class="mt-[18px]">
<button
type="button"
[class]="col.actionButtonClass"
(click)="onAction($event, pkg, col)"
[attr.data-testid]="'akrites-triage-action-' + pkg.id">
{{ col.actionLabel }}
</button>
</div>
}
</div>
}
</div>
}
</div>
<!-- All columns empty -->
@if (allColumnsEmpty()) {
<div class="flex flex-col items-center justify-center py-20 text-center" data-testid="akrites-triage-empty">
<i class="fa-light fa-circle-check text-3xl text-emerald-400 mb-3" aria-hidden="true"></i>
<p class="text-sm font-medium text-gray-700">All clear</p>
<p class="text-xs text-gray-500 mt-1">No packages require triage action right now.</p>
</div>
}
}
<lfx-akrites-assign-steward-modal
[(visible)]="assignModalVisible"
[packageName]="assignTargetPackage()?.name ?? null"
[loading]="actionLoading()"
(confirm)="onAssignStewardConfirm($event)" />