Add app source and tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
36
lib/Peach/see/kafka/ConnectKafka.dart
Normal file
36
lib/Peach/see/kafka/ConnectKafka.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import '../../about/external/data/Kafka.dart';
|
||||
|
||||
/// One scan's worth of cluster state.
|
||||
class KafkaScan {
|
||||
final List<KafkaTopic> topics;
|
||||
final List<KafkaConsumerGroup> groups;
|
||||
final KafkaCluster cluster;
|
||||
|
||||
const KafkaScan({
|
||||
required this.topics,
|
||||
required this.groups,
|
||||
required this.cluster,
|
||||
});
|
||||
|
||||
/// Nothing came back at all — usually an unreachable bootstrap server rather
|
||||
/// than a genuinely empty cluster.
|
||||
bool get isEmpty =>
|
||||
topics.isEmpty && groups.isEmpty && cluster.brokers.isEmpty;
|
||||
}
|
||||
|
||||
abstract class ConnectKafka {
|
||||
/// Whether the Kafka CLI tools were found, and where.
|
||||
void onKafkaDetected(KafkaStatus status);
|
||||
|
||||
/// Detection itself failed (the server couldn't be probed).
|
||||
void onDetectFailed(String message);
|
||||
|
||||
/// Topics, groups and cluster health, freshly read.
|
||||
void onScanned(KafkaScan scan);
|
||||
|
||||
/// The scan failed.
|
||||
void onScanFailed(String message);
|
||||
|
||||
/// Output of the broker probe, to show in a sheet.
|
||||
void onProbeOutput(String title, String output);
|
||||
}
|
||||
16
lib/Peach/see/kafka/Kafka.dart
Normal file
16
lib/Peach/see/kafka/Kafka.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
import '../../about/internal/application/ServerProfile.dart';
|
||||
import 'KafkaState.dart';
|
||||
|
||||
/// Live Kafka monitoring over SSH: topics with message offsets, consumer
|
||||
/// groups with lag, and cluster/broker health. Everything comes from the
|
||||
/// standard Kafka CLI tools run on the server.
|
||||
class Kafka extends StatefulWidget {
|
||||
final ServerProfile server;
|
||||
|
||||
const Kafka({super.key, required this.server});
|
||||
|
||||
@override
|
||||
State<Kafka> createState() => KafkaState();
|
||||
}
|
||||
1179
lib/Peach/see/kafka/KafkaState.dart
Normal file
1179
lib/Peach/see/kafka/KafkaState.dart
Normal file
File diff suppressed because it is too large
Load Diff
94
lib/Peach/see/kafka/ViewKafka.dart
Normal file
94
lib/Peach/see/kafka/ViewKafka.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
import '../../about/internal/application/ServerProfile.dart';
|
||||
import '../parent/ParentViewModel.dart';
|
||||
import 'ConnectKafka.dart';
|
||||
|
||||
class ViewKafka extends ParentViewModel {
|
||||
ConnectKafka connection;
|
||||
|
||||
ViewKafka(super.context, this.connection);
|
||||
|
||||
/// Looks for the Kafka CLI tools on PATH and in the usual install dirs.
|
||||
void detectKafka(ServerProfile server) async {
|
||||
if (!await canReach(server, () => detectKafka(server))) return;
|
||||
try {
|
||||
final status = await getDataManager().kafkaDetect(server);
|
||||
connection.onKafkaDetected(status);
|
||||
} catch (error) {
|
||||
connection.onDetectFailed(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads topics, consumer groups and cluster health in one pass. They're
|
||||
/// three separate CLI invocations, so this is where they're sequenced.
|
||||
void scan(
|
||||
ServerProfile server, {
|
||||
required String binDir,
|
||||
required String suffix,
|
||||
required String bootstrap,
|
||||
required bool includeInternal,
|
||||
}) async {
|
||||
try {
|
||||
final topics = await getDataManager().kafkaTopics(
|
||||
server,
|
||||
binDir: binDir,
|
||||
suffix: suffix,
|
||||
bootstrap: bootstrap,
|
||||
includeInternal: includeInternal,
|
||||
);
|
||||
final groups = await getDataManager().kafkaGroups(
|
||||
server,
|
||||
binDir: binDir,
|
||||
suffix: suffix,
|
||||
bootstrap: bootstrap,
|
||||
);
|
||||
final cluster = await getDataManager().kafkaCluster(
|
||||
server,
|
||||
binDir: binDir,
|
||||
suffix: suffix,
|
||||
bootstrap: bootstrap,
|
||||
);
|
||||
|
||||
connection.onScanned(
|
||||
KafkaScan(topics: topics, groups: groups, cluster: cluster),
|
||||
);
|
||||
} catch (error) {
|
||||
connection.onScanFailed(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs `kafka-broker-api-versions` so the user can see exactly what the
|
||||
/// broker says when a scan comes back empty.
|
||||
void probeBroker(
|
||||
ServerProfile server, {
|
||||
required String binDir,
|
||||
required String suffix,
|
||||
required String bootstrap,
|
||||
}) async {
|
||||
showLoading("Probing $bootstrap …");
|
||||
try {
|
||||
final output = await getDataManager().kafkaTest(
|
||||
server,
|
||||
binDir: binDir,
|
||||
suffix: suffix,
|
||||
bootstrap: bootstrap,
|
||||
);
|
||||
closeLoading();
|
||||
connection.onProbeOutput(
|
||||
'kafka-broker-api-versions · $bootstrap',
|
||||
output.isEmpty ? '(no output — broker unreachable?)' : output,
|
||||
);
|
||||
} catch (error) {
|
||||
handleError(
|
||||
error,
|
||||
() => probeBroker(
|
||||
server,
|
||||
binDir: binDir,
|
||||
suffix: suffix,
|
||||
bootstrap: bootstrap,
|
||||
),
|
||||
() {},
|
||||
"Retry",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user