Add app source and tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
147
lib/Peach/see/logs/ViewLogs.dart
Normal file
147
lib/Peach/see/logs/ViewLogs.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../about/internal/application/ServerProfile.dart';
|
||||
import '../../about/internal/application/ServiceConfig.dart';
|
||||
import '../../comms/Comms.dart' show LogLine;
|
||||
import '../parent/ParentViewModel.dart';
|
||||
import 'ConnectLogs.dart';
|
||||
|
||||
class ViewLogs extends ParentViewModel {
|
||||
ConnectLogs connection;
|
||||
|
||||
ViewLogs(super.context, this.connection);
|
||||
|
||||
/// Log lines as they arrive from the server. The screen subscribes to this
|
||||
/// directly — a stream of remote output has no request/response shape to
|
||||
/// hand back through the Connect interface one call at a time.
|
||||
Stream<LogLine> get logs => getDataManager().logs;
|
||||
|
||||
/// Pulls the journal for [service], optionally bounded by a date range.
|
||||
void fetchLogs(
|
||||
ServerProfile server,
|
||||
ServiceConfig service, {
|
||||
DateTime? since,
|
||||
DateTime? until,
|
||||
}) async {
|
||||
if (!await canReach(
|
||||
server,
|
||||
() => fetchLogs(server, service, since: since, until: until),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
getDataManager().stopLogs();
|
||||
try {
|
||||
await getDataManager().fetchLogs(
|
||||
server,
|
||||
service,
|
||||
since: since,
|
||||
until: until,
|
||||
);
|
||||
connection.onLogsFetched();
|
||||
} catch (error) {
|
||||
connection.onLogsFetched();
|
||||
handleError(
|
||||
error,
|
||||
() => fetchLogs(server, service, since: since, until: until),
|
||||
() {},
|
||||
"Retry",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads the unit's systemd state. Failures are reported as `unknown`
|
||||
/// rather than raised — the status pill is ambient, not an operation the
|
||||
/// user asked for.
|
||||
void loadStatus(ServerProfile server, ServiceConfig service) async {
|
||||
try {
|
||||
final status = await getDataManager().serviceStatus(server, service);
|
||||
connection.onStatusLoaded(status);
|
||||
} catch (_) {
|
||||
connection.onStatusLoaded('unknown');
|
||||
}
|
||||
}
|
||||
|
||||
/// start | stop | restart on the service's unit.
|
||||
void control(
|
||||
ServerProfile server,
|
||||
ServiceConfig service,
|
||||
String action,
|
||||
) async {
|
||||
if (!await canReach(server, () => control(server, service, action))) return;
|
||||
getDataManager().stopLogs();
|
||||
try {
|
||||
final result = await getDataManager().serviceControl(
|
||||
server,
|
||||
service,
|
||||
action,
|
||||
);
|
||||
connection.onControlFinished(result.message);
|
||||
loadStatus(server, service);
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
() => control(server, service, action),
|
||||
() {},
|
||||
"Retry",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts or stops the live tail.
|
||||
void toggleFollow(
|
||||
ServerProfile server,
|
||||
ServiceConfig service,
|
||||
bool currentlyFollowing,
|
||||
) async {
|
||||
if (currentlyFollowing) {
|
||||
getDataManager().stopLogs();
|
||||
connection.onFollowChanged(false);
|
||||
return;
|
||||
}
|
||||
if (!await canReach(
|
||||
server,
|
||||
() => toggleFollow(server, service, currentlyFollowing),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
connection.onFollowChanged(true);
|
||||
try {
|
||||
await getDataManager().followLogs(server, service);
|
||||
} catch (error) {
|
||||
connection.onFollowChanged(false);
|
||||
handleError(
|
||||
error,
|
||||
() => toggleFollow(server, service, false),
|
||||
() {},
|
||||
"Retry",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes [lines] to a file the user picks.
|
||||
void saveLogs(String serviceName, List<String> lines) async {
|
||||
try {
|
||||
final body = lines.join('\n');
|
||||
final stamp = DateTime.now()
|
||||
.toIso8601String()
|
||||
.replaceAll(':', '-')
|
||||
.split('.')
|
||||
.first;
|
||||
final safeName = serviceName.replaceAll(RegExp(r'[^A-Za-z0-9_-]'), '_');
|
||||
final path = await FilePicker.saveFile(
|
||||
dialogTitle: 'Save logs',
|
||||
fileName: '$safeName-logs-$stamp.txt',
|
||||
bytes: Uint8List.fromList(utf8.encode(body)),
|
||||
);
|
||||
connection.onLogsSaved(path);
|
||||
} catch (error) {
|
||||
handleError(error, () => saveLogs(serviceName, lines), () {}, "Retry");
|
||||
}
|
||||
}
|
||||
|
||||
/// Stops any tail still running when the screen goes away.
|
||||
void stopLogs() => getDataManager().stopLogs();
|
||||
}
|
||||
Reference in New Issue
Block a user