491 lines
17 KiB
Python
491 lines
17 KiB
Python
from flask import Flask, jsonify, request, render_template_string
|
|
import redis
|
|
import time
|
|
import os
|
|
import json
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Connect to Redis (same pod)
|
|
redis_client = redis.Redis(
|
|
host='localhost',
|
|
port=6379,
|
|
decode_responses=True
|
|
)
|
|
|
|
# HTML template for the interface
|
|
HTML_TEMPLATE = '''
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Redis Cache Visualizer</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 20px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
}
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
background: rgba(255,255,255,0.1);
|
|
padding: 30px;
|
|
border-radius: 15px;
|
|
}
|
|
.section {
|
|
background: rgba(255,255,255,0.1);
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
border-radius: 10px;
|
|
}
|
|
.key-item {
|
|
background: rgba(0,0,0,0.2);
|
|
margin: 5px 0;
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
font-family: monospace;
|
|
}
|
|
.value {
|
|
color: #90EE90;
|
|
font-weight: bold;
|
|
}
|
|
button {
|
|
background: #4CAF50;
|
|
color: white;
|
|
border: none;
|
|
padding: 10px 20px;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
margin: 5px;
|
|
}
|
|
button:hover {
|
|
background: #45a049;
|
|
}
|
|
input {
|
|
padding: 8px;
|
|
margin: 5px;
|
|
border-radius: 5px;
|
|
border: 1px solid #ccc;
|
|
}
|
|
.redis-logo {
|
|
font-size: 2em;
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
color: #DC382D;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
}
|
|
.stat {
|
|
display: inline-block;
|
|
background: rgba(0,0,0,0.3);
|
|
padding: 15px;
|
|
margin: 10px;
|
|
border-radius: 10px;
|
|
text-align: center;
|
|
}
|
|
.stat-number {
|
|
font-size: 2em;
|
|
font-weight: bold;
|
|
color: #FFD700;
|
|
}
|
|
.stat-label {
|
|
font-size: 0.9em;
|
|
opacity: 0.8;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="redis-logo">Redis</div>
|
|
<h1 style="text-align: center;">Redis Cache Visualizer</h1>
|
|
<p style="text-align: center; font-size: 1.2em;">Interactive Redis data management and visualization</p>
|
|
|
|
<div class="section">
|
|
<h2>📊 Redis Statistics</h2>
|
|
<div style="text-align: center;">
|
|
<div class="stat">
|
|
<div class="stat-number">{{ stats.total_keys }}</div>
|
|
<div class="stat-label">Total Keys</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-number">{{ stats.used_memory_human }}</div>
|
|
<div class="stat-label">Memory Used</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-number">{{ stats.connected_clients }}</div>
|
|
<div class="stat-label">Connected Clients</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-number">{{ stats.uptime_in_seconds // 60 }}</div>
|
|
<div class="stat-label">Uptime (min)</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>🔧 Quick Actions</h2>
|
|
<button onclick="addSampleData()">Add Sample Data</button>
|
|
<button onclick="clearAll()">Clear All Data</button>
|
|
<button onclick="refreshData()">Refresh</button>
|
|
<button onclick="showMemoryInfo()">Memory Info</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>📝 Add Data</h2>
|
|
<input type="text" id="keyInput" placeholder="Enter key...">
|
|
<input type="text" id="valueInput" placeholder="Enter value...">
|
|
<input type="number" id="ttlInput" placeholder="TTL (seconds, optional)" min="0">
|
|
<button onclick="addData()">Add to Redis</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>🔍 Search Keys</h2>
|
|
<input type="text" id="patternInput" placeholder="Search pattern (e.g., user:*)">
|
|
<button onclick="searchKeys()">Search</button>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>📋 All Redis Keys</h2>
|
|
<div id="keysList">
|
|
{% for key, value in redis_data.items() %}
|
|
<div class="key-item">
|
|
<strong>{{ key }}</strong>: <span class="value">{{ value }}</span>
|
|
<button onclick="getKey('{{ key }}')">View</button>
|
|
<button onclick="deleteKey('{{ key }}')">Delete</button>
|
|
<button onclick="updateKey('{{ key }}')">Update</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>📈 Cache Performance Examples</h2>
|
|
<button onclick="demoCache()">Simulate API Cache</button>
|
|
<button onclick="demoSession()">Simulate Session Store</button>
|
|
<button onclick="demoCounter()">Demo Counter/Rate Limiting</button>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function refreshData() {
|
|
window.location.reload();
|
|
}
|
|
|
|
function addSampleData() {
|
|
fetch('/api/add_sample', {method: 'POST'})
|
|
.then(() => refreshData());
|
|
}
|
|
|
|
function clearAll() {
|
|
if (confirm('Are you sure you want to clear all data?')) {
|
|
fetch('/api/clear_all', {method: 'POST'})
|
|
.then(() => refreshData());
|
|
}
|
|
}
|
|
|
|
function addData() {
|
|
const key = document.getElementById('keyInput').value;
|
|
const value = document.getElementById('valueInput').value;
|
|
const ttl = document.getElementById('ttlInput').value;
|
|
|
|
fetch('/api/set', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({key, value, ttl: ttl ? parseInt(ttl) : null})
|
|
}).then(() => refreshData());
|
|
}
|
|
|
|
function searchKeys() {
|
|
const pattern = document.getElementById('patternInput').value;
|
|
fetch('/api/search/' + pattern)
|
|
.then(response => response.json())
|
|
.then(data => displayKeys(data));
|
|
}
|
|
|
|
function deleteKey(key) {
|
|
fetch('/api/delete/' + key, {method: 'DELETE'})
|
|
.then(() => refreshData());
|
|
}
|
|
|
|
function getKey(key) {
|
|
fetch('/api/get/' + key)
|
|
.then(response => response.json())
|
|
.then(data => alert('Key: ' + key + '\\nValue: ' + data.value + '\\nTTL: ' + data.ttl));
|
|
}
|
|
|
|
function updateKey(key) {
|
|
const newValue = prompt('Enter new value for ' + key);
|
|
if (newValue) {
|
|
fetch('/api/update', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({key, value: newValue})
|
|
}).then(() => refreshData());
|
|
}
|
|
}
|
|
|
|
function demoCache() {
|
|
fetch('/api/demo_cache', {method: 'POST'})
|
|
.then(() => refreshData());
|
|
}
|
|
|
|
function demoSession() {
|
|
fetch('/api/demo_session', {method: 'POST'})
|
|
.then(() => refreshData());
|
|
}
|
|
|
|
function demoCounter() {
|
|
fetch('/api/demo_counter', {method: 'POST'})
|
|
.then(() => refreshData());
|
|
}
|
|
|
|
function showMemoryInfo() {
|
|
fetch('/api/memory_info')
|
|
.then(response => response.json())
|
|
.then(data => alert('Memory Info:\\n' + JSON.stringify(data, null, 2)));
|
|
}
|
|
|
|
function displayKeys(keys) {
|
|
const keysList = document.getElementById('keysList');
|
|
keysList.innerHTML = '';
|
|
keys.forEach(key => {
|
|
const div = document.createElement('div');
|
|
div.className = 'key-item';
|
|
div.innerHTML = '<strong>' + key + '</strong> <button onclick="getKey(\'' + key + '\')">View</button> <button onclick="deleteKey(\'' + key + '\')">Delete</button>';
|
|
keysList.appendChild(div);
|
|
});
|
|
}
|
|
|
|
// Auto-refresh every 30 seconds
|
|
setInterval(refreshData, 30000);
|
|
</script>
|
|
</body>
|
|
</html>
|
|
'''
|
|
|
|
@app.route('/')
|
|
def index():
|
|
"""Main dashboard page"""
|
|
try:
|
|
# Get Redis statistics
|
|
info = redis_client.info()
|
|
|
|
# Get all keys (limited for display)
|
|
keys = redis_client.keys('*')
|
|
redis_data = {}
|
|
|
|
# Get values for keys (limit to first 20 for display)
|
|
for key in keys[:20]:
|
|
try:
|
|
value = redis_client.get(key)
|
|
ttl = redis_client.ttl(key)
|
|
if ttl > 0:
|
|
redis_data[key] = f"{value} (TTL: {ttl}s)"
|
|
else:
|
|
redis_data[key] = value
|
|
except:
|
|
redis_data[key] = "[Binary Data]"
|
|
|
|
# Format statistics for display
|
|
stats = {
|
|
'total_keys': len(keys),
|
|
'used_memory_human': info.get('used_memory_human', 'N/A'),
|
|
'connected_clients': info.get('connected_clients', 'N/A'),
|
|
'uptime_in_seconds': info.get('uptime_in_seconds', 'N/A'),
|
|
'total_commands_processed': info.get('total_commands_processed', 'N/A'),
|
|
'keyspace_hits': info.get('keyspace_hits', 'N/A'),
|
|
'keyspace_misses': info.get('keyspace_misses', 'N/A'),
|
|
'expired_keys': info.get('expired_keys', 'N/A'),
|
|
'evicted_keys': info.get('evicted_keys', 'N/A')
|
|
}
|
|
|
|
return render_template_string(HTML_TEMPLATE, redis_data=redis_data, stats=stats)
|
|
except Exception as e:
|
|
return f"<h1>Error connecting to Redis: {e}</h1>"
|
|
|
|
@app.route('/api/stats')
|
|
def get_stats():
|
|
"""API endpoint for Redis statistics"""
|
|
try:
|
|
info = redis_client.info()
|
|
return jsonify({
|
|
'status': 'success',
|
|
'data': {
|
|
'keys': len(redis_client.keys('*')),
|
|
'memory': info.get('used_memory_human'),
|
|
'clients': info.get('connected_clients'),
|
|
'uptime_minutes': info.get('uptime_in_seconds', 0) // 60
|
|
}
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/keys')
|
|
def get_keys():
|
|
"""API endpoint to get all keys"""
|
|
try:
|
|
keys = redis_client.keys('*')
|
|
return jsonify({'status': 'success', 'keys': keys})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/set', methods=['POST'])
|
|
def set_key():
|
|
"""API endpoint to set a key-value pair"""
|
|
try:
|
|
data = request.json
|
|
key = data.get('key')
|
|
value = data.get('value')
|
|
ttl = data.get('ttl')
|
|
|
|
if not key or value is None:
|
|
return jsonify({'status': 'error', 'message': 'Key and value are required'}), 400
|
|
|
|
if ttl and ttl > 0:
|
|
redis_client.setex(key, ttl, value)
|
|
else:
|
|
redis_client.set(key, value)
|
|
|
|
return jsonify({'status': 'success', 'message': f'Key {key} set successfully'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/get/<key>')
|
|
def get_key(key):
|
|
"""API endpoint to get a key-value pair"""
|
|
try:
|
|
value = redis_client.get(key)
|
|
ttl = redis_client.ttl(key)
|
|
if value is None:
|
|
return jsonify({'status': 'error', 'message': 'Key not found'}), 404
|
|
|
|
return jsonify({
|
|
'status': 'success',
|
|
'data': {
|
|
'key': key,
|
|
'value': value,
|
|
'ttl': ttl
|
|
}
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/delete/<key>', methods=['DELETE'])
|
|
def delete_key(key):
|
|
"""API endpoint to delete a key"""
|
|
try:
|
|
deleted = redis_client.delete(key)
|
|
if deleted == 0:
|
|
return jsonify({'status': 'error', 'message': 'Key not found'}), 404
|
|
|
|
return jsonify({'status': 'success', 'message': f'Key {key} deleted successfully'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/search/<pattern>')
|
|
def search_keys(pattern):
|
|
"""API endpoint to search for keys"""
|
|
try:
|
|
keys = redis_client.keys(pattern)
|
|
return jsonify({'status': 'success', 'keys': keys})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/add_sample', methods=['POST'])
|
|
def add_sample_data():
|
|
"""Add sample data for demonstration"""
|
|
try:
|
|
# Add various types of data
|
|
redis_client.set('user:1001', 'Alice Johnson')
|
|
redis_client.set('user:1002', 'Bob Smith')
|
|
redis_client.set('session:abc123', 'logged_in')
|
|
redis_client.setex('temp:token', 300, 'temporary_data')
|
|
redis_client.set('api:request:count', '0')
|
|
redis_client.set('cache:homepage', 'Cached homepage content')
|
|
redis_client.setex('user:login:last', 3600, '2025-11-04T17:00:00')
|
|
|
|
return jsonify({'status': 'success', 'message': 'Sample data added'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/clear_all', methods=['POST'])
|
|
def clear_all():
|
|
"""Clear all Redis data"""
|
|
try:
|
|
redis_client.flushall()
|
|
return jsonify({'status': 'success', 'message': 'All data cleared'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/demo_cache', methods=['POST'])
|
|
def demo_cache():
|
|
"""Demonstrate caching patterns"""
|
|
try:
|
|
# Simulate cache-aside pattern
|
|
redis_client.setex('api:users:1001', 600, 'User data from database')
|
|
redis_client.setex('api:posts:recent', 300, 'Recent blog posts')
|
|
redis_client.setex('api:stats:daily', 1800, 'Daily statistics')
|
|
|
|
return jsonify({'status': 'success', 'message': 'Cache examples added'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/demo_session', methods=['POST'])
|
|
def demo_session():
|
|
"""Demonstrate session storage"""
|
|
try:
|
|
# Simulate session storage
|
|
redis_client.setex('session:user123', 3600, 'active')
|
|
redis_client.setex('session:user456', 7200, 'premium')
|
|
redis_client.setex('session:admin789', 1800, 'administrator')
|
|
|
|
return jsonify({'status': 'success', 'message': 'Session examples added'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/demo_counter', methods=['POST'])
|
|
def demo_counter():
|
|
"""Demonstrate counter/rate limiting"""
|
|
try:
|
|
# Simulate counters
|
|
redis_client.set('counter:api_requests', '0')
|
|
redis_client.set('counter:user_visits', '0')
|
|
redis_client.set('counter:page_views', '0')
|
|
|
|
return jsonify({'status': 'success', 'message': 'Counter examples added'})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
@app.route('/api/memory_info')
|
|
def memory_info():
|
|
"""Get detailed memory information"""
|
|
try:
|
|
info = redis_client.info('memory')
|
|
return jsonify({
|
|
'status': 'success',
|
|
'memory': {
|
|
'used_memory': info.get('used_memory'),
|
|
'used_memory_human': info.get('used_memory_human'),
|
|
'used_memory_rss': info.get('used_memory_rss'),
|
|
'maxmemory': info.get('maxmemory'),
|
|
'maxmemory_policy': info.get('maxmemory_policy'),
|
|
'mem_fragmentation_ratio': info.get('mem_fragmentation_ratio')
|
|
}
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'status': 'error', 'message': str(e)}), 500
|
|
|
|
if __name__ == '__main__':
|
|
print("Starting Redis Web Interface...")
|
|
print("Access Redis at: localhost:6379")
|
|
print("Access Web Interface at: localhost:8080")
|
|
|
|
# Test Redis connection
|
|
try:
|
|
redis_client.ping()
|
|
print("✅ Redis connection successful")
|
|
except Exception as e:
|
|
print(f"❌ Redis connection failed: {e}")
|
|
exit(1)
|
|
|
|
app.run(host='0.0.0.0', port=8080, debug=False) |