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 = []; final keyTypes = {}; 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; } }