Add per-server notes field and show hardware info on dashboard
This commit is contained in:
12
app/app.py
12
app/app.py
@@ -39,6 +39,7 @@ class Server(db.Model):
|
||||
is_online = db.Column(db.Boolean, default=False)
|
||||
last_collected = db.Column(db.DateTime, nullable=True)
|
||||
details = db.Column(db.JSON, nullable=True)
|
||||
notes = db.Column(db.Text, default='')
|
||||
__table_args__ = (db.UniqueConstraint('username', 'hostname', name='uq_user_host'),)
|
||||
|
||||
|
||||
@@ -282,11 +283,22 @@ def api_servers():
|
||||
'url': s.url,
|
||||
'is_online': s.is_online,
|
||||
'last_collected': s.last_collected.isoformat() if s.last_collected else None,
|
||||
'notes': s.notes,
|
||||
'details': s.details,
|
||||
})
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.route('/api/servers/<int:server_id>/notes', methods=['PUT'])
|
||||
def api_update_notes(server_id):
|
||||
from flask import request
|
||||
server = Server.query.get_or_404(server_id)
|
||||
data = request.get_json()
|
||||
server.notes = data.get('notes', '')
|
||||
db.session.commit()
|
||||
return jsonify({'ok': True})
|
||||
|
||||
|
||||
def _ip_sort_key(ip_str):
|
||||
if not ip_str:
|
||||
return [999, 999, 999, 999]
|
||||
|
||||
@@ -288,6 +288,36 @@ main {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* --- Notes --- */
|
||||
|
||||
.notes-section {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.notes-input {
|
||||
width: 100%;
|
||||
min-height: 32px;
|
||||
background: #0f172a;
|
||||
border: 1px solid #1e3a5f;
|
||||
border-radius: 6px;
|
||||
color: #cbd5e1;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 0.8rem;
|
||||
padding: 8px 10px;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.notes-input:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.notes-input::placeholder {
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
/* --- Container / VM Sub-cards --- */
|
||||
|
||||
.container-grid {
|
||||
|
||||
@@ -55,6 +55,13 @@
|
||||
</div>
|
||||
<div class="server-ip">{{ server.primary_ip or 'No IP' }}</div>
|
||||
<div class="server-os">{{ sys.get('os_pretty', '') }}</div>
|
||||
{% if server.is_online and (cpu.get('model') or mem.get('total_mb')) %}
|
||||
<div class="server-hw">
|
||||
{%- if cpu.get('model') %}{{ cpu.get('model') }}{% endif %}
|
||||
{%- if cpu.get('cores') %} ({{ cpu.get('cores') }}c){% endif %}
|
||||
{%- if mem.get('total_mb') %} / {{ mem.get('total_mb', '')|format_mb }}{% endif -%}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if server.is_online %}
|
||||
<div class="usage-bars">
|
||||
@@ -96,6 +103,13 @@
|
||||
|
||||
<!-- Expanded Details -->
|
||||
<div class="card-details" style="display: none;">
|
||||
<div class="notes-section">
|
||||
<textarea class="notes-input"
|
||||
data-server-id="{{ server.id }}"
|
||||
placeholder="Add notes..."
|
||||
onclick="event.stopPropagation();"
|
||||
oninput="autoResizeNotes(this); debounceSaveNotes(this);">{{ server.notes or '' }}</textarea>
|
||||
</div>
|
||||
<div class="details-grid">
|
||||
<!-- System Info -->
|
||||
<div class="detail-section">
|
||||
@@ -170,6 +184,7 @@
|
||||
<tr class="table-header"><td>Interface</td><td>IPv4</td><td>IPv6</td><td>MAC</td><td>State</td><td>Speed</td><td>Driver</td></tr>
|
||||
{% set nets = d.get('net', []) if d.get('net') else [] %}
|
||||
{% for iface in nets %}
|
||||
{% if iface.get('ipv4') or iface.get('ipv6') %}
|
||||
<tr>
|
||||
<td>{{ iface.get('name', iface.get('_name', '-')) }}</td>
|
||||
<td>{{ iface.get('ipv4', '-') or '-' }}</td>
|
||||
@@ -179,6 +194,7 @@
|
||||
<td>{% if iface.get('speed_mbps') %}{{ iface.get('speed_mbps') }} Mbps{% else %}-{% endif %}</td>
|
||||
<td>{{ iface.get('driver', '-') or '-' }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
@@ -322,8 +338,32 @@
|
||||
if (!isOpen) {
|
||||
details.style.display = 'block';
|
||||
card.classList.add('expanded');
|
||||
// Auto-resize notes textarea
|
||||
const ta = details.querySelector('.notes-input');
|
||||
if (ta) autoResizeNotes(ta);
|
||||
}
|
||||
}
|
||||
|
||||
function autoResizeNotes(ta) {
|
||||
ta.style.height = 'auto';
|
||||
ta.style.height = ta.scrollHeight + 'px';
|
||||
}
|
||||
|
||||
let _notesTimers = {};
|
||||
function debounceSaveNotes(ta) {
|
||||
const id = ta.dataset.serverId;
|
||||
clearTimeout(_notesTimers[id]);
|
||||
_notesTimers[id] = setTimeout(() => saveNotes(ta), 800);
|
||||
}
|
||||
|
||||
function saveNotes(ta) {
|
||||
const id = ta.dataset.serverId;
|
||||
fetch('/api/servers/' + id + '/notes', {
|
||||
method: 'PUT',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({notes: ta.value})
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user