Add manual refresh buttons for all servers and individual servers
This commit is contained in:
57
app/app.py
57
app/app.py
@@ -248,6 +248,12 @@ def collect_all():
|
|||||||
logger.info("Collection complete, updated %d servers", len(results))
|
logger.info("Collection complete, updated %d servers", len(results))
|
||||||
|
|
||||||
|
|
||||||
|
_collect_event = threading.Event()
|
||||||
|
|
||||||
|
def trigger_collect():
|
||||||
|
"""Wake up the collector loop to run immediately."""
|
||||||
|
_collect_event.set()
|
||||||
|
|
||||||
def collector_loop():
|
def collector_loop():
|
||||||
time.sleep(2) # Brief pause to let Flask start
|
time.sleep(2) # Brief pause to let Flask start
|
||||||
while True:
|
while True:
|
||||||
@@ -255,7 +261,8 @@ def collector_loop():
|
|||||||
collect_all()
|
collect_all()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Collection loop error: %s", e)
|
logger.error("Collection loop error: %s", e)
|
||||||
time.sleep(COLLECTION_INTERVAL)
|
_collect_event.wait(timeout=COLLECTION_INTERVAL)
|
||||||
|
_collect_event.clear()
|
||||||
|
|
||||||
|
|
||||||
# --- Web Routes ---
|
# --- Web Routes ---
|
||||||
@@ -297,6 +304,54 @@ def api_servers():
|
|||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/refresh', methods=['POST'])
|
||||||
|
def api_refresh():
|
||||||
|
trigger_collect()
|
||||||
|
return jsonify({'ok': True, 'message': 'Collection triggered'})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/servers/<int:server_id>/refresh', methods=['POST'])
|
||||||
|
def api_refresh_one(server_id):
|
||||||
|
server = Server.query.get_or_404(server_id)
|
||||||
|
try:
|
||||||
|
ssh_key = load_ssh_key()
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'ok': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
'group': server.group_name,
|
||||||
|
'username': server.username,
|
||||||
|
'hostname': server.hostname,
|
||||||
|
'url': server.url,
|
||||||
|
}
|
||||||
|
result = collect_one(entry, ssh_key)
|
||||||
|
|
||||||
|
server.is_online = result.get('is_online', False)
|
||||||
|
server.last_collected = datetime.now(timezone.utc)
|
||||||
|
server.details = result
|
||||||
|
|
||||||
|
# Extract primary IP
|
||||||
|
default_iface = ''
|
||||||
|
routing = result.get('routing', {})
|
||||||
|
if isinstance(routing, dict):
|
||||||
|
default_iface = routing.get('interface', '')
|
||||||
|
primary_ip = ''
|
||||||
|
for iface in result.get('net', []):
|
||||||
|
ipv4 = iface.get('ipv4', '')
|
||||||
|
if not ipv4 or ipv4.startswith('127.'):
|
||||||
|
continue
|
||||||
|
iface_name = iface.get('name', '') or iface.get('_name', '')
|
||||||
|
if iface_name == default_iface:
|
||||||
|
primary_ip = ipv4
|
||||||
|
break
|
||||||
|
if not primary_ip:
|
||||||
|
primary_ip = ipv4
|
||||||
|
server.primary_ip = primary_ip
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
return jsonify({'ok': True})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/servers/<int:server_id>/notes', methods=['PUT'])
|
@app.route('/api/servers/<int:server_id>/notes', methods=['PUT'])
|
||||||
def api_update_notes(server_id):
|
def api_update_notes(server_id):
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|||||||
@@ -31,6 +31,50 @@ header h1 {
|
|||||||
color: #64748b;
|
color: #64748b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
margin-left: auto;
|
||||||
|
background: #334155;
|
||||||
|
color: #94a3b8;
|
||||||
|
border: 1px solid #475569;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn-sm {
|
||||||
|
margin-left: auto;
|
||||||
|
background: none;
|
||||||
|
color: #475569;
|
||||||
|
border: none;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 4px;
|
||||||
|
transition: color 0.2s;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn-sm:hover {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn-sm:disabled {
|
||||||
|
opacity: 0.4;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<header>
|
<header>
|
||||||
<h1>Infrastructure Map</h1>
|
<h1>Infrastructure Map</h1>
|
||||||
<span class="subtitle">Auto-refreshes every 60s | Built: {{ build_date }}</span>
|
<span class="subtitle">Auto-refreshes every 60s | Built: {{ build_date }}</span>
|
||||||
|
<button class="refresh-btn" onclick="triggerRefresh(this)" title="Force re-collect from all servers">↻ Refresh</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
@@ -52,6 +53,7 @@
|
|||||||
{% if server.url %}
|
{% if server.url %}
|
||||||
<a href="{{ server.url }}" class="server-link" target="_blank" rel="noopener" onclick="event.stopPropagation();" title="{{ server.url }}">↗</a>
|
<a href="{{ server.url }}" class="server-link" target="_blank" rel="noopener" onclick="event.stopPropagation();" title="{{ server.url }}">↗</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<button class="refresh-btn-sm" onclick="event.stopPropagation(); refreshServer(this, {{ server.id }})" title="Refresh this server">↻</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="server-ip">{{ server.primary_ip or 'No IP' }}</div>
|
<div class="server-ip">{{ server.primary_ip or 'No IP' }}</div>
|
||||||
<div class="server-os">{% if sys.get('platform') %}{{ sys.get('platform')|capitalize }} {{ sys.get('platform_version', '') }} / {% endif %}{{ sys.get('os_pretty', '') }}</div>
|
<div class="server-os">{% if sys.get('platform') %}{{ sys.get('platform')|capitalize }} {{ sys.get('platform_version', '') }} / {% endif %}{{ sys.get('os_pretty', '') }}</div>
|
||||||
@@ -411,6 +413,22 @@
|
|||||||
if (card) toggleDetails(card);
|
if (card) toggleDetails(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function refreshServer(btn, serverId) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = '...';
|
||||||
|
fetch('/api/servers/' + serverId + '/refresh', {method: 'POST'})
|
||||||
|
.then(() => location.reload());
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerRefresh(btn) {
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = 'Collecting...';
|
||||||
|
fetch('/api/refresh', {method: 'POST'}).then(() => {
|
||||||
|
// Wait a few seconds for collection to finish, then reload
|
||||||
|
setTimeout(() => location.reload(), 8000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If no servers have data yet, refresh quickly; otherwise every 60s
|
// If no servers have data yet, refresh quickly; otherwise every 60s
|
||||||
const hasData = document.querySelectorAll('.server-card').length > 0;
|
const hasData = document.querySelectorAll('.server-card').length > 0;
|
||||||
const refreshMs = hasData ? 60000 : 5000;
|
const refreshMs = hasData ? 60000 : 5000;
|
||||||
|
|||||||
Reference in New Issue
Block a user