initial commit
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
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/empty_state.dart';
|
||||
import '../../../core/widgets/status_pill.dart';
|
||||
|
||||
/// One card per registered node showing its live services (via
|
||||
/// `/nodes/aggregated`), or an inline error card if this particular node
|
||||
/// was unreachable — a per-entry condition the local API itself reports,
|
||||
/// distinct from the aggregated fetch as a whole failing.
|
||||
class FleetCardGrid extends StatelessWidget {
|
||||
const FleetCardGrid({super.key, required this.entries});
|
||||
|
||||
final List<AggregatedNodeStatus> entries;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (entries.isEmpty) {
|
||||
return const EmptyState(
|
||||
icon: Icons.hub_outlined,
|
||||
title: 'Nothing to show yet',
|
||||
message: 'Register a node to see its live services here.',
|
||||
);
|
||||
}
|
||||
|
||||
return GridView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 320,
|
||||
mainAxisExtent: 180,
|
||||
crossAxisSpacing: 12,
|
||||
mainAxisSpacing: 12,
|
||||
),
|
||||
itemCount: entries.length,
|
||||
itemBuilder: (context, index) => _NodeCard(entry: entries[index]),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NodeCard extends StatelessWidget {
|
||||
const _NodeCard({required this.entry});
|
||||
|
||||
final AggregatedNodeStatus entry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasError = entry.error != null;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: context.colors.surface,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
border: Border.all(
|
||||
color: hasError ? context.status.critical.withValues(alpha: 0.4) : context.colors.outlineVariant,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(entry.node.name, style: context.text.labelLarge),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: hasError
|
||||
? _ErrorCallout(message: entry.error!)
|
||||
: entry.services.isEmpty
|
||||
? Text('No services reported.', style: context.text.bodySmall)
|
||||
: ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
for (final service in entry.services)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3.5),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
service.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.text.bodyMedium,
|
||||
),
|
||||
),
|
||||
StatusPill(
|
||||
label: service.status,
|
||||
severity: severityForServiceStatus(service.status),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ErrorCallout extends StatelessWidget {
|
||||
const _ErrorCallout({required this.message});
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Color.alphaBlend(context.status.critical.withValues(alpha: 0.08), context.colors.surface),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: context.status.critical.withValues(alpha: 0.35)),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 15, color: context.status.critical),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
message,
|
||||
style: context.dataStyles.dataMonoSmall,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user