178 lines
5.0 KiB
Dart
178 lines
5.0 KiB
Dart
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;
|
|
}
|
|
}
|