Add parent-child VM nesting support from infrastructure.conf
All checks were successful
Build-Publish / build (linux/amd64) (push) Successful in 5s
Build-Publish / build (linux/arm64) (push) Successful in 13s
Build-Publish / create-manifest (push) Successful in 2s
Build-Publish / publish-template (push) Successful in 8s

This commit is contained in:
j
2026-03-08 19:26:09 +13:00
parent f953eef8cf
commit be032e66d7
3 changed files with 120 additions and 4 deletions

View File

@@ -42,6 +42,7 @@ class Server(db.Model):
last_collected = db.Column(db.DateTime, nullable=True)
details = db.Column(db.JSON, nullable=True)
notes = db.Column(db.Text, default='')
parent_hostname = db.Column(db.String(255), default='')
__table_args__ = (db.UniqueConstraint('username', 'hostname', name='uq_user_host'),)
@@ -50,6 +51,7 @@ class Server(db.Model):
def parse_infrastructure_conf():
servers = []
current_group = None
current_host = None # track the last top-level host for nesting
try:
with open(INFRA_CONF_PATH) as f:
for line in f:
@@ -58,7 +60,18 @@ def parse_infrastructure_conf():
continue
if line[0] not in (' ', '\t'):
current_group = line.strip()
current_host = None
else:
# Detect indent level: double indent = child VM
stripped = line.lstrip('\t')
tab_count = len(line) - len(stripped)
if tab_count < 2:
# Also check spaces: 8+ spaces = double indent
space_indent = len(line) - len(line.lstrip(' '))
is_child = space_indent >= 8
else:
is_child = tab_count >= 2
parts = line.strip().split(None, 1)
entry = parts[0] if parts else ''
url = parts[1] if len(parts) > 1 else ''
@@ -67,11 +80,17 @@ def parse_infrastructure_conf():
else:
user, host = 'infmap', entry
if host:
parent = ''
if is_child and current_host:
parent = current_host
else:
current_host = host.strip()
servers.append({
'group': current_group or 'Default',
'username': user.strip(),
'hostname': host.strip(),
'url': url.strip(),
'parent_hostname': parent,
})
except FileNotFoundError:
logger.error("infrastructure.conf not found at %s", INFRA_CONF_PATH)
@@ -221,6 +240,7 @@ def collect_all():
server.group_name = entry['group']
server.url = entry.get('url', '')
server.parent_hostname = entry.get('parent_hostname', '')
server.is_online = result.get('is_online', False)
server.last_collected = datetime.now(timezone.utc)
server.details = result
@@ -269,9 +289,24 @@ def collector_loop():
@app.route('/')
def index():
servers = Server.query.order_by(Server.group_name, Server.primary_ip).all()
all_servers = Server.query.order_by(Server.group_name, Server.primary_ip).all()
# Separate parents and children
children_map = {} # parent_hostname -> [child_servers]
parents = []
for s in all_servers:
if s.parent_hostname:
children_map.setdefault(s.parent_hostname, []).append(s)
else:
parents.append(s)
# Sort children by IP
for hostname in children_map:
children_map[hostname].sort(key=lambda s: _ip_sort_key(s.primary_ip))
# Group parents
groups = {}
for s in servers:
for s in parents:
g = s.group_name or 'Default'
if g not in groups:
groups[g] = []
@@ -281,7 +316,7 @@ def index():
for g in groups:
groups[g].sort(key=lambda s: _ip_sort_key(s.primary_ip))
return render_template('index.html', groups=groups, build_date=BUILD_DATE)
return render_template('index.html', groups=groups, children_map=children_map, build_date=BUILD_DATE)
@app.route('/api/servers')
@@ -299,6 +334,7 @@ def api_servers():
'is_online': s.is_online,
'last_collected': s.last_collected.isoformat() if s.last_collected else None,
'notes': s.notes,
'parent_hostname': s.parent_hostname,
'details': s.details,
})
return jsonify(result)
@@ -510,6 +546,7 @@ def migrate_db():
migrations = {
'url': "ALTER TABLE servers ADD COLUMN url VARCHAR(1024) DEFAULT ''",
'notes': "ALTER TABLE servers ADD COLUMN notes TEXT DEFAULT ''",
'parent_hostname': "ALTER TABLE servers ADD COLUMN parent_hostname VARCHAR(255) DEFAULT ''",
}
for col, sql in migrations.items():
if col not in existing: