Netdata was never deployed and code-server was decommissioned on 2026-07-19; their quick links pointed at dead domains. The portainerUrl/netdataUrl constants had no consumers (quick links hardcode their own URLs). The Portainer quick link stays — its portainer.schweitz.net host arrives with the port-lockdown phase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
212 lines
6.0 KiB
Dart
212 lines
6.0 KiB
Dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import 'package:tatlock_ui/features/front_hall/data/datasources/quick_links_datasource.dart';
|
|
import 'package:tatlock_ui/features/front_hall/domain/entities/quick_link.dart';
|
|
|
|
part 'quick_links_provider.g.dart';
|
|
|
|
/// Provides the list of all quick links.
|
|
/// Falls back to default links if API is unavailable.
|
|
@riverpod
|
|
Future<List<QuickLink>> quickLinks(Ref ref) async {
|
|
try {
|
|
final datasource = ref.watch(quickLinksDatasourceProvider);
|
|
final links = await datasource.getQuickLinks();
|
|
// Return defaults if API returns empty list
|
|
return links.isEmpty ? getDefaultQuickLinks() : links;
|
|
} catch (_) {
|
|
// Return default links if API fails (e.g., backend not running)
|
|
return getDefaultQuickLinks();
|
|
}
|
|
}
|
|
|
|
/// Provides a single quick link by ID.
|
|
/// Falls back to default links if API is unavailable.
|
|
@riverpod
|
|
Future<QuickLink> quickLink(Ref ref, String id) async {
|
|
// First try to find in defaults (works offline)
|
|
final defaults = getDefaultQuickLinks();
|
|
final defaultLink = defaults.where((link) => link.id == id).firstOrNull;
|
|
if (defaultLink != null) {
|
|
return defaultLink;
|
|
}
|
|
|
|
// If not in defaults, try API
|
|
try {
|
|
final datasource = ref.watch(quickLinksDatasourceProvider);
|
|
final intId = int.tryParse(id);
|
|
if (intId == null) {
|
|
throw Exception('Invalid link ID: $id');
|
|
}
|
|
return await datasource.getQuickLink(intId);
|
|
} catch (_) {
|
|
throw Exception('Link not found: $id');
|
|
}
|
|
}
|
|
|
|
/// Controller for quick link CRUD operations.
|
|
@riverpod
|
|
class QuickLinkActions extends _$QuickLinkActions {
|
|
@override
|
|
AsyncValue<void> build() => const AsyncValue.data(null);
|
|
|
|
/// Safely sets state, ignoring disposal errors.
|
|
void _safeSetState(AsyncValue<void> newState) {
|
|
try {
|
|
state = newState;
|
|
} catch (_) {
|
|
// Provider was disposed - ignore
|
|
}
|
|
}
|
|
|
|
/// Safely invalidates providers after async operations.
|
|
void _safeInvalidate(List<ProviderOrFamily> providers) {
|
|
try {
|
|
for (final provider in providers) {
|
|
ref.invalidate(provider);
|
|
}
|
|
} catch (_) {
|
|
// Provider was disposed - ignore
|
|
}
|
|
}
|
|
|
|
/// Creates a new quick link.
|
|
Future<QuickLink?> create(QuickLink link) async {
|
|
QuickLink? result;
|
|
try {
|
|
_safeSetState(const AsyncValue.loading());
|
|
final datasource = ref.read(quickLinksDatasourceProvider);
|
|
result = await datasource.createQuickLink(link);
|
|
_safeInvalidate([quickLinksProvider]);
|
|
_safeSetState(const AsyncValue.data(null));
|
|
} catch (e, st) {
|
|
_safeSetState(AsyncValue.error(e, st));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Updates an existing quick link.
|
|
Future<QuickLink?> update(QuickLink link) async {
|
|
QuickLink? result;
|
|
try {
|
|
_safeSetState(const AsyncValue.loading());
|
|
final datasource = ref.read(quickLinksDatasourceProvider);
|
|
result = await datasource.updateQuickLink(link);
|
|
_safeInvalidate([quickLinksProvider, quickLinkProvider(link.id)]);
|
|
_safeSetState(const AsyncValue.data(null));
|
|
} catch (e, st) {
|
|
_safeSetState(AsyncValue.error(e, st));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Deletes a quick link.
|
|
Future<void> delete(String id) async {
|
|
try {
|
|
_safeSetState(const AsyncValue.loading());
|
|
final datasource = ref.read(quickLinksDatasourceProvider);
|
|
final intId = int.tryParse(id) ?? 0;
|
|
await datasource.deleteQuickLink(intId);
|
|
_safeInvalidate([quickLinksProvider]);
|
|
_safeSetState(const AsyncValue.data(null));
|
|
} catch (e, st) {
|
|
_safeSetState(AsyncValue.error(e, st));
|
|
}
|
|
}
|
|
|
|
/// Reorders quick links.
|
|
Future<void> reorder(List<String> orderedIds) async {
|
|
try {
|
|
_safeSetState(const AsyncValue.loading());
|
|
final datasource = ref.read(quickLinksDatasourceProvider);
|
|
final intIds = orderedIds.map((id) => int.tryParse(id) ?? 0).toList();
|
|
await datasource.reorderQuickLinks(intIds);
|
|
_safeInvalidate([quickLinksProvider]);
|
|
_safeSetState(const AsyncValue.data(null));
|
|
} catch (e, st) {
|
|
_safeSetState(AsyncValue.error(e, st));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Default seed data for quick links (used when API returns empty).
|
|
List<QuickLink> getDefaultQuickLinks() {
|
|
return [
|
|
// Home category
|
|
const QuickLink(
|
|
id: 'jellyfin',
|
|
name: 'Jellyfin',
|
|
url: 'https://jellyfin.schweitz.net',
|
|
iconName: 'movie',
|
|
category: 'Home',
|
|
type: QuickLinkType.iframe,
|
|
sortOrder: 0,
|
|
),
|
|
const QuickLink(
|
|
id: 'webui',
|
|
name: 'Open WebUI',
|
|
url: 'https://webui.schweitz.net',
|
|
iconName: 'chat',
|
|
category: 'Home',
|
|
type: QuickLinkType.iframe,
|
|
sortOrder: 1,
|
|
),
|
|
const QuickLink(
|
|
id: 'searxng',
|
|
name: 'SearXNG',
|
|
url: 'https://search.schweitz.net',
|
|
iconName: 'search',
|
|
category: 'Home',
|
|
type: QuickLinkType.newTab,
|
|
sortOrder: 2,
|
|
),
|
|
// AMP category
|
|
const QuickLink(
|
|
id: 'amp',
|
|
name: 'AMP Home',
|
|
url: 'https://amp.schweitz.net',
|
|
iconName: 'videogame_asset',
|
|
category: 'AMP',
|
|
type: QuickLinkType.newTab,
|
|
sortOrder: 0,
|
|
),
|
|
// Coding category
|
|
const QuickLink(
|
|
id: 'gitea',
|
|
name: 'Gitea',
|
|
url: 'https://git.schweitz.net',
|
|
iconName: 'code',
|
|
category: 'Coding',
|
|
type: QuickLinkType.iframe,
|
|
sortOrder: 0,
|
|
),
|
|
// Infrastructure category
|
|
const QuickLink(
|
|
id: 'portainer',
|
|
name: 'Portainer',
|
|
url: 'https://portainer.schweitz.net',
|
|
iconName: 'dns',
|
|
category: 'Infrastructure',
|
|
type: QuickLinkType.iframe,
|
|
sortOrder: 1,
|
|
),
|
|
const QuickLink(
|
|
id: 'npm',
|
|
name: 'Proxy Manager',
|
|
url: 'https://npm.schweitz.net',
|
|
iconName: 'public',
|
|
category: 'Infrastructure',
|
|
type: QuickLinkType.iframe,
|
|
sortOrder: 2,
|
|
),
|
|
const QuickLink(
|
|
id: 'core-api',
|
|
name: 'Core API',
|
|
url: 'https://api.schweitz.net/docs',
|
|
iconName: 'api',
|
|
category: 'Infrastructure',
|
|
type: QuickLinkType.iframe,
|
|
sortOrder: 3,
|
|
),
|
|
];
|
|
}
|