feat: store + install + channel status (v0.3.0)

Catches the Dart bindings up to the platform's gRPC additions:
  * `searchStore({query, category, tag, status, limit})` — wraps
    HubAdmin.SearchStore so Studio can build a Store browser
    page.
  * `installModule({source, expectedSha256})` — wraps
    HubAdmin.InstallModule so the operator can one-click install
    a `.fai` bundle from the UI.
  * `channelStatus()` — new HubAdmin.ChannelStatus RPC. Returns
    active channel name + per-channel running flag + endpoint.

Re-exports the new proto types (StoreEntry,
StoreSearchResponse, InstallModuleResponse, ChannelStatusResponse,
ChannelEntry) so callers don't import protobuf directly.

LoggedEvent gains a `detail` field (free-form JSON string)
upstream; the regenerated bindings expose it without further
work — Studio's audit drill-down renders it pretty-printed.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-07 11:54:30 +02:00
parent c5e11395fb
commit 73dbdc16ef
5 changed files with 262 additions and 2 deletions

View file

@ -42,8 +42,13 @@ typedef PendingApprovalEntry = pb.PendingApprovalEntry;
typedef DeclaredService = pb.DeclaredService;
typedef VerifyEventChainResponse = pb.VerifyEventChainResponse;
typedef CheckUpdateResponse = pb.CheckUpdateResponse;
typedef ChannelStatusResponse = pb.ChannelStatusResponse;
typedef ChannelEntry = pb.ChannelEntry;
typedef ModuleInfoResponse = pb.ModuleInfoResponse;
typedef FlowSummary = pb.FlowSummary;
typedef StoreEntry = pb.StoreEntry;
typedef StoreSearchResponse = pb.StoreSearchResponse;
typedef InstallModuleResponse = pb.InstallModuleResponse;
typedef Payload = pb_common.Payload;
typedef SubmitResponse = pb.SubmitResponse;
@ -172,6 +177,46 @@ class HubClient {
return _admin.checkUpdate(Empty());
}
/// Active channel name + per-channel daemon status. Studio's
/// settings page calls this to render which channel is current
/// and whether each daemon is up.
Future<ChannelStatusResponse> channelStatus() {
return _admin.channelStatus(Empty());
}
/// Search the hub's bundled store index. All filters are
/// optional; an empty query returns the first [limit] entries
/// the index ships with.
Future<List<StoreEntry>> searchStore({
String query = '',
String category = '',
String tag = '',
String status = '',
int limit = 50,
}) async {
final r = await _admin.searchStore(pb.SearchStoreRequest(
query: query,
category: category,
tag: tag,
status: status,
limit: limit,
));
return r.entries;
}
/// Install a module from a `.fai` bundle. [source] is either a
/// URL or a local filesystem path; [expectedSha256] is an
/// optional hex digest the hub verifies before unpacking.
Future<InstallModuleResponse> installModule({
required String source,
String expectedSha256 = '',
}) {
return _admin.installModule(pb.InstallModuleRequest(
source: source,
expectedSha256: expectedSha256,
));
}
/// All saved flows known to the hub. Each entry carries the
/// flow name, on-disk path and byte size.
Future<List<FlowSummary>> listFlows() async {