89 lines
3.2 KiB
Dart
89 lines
3.2 KiB
Dart
import 'package:flutter/material.dart';
|
|||
|
|
|
||
|
|
import '../../../core/models/models.dart';
|
||
|
|
import '../../../core/models/severity_mapping.dart';
|
||
|
|
import '../../../core/theme/theme_x.dart';
|
||
|
|
import '../../../core/widgets/data_table_scaffold.dart';
|
||
|
|
import '../../../core/widgets/empty_state.dart';
|
||
|
|
import '../../../core/widgets/mono_text.dart';
|
||
|
|
import '../../../core/widgets/status_pill.dart';
|
||
|
|
|
||
|
|
class ServicesTable extends StatelessWidget {
|
||
|
|
const ServicesTable({
|
||
|
|
super.key,
|
||
|
|
required this.services,
|
||
|
|
required this.onRowTap,
|
||
|
|
required this.onToggleRunning,
|
||
|
|
required this.busyServiceIds,
|
||
|
|
});
|
||
|
|
|
||
|
|
final List<Service> services;
|
||
|
|
final void Function(Service service) onRowTap;
|
||
|
|
final void Function(Service service) onToggleRunning;
|
||
|
|
final Set<String> busyServiceIds;
|
||
|
|
|
||
|
|
String _backupSummary(Service service) {
|
||
|
|
final targets = service.backup?.targets ?? const [];
|
||
|
|
if (targets.isEmpty) return 'none';
|
||
|
|
if (targets.length == 1) return targets.first.method.name;
|
||
|
|
return '${targets.length} targets';
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return DataTableScaffold(
|
||
|
|
empty: const EmptyState(
|
||
|
|
icon: Icons.layers_outlined,
|
||
|
|
title: 'No services yet',
|
||
|
|
message: 'Scan a compose folder from the Dashboard, or add one manually.',
|
||
|
|
),
|
||
|
|
columns: const [
|
||
|
|
DataColumn(label: Text('Service')),
|
||
|
|
DataColumn(label: Text('Status')),
|
||
|
|
DataColumn(label: Text('Compose file')),
|
||
|
|
DataColumn(label: Text('Backup')),
|
||
|
|
DataColumn(label: Text('')),
|
||
|
|
],
|
||
|
|
rows: [
|
||
|
|
for (final service in services)
|
||
|
|
DataRow(
|
||
|
|
onSelectChanged: (_) => onRowTap(service),
|
||
|
|
cells: [
|
||
|
|
DataCell(
|
||
|
|
Column(
|
||
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||
|
|
mainAxisSize: MainAxisSize.min,
|
||
|
|
children: [
|
||
|
|
Text(service.name, style: context.text.labelLarge),
|
||
|
|
Text(
|
||
|
|
service.composeType?.isNotEmpty == true ? service.composeType! : 'docker compose',
|
||
|
|
style: context.text.bodySmall,
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
),
|
||
|
|
DataCell(
|
||
|
|
StatusPill(label: service.status, severity: severityForServiceStatus(service.status)),
|
||
|
|
),
|
||
|
|
DataCell(MonoText(service.composeFile)),
|
||
|
|
DataCell(
|
||
|
|
_backupSummary(service) == 'none'
|
||
|
|
? Text('none', style: context.text.bodySmall)
|
||
|
|
: Text(_backupSummary(service), style: context.text.bodyMedium),
|
||
|
|
),
|
||
|
|
DataCell(
|
||
|
|
busyServiceIds.contains(service.id)
|
||
|
|
? const SizedBox(width: 18, height: 18, child: CircularProgressIndicator(strokeWidth: 2))
|
||
|
|
: IconButton(
|
||
|
|
tooltip: service.status == 'up' ? 'Stop' : 'Start',
|
||
|
|
icon: Icon(service.status == 'up' ? Icons.stop_rounded : Icons.play_arrow_rounded, size: 20),
|
||
|
|
onPressed: () => onToggleRunning(service),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
],
|
||
|
|
),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|