Add app source and tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
177
lib/Peach/see/redis/ViewRedis.dart
Normal file
177
lib/Peach/see/redis/ViewRedis.dart
Normal file
@@ -0,0 +1,177 @@
|
||||
import '../../about/external/data/Redis.dart';
|
||||
import '../../about/internal/application/ServerProfile.dart';
|
||||
import '../parent/ParentViewModel.dart';
|
||||
import 'ConnectRedis.dart';
|
||||
|
||||
class ViewRedis extends ParentViewModel {
|
||||
ConnectRedis connection;
|
||||
|
||||
ViewRedis(super.context, this.connection);
|
||||
|
||||
/// Pings Redis, then reads the database list, the current DB's size and the
|
||||
/// keys matching [pattern]. On a NOAUTH bounce with no password entered, it
|
||||
/// reads `requirepass` out of redis.conf once and retries — the common case
|
||||
/// where the user has SSH access but hasn't been told the Redis password.
|
||||
void scan(
|
||||
ServerProfile server, {
|
||||
required String host,
|
||||
required int port,
|
||||
required String password,
|
||||
required int database,
|
||||
required String pattern,
|
||||
required String typeFilter,
|
||||
bool triedDetect = false,
|
||||
}) async {
|
||||
if (!await canReach(
|
||||
server,
|
||||
() => scan(
|
||||
server,
|
||||
host: host,
|
||||
port: port,
|
||||
password: password,
|
||||
database: database,
|
||||
pattern: pattern,
|
||||
typeFilter: typeFilter,
|
||||
triedDetect: triedDetect,
|
||||
),
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final (ok, detail) = await getDataManager().redisPing(
|
||||
server,
|
||||
host: host,
|
||||
port: port,
|
||||
pass: password,
|
||||
db: database,
|
||||
);
|
||||
|
||||
if (!ok) {
|
||||
if (!triedDetect &&
|
||||
detail.toUpperCase().contains('NOAUTH') &&
|
||||
password.trim().isEmpty) {
|
||||
final found = await getDataManager().redisConfigPassword(server);
|
||||
if (found.isNotEmpty) {
|
||||
connection.onPasswordDetected(found);
|
||||
return;
|
||||
}
|
||||
}
|
||||
connection.onScanFailed(_hint(detail, host, port));
|
||||
return;
|
||||
}
|
||||
|
||||
final effectivePattern = pattern.trim().isEmpty ? '*' : pattern.trim();
|
||||
|
||||
final (databaseCount, databaseCounts) = await getDataManager()
|
||||
.redisDatabases(server, host: host, port: port, pass: password);
|
||||
final total = await getDataManager().redisDbSize(
|
||||
server,
|
||||
host: host,
|
||||
port: port,
|
||||
pass: password,
|
||||
db: database,
|
||||
);
|
||||
|
||||
final keys = <String>[];
|
||||
final keyTypes = <String, String>{};
|
||||
|
||||
if (typeFilter == 'all') {
|
||||
keys.addAll(
|
||||
await getDataManager().redisKeys(
|
||||
server,
|
||||
pattern: effectivePattern,
|
||||
host: host,
|
||||
port: port,
|
||||
pass: password,
|
||||
db: database,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final typed = await getDataManager().redisKeysTyped(
|
||||
server,
|
||||
pattern: effectivePattern,
|
||||
filterType: typeFilter,
|
||||
host: host,
|
||||
port: port,
|
||||
pass: password,
|
||||
db: database,
|
||||
);
|
||||
for (final (key, type) in typed) {
|
||||
keys.add(key);
|
||||
keyTypes[key] = type;
|
||||
}
|
||||
}
|
||||
|
||||
connection.onScanned(
|
||||
RedisScan(
|
||||
databaseCount: databaseCount,
|
||||
databaseCounts: databaseCounts,
|
||||
total: total,
|
||||
keys: keys,
|
||||
keyTypes: keyTypes,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
connection.onScanFailed(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads `requirepass` from the server's redis.conf.
|
||||
void detectPassword(ServerProfile server) async {
|
||||
if (!await canReach(server, () => detectPassword(server))) return;
|
||||
try {
|
||||
final found = await getDataManager().redisConfigPassword(server);
|
||||
if (found.isEmpty) {
|
||||
connection.onPasswordMissing();
|
||||
} else {
|
||||
connection.onPasswordDetected(found);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, () => detectPassword(server), () {}, "Retry");
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads one key. A failure is surfaced as an `error`-typed entry rather
|
||||
/// than an overlay — the value panel is the natural place to show it.
|
||||
void loadValue(
|
||||
ServerProfile server,
|
||||
String key, {
|
||||
required String host,
|
||||
required int port,
|
||||
required String password,
|
||||
required int database,
|
||||
}) async {
|
||||
try {
|
||||
final entry = await getDataManager().redisGet(
|
||||
server,
|
||||
key,
|
||||
host: host,
|
||||
port: port,
|
||||
pass: password,
|
||||
db: database,
|
||||
);
|
||||
connection.onValueLoaded(entry);
|
||||
} catch (error) {
|
||||
connection.onValueLoaded(
|
||||
RedisEntry(key: key, type: 'error', ttl: '', value: '$error'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Turns a raw redis-cli complaint into something the user can act on.
|
||||
String _hint(String detail, String host, int port) {
|
||||
final message = detail.isEmpty ? 'Redis did not respond.' : detail;
|
||||
if (message.toUpperCase().contains('NOAUTH')) {
|
||||
return 'Redis requires a password. Enter it below (or tap Detect to read '
|
||||
'it from redis.conf), then Scan.';
|
||||
}
|
||||
if (message.toLowerCase().contains('wrongpass')) {
|
||||
return 'Wrong Redis password. Fix it and Scan again.';
|
||||
}
|
||||
if (message.toLowerCase().contains('connection refused')) {
|
||||
return 'Nothing is listening on $host:$port. Check the host/port '
|
||||
'(Redis in Docker often maps a different port).';
|
||||
}
|
||||
return message;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user