@ai-qa/workflow 2.0.17 → 2.0.18
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.
- package/.opencode/qa-workflow-skill.md +232 -0
- package/PROJECT_GUIDE.md +114 -74
- package/install.js +43 -6
- package/opencode.json +6 -0
- package/package.json +9 -3
- package/playwright.config.ts +10 -0
- package/qa-dashboard/app.js +6 -2
- package/qa-dashboard/package.json +4 -1
- package/qa-dashboard/routes/index.js +26 -4
- package/qa-dashboard/routes/projects.js +25 -1
- package/qa-dashboard/routes/runs.js +38 -0
- package/qa-dashboard/routes/stories.js +18 -1
- package/qa-dashboard/routes/terminal.js +51 -0
- package/qa-dashboard/routes/test-data.js +46 -2
- package/qa-dashboard/services/cli-bridge.js +4 -0
- package/qa-dashboard/views/index.ejs +250 -238
- package/qa-dashboard/views/layouts/main.ejs +3 -2
- package/qa-dashboard/views/project.ejs +78 -51
- package/qa-dashboard/views/run.ejs +63 -3
- package/qa-dashboard/views/stories.ejs +36 -12
- package/qa-dashboard/views/story.ejs +125 -86
- package/qa-dashboard/views/terminal.ejs +101 -0
- package/qa-dashboard/views/test-data.ejs +59 -22
- package/router.md +27 -17
- package/test-results/run-2026-05-18T14-51-36/final-test-report.md +10 -2
|
@@ -1,242 +1,254 @@
|
|
|
1
1
|
<% layout='layouts/main' %>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
<% title='Dashboard' %>
|
|
3
|
+
|
|
4
|
+
<div class="hero">
|
|
5
|
+
<h1>Orchestrator QA</h1>
|
|
6
|
+
<p>User Story → Test Plan → Test Generation → Execution → Self-Healing → Report</p>
|
|
7
|
+
<% if (project) { %>
|
|
8
|
+
<p class="hero-project">
|
|
9
|
+
Project: <strong><%= project.name %></strong>
|
|
10
|
+
<% if (stats.lastRunStatus==='passed' ) { %><span class="badge badge-pass" style="margin-left:0.5rem;">Last Run ✅</span><% } %>
|
|
11
|
+
<% if (stats.lastRunStatus==='failed' ) { %><span class="badge badge-fail" style="margin-left:0.5rem;">Last Run ❌</span><% } %>
|
|
12
|
+
</p>
|
|
13
|
+
<% } %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<% if (!project) { %>
|
|
17
|
+
<div class="empty-state">
|
|
18
|
+
<div style="font-size:3rem;margin-bottom:1rem;">🚀</div>
|
|
19
|
+
<h3>Aucun projet configuré</h3>
|
|
20
|
+
<p>Pointez le dashboard vers votre projet pour voir les données.</p>
|
|
21
|
+
<a href="/projects/add" class="btn btn-primary" style="margin-top:0.5rem;">Configurer un projet</a>
|
|
22
|
+
</div>
|
|
23
|
+
<% } else { %>
|
|
24
|
+
|
|
25
|
+
<!-- ─── Stats cliquables ─── -->
|
|
26
|
+
<div class="stats-row">
|
|
27
|
+
<a href="/stories?project=<%= project.id %>" class="stat-card" style="text-decoration:none;color:inherit;display:block;cursor:pointer;">
|
|
28
|
+
<div class="stat-number"><%= stats.stories %></div>
|
|
29
|
+
<div class="stat-label">User Stories</div>
|
|
30
|
+
</a>
|
|
31
|
+
<a href="/stories?project=<%= project.id %>&filter=planned" class="stat-card" style="text-decoration:none;color:inherit;display:block;cursor:pointer;">
|
|
32
|
+
<div class="stat-number"><%= stats.plans %></div>
|
|
33
|
+
<div class="stat-label">Test Plans</div>
|
|
34
|
+
</a>
|
|
35
|
+
<a href="/stories?project=<%= project.id %>&filter=tested" class="stat-card" style="text-decoration:none;color:inherit;display:block;cursor:pointer;">
|
|
36
|
+
<div class="stat-number"><%= stats.specs %></div>
|
|
37
|
+
<div class="stat-label">Test Specs</div>
|
|
38
|
+
</a>
|
|
39
|
+
<a href="/runs?project=<%= project.id %>" class="stat-card" style="text-decoration:none;color:inherit;display:block;cursor:pointer;">
|
|
40
|
+
<div class="stat-number"><%= stats.runs %></div>
|
|
41
|
+
<div class="stat-label">Executions</div>
|
|
42
|
+
</a>
|
|
43
|
+
<a href="/analytics?project=<%= project.id %>" class="stat-card" style="text-decoration:none;color:inherit;display:block;cursor:pointer;">
|
|
44
|
+
<div class="stat-number <%= stats.passRate >= 80 ? 'text-success' : stats.passRate >= 50 ? 'text-warning' : stats.runs > 0 ? 'text-danger' : '' %>">
|
|
45
|
+
<%= stats.runs > 0 ? stats.passRate + '%' : '—' %>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="stat-label">Pass Rate</div>
|
|
48
|
+
</a>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<!-- ─── Activités récentes (stories) ─── -->
|
|
52
|
+
<% if (stats.storiesList && stats.storiesList.length > 0) { %>
|
|
53
|
+
<div class="card">
|
|
54
|
+
<h3>📋 Activités récentes</h3>
|
|
55
|
+
<table class="info-table">
|
|
56
|
+
<thead>
|
|
57
|
+
<tr>
|
|
58
|
+
<th>User Story</th>
|
|
59
|
+
<th>Plan</th>
|
|
60
|
+
<th>Test</th>
|
|
61
|
+
<th>Actions</th>
|
|
62
|
+
</tr>
|
|
63
|
+
</thead>
|
|
64
|
+
<tbody>
|
|
65
|
+
<% stats.storiesList.slice(0, 10).forEach(s => { %>
|
|
66
|
+
<tr>
|
|
67
|
+
<td><a href="/stories/<%= s.name %>?project=<%= project.id %>" style="font-weight:600;"><%= s.name.replace('.md', '') %></a></td>
|
|
68
|
+
<td>
|
|
69
|
+
<% if (s.hasPlan) { %><span class="badge badge-plan">✅ Planned</span>
|
|
70
|
+
<% } else { %><span class="badge badge-draft">⏳ Pending</span><% } %>
|
|
71
|
+
</td>
|
|
72
|
+
<td>
|
|
73
|
+
<% if (s.hasSpec) { %><span class="badge badge-spec">✅ Tested</span>
|
|
74
|
+
<% } else if (s.hasPlan) { %><span class="badge badge-draft">⏳ Pending</span>
|
|
75
|
+
<% } else { %><span class="badge badge-draft">—</span><% } %>
|
|
76
|
+
</td>
|
|
77
|
+
<td>
|
|
78
|
+
<a href="/stories/<%= s.name %>?project=<%= project.id %>" class="btn btn-sm">View</a>
|
|
79
|
+
<% if (s.hasSpec) { %>
|
|
80
|
+
<button class="btn btn-sm btn-primary" onclick="quickExecute('<%= project.id %>', '<%= s.name %>')">▶ Run</button>
|
|
14
81
|
<% } %>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
<% }) %>
|
|
85
|
+
</tbody>
|
|
86
|
+
</table>
|
|
87
|
+
</div>
|
|
88
|
+
<% } %>
|
|
89
|
+
|
|
90
|
+
<!-- ─── Latest Run Summary ─── -->
|
|
91
|
+
<% if (stats.latestRun) { %>
|
|
92
|
+
<div class="card card-lavender">
|
|
93
|
+
<h3>⚡ Dernière exécution</h3>
|
|
94
|
+
<div style="display:flex;gap:2rem;flex-wrap:wrap;">
|
|
95
|
+
<div><strong>Test:</strong> <%= stats.latestRun.testName || 'All' %></div>
|
|
96
|
+
<div><strong>Durée:</strong> <%= stats.latestRun.duration ? (stats.latestRun.duration / 1000).toFixed(1) + 's' : 'N/A' %></div>
|
|
97
|
+
<div><strong>Passed:</strong> <%= stats.latestRun.passedCount %></div>
|
|
98
|
+
<div><strong>Failed:</strong> <%= stats.latestRun.failedCount %></div>
|
|
99
|
+
<div>
|
|
100
|
+
<a href="/runs/<%= stats.latestRun.id %>?project=<%= project.id %>" class="btn btn-sm">Details</a>
|
|
101
|
+
<a href="/export/report/<%= stats.latestRun.id %>?project=<%= project.id %>" class="btn btn-sm">📥 Report</a>
|
|
102
|
+
</div>
|
|
20
103
|
</div>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
104
|
+
</div>
|
|
105
|
+
<% } %>
|
|
106
|
+
|
|
107
|
+
<!-- ─── Pipeline flow ─── -->
|
|
108
|
+
<div class="pipeline-flow">
|
|
109
|
+
<h2>Pipeline</h2>
|
|
110
|
+
<div class="flow-steps">
|
|
111
|
+
<div class="flow-step <%= stats.stories > 0 ? 'step-active' : '' %>">
|
|
112
|
+
<div class="step-icon">📝</div>
|
|
113
|
+
<div class="step-name">User Story</div>
|
|
114
|
+
<div class="step-desc"><%= stats.stories %> story<%= stats.stories !== 1 ? 's' : '' %></div>
|
|
29
115
|
</div>
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<div class="
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.quick-links-row {
|
|
170
|
-
display: flex;
|
|
171
|
-
gap: 1rem;
|
|
172
|
-
flex-wrap: wrap;
|
|
173
|
-
margin: 2rem 0;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
.quick-link-card {
|
|
177
|
-
flex: 1;
|
|
178
|
-
min-width: 140px;
|
|
179
|
-
background: var(--surface);
|
|
180
|
-
border: 1px solid var(--border);
|
|
181
|
-
border-radius: var(--radius);
|
|
182
|
-
padding: 1.25rem 1rem;
|
|
183
|
-
text-align: center;
|
|
184
|
-
text-decoration: none;
|
|
185
|
-
color: var(--text);
|
|
186
|
-
transition: border-color 0.2s, transform 0.15s;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.quick-link-card:hover {
|
|
190
|
-
border-color: var(--primary);
|
|
191
|
-
transform: translateY(-2px);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.ql-icon {
|
|
195
|
-
font-size: 1.75rem;
|
|
196
|
-
margin-bottom: 0.4rem;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
.ql-label {
|
|
200
|
-
font-size: 0.9rem;
|
|
201
|
-
font-weight: 600;
|
|
202
|
-
display: flex;
|
|
203
|
-
align-items: center;
|
|
204
|
-
justify-content: center;
|
|
205
|
-
gap: 0.4rem;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/* Info alert */
|
|
209
|
-
.alert-info {
|
|
210
|
-
background: #1e1b4b;
|
|
211
|
-
border: 1px solid #3730a3;
|
|
212
|
-
color: #a5b4fc;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
@media (max-width: 768px) {
|
|
216
|
-
.stats-row {
|
|
217
|
-
flex-wrap: wrap;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
.stat-card {
|
|
221
|
-
flex: 1 1 calc(50% - 0.5rem);
|
|
222
|
-
min-width: unset;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
.quick-links-row {
|
|
226
|
-
gap: 0.75rem;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
.quick-link-card {
|
|
230
|
-
min-width: calc(50% - 0.375rem);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
.flow-steps {
|
|
234
|
-
flex-direction: column;
|
|
235
|
-
align-items: flex-start;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
.flow-arrow {
|
|
239
|
-
transform: rotate(90deg);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
</style>
|
|
116
|
+
<div class="flow-arrow">→</div>
|
|
117
|
+
<div class="flow-step <%= stats.plans > 0 ? 'step-active' : '' %>">
|
|
118
|
+
<div class="step-icon">📋</div>
|
|
119
|
+
<div class="step-name">Plan</div>
|
|
120
|
+
<div class="step-desc"><%= stats.plans %> plan<%= stats.plans !== 1 ? 's' : '' %></div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="flow-arrow">→</div>
|
|
123
|
+
<div class="flow-step <%= stats.specs > 0 ? 'step-active' : '' %>">
|
|
124
|
+
<div class="step-icon">🧪</div>
|
|
125
|
+
<div class="step-name">Generate</div>
|
|
126
|
+
<div class="step-desc"><%= stats.specs %> spec<%= stats.specs !== 1 ? 's' : '' %></div>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="flow-arrow">→</div>
|
|
129
|
+
<div class="flow-step <%= stats.runs > 0 ? 'step-active' : '' %>">
|
|
130
|
+
<div class="step-icon">⚡</div>
|
|
131
|
+
<div class="step-name">Execute</div>
|
|
132
|
+
<div class="step-desc"><%= stats.runs %> run<%= stats.runs !== 1 ? 's' : '' %></div>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="flow-arrow">→</div>
|
|
135
|
+
<div class="flow-step">
|
|
136
|
+
<div class="step-icon">🛠️</div>
|
|
137
|
+
<div class="step-name">Heal</div>
|
|
138
|
+
<div class="step-desc">Auto-fix failures</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div class="flow-arrow">→</div>
|
|
141
|
+
<div class="flow-step">
|
|
142
|
+
<div class="step-icon">📊</div>
|
|
143
|
+
<div class="step-name">Report</div>
|
|
144
|
+
<div class="step-desc">Comprehensive results</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- ─── Quick Actions ─── -->
|
|
150
|
+
<div class="quick-links-row">
|
|
151
|
+
<a href="/stories?project=<%= project.id %>" class="quick-link-card">
|
|
152
|
+
<div class="ql-icon">📝</div>
|
|
153
|
+
<div class="ql-label">Stories <span class="badge badge-project"><%= stats.stories %></span></div>
|
|
154
|
+
</a>
|
|
155
|
+
<a href="/runs?project=<%= project.id %>" class="quick-link-card">
|
|
156
|
+
<div class="ql-icon">⚡</div>
|
|
157
|
+
<div class="ql-label">Runs <span class="badge badge-project"><%= stats.runs %></span></div>
|
|
158
|
+
</a>
|
|
159
|
+
<a href="/analytics?project=<%= project.id %>" class="quick-link-card">
|
|
160
|
+
<div class="ql-icon">📊</div>
|
|
161
|
+
<div class="ql-label">Analytics</div>
|
|
162
|
+
</a>
|
|
163
|
+
<a href="/projects/<%= project.id %>" class="quick-link-card">
|
|
164
|
+
<div class="ql-icon">⚙️</div>
|
|
165
|
+
<div class="ql-label">Pipeline Orchestrator</div>
|
|
166
|
+
</a>
|
|
167
|
+
<% if (stats.hasAllure) { %>
|
|
168
|
+
<a href="/allure-report?project=<%= project.id %>" class="quick-link-card" target="_blank">
|
|
169
|
+
<div class="ql-icon">📈</div>
|
|
170
|
+
<div class="ql-label">Allure Report</div>
|
|
171
|
+
</a>
|
|
172
|
+
<% } %>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<% if (stats.stories === 0) { %>
|
|
176
|
+
<div class="alert alert-info" style="margin-top:1.5rem;">
|
|
177
|
+
💡 <strong>Aucune user story trouvée</strong> dans <code><%= project.path %>/user-story/</code>.
|
|
178
|
+
Ajoutez un fichier <code>.md</code> pour commencer.
|
|
179
|
+
</div>
|
|
180
|
+
<% } %>
|
|
181
|
+
<% } %>
|
|
182
|
+
|
|
183
|
+
<script>
|
|
184
|
+
async function quickExecute(projectId, storyName) {
|
|
185
|
+
window.location.href = `/stories/${encodeURIComponent(storyName)}?project=${projectId}&autoRun=true`;
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
188
|
+
|
|
189
|
+
<style>
|
|
190
|
+
.hero-project {
|
|
191
|
+
color: var(--text-muted);
|
|
192
|
+
font-size: 0.95rem;
|
|
193
|
+
margin-top: 0.5rem;
|
|
194
|
+
}
|
|
195
|
+
.step-active {
|
|
196
|
+
border-color: var(--primary);
|
|
197
|
+
background: rgba(99, 102, 241, 0.08);
|
|
198
|
+
}
|
|
199
|
+
.step-active .step-name {
|
|
200
|
+
color: var(--primary);
|
|
201
|
+
}
|
|
202
|
+
.quick-links-row {
|
|
203
|
+
display: flex;
|
|
204
|
+
gap: 1rem;
|
|
205
|
+
flex-wrap: wrap;
|
|
206
|
+
margin: 2rem 0;
|
|
207
|
+
}
|
|
208
|
+
.quick-link-card {
|
|
209
|
+
flex: 1;
|
|
210
|
+
min-width: 140px;
|
|
211
|
+
background: var(--surface);
|
|
212
|
+
border: 1px solid var(--border);
|
|
213
|
+
border-radius: var(--radius);
|
|
214
|
+
padding: 1.25rem 1rem;
|
|
215
|
+
text-align: center;
|
|
216
|
+
text-decoration: none;
|
|
217
|
+
color: var(--text);
|
|
218
|
+
transition: border-color 0.2s, transform 0.15s;
|
|
219
|
+
}
|
|
220
|
+
.quick-link-card:hover {
|
|
221
|
+
border-color: var(--primary);
|
|
222
|
+
transform: translateY(-2px);
|
|
223
|
+
}
|
|
224
|
+
.ql-icon {
|
|
225
|
+
font-size: 1.75rem;
|
|
226
|
+
margin-bottom: 0.4rem;
|
|
227
|
+
}
|
|
228
|
+
.ql-label {
|
|
229
|
+
font-size: 0.9rem;
|
|
230
|
+
font-weight: 600;
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
justify-content: center;
|
|
234
|
+
gap: 0.4rem;
|
|
235
|
+
}
|
|
236
|
+
.alert-info {
|
|
237
|
+
background: #1e1b4b;
|
|
238
|
+
border: 1px solid #3730a3;
|
|
239
|
+
color: #a5b4fc;
|
|
240
|
+
}
|
|
241
|
+
.stat-card:hover {
|
|
242
|
+
border-color: var(--primary) !important;
|
|
243
|
+
transform: translateY(-2px);
|
|
244
|
+
box-shadow: var(--shadow-2);
|
|
245
|
+
}
|
|
246
|
+
@media (max-width: 768px) {
|
|
247
|
+
.stats-row { flex-wrap: wrap; }
|
|
248
|
+
.stat-card { flex: 1 1 calc(50% - 0.5rem); min-width: unset; }
|
|
249
|
+
.quick-links-row { gap: 0.75rem; }
|
|
250
|
+
.quick-link-card { min-width: calc(50% - 0.375rem); }
|
|
251
|
+
.flow-steps { flex-direction: column; align-items: flex-start; }
|
|
252
|
+
.flow-arrow { transform: rotate(90deg); }
|
|
253
|
+
}
|
|
254
|
+
</style>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title><%= title || 'QA Dashboard' %> —
|
|
6
|
+
<title><%= title || 'QA Dashboard' %> — Orchestrator QA</title>
|
|
7
7
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@500;600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
|
|
8
8
|
<link rel="stylesheet" href="/css/style.css">
|
|
9
9
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
<body>
|
|
13
13
|
<nav class="navbar">
|
|
14
14
|
<div class="nav-inner">
|
|
15
|
-
<a href="/" class="nav-brand">
|
|
15
|
+
<a href="/" class="nav-brand">Orchestrator QA</a>
|
|
16
16
|
<div class="nav-links">
|
|
17
17
|
<a href="/projects">Projects</a>
|
|
18
18
|
<a href="/stories">Stories</a>
|
|
19
19
|
<a href="/runs">Runs</a>
|
|
20
20
|
<a href="/review">Review</a>
|
|
21
21
|
<a href="/analytics">Analytics</a>
|
|
22
|
+
<a href="/terminal">Terminal</a>
|
|
22
23
|
<a href="/data">Test Data</a>
|
|
23
24
|
<button onclick="location.reload()" class="btn-refresh" title="Refresh">↻</button>
|
|
24
25
|
</div>
|