Skip to content

Commit eef0061

Browse files
ismoilovdevmlclaude
andcommitted
feat: Improve runner information display
- Enhanced runner cards: show Platform, Version, Created, Last Contact - Added tags display below runner name with orange badges - Improved RunnerDetailsModal Overview tab: - Removed IP Address field (API returns null) - Added Version, Revision, Created date cards - Added Access Level, Max Timeout information - Added dedicated Tags section with orange styling - Added Configuration section (Run Untagged, Locked, Paused, Active) - Applied gradient backgrounds to all info cards - Updated Runner interface with complete GitLab API fields - Modified getRunners() to fetch detailed runner info via /runners/{id} 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 0a284f4 commit eef0061

File tree

3 files changed

+218
-29
lines changed

3 files changed

+218
-29
lines changed

src/components/RunnerDetailsModal.tsx

Lines changed: 144 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import {
1414
Server as ServerIcon,
1515
Zap as BoltIcon,
1616
BarChart3 as ChartBarIcon,
17+
Tag as TagIcon,
18+
Settings as SettingsIcon,
19+
Timer as TimerIcon,
20+
Shield as ShieldIcon,
21+
Calendar as CalendarIcon,
22+
GitCommit as GitCommitIcon,
1723
} from 'lucide-react';
1824
import { useTheme } from '@/hooks/useTheme';
1925
import { formatPercentage } from '@/lib/utils';
@@ -194,38 +200,84 @@ export default function RunnerDetailsModal({
194200
<div className="p-6 space-y-6">
195201
{/* Runner Info Grid */}
196202
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
203+
{/* Version */}
197204
<div className={`rounded-lg p-4 border ${
198-
theme === 'light' ? 'bg-gray-50 border-gray-200' : 'bg-gray-800/50 border-gray-700'
205+
theme === 'light' ? 'bg-gradient-to-br from-blue-50 to-blue-100 border-blue-200' : 'bg-gradient-to-br from-blue-500/10 to-blue-600/10 border-blue-500/30'
199206
}`}>
200207
<div className="flex items-center gap-3">
201-
<CpuChipIcon className="w-6 h-6 text-blue-400" />
208+
<GitCommitIcon className="w-6 h-6 text-blue-500" />
209+
<div>
210+
<p className={`text-xs ${textSecondary}`}>Version</p>
211+
<p className={`text-lg font-semibold ${textPrimary}`}>{runner.version || 'N/A'}</p>
212+
</div>
213+
</div>
214+
</div>
215+
216+
{/* Revision */}
217+
<div className={`rounded-lg p-4 border ${
218+
theme === 'light' ? 'bg-gradient-to-br from-purple-50 to-purple-100 border-purple-200' : 'bg-gradient-to-br from-purple-500/10 to-purple-600/10 border-purple-500/30'
219+
}`}>
220+
<div className="flex items-center gap-3">
221+
<SettingsIcon className="w-6 h-6 text-purple-500" />
222+
<div>
223+
<p className={`text-xs ${textSecondary}`}>Revision</p>
224+
<p className={`text-sm font-mono ${textPrimary}`}>{runner.revision ? runner.revision.substring(0, 8) : 'N/A'}</p>
225+
</div>
226+
</div>
227+
</div>
228+
229+
{/* Architecture */}
230+
<div className={`rounded-lg p-4 border ${
231+
theme === 'light' ? 'bg-gradient-to-br from-cyan-50 to-cyan-100 border-cyan-200' : 'bg-gradient-to-br from-cyan-500/10 to-cyan-600/10 border-cyan-500/30'
232+
}`}>
233+
<div className="flex items-center gap-3">
234+
<CpuChipIcon className="w-6 h-6 text-cyan-500" />
202235
<div>
203236
<p className={`text-xs ${textSecondary}`}>Architecture</p>
204237
<p className={`text-lg font-semibold ${textPrimary}`}>{runner.architecture || 'N/A'}</p>
205238
</div>
206239
</div>
207240
</div>
208241

242+
{/* Platform */}
209243
<div className={`rounded-lg p-4 border ${
210-
theme === 'light' ? 'bg-gray-50 border-gray-200' : 'bg-gray-800/50 border-gray-700'
244+
theme === 'light' ? 'bg-gradient-to-br from-green-50 to-green-100 border-green-200' : 'bg-gradient-to-br from-green-500/10 to-green-600/10 border-green-500/30'
211245
}`}>
212246
<div className="flex items-center gap-3">
213-
<ServerIcon className="w-6 h-6 text-purple-400" />
247+
<ServerIcon className="w-6 h-6 text-green-500" />
214248
<div>
215249
<p className={`text-xs ${textSecondary}`}>Platform</p>
216250
<p className={`text-lg font-semibold ${textPrimary}`}>{runner.platform || 'N/A'}</p>
217251
</div>
218252
</div>
219253
</div>
220254

255+
{/* Created Date */}
256+
<div className={`rounded-lg p-4 border ${
257+
theme === 'light' ? 'bg-gradient-to-br from-orange-50 to-orange-100 border-orange-200' : 'bg-gradient-to-br from-orange-500/10 to-orange-600/10 border-orange-500/30'
258+
}`}>
259+
<div className="flex items-center gap-3">
260+
<CalendarIcon className="w-6 h-6 text-orange-500" />
261+
<div>
262+
<p className={`text-xs ${textSecondary}`}>Created</p>
263+
<p className={`text-sm font-semibold ${textPrimary}`}>
264+
{runner.created_at
265+
? formatDistanceToNow(new Date(runner.created_at), { addSuffix: true })
266+
: 'N/A'}
267+
</p>
268+
</div>
269+
</div>
270+
</div>
271+
272+
{/* Last Contact */}
221273
<div className={`rounded-lg p-4 border ${
222-
theme === 'light' ? 'bg-gray-50 border-gray-200' : 'bg-gray-800/50 border-gray-700'
274+
theme === 'light' ? 'bg-gradient-to-br from-teal-50 to-teal-100 border-teal-200' : 'bg-gradient-to-br from-teal-500/10 to-teal-600/10 border-teal-500/30'
223275
}`}>
224276
<div className="flex items-center gap-3">
225-
<ClockIcon className="w-6 h-6 text-green-400" />
277+
<ClockIcon className="w-6 h-6 text-teal-500" />
226278
<div>
227279
<p className={`text-xs ${textSecondary}`}>Last Contact</p>
228-
<p className={`text-lg font-semibold ${textPrimary}`}>
280+
<p className={`text-sm font-semibold ${textPrimary}`}>
229281
{runner.contacted_at
230282
? formatDistanceToNow(new Date(runner.contacted_at), { addSuffix: true })
231283
: 'Never'}
@@ -234,43 +286,118 @@ export default function RunnerDetailsModal({
234286
</div>
235287
</div>
236288

289+
{/* Access Level */}
237290
<div className={`rounded-lg p-4 border ${
238-
theme === 'light' ? 'bg-gray-50 border-gray-200' : 'bg-gray-800/50 border-gray-700'
291+
theme === 'light' ? 'bg-gradient-to-br from-indigo-50 to-indigo-100 border-indigo-200' : 'bg-gradient-to-br from-indigo-500/10 to-indigo-600/10 border-indigo-500/30'
239292
}`}>
240293
<div className="flex items-center gap-3">
241-
<BoltIcon className="w-6 h-6 text-yellow-400" />
294+
<ShieldIcon className="w-6 h-6 text-indigo-500" />
242295
<div>
243-
<p className={`text-xs ${textSecondary}`}>IP Address</p>
244-
<p className={`text-lg font-semibold ${textPrimary}`}>{runner.ip_address || 'N/A'}</p>
296+
<p className={`text-xs ${textSecondary}`}>Access Level</p>
297+
<p className={`text-lg font-semibold ${textPrimary} capitalize`}>{runner.access_level || 'N/A'}</p>
245298
</div>
246299
</div>
247300
</div>
248301

302+
{/* Type */}
249303
<div className={`rounded-lg p-4 border ${
250-
theme === 'light' ? 'bg-gray-50 border-gray-200' : 'bg-gray-800/50 border-gray-700'
304+
theme === 'light' ? 'bg-gradient-to-br from-pink-50 to-pink-100 border-pink-200' : 'bg-gradient-to-br from-pink-500/10 to-pink-600/10 border-pink-500/30'
251305
}`}>
252306
<div className="flex items-center gap-3">
253-
<ChartBarIcon className="w-6 h-6 text-indigo-400" />
307+
<ChartBarIcon className="w-6 h-6 text-pink-500" />
254308
<div>
255309
<p className={`text-xs ${textSecondary}`}>Type</p>
256310
<p className={`text-lg font-semibold ${textPrimary} capitalize`}>{runner.runner_type || 'N/A'}</p>
257311
</div>
258312
</div>
259313
</div>
260314

315+
{/* Maximum Timeout */}
261316
<div className={`rounded-lg p-4 border ${
262-
theme === 'light' ? 'bg-gray-50 border-gray-200' : 'bg-gray-800/50 border-gray-700'
317+
theme === 'light' ? 'bg-gradient-to-br from-yellow-50 to-yellow-100 border-yellow-200' : 'bg-gradient-to-br from-yellow-500/10 to-yellow-600/10 border-yellow-500/30'
263318
}`}>
264319
<div className="flex items-center gap-3">
265-
<CheckCircleIcon className="w-6 h-6 text-teal-400" />
320+
<TimerIcon className="w-6 h-6 text-yellow-500" />
266321
<div>
267-
<p className={`text-xs ${textSecondary}`}>Status</p>
268-
<p className={`text-lg font-semibold ${textPrimary} capitalize`}>{runner.active ? 'Active' : 'Inactive'}</p>
322+
<p className={`text-xs ${textSecondary}`}>Max Timeout</p>
323+
<p className={`text-lg font-semibold ${textPrimary}`}>
324+
{runner.maximum_timeout ? `${runner.maximum_timeout}s` : 'Unlimited'}
325+
</p>
269326
</div>
270327
</div>
271328
</div>
272329
</div>
273330

331+
{/* Tags Section */}
332+
{runner.tag_list && runner.tag_list.length > 0 && (
333+
<div className={`rounded-lg p-4 border ${
334+
theme === 'light' ? 'bg-orange-50 border-orange-200' : 'bg-orange-500/10 border-orange-500/30'
335+
}`}>
336+
<div className="flex items-center gap-2 mb-3">
337+
<TagIcon className="w-5 h-5 text-orange-500" />
338+
<h3 className={`text-lg font-semibold ${textPrimary}`}>Tags ({runner.tag_list.length})</h3>
339+
</div>
340+
<div className="flex flex-wrap gap-2">
341+
{runner.tag_list.map((tag) => (
342+
<span
343+
key={tag}
344+
className={`px-3 py-1.5 rounded-lg font-medium text-sm ${
345+
theme === 'light'
346+
? 'bg-orange-100 text-orange-700 border border-orange-300'
347+
: 'bg-orange-500/20 text-orange-400 border border-orange-500/40'
348+
}`}
349+
>
350+
{tag}
351+
</span>
352+
))}
353+
</div>
354+
</div>
355+
)}
356+
357+
{/* Configuration Section */}
358+
<div className={`rounded-lg p-4 border ${
359+
theme === 'light' ? 'bg-blue-50 border-blue-200' : 'bg-blue-500/10 border-blue-500/30'
360+
}`}>
361+
<div className="flex items-center gap-2 mb-3">
362+
<SettingsIcon className="w-5 h-5 text-blue-500" />
363+
<h3 className={`text-lg font-semibold ${textPrimary}`}>Configuration</h3>
364+
</div>
365+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
366+
<div className={`rounded-lg p-3 ${
367+
theme === 'light' ? 'bg-white border border-blue-200' : 'bg-blue-500/5 border border-blue-500/20'
368+
}`}>
369+
<p className={`text-xs ${textSecondary} mb-1`}>Run Untagged</p>
370+
<p className={`font-semibold ${textPrimary}`}>
371+
{runner.run_untagged ? '✅ Yes' : '❌ No'}
372+
</p>
373+
</div>
374+
<div className={`rounded-lg p-3 ${
375+
theme === 'light' ? 'bg-white border border-blue-200' : 'bg-blue-500/5 border border-blue-500/20'
376+
}`}>
377+
<p className={`text-xs ${textSecondary} mb-1`}>Locked</p>
378+
<p className={`font-semibold ${textPrimary}`}>
379+
{runner.locked ? '🔒 Yes' : '🔓 No'}
380+
</p>
381+
</div>
382+
<div className={`rounded-lg p-3 ${
383+
theme === 'light' ? 'bg-white border border-blue-200' : 'bg-blue-500/5 border border-blue-500/20'
384+
}`}>
385+
<p className={`text-xs ${textSecondary} mb-1`}>Paused</p>
386+
<p className={`font-semibold ${textPrimary}`}>
387+
{runner.paused ? '⏸️ Yes' : '▶️ No'}
388+
</p>
389+
</div>
390+
<div className={`rounded-lg p-3 ${
391+
theme === 'light' ? 'bg-white border border-blue-200' : 'bg-blue-500/5 border border-blue-500/20'
392+
}`}>
393+
<p className={`text-xs ${textSecondary} mb-1`}>Active</p>
394+
<p className={`font-semibold ${textPrimary}`}>
395+
{runner.active ? '✅ Yes' : '❌ No'}
396+
</p>
397+
</div>
398+
</div>
399+
</div>
400+
274401
{/* Projects */}
275402
{runner.projects && runner.projects.length > 0 && (
276403
<div className={`rounded-lg p-4 border ${

src/components/RunnersTab.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { useEffect, useState, useMemo } from 'react';
4-
import { Server, Circle, Search, Filter, Activity, CheckCircle, XCircle, Pause, TrendingUp, Clock, Cpu, HardDrive } from 'lucide-react';
4+
import { Server, Circle, Search, Filter, Activity, CheckCircle, XCircle, Pause, TrendingUp, Clock, Cpu } from 'lucide-react';
55
import { useDashboardStore } from '@/store/dashboard-store';
66
import { getGitLabAPIAsync, Runner } from '@/lib/gitlab-api';
77
import { formatRelativeTime, cn } from '@/lib/utils';
@@ -394,7 +394,7 @@ export default function RunnersTab() {
394394
<h3 className={`font-semibold text-sm truncate ${textPrimary}`}>
395395
{runner.description || runner.name || `Runner #${runner.id}`}
396396
</h3>
397-
<div className="flex items-center gap-2 mt-0.5">
397+
<div className="flex items-center gap-2 mt-0.5 flex-wrap">
398398
<span className={`text-xs ${textSecondary}`}>
399399
{runner.runner_type}
400400
</span>
@@ -405,6 +405,14 @@ export default function RunnersTab() {
405405
Shared
406406
</span>
407407
)}
408+
{/* Tags */}
409+
{runner.tag_list && runner.tag_list.length > 0 && runner.tag_list.map((tag) => (
410+
<span key={tag} className={`text-xs px-1.5 py-0.5 rounded ${
411+
theme === 'light' ? 'bg-orange-50 text-orange-600' : 'bg-orange-500/10 text-orange-400'
412+
}`}>
413+
{tag}
414+
</span>
415+
))}
408416
</div>
409417
</div>
410418
</div>
@@ -431,21 +439,21 @@ export default function RunnersTab() {
431439

432440
<div className={`rounded-lg p-2 ${theme === 'light' ? 'bg-[#f5f5f7]' : 'bg-zinc-800/50'}`}>
433441
<div className="flex items-center gap-2 mb-1">
434-
<HardDrive className={`w-3.5 h-3.5 ${textSecondary}`} />
435-
<span className={`text-xs ${textSecondary}`}>Arch</span>
442+
<Clock className={`w-3.5 h-3.5 ${textSecondary}`} />
443+
<span className={`text-xs ${textSecondary}`}>Created</span>
436444
</div>
437445
<p className={`text-sm font-medium truncate ${textPrimary}`}>
438-
{runner.architecture || 'N/A'}
446+
{runner.created_at ? formatRelativeTime(runner.created_at) : 'N/A'}
439447
</p>
440448
</div>
441449

442450
<div className={`rounded-lg p-2 ${theme === 'light' ? 'bg-[#f5f5f7]' : 'bg-zinc-800/50'}`}>
443451
<div className="flex items-center gap-2 mb-1">
444452
<Activity className={`w-3.5 h-3.5 ${textSecondary}`} />
445-
<span className={`text-xs ${textSecondary}`}>IP Address</span>
453+
<span className={`text-xs ${textSecondary}`}>Version</span>
446454
</div>
447455
<p className={`text-sm font-mono truncate ${textPrimary}`}>
448-
{runner.ip_address || 'N/A'}
456+
{runner.version || 'N/A'}
449457
</p>
450458
</div>
451459

src/lib/gitlab-api.ts

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,23 @@ export interface Runner {
148148
description: string;
149149
ip_address: string;
150150
active: boolean;
151+
paused: boolean;
151152
is_shared: boolean;
152153
runner_type: string;
153154
name: string;
154155
online: boolean;
155156
status: 'online' | 'offline' | 'not_connected' | 'paused';
156-
contacted_at: string;
157-
architecture: string;
158-
platform: string;
157+
contacted_at: string | null;
158+
architecture: string | null;
159+
platform: string | null;
160+
revision: string;
161+
version: string;
162+
access_level: string;
163+
maximum_timeout: number | null;
164+
tag_list: string[];
165+
run_untagged: boolean;
166+
locked: boolean;
167+
created_at: string;
159168
projects: Array<{
160169
id: number;
161170
name: string;
@@ -445,7 +454,30 @@ class GitLabAPI {
445454
page,
446455
},
447456
});
448-
return response.data;
457+
458+
// Fetch detailed information for each runner
459+
const runners = response.data;
460+
const detailedRunners = await Promise.all(
461+
runners.map(async (runner: Runner) => {
462+
try {
463+
const detailResponse = await this.api.get(`/runners/${runner.id}`);
464+
console.log(`[GitLab API] Runner ${runner.id} details:`, {
465+
id: detailResponse.data.id,
466+
description: detailResponse.data.description,
467+
ip_address: detailResponse.data.ip_address,
468+
platform: detailResponse.data.platform,
469+
architecture: detailResponse.data.architecture,
470+
contacted_at: detailResponse.data.contacted_at,
471+
});
472+
return detailResponse.data;
473+
} catch (error) {
474+
console.warn(`Failed to fetch details for runner ${runner.id}:`, error);
475+
return runner; // Return basic info if detail fetch fails
476+
}
477+
})
478+
);
479+
480+
return detailedRunners;
449481
} catch {
450482
console.log('Admin access not available, fetching project runners...');
451483

@@ -468,7 +500,29 @@ class GitLabAPI {
468500
}
469501
});
470502

471-
return Array.from(runnersMap.values());
503+
// Fetch detailed information for each unique runner
504+
const uniqueRunners = Array.from(runnersMap.values());
505+
const detailedRunners = await Promise.all(
506+
uniqueRunners.map(async (runner) => {
507+
try {
508+
const detailResponse = await this.api.get(`/runners/${runner.id}`);
509+
console.log(`[GitLab API] Runner ${runner.id} details (project fallback):`, {
510+
id: detailResponse.data.id,
511+
description: detailResponse.data.description,
512+
ip_address: detailResponse.data.ip_address,
513+
platform: detailResponse.data.platform,
514+
architecture: detailResponse.data.architecture,
515+
contacted_at: detailResponse.data.contacted_at,
516+
});
517+
return detailResponse.data;
518+
} catch (error) {
519+
console.warn(`Failed to fetch details for runner ${runner.id}:`, error);
520+
return runner; // Return basic info if detail fetch fails
521+
}
522+
})
523+
);
524+
525+
return detailedRunners;
472526
} catch (fallbackError) {
473527
console.error('Failed to fetch runners from projects:', fallbackError);
474528
return [];

0 commit comments

Comments
 (0)