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:
parent
c5e11395fb
commit
73dbdc16ef
5 changed files with 262 additions and 2 deletions
|
|
@ -777,6 +777,7 @@ class LoggedEvent extends $pb.GeneratedMessage {
|
|||
$core.String? moduleVersion,
|
||||
$fixnum.Int64? durationMs,
|
||||
$core.String? error,
|
||||
$core.String? detail,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (eventId != null) {
|
||||
|
|
@ -812,6 +813,9 @@ class LoggedEvent extends $pb.GeneratedMessage {
|
|||
if (error != null) {
|
||||
$result.error = error;
|
||||
}
|
||||
if (detail != null) {
|
||||
$result.detail = detail;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
LoggedEvent._() : super();
|
||||
|
|
@ -830,6 +834,7 @@ class LoggedEvent extends $pb.GeneratedMessage {
|
|||
..aOS(9, _omitFieldNames ? '' : 'moduleVersion')
|
||||
..aInt64(10, _omitFieldNames ? '' : 'durationMs')
|
||||
..aOS(11, _omitFieldNames ? '' : 'error')
|
||||
..aOS(12, _omitFieldNames ? '' : 'detail')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -946,6 +951,19 @@ class LoggedEvent extends $pb.GeneratedMessage {
|
|||
$core.bool hasError() => $_has(10);
|
||||
@$pb.TagNumber(11)
|
||||
void clearError() => $_clearField(11);
|
||||
|
||||
/// Free-form JSON-string carrying event-type-specific fields:
|
||||
/// input/output sizes, model digest, sha256, etc. Empty when
|
||||
/// the event type has no structured detail. Studio renders
|
||||
/// this as a code block in the audit drill-down.
|
||||
@$pb.TagNumber(12)
|
||||
$core.String get detail => $_getSZ(11);
|
||||
@$pb.TagNumber(12)
|
||||
set detail($core.String v) { $_setString(11, v); }
|
||||
@$pb.TagNumber(12)
|
||||
$core.bool hasDetail() => $_has(11);
|
||||
@$pb.TagNumber(12)
|
||||
void clearDetail() => $_clearField(12);
|
||||
}
|
||||
|
||||
class SaveFlowRequest extends $pb.GeneratedMessage {
|
||||
|
|
@ -2457,6 +2475,149 @@ class StoreEntry extends $pb.GeneratedMessage {
|
|||
void clearInstalled() => $_clearField(14);
|
||||
}
|
||||
|
||||
class ChannelStatusResponse extends $pb.GeneratedMessage {
|
||||
factory ChannelStatusResponse({
|
||||
$core.String? active,
|
||||
$core.Iterable<ChannelEntry>? channels,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (active != null) {
|
||||
$result.active = active;
|
||||
}
|
||||
if (channels != null) {
|
||||
$result.channels.addAll(channels);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ChannelStatusResponse._() : super();
|
||||
factory ChannelStatusResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ChannelStatusResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ChannelStatusResponse', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'active')
|
||||
..pc<ChannelEntry>(2, _omitFieldNames ? '' : 'channels', $pb.PbFieldType.PM, subBuilder: ChannelEntry.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ChannelStatusResponse clone() => ChannelStatusResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ChannelStatusResponse copyWith(void Function(ChannelStatusResponse) updates) => super.copyWith((message) => updates(message as ChannelStatusResponse)) as ChannelStatusResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ChannelStatusResponse create() => ChannelStatusResponse._();
|
||||
ChannelStatusResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<ChannelStatusResponse> createRepeated() => $pb.PbList<ChannelStatusResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ChannelStatusResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ChannelStatusResponse>(create);
|
||||
static ChannelStatusResponse? _defaultInstance;
|
||||
|
||||
/// Active channel pointer from `~/.fai/current-channel`.
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get active => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set active($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasActive() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearActive() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$pb.PbList<ChannelEntry> get channels => $_getList(1);
|
||||
}
|
||||
|
||||
class ChannelEntry extends $pb.GeneratedMessage {
|
||||
factory ChannelEntry({
|
||||
$core.String? name,
|
||||
$core.int? port,
|
||||
$core.bool? running,
|
||||
$core.String? endpoint,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (name != null) {
|
||||
$result.name = name;
|
||||
}
|
||||
if (port != null) {
|
||||
$result.port = port;
|
||||
}
|
||||
if (running != null) {
|
||||
$result.running = running;
|
||||
}
|
||||
if (endpoint != null) {
|
||||
$result.endpoint = endpoint;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
ChannelEntry._() : super();
|
||||
factory ChannelEntry.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory ChannelEntry.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ChannelEntry', package: const $pb.PackageName(_omitMessageNames ? '' : 'fai.v1'), createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'name')
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'port', $pb.PbFieldType.OU3)
|
||||
..aOB(3, _omitFieldNames ? '' : 'running')
|
||||
..aOS(4, _omitFieldNames ? '' : 'endpoint')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ChannelEntry clone() => ChannelEntry()..mergeFromMessage(this);
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
ChannelEntry copyWith(void Function(ChannelEntry) updates) => super.copyWith((message) => updates(message as ChannelEntry)) as ChannelEntry;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ChannelEntry create() => ChannelEntry._();
|
||||
ChannelEntry createEmptyInstance() => create();
|
||||
static $pb.PbList<ChannelEntry> createRepeated() => $pb.PbList<ChannelEntry>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ChannelEntry getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ChannelEntry>(create);
|
||||
static ChannelEntry? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get name => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set name($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasName() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearName() => $_clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get port => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set port($core.int v) { $_setUnsignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasPort() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearPort() => $_clearField(2);
|
||||
|
||||
/// True iff a daemon is currently bound to that channel's port
|
||||
/// (PID file present + process alive).
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool get running => $_getBF(2);
|
||||
@$pb.TagNumber(3)
|
||||
set running($core.bool v) { $_setBool(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasRunning() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearRunning() => $_clearField(3);
|
||||
|
||||
/// Endpoint string from `~/.fai/run/<channel>.endpoint` when
|
||||
/// the daemon is up, otherwise empty.
|
||||
@$pb.TagNumber(4)
|
||||
$core.String get endpoint => $_getSZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set endpoint($core.String v) { $_setString(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasEndpoint() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearEndpoint() => $_clearField(4);
|
||||
}
|
||||
|
||||
class CheckUpdateResponse extends $pb.GeneratedMessage {
|
||||
factory CheckUpdateResponse({
|
||||
$core.String? channel,
|
||||
|
|
|
|||
|
|
@ -183,6 +183,10 @@ class HubAdminClient extends $grpc.Client {
|
|||
'/fai.v1.HubAdmin/CheckUpdate',
|
||||
($1.Empty value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.CheckUpdateResponse.fromBuffer(value));
|
||||
static final _$channelStatus = $grpc.ClientMethod<$1.Empty, $0.ChannelStatusResponse>(
|
||||
'/fai.v1.HubAdmin/ChannelStatus',
|
||||
($1.Empty value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.ChannelStatusResponse.fromBuffer(value));
|
||||
|
||||
HubAdminClient(super.channel, {super.options, super.interceptors});
|
||||
|
||||
|
|
@ -272,6 +276,13 @@ class HubAdminClient extends $grpc.Client {
|
|||
$grpc.ResponseFuture<$0.CheckUpdateResponse> checkUpdate($1.Empty request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$checkUpdate, request, options: options);
|
||||
}
|
||||
|
||||
/// Active channel + per-channel daemon status. Lets Studio's
|
||||
/// settings page render which channel is current and whether
|
||||
/// each daemon is up. Mirrors the CLI's `fai daemon status`.
|
||||
$grpc.ResponseFuture<$0.ChannelStatusResponse> channelStatus($1.Empty request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$channelStatus, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('fai.v1.HubAdmin')
|
||||
|
|
@ -384,6 +395,13 @@ abstract class HubAdminServiceBase extends $grpc.Service {
|
|||
false,
|
||||
($core.List<$core.int> value) => $1.Empty.fromBuffer(value),
|
||||
($0.CheckUpdateResponse value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$1.Empty, $0.ChannelStatusResponse>(
|
||||
'ChannelStatus',
|
||||
channelStatus_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $1.Empty.fromBuffer(value),
|
||||
($0.ChannelStatusResponse value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.CapabilityList> listCapabilities_Pre($grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async {
|
||||
|
|
@ -446,6 +464,10 @@ abstract class HubAdminServiceBase extends $grpc.Service {
|
|||
return checkUpdate($call, await $request);
|
||||
}
|
||||
|
||||
$async.Future<$0.ChannelStatusResponse> channelStatus_Pre($grpc.ServiceCall $call, $async.Future<$1.Empty> $request) async {
|
||||
return channelStatus($call, await $request);
|
||||
}
|
||||
|
||||
$async.Future<$0.CapabilityList> listCapabilities($grpc.ServiceCall call, $1.Empty request);
|
||||
$async.Future<$0.ModuleInfoResponse> moduleInfo($grpc.ServiceCall call, $0.ModuleInfoRequest request);
|
||||
$async.Future<$0.CheckPermissionResponse> checkPermission($grpc.ServiceCall call, $0.CheckPermissionRequest request);
|
||||
|
|
@ -461,4 +483,5 @@ abstract class HubAdminServiceBase extends $grpc.Service {
|
|||
$async.Future<$0.VerifyEventChainResponse> verifyEventChain($grpc.ServiceCall call, $1.Empty request);
|
||||
$async.Future<$0.ServiceList> listServices($grpc.ServiceCall call, $1.Empty request);
|
||||
$async.Future<$0.CheckUpdateResponse> checkUpdate($grpc.ServiceCall call, $1.Empty request);
|
||||
$async.Future<$0.ChannelStatusResponse> channelStatus($grpc.ServiceCall call, $1.Empty request);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ const LoggedEvent$json = {
|
|||
{'1': 'module_version', '3': 9, '4': 1, '5': 9, '10': 'moduleVersion'},
|
||||
{'1': 'duration_ms', '3': 10, '4': 1, '5': 3, '10': 'durationMs'},
|
||||
{'1': 'error', '3': 11, '4': 1, '5': 9, '10': 'error'},
|
||||
{'1': 'detail', '3': 12, '4': 1, '5': 9, '10': 'detail'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -264,7 +265,7 @@ final $typed_data.Uint8List loggedEventDescriptor = $convert.base64Decode(
|
|||
'52b2NhdGlvbklkEhsKCWZsb3dfbmFtZRgGIAEoCVIIZmxvd05hbWUSFwoHc3RlcF9pZBgHIAEo'
|
||||
'CVIGc3RlcElkEh8KC21vZHVsZV9uYW1lGAggASgJUgptb2R1bGVOYW1lEiUKDm1vZHVsZV92ZX'
|
||||
'JzaW9uGAkgASgJUg1tb2R1bGVWZXJzaW9uEh8KC2R1cmF0aW9uX21zGAogASgDUgpkdXJhdGlv'
|
||||
'bk1zEhQKBWVycm9yGAsgASgJUgVlcnJvcg==');
|
||||
'bk1zEhQKBWVycm9yGAsgASgJUgVlcnJvchIWCgZkZXRhaWwYDCABKAlSBmRldGFpbA==');
|
||||
|
||||
@$core.Deprecated('Use saveFlowRequestDescriptor instead')
|
||||
const SaveFlowRequest$json = {
|
||||
|
|
@ -612,6 +613,36 @@ final $typed_data.Uint8List storeEntryDescriptor = $convert.base64Decode(
|
|||
'Npb24YDCABKAlSC2Jlc3RWZXJzaW9uEhYKBnN0YXR1cxgNIAEoCVIGc3RhdHVzEhwKCWluc3Rh'
|
||||
'bGxlZBgOIAEoCFIJaW5zdGFsbGVk');
|
||||
|
||||
@$core.Deprecated('Use channelStatusResponseDescriptor instead')
|
||||
const ChannelStatusResponse$json = {
|
||||
'1': 'ChannelStatusResponse',
|
||||
'2': [
|
||||
{'1': 'active', '3': 1, '4': 1, '5': 9, '10': 'active'},
|
||||
{'1': 'channels', '3': 2, '4': 3, '5': 11, '6': '.fai.v1.ChannelEntry', '10': 'channels'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ChannelStatusResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List channelStatusResponseDescriptor = $convert.base64Decode(
|
||||
'ChVDaGFubmVsU3RhdHVzUmVzcG9uc2USFgoGYWN0aXZlGAEgASgJUgZhY3RpdmUSMAoIY2hhbm'
|
||||
'5lbHMYAiADKAsyFC5mYWkudjEuQ2hhbm5lbEVudHJ5UghjaGFubmVscw==');
|
||||
|
||||
@$core.Deprecated('Use channelEntryDescriptor instead')
|
||||
const ChannelEntry$json = {
|
||||
'1': 'ChannelEntry',
|
||||
'2': [
|
||||
{'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
|
||||
{'1': 'port', '3': 2, '4': 1, '5': 13, '10': 'port'},
|
||||
{'1': 'running', '3': 3, '4': 1, '5': 8, '10': 'running'},
|
||||
{'1': 'endpoint', '3': 4, '4': 1, '5': 9, '10': 'endpoint'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ChannelEntry`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List channelEntryDescriptor = $convert.base64Decode(
|
||||
'CgxDaGFubmVsRW50cnkSEgoEbmFtZRgBIAEoCVIEbmFtZRISCgRwb3J0GAIgASgNUgRwb3J0Eh'
|
||||
'gKB3J1bm5pbmcYAyABKAhSB3J1bm5pbmcSGgoIZW5kcG9pbnQYBCABKAlSCGVuZHBvaW50');
|
||||
|
||||
@$core.Deprecated('Use checkUpdateResponseDescriptor instead')
|
||||
const CheckUpdateResponse$json = {
|
||||
'1': 'CheckUpdateResponse',
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue