Mirrors the two new HubAdmin RPCs in fai_platform 0.10.44. Studio's Doctor page consumes these directly. Generated proto bindings refreshed to include VerifyEventChainResponse, ServiceList, DeclaredService. Bumps fai_dart_sdk 0.2.0 -> 0.3.0. Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
173 lines
5.4 KiB
Dart
173 lines
5.4 KiB
Dart
// HubClient — typed Dart wrapper around the F∆I gRPC surface.
|
|
//
|
|
// Holds a single grpc.ClientChannel keyed by [HubEndpoint]. Each
|
|
// public method maps onto exactly one RPC on Hub or HubAdmin, so
|
|
// callers don't have to know which service hosts which call.
|
|
|
|
import 'package:grpc/grpc.dart';
|
|
|
|
import 'generated/fai/v1/hub.pb.dart' as pb;
|
|
import 'generated/fai/v1/hub.pbgrpc.dart' as grpc_hub;
|
|
import 'generated/google/protobuf/empty.pb.dart' show Empty;
|
|
|
|
/// Connection coordinates for a hub endpoint.
|
|
class HubEndpoint {
|
|
/// Hostname or IP. Defaults to localhost for the canonical
|
|
/// `fai serve` setup.
|
|
final String host;
|
|
|
|
/// gRPC port. Defaults to 50051.
|
|
final int port;
|
|
|
|
/// When true, use TLS. Phase 1+ will add credential support.
|
|
final bool secure;
|
|
|
|
const HubEndpoint({
|
|
this.host = '127.0.0.1',
|
|
this.port = 50051,
|
|
this.secure = false,
|
|
});
|
|
|
|
@override
|
|
String toString() => '${secure ? "https" : "http"}://$host:$port';
|
|
}
|
|
|
|
/// Re-exports of generated protobuf types so callers don't have
|
|
/// to import the `generated/` directory directly.
|
|
typedef CapabilityEntry = pb.CapabilityEntry;
|
|
typedef CapabilityList = pb.CapabilityList;
|
|
typedef LoggedEvent = pb.LoggedEvent;
|
|
typedef PendingApprovalEntry = pb.PendingApprovalEntry;
|
|
typedef DeclaredService = pb.DeclaredService;
|
|
typedef VerifyEventChainResponse = pb.VerifyEventChainResponse;
|
|
|
|
/// Typed client for the F∆I Hub gRPC surface. Construct once,
|
|
/// reuse for the process lifetime, call [close] on shutdown.
|
|
class HubClient {
|
|
final HubEndpoint endpoint;
|
|
final ClientChannel _channel;
|
|
final grpc_hub.HubClient _hub;
|
|
final grpc_hub.HubAdminClient _admin;
|
|
|
|
HubClient({this.endpoint = const HubEndpoint()})
|
|
: _channel = ClientChannel(
|
|
endpoint.host,
|
|
port: endpoint.port,
|
|
options: ChannelOptions(
|
|
credentials: endpoint.secure
|
|
? const ChannelCredentials.secure()
|
|
: const ChannelCredentials.insecure(),
|
|
),
|
|
),
|
|
_hub = grpc_hub.HubClient(
|
|
ClientChannel(
|
|
endpoint.host,
|
|
port: endpoint.port,
|
|
options: ChannelOptions(
|
|
credentials: endpoint.secure
|
|
? const ChannelCredentials.secure()
|
|
: const ChannelCredentials.insecure(),
|
|
),
|
|
),
|
|
),
|
|
_admin = grpc_hub.HubAdminClient(
|
|
ClientChannel(
|
|
endpoint.host,
|
|
port: endpoint.port,
|
|
options: ChannelOptions(
|
|
credentials: endpoint.secure
|
|
? const ChannelCredentials.secure()
|
|
: const ChannelCredentials.insecure(),
|
|
),
|
|
),
|
|
);
|
|
|
|
/// Liveness probe. Returns true when the hub responds with
|
|
/// SERVING. False on any failure (includes connection refused).
|
|
Future<bool> healthy() async {
|
|
try {
|
|
final r = await _hub.health(Empty());
|
|
return r.state == pb.HealthStatus_State.SERVING;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// All capabilities provided by installed modules.
|
|
Future<List<CapabilityEntry>> listCapabilities() async {
|
|
final r = await _admin.listCapabilities(Empty());
|
|
return r.capabilities;
|
|
}
|
|
|
|
/// Recent events from the audit log, newest-first. Pass an
|
|
/// empty `types` list for all event types.
|
|
Future<List<LoggedEvent>> eventLog({
|
|
int limit = 100,
|
|
List<String> types = const [],
|
|
}) async {
|
|
final req = pb.EventLogRequest()
|
|
..limit = limit
|
|
..eventTypes.addAll(types);
|
|
final r = await _admin.eventLog(req);
|
|
return r.events;
|
|
}
|
|
|
|
/// Pending approvals filtered by status. Defaults to all.
|
|
Future<List<PendingApprovalEntry>> listApprovals({
|
|
List<String> statuses = const [],
|
|
int limit = 100,
|
|
}) async {
|
|
final req = pb.ListApprovalsRequest()
|
|
..limit = limit
|
|
..statuses.addAll(statuses);
|
|
final r = await _admin.listApprovals(req);
|
|
return r.entries;
|
|
}
|
|
|
|
/// Approve a pending approval. The flow that posted it resumes
|
|
/// on its next polling tick.
|
|
Future<void> approve({
|
|
required String approvalId,
|
|
required String reviewer,
|
|
}) async {
|
|
final req = pb.DecideApprovalRequest()
|
|
..approvalId = approvalId
|
|
..reviewer = reviewer
|
|
..decision = pb.DecideApprovalRequest_Decision.APPROVE;
|
|
await _admin.decideApproval(req);
|
|
}
|
|
|
|
/// Reject a pending approval. The flow fails with the supplied
|
|
/// reason on its next polling tick.
|
|
Future<void> reject({
|
|
required String approvalId,
|
|
required String reviewer,
|
|
required String reason,
|
|
}) async {
|
|
final req = pb.DecideApprovalRequest()
|
|
..approvalId = approvalId
|
|
..reviewer = reviewer
|
|
..reason = reason
|
|
..decision = pb.DecideApprovalRequest_Decision.REJECT;
|
|
await _admin.decideApproval(req);
|
|
}
|
|
|
|
/// Walk the event-log hash chain on the hub. Returns total /
|
|
/// verified counts and an `tamperedAt` id (empty when clean).
|
|
Future<VerifyEventChainResponse> verifyEventChain() {
|
|
return _admin.verifyEventChain(Empty());
|
|
}
|
|
|
|
/// Host services declared in the operator config. Each entry
|
|
/// is a name + endpoint + tags; reachability is up to the
|
|
/// caller (typically a follow-up HTTP probe).
|
|
Future<List<DeclaredService>> listServices() async {
|
|
final r = await _admin.listServices(Empty());
|
|
return r.services;
|
|
}
|
|
|
|
/// Closes the gRPC channel. Idempotent.
|
|
Future<void> close() async {
|
|
await _channel.shutdown();
|
|
}
|
|
}
|