fix(doctor): honest module count, temp-path audit warning, connection line
Some checks are pending
Security / Security check (push) Waiting to run
Some checks are pending
Security / Security check (push) Waiting to run
Three doctor-page findings from the usertest panel: - Module tile no longer counts the hub's built-in 'system' pseudo-module — a fresh hub shows 0 modules, matching the welcome checklist's definition of an install. Counting is a top-level function with unit tests. - When the audit DB lives in an OS-cleanable temp directory (/var/folders, /tmp, Windows Temp), the daemon-files panel says so instead of presenting the state as healthy. The classifier is a top-level function with unit tests. - The daemon card states who-talks-to-whom-how in one line: endpoint, transport security (TLS / unencrypted-local / unencrypted), and whether a bearer token is attached (length only) — the auditor's baseline the green dot cannot answer. Also syncs pubspec.yaml (0.70.0 -> 0.72.0) with kStudioVersion, which had drifted to 0.71.0 while pubspec stayed behind. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
afa3fa7eae
commit
703d961cec
10 changed files with 303 additions and 3 deletions
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
/// Studio's own build version. Bump on every UI release so the
|
||||
/// running app self-identifies.
|
||||
const String kStudioVersion = '0.71.0';
|
||||
const String kStudioVersion = '0.72.0';
|
||||
|
||||
const String kProductName = 'Ch∆In Studio';
|
||||
const String kVendorName = 'Flemming.AI (F∆I)';
|
||||
|
|
|
|||
|
|
@ -1238,7 +1238,7 @@ class HubService {
|
|||
final paths = results[5] as DaemonPathsResponse;
|
||||
|
||||
// Module count = distinct module_name across capabilities.
|
||||
final moduleNames = <String>{for (final c in caps) c.moduleName};
|
||||
final moduleNames = installedModuleNames(caps);
|
||||
|
||||
// Per-source-kind breakdown (0.12+). Pre-0.12 hubs return
|
||||
// empty `source_kind` strings — group those under `kind`
|
||||
|
|
@ -1306,6 +1306,17 @@ class UpdateStatus {
|
|||
});
|
||||
}
|
||||
|
||||
/// Distinct installed-module names behind a capability list.
|
||||
/// Excludes the hub's built-in `system` pseudo-module
|
||||
/// (`builtin_capabilities` reports `module_name: "system"`):
|
||||
/// the welcome checklist already treats `system.*` as "built
|
||||
/// in, not an install", and the doctor tile must count the
|
||||
/// same way — a fresh hub shows 0 modules, not 1.
|
||||
Set<String> installedModuleNames(Iterable<CapabilityEntry> caps) => {
|
||||
for (final c in caps)
|
||||
if (c.moduleName != 'system') c.moduleName,
|
||||
};
|
||||
|
||||
class DoctorSnapshot {
|
||||
final int moduleCount;
|
||||
final int capabilityCount;
|
||||
|
|
|
|||
|
|
@ -1267,6 +1267,25 @@
|
|||
"doctorPillStopped": "gestoppt",
|
||||
"doctorDaemonControlHint": "Studio ruft das Platform-Binary auf — entspricht chain daemon …, sodass CLI und UI im Gleichschritt bleiben.",
|
||||
"doctorDaemonControlWorking": "Wird ausgeführt…",
|
||||
"doctorConnLine": "Verbindung: {endpoint} · {transport} · {auth}",
|
||||
"@doctorConnLine": {
|
||||
"placeholders": {
|
||||
"endpoint": {"type": "String"},
|
||||
"transport": {"type": "String"},
|
||||
"auth": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"doctorConnTls": "TLS-verschlüsselt",
|
||||
"doctorConnPlainLocal": "unverschlüsselt (bleibt auf diesem Rechner)",
|
||||
"doctorConnPlainRemote": "unverschlüsselt",
|
||||
"doctorConnToken": "Token gesetzt ({count} Zeichen)",
|
||||
"@doctorConnToken": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"doctorConnAnonymous": "ohne Token (anonym)",
|
||||
"doctorDbVolatileWarning": "Die Audit-Datenbank liegt in einem temporären Ordner, den das Betriebssystem jederzeit aufräumen kann. Für belastbare Nachweise gehört das Datenverzeichnis des Hubs an einen dauerhaften Ort.",
|
||||
"doctorPathLog": "Log",
|
||||
"doctorPathConfig": "Konfig",
|
||||
"doctorPathDb": "Audit-DB",
|
||||
|
|
|
|||
|
|
@ -1291,6 +1291,25 @@
|
|||
"doctorPillStopped": "stopped",
|
||||
"doctorDaemonControlHint": "Studio shells out to the platform binary — mirrors chain daemon … exactly so the CLI and UI stay in lockstep.",
|
||||
"doctorDaemonControlWorking": "Working…",
|
||||
"doctorConnLine": "Connection: {endpoint} · {transport} · {auth}",
|
||||
"@doctorConnLine": {
|
||||
"placeholders": {
|
||||
"endpoint": {"type": "String"},
|
||||
"transport": {"type": "String"},
|
||||
"auth": {"type": "String"}
|
||||
}
|
||||
},
|
||||
"doctorConnTls": "TLS-encrypted",
|
||||
"doctorConnPlainLocal": "unencrypted (stays on this machine)",
|
||||
"doctorConnPlainRemote": "unencrypted",
|
||||
"doctorConnToken": "token configured ({count} characters)",
|
||||
"@doctorConnToken": {
|
||||
"placeholders": {
|
||||
"count": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"doctorConnAnonymous": "no token (anonymous)",
|
||||
"doctorDbVolatileWarning": "The audit database is stored in a temporary folder the operating system may clean up at any time. For real evidence, move the hub's data directory to a permanent location.",
|
||||
"doctorPathLog": "Log",
|
||||
"doctorPathConfig": "Config",
|
||||
"doctorPathDb": "Audit DB",
|
||||
|
|
|
|||
|
|
@ -3836,6 +3836,48 @@ abstract class AppLocalizations {
|
|||
/// **'Working…'**
|
||||
String get doctorDaemonControlWorking;
|
||||
|
||||
/// No description provided for @doctorConnLine.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connection: {endpoint} · {transport} · {auth}'**
|
||||
String doctorConnLine(String endpoint, String transport, String auth);
|
||||
|
||||
/// No description provided for @doctorConnTls.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'TLS-encrypted'**
|
||||
String get doctorConnTls;
|
||||
|
||||
/// No description provided for @doctorConnPlainLocal.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'unencrypted (stays on this machine)'**
|
||||
String get doctorConnPlainLocal;
|
||||
|
||||
/// No description provided for @doctorConnPlainRemote.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'unencrypted'**
|
||||
String get doctorConnPlainRemote;
|
||||
|
||||
/// No description provided for @doctorConnToken.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'token configured ({count} characters)'**
|
||||
String doctorConnToken(int count);
|
||||
|
||||
/// No description provided for @doctorConnAnonymous.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'no token (anonymous)'**
|
||||
String get doctorConnAnonymous;
|
||||
|
||||
/// No description provided for @doctorDbVolatileWarning.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The audit database is stored in a temporary folder the operating system may clean up at any time. For real evidence, move the hub\'s data directory to a permanent location.'**
|
||||
String get doctorDbVolatileWarning;
|
||||
|
||||
/// No description provided for @doctorPathLog.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -2255,6 +2255,33 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get doctorDaemonControlWorking => 'Wird ausgeführt…';
|
||||
|
||||
@override
|
||||
String doctorConnLine(String endpoint, String transport, String auth) {
|
||||
return 'Verbindung: $endpoint · $transport · $auth';
|
||||
}
|
||||
|
||||
@override
|
||||
String get doctorConnTls => 'TLS-verschlüsselt';
|
||||
|
||||
@override
|
||||
String get doctorConnPlainLocal =>
|
||||
'unverschlüsselt (bleibt auf diesem Rechner)';
|
||||
|
||||
@override
|
||||
String get doctorConnPlainRemote => 'unverschlüsselt';
|
||||
|
||||
@override
|
||||
String doctorConnToken(int count) {
|
||||
return 'Token gesetzt ($count Zeichen)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get doctorConnAnonymous => 'ohne Token (anonym)';
|
||||
|
||||
@override
|
||||
String get doctorDbVolatileWarning =>
|
||||
'Die Audit-Datenbank liegt in einem temporären Ordner, den das Betriebssystem jederzeit aufräumen kann. Für belastbare Nachweise gehört das Datenverzeichnis des Hubs an einen dauerhaften Ort.';
|
||||
|
||||
@override
|
||||
String get doctorPathLog => 'Log';
|
||||
|
||||
|
|
|
|||
|
|
@ -2261,6 +2261,32 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get doctorDaemonControlWorking => 'Working…';
|
||||
|
||||
@override
|
||||
String doctorConnLine(String endpoint, String transport, String auth) {
|
||||
return 'Connection: $endpoint · $transport · $auth';
|
||||
}
|
||||
|
||||
@override
|
||||
String get doctorConnTls => 'TLS-encrypted';
|
||||
|
||||
@override
|
||||
String get doctorConnPlainLocal => 'unencrypted (stays on this machine)';
|
||||
|
||||
@override
|
||||
String get doctorConnPlainRemote => 'unencrypted';
|
||||
|
||||
@override
|
||||
String doctorConnToken(int count) {
|
||||
return 'token configured ($count characters)';
|
||||
}
|
||||
|
||||
@override
|
||||
String get doctorConnAnonymous => 'no token (anonymous)';
|
||||
|
||||
@override
|
||||
String get doctorDbVolatileWarning =>
|
||||
'The audit database is stored in a temporary folder the operating system may clean up at any time. For real evidence, move the hub\'s data directory to a permanent location.';
|
||||
|
||||
@override
|
||||
String get doctorPathLog => 'Log';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
|||
import '../data/error_presentation.dart';
|
||||
import '../data/chain_log.dart';
|
||||
import '../data/hub.dart';
|
||||
import '../data/hub_auth_token.dart';
|
||||
import '../data/system_actions.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../theme/theme.dart';
|
||||
|
|
@ -334,6 +335,24 @@ class _EventLogPanel extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
/// True when [path] points into an OS-managed temporary
|
||||
/// directory (macOS `/var/folders`, Unix `/tmp`, the Windows
|
||||
/// user/system temp folders) — locations the OS may clean up
|
||||
/// at any time. The doctor page warns when the audit DB lives
|
||||
/// in one: an evidence log the OS can silently delete is not
|
||||
/// evidence. Top-level so the unit test can exercise the
|
||||
/// classification directly.
|
||||
bool isVolatilePath(String path) {
|
||||
if (path.isEmpty) return false;
|
||||
final p = path.toLowerCase().replaceAll('\\', '/');
|
||||
return p.startsWith('/tmp/') ||
|
||||
p.startsWith('/private/tmp/') ||
|
||||
p.startsWith('/var/folders/') ||
|
||||
p.startsWith('/private/var/folders/') ||
|
||||
p.contains('/appdata/local/temp/') ||
|
||||
p.contains('/windows/temp/');
|
||||
}
|
||||
|
||||
/// Lists the daemon's filesystem paths with one "Open" button
|
||||
/// per row. Windows operators who never touch a shell still
|
||||
/// need a way to inspect the audit DB or edit the operator
|
||||
|
|
@ -374,10 +393,33 @@ class _DaemonPathsPanel extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
final theme = Theme.of(context);
|
||||
return ChainCard(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isVolatilePath(paths.dbPath)) ...[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.warning_amber_outlined,
|
||||
size: 16,
|
||||
color: ChainColors.warning,
|
||||
),
|
||||
const SizedBox(width: ChainSpace.sm),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l.doctorDbVolatileWarning,
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: ChainSpace.sm),
|
||||
],
|
||||
for (final e in entries)
|
||||
_PathRow(label: e.$1, path: e.$2, icon: e.$3, isDirectory: e.$4),
|
||||
],
|
||||
|
|
@ -512,10 +554,20 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
|||
bool _binaryMissing = false;
|
||||
ChannelStatusSnapshot? _channels;
|
||||
|
||||
/// Trimmed length of `~/.chain/hub-auth-token`, or null when
|
||||
/// Studio talks to the hub anonymously. Length only — the
|
||||
/// secret itself never reaches the UI.
|
||||
int? _tokenChars;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_refreshStatus();
|
||||
HubAuthToken.charCount()
|
||||
.then((n) {
|
||||
if (mounted) setState(() => _tokenChars = n);
|
||||
})
|
||||
.catchError((_) {});
|
||||
}
|
||||
|
||||
Future<void> _refreshStatus() async {
|
||||
|
|
@ -528,6 +580,21 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
|||
}
|
||||
}
|
||||
|
||||
String _connectionLine(AppLocalizations l) {
|
||||
final ep = HubService.instance.currentEndpoint;
|
||||
final isLocal =
|
||||
ep.host == '127.0.0.1' || ep.host == 'localhost' || ep.host == '::1';
|
||||
final transport = ep.secure
|
||||
? l.doctorConnTls
|
||||
: isLocal
|
||||
? l.doctorConnPlainLocal
|
||||
: l.doctorConnPlainRemote;
|
||||
final auth = _tokenChars == null
|
||||
? l.doctorConnAnonymous
|
||||
: l.doctorConnToken(_tokenChars!);
|
||||
return l.doctorConnLine(ep.toString(), transport, auth);
|
||||
}
|
||||
|
||||
Future<void> _run(
|
||||
String label,
|
||||
Future<({bool ok, String stdout, String stderr})> Function() action,
|
||||
|
|
@ -626,6 +693,17 @@ class _DaemonActionsCardState extends State<_DaemonActionsCard> {
|
|||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
// Who-talks-to-whom-how in one line: endpoint,
|
||||
// transport security, and whether a bearer token is
|
||||
// attached — the auditor's baseline questions the
|
||||
// green "connected" dot alone cannot answer.
|
||||
SelectableText(
|
||||
_connectionLine(l),
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: ChainSpace.md),
|
||||
Wrap(
|
||||
spacing: ChainSpace.sm,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue