feat: add dynamic environment widgets Add live weather, sun position, and forecast widgets to Front Hall dashboard, powered by data from the Qdrant volatile collection via core-api. New widgets: - SunPositionWidget: Animated arc showing sun/moon position with gradient colors - ForecastWidget: Multi-day weather outlook - Updated WeatherWidget and AirQualityWidget to accept API data Infrastructure: - Environment datasource calling GET /tools/environment - Environment provider with 5-minute auto-refresh - Freezed models for environment data Tests: - 12 widget tests for environment section - Updated existing tests with givenEnvironment() harness method 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
411 lines
10 KiB
Dart
411 lines
10 KiB
Dart
/// Test fixtures for mock API responses.
|
|
///
|
|
/// Provides realistic test data for various API endpoints.
|
|
class Fixtures {
|
|
Fixtures._();
|
|
|
|
// ============================================================
|
|
// Containers
|
|
// ============================================================
|
|
|
|
static const containers = [
|
|
{
|
|
'Id': 'abc123def456',
|
|
'Names': ['/nginx-proxy'],
|
|
'Image': 'nginx:latest',
|
|
'State': 'running',
|
|
'Status': 'Up 3 days (healthy)',
|
|
'Created': 1704067200,
|
|
'Ports': [
|
|
{'PrivatePort': 80, 'PublicPort': 80, 'Type': 'tcp'},
|
|
{'PrivatePort': 443, 'PublicPort': 443, 'Type': 'tcp'},
|
|
],
|
|
},
|
|
{
|
|
'Id': 'def456ghi789',
|
|
'Names': ['/postgres-db'],
|
|
'Image': 'postgres:15',
|
|
'State': 'running',
|
|
'Status': 'Up 5 days',
|
|
'Created': 1703894400,
|
|
'Ports': [
|
|
{'PrivatePort': 5432, 'PublicPort': 5432, 'Type': 'tcp'},
|
|
],
|
|
},
|
|
{
|
|
'Id': 'ghi789jkl012',
|
|
'Names': ['/redis-cache'],
|
|
'Image': 'redis:7-alpine',
|
|
'State': 'running',
|
|
'Status': 'Up 2 days',
|
|
'Created': 1704153600,
|
|
'Ports': [
|
|
{'PrivatePort': 6379, 'Type': 'tcp'},
|
|
],
|
|
},
|
|
{
|
|
'Id': 'jkl012mno345',
|
|
'Names': ['/stopped-service'],
|
|
'Image': 'myapp:latest',
|
|
'State': 'exited',
|
|
'Status': 'Exited (0) 12 hours ago',
|
|
'Created': 1704067200,
|
|
'Ports': [],
|
|
},
|
|
];
|
|
|
|
static Map<String, dynamic> container(int index) => containers[index];
|
|
|
|
// ============================================================
|
|
// Domains (for ProxyHostsPage DataGrid)
|
|
// ============================================================
|
|
|
|
static const domains = [
|
|
{
|
|
'domain': 'home.schweitz.net',
|
|
'service': '192.168.86.149:8092',
|
|
'proxy_host_id': 1,
|
|
'ssl_enabled': true,
|
|
'certificate_id': 1,
|
|
},
|
|
{
|
|
'domain': 'api.schweitz.net',
|
|
'service': '192.168.86.149:8083',
|
|
'proxy_host_id': 2,
|
|
'ssl_enabled': true,
|
|
'certificate_id': 2,
|
|
},
|
|
{
|
|
'domain': 'git.schweitz.net',
|
|
'service': '192.168.86.149:3000',
|
|
'proxy_host_id': 3,
|
|
'ssl_enabled': true,
|
|
'certificate_id': 3,
|
|
},
|
|
{
|
|
'domain': 'internal.schweitz.net',
|
|
'service': '192.168.86.100:80',
|
|
'proxy_host_id': 4,
|
|
'ssl_enabled': false,
|
|
'certificate_id': null,
|
|
},
|
|
];
|
|
|
|
static Map<String, dynamic> domain(int index) =>
|
|
Map<String, dynamic>.from(domains[index]);
|
|
|
|
// ============================================================
|
|
// Proxy Hosts (raw NPM format)
|
|
// ============================================================
|
|
|
|
static const proxyHosts = [
|
|
{
|
|
'id': 1,
|
|
'domain_names': ['home.schweitz.net'],
|
|
'forward_host': '192.168.86.149',
|
|
'forward_port': 8092,
|
|
'forward_scheme': 'http',
|
|
'ssl_forced': true,
|
|
'enabled': 1,
|
|
'meta': {'nginx_online': true},
|
|
},
|
|
{
|
|
'id': 2,
|
|
'domain_names': ['api.schweitz.net'],
|
|
'forward_host': '192.168.86.149',
|
|
'forward_port': 8083,
|
|
'forward_scheme': 'http',
|
|
'ssl_forced': true,
|
|
'enabled': 1,
|
|
'meta': {'nginx_online': true},
|
|
},
|
|
{
|
|
'id': 3,
|
|
'domain_names': ['git.schweitz.net'],
|
|
'forward_host': '192.168.86.149',
|
|
'forward_port': 3000,
|
|
'forward_scheme': 'http',
|
|
'ssl_forced': true,
|
|
'enabled': 1,
|
|
'meta': {'nginx_online': true},
|
|
},
|
|
];
|
|
|
|
static Map<String, dynamic> proxyHost(int index) =>
|
|
Map<String, dynamic>.from(proxyHosts[index]);
|
|
|
|
// ============================================================
|
|
// Users (for UsersListPage DataGrid)
|
|
// ============================================================
|
|
|
|
static const users = [
|
|
{
|
|
'id': 'user-001',
|
|
'email': 'admin@example.com',
|
|
'name': 'Admin User',
|
|
'avatar_url': null,
|
|
'created_at': '2024-01-01T00:00:00Z',
|
|
'last_login': '2024-01-15T10:30:00Z',
|
|
'roles': ['admin.general:admin'],
|
|
},
|
|
{
|
|
'id': 'user-002',
|
|
'email': 'john@example.com',
|
|
'name': 'John Doe',
|
|
'avatar_url': null,
|
|
'created_at': '2024-01-05T00:00:00Z',
|
|
'last_login': '2024-01-14T08:00:00Z',
|
|
'roles': ['security.users:viewer'],
|
|
},
|
|
{
|
|
'id': 'user-003',
|
|
'email': 'jane@example.com',
|
|
'name': 'Jane Smith',
|
|
'avatar_url': null,
|
|
'created_at': '2024-01-10T00:00:00Z',
|
|
'last_login': null,
|
|
'roles': [],
|
|
},
|
|
];
|
|
|
|
static Map<String, dynamic> user(int index) =>
|
|
Map<String, dynamic>.from(users[index]);
|
|
|
|
// ============================================================
|
|
// Groups
|
|
// ============================================================
|
|
|
|
static const groups = [
|
|
{
|
|
'id': 'group-001',
|
|
'name': 'admins',
|
|
'is_superuser': true,
|
|
'member_count': 1,
|
|
'parent_name': null,
|
|
},
|
|
{
|
|
'id': 'group-002',
|
|
'name': 'users',
|
|
'is_superuser': false,
|
|
'member_count': 3,
|
|
'parent_name': null,
|
|
},
|
|
{
|
|
'id': 'group-003',
|
|
'name': 'developers',
|
|
'is_superuser': false,
|
|
'member_count': 1,
|
|
'parent_name': 'users',
|
|
},
|
|
];
|
|
|
|
static Map<String, dynamic> group(int index) =>
|
|
Map<String, dynamic>.from(groups[index]);
|
|
|
|
// ============================================================
|
|
// Auth Sync Response
|
|
// ============================================================
|
|
|
|
static Map<String, dynamic> authSyncResponse({
|
|
String userId = 'user-001',
|
|
String authentikId = 'ak-001',
|
|
String name = 'Test User',
|
|
String email = 'test@example.com',
|
|
bool isAdmin = false,
|
|
}) {
|
|
return {
|
|
'user_id': userId,
|
|
'authentik_id': authentikId,
|
|
'name': name,
|
|
'email': email,
|
|
'avatar_url': null,
|
|
'roles': isAdmin
|
|
? [
|
|
{
|
|
'id': 'admin-role',
|
|
'name': 'admin.general:admin',
|
|
'domain': 'admin',
|
|
'category': 'general',
|
|
'action': 'admin',
|
|
}
|
|
]
|
|
: [],
|
|
'preferences': {
|
|
'theme': 'system',
|
|
'default_room': 'front-hall',
|
|
'preferences_json': {},
|
|
},
|
|
};
|
|
}
|
|
|
|
// ============================================================
|
|
// System Stats (for DashboardContent)
|
|
// ============================================================
|
|
|
|
static const systemStats = {
|
|
'cpu': {
|
|
'usage_percent': 23.5,
|
|
'cores': 8,
|
|
'load_1m': 1.2,
|
|
'load_5m': 1.5,
|
|
'load_15m': 1.3,
|
|
},
|
|
'memory': {
|
|
'usage_percent': 45.2,
|
|
'total_bytes': 17179869184,
|
|
'used_bytes': 7771729306,
|
|
'available_bytes': 9408139878,
|
|
},
|
|
'disks': [
|
|
{
|
|
'mount_point': '/',
|
|
'device': '/dev/sda1',
|
|
'fstype': 'ext4',
|
|
'usage_percent': 67.8,
|
|
'total_bytes': 500000000000,
|
|
'used_bytes': 339000000000,
|
|
'free_bytes': 161000000000,
|
|
},
|
|
],
|
|
'network': {
|
|
'bytes_sent': 1234567890,
|
|
'bytes_recv': 9876543210,
|
|
'bytes_total': 11111111100,
|
|
},
|
|
'gpu': {
|
|
'available': false,
|
|
},
|
|
'hostname': 'test-server',
|
|
'queried_at': '2024-01-15T10:30:00Z',
|
|
};
|
|
|
|
// ============================================================
|
|
// Quick Links
|
|
// ============================================================
|
|
|
|
static const quickLinks = [
|
|
{
|
|
'id': 1,
|
|
'title': 'Portainer',
|
|
'url': 'https://portainer.example.com',
|
|
'icon': 'dns',
|
|
'category': 'Infrastructure',
|
|
'position': 0,
|
|
'is_visible': true,
|
|
'link_type': 'iframe',
|
|
},
|
|
{
|
|
'id': 2,
|
|
'title': 'Gitea',
|
|
'url': 'https://git.example.com',
|
|
'icon': 'code',
|
|
'category': 'Development',
|
|
'position': 1,
|
|
'is_visible': true,
|
|
'link_type': 'new_tab',
|
|
},
|
|
{
|
|
'id': 3,
|
|
'title': 'Jellyfin',
|
|
'url': 'https://media.example.com',
|
|
'icon': 'movie',
|
|
'category': 'Home',
|
|
'position': 2,
|
|
'is_visible': true,
|
|
'link_type': 'iframe',
|
|
},
|
|
];
|
|
|
|
static Map<String, dynamic> quickLink(int index) =>
|
|
Map<String, dynamic>.from(quickLinks[index]);
|
|
|
|
// ============================================================
|
|
// Environment Data
|
|
// ============================================================
|
|
|
|
static const environment = {
|
|
'weather': {
|
|
'temperature': 8.5,
|
|
'feels_like': 5.0,
|
|
'conditions': 'Partly Cloudy',
|
|
'humidity': 72,
|
|
'wind_speed': 18.5,
|
|
'wind_direction': 'SW',
|
|
'location': 'Rotterdam, NL',
|
|
'icon': '03d',
|
|
},
|
|
'forecast': [
|
|
{
|
|
'date': '2026-01-06',
|
|
'high': 10.0,
|
|
'low': 4.0,
|
|
'conditions': 'Cloudy',
|
|
'precipitation_chance': 20,
|
|
},
|
|
{
|
|
'date': '2026-01-07',
|
|
'high': 12.0,
|
|
'low': 5.0,
|
|
'conditions': 'Partly Cloudy',
|
|
'precipitation_chance': 10,
|
|
},
|
|
{
|
|
'date': '2026-01-08',
|
|
'high': 9.0,
|
|
'low': 3.0,
|
|
'conditions': 'Rain',
|
|
'precipitation_chance': 80,
|
|
},
|
|
{
|
|
'date': '2026-01-09',
|
|
'high': 11.0,
|
|
'low': 6.0,
|
|
'conditions': 'Sunny',
|
|
'precipitation_chance': 0,
|
|
},
|
|
],
|
|
'sun_times': {
|
|
'sunrise': '2026-01-06T08:45:00Z',
|
|
'sunset': '2026-01-06T16:50:00Z',
|
|
'daylight_minutes': 485,
|
|
'solar_noon': '2026-01-06T12:47:30Z',
|
|
},
|
|
'air_quality': {
|
|
'aqi': 42,
|
|
'quality': 'Good',
|
|
'pm25': 8.5,
|
|
'pm10': 15.0,
|
|
'o3': 32.0,
|
|
},
|
|
'updated_at': '2026-01-06T10:30:00Z',
|
|
'user': 'default',
|
|
};
|
|
|
|
/// Environment data without air quality (for conditional rendering test).
|
|
static const environmentNoAirQuality = {
|
|
'weather': {
|
|
'temperature': 8.5,
|
|
'conditions': 'Partly Cloudy',
|
|
'humidity': 72,
|
|
'wind_speed': 18.5,
|
|
'location': 'Rotterdam, NL',
|
|
},
|
|
'forecast': [
|
|
{
|
|
'date': '2026-01-06',
|
|
'high': 10.0,
|
|
'low': 4.0,
|
|
'conditions': 'Cloudy',
|
|
},
|
|
],
|
|
'sun_times': {
|
|
'sunrise': '2026-01-06T08:45:00Z',
|
|
'sunset': '2026-01-06T16:50:00Z',
|
|
'daylight_minutes': 485,
|
|
},
|
|
'air_quality': null,
|
|
'updated_at': '2026-01-06T10:30:00Z',
|
|
'user': 'default',
|
|
};
|
|
}
|