Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c8c483ae95 | |||
| fd2b561308 | |||
| 3e5ee0800e | |||
| 5850898d5b | |||
| ae28d3fb26 | |||
| 4d6a95e2b0 |
@@ -8,6 +8,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
|
name: Build & Release
|
||||||
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'v')
|
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'v')
|
||||||
runs-on: build-htz-01
|
runs-on: build-htz-01
|
||||||
steps:
|
steps:
|
||||||
@@ -44,3 +45,30 @@ jobs:
|
|||||||
body: ${{ steps.changelog.outputs.changelog }}
|
body: ${{ steps.changelog.outputs.changelog }}
|
||||||
draft: false
|
draft: false
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
|
sonarqube:
|
||||||
|
name: SonarQube
|
||||||
|
runs-on: build-htz-01
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Create Valid Project Key
|
||||||
|
id: sonar_setup
|
||||||
|
run: |
|
||||||
|
CLEAN_KEY=$(echo "${{ gitea.repository }}" | tr '/' ':')
|
||||||
|
echo "key=$CLEAN_KEY" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: SonarQube Scan
|
||||||
|
uses: sonarsource/sonarqube-scan-action@master
|
||||||
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||||
|
with:
|
||||||
|
args: >
|
||||||
|
-Dsonar.projectKey=${{ steps.sonar_setup.outputs.key }}
|
||||||
|
-Dsonar.projectName=${{ gitea.repository }}
|
||||||
|
-Dsonar.qualitygate.wait=true
|
||||||
@@ -539,7 +539,7 @@ def prewarm_cache(app):
|
|||||||
|
|
||||||
# Pre-warm index page (all subnets with utilization)
|
# Pre-warm index page (all subnets with utilization)
|
||||||
logging.info("Pre-warming cache: Loading all subnets for index page...")
|
logging.info("Pre-warming cache: Loading all subnets for index page...")
|
||||||
cursor.execute('SELECT id, name, cidr, site FROM Subnet')
|
cursor.execute('SELECT id, name, cidr, site, vlan_id FROM Subnet')
|
||||||
subnets = cursor.fetchall()
|
subnets = cursor.fetchall()
|
||||||
sites_subnets = {}
|
sites_subnets = {}
|
||||||
for subnet in subnets:
|
for subnet in subnets:
|
||||||
@@ -570,6 +570,7 @@ def prewarm_cache(app):
|
|||||||
'id': subnet[0],
|
'id': subnet[0],
|
||||||
'name': subnet[1],
|
'name': subnet[1],
|
||||||
'cidr': subnet[2],
|
'cidr': subnet[2],
|
||||||
|
'vlan_id': subnet[4],
|
||||||
'utilization': round(utilization_percent, 1)
|
'utilization': round(utilization_percent, 1)
|
||||||
})
|
})
|
||||||
cache.set('index', sites_subnets, ttl=10800)
|
cache.set('index', sites_subnets, ttl=10800)
|
||||||
@@ -631,8 +632,15 @@ def prewarm_cache(app):
|
|||||||
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = %s', (subnet_id,))
|
cursor.execute('SELECT id, name, cidr FROM Subnet WHERE id = %s', (subnet_id,))
|
||||||
subnet_row = cursor.fetchone()
|
subnet_row = cursor.fetchone()
|
||||||
if subnet_row:
|
if subnet_row:
|
||||||
cursor.execute('SELECT id, ip, hostname, notes FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
cursor.execute('''
|
||||||
ip_addresses = cursor.fetchall()
|
SELECT ip.id, ip.ip, ip.hostname, d.id, d.description, ip.notes
|
||||||
|
FROM IPAddress ip
|
||||||
|
LEFT JOIN DeviceIPAddress dia ON ip.id = dia.ip_id
|
||||||
|
LEFT JOIN Device d ON dia.device_id = d.id
|
||||||
|
WHERE ip.subnet_id = %s
|
||||||
|
ORDER BY INET_ATON(ip.ip)
|
||||||
|
''', (subnet_id,))
|
||||||
|
ip_addresses_with_device = cursor.fetchall()
|
||||||
|
|
||||||
cursor.execute('SELECT COUNT(*) FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
cursor.execute('SELECT COUNT(*) FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
||||||
total_ips = cursor.fetchone()[0]
|
total_ips = cursor.fetchone()[0]
|
||||||
@@ -661,23 +669,6 @@ def prewarm_cache(app):
|
|||||||
'percent': round(utilization_percent, 1)
|
'percent': round(utilization_percent, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.execute('SELECT id, name, description FROM Device')
|
|
||||||
devices = cursor.fetchall()
|
|
||||||
device_name_map = {name.lower(): (id, description) for id, name, description in devices}
|
|
||||||
ip_addresses_with_device = []
|
|
||||||
for ip in ip_addresses:
|
|
||||||
ip_id = ip[0]
|
|
||||||
ip_address = ip[1]
|
|
||||||
hostname = ip[2]
|
|
||||||
ip_notes = ip[3] if len(ip) > 3 else None
|
|
||||||
device_id = None
|
|
||||||
device_description = None
|
|
||||||
if hostname:
|
|
||||||
match = device_name_map.get(hostname.lower())
|
|
||||||
if match:
|
|
||||||
device_id, device_description = match
|
|
||||||
ip_addresses_with_device.append((ip_id, ip_address, hostname, device_id, device_description, ip_notes))
|
|
||||||
|
|
||||||
subnet_dict = {'id': subnet_row[0], 'name': subnet_row[1], 'cidr': subnet_row[2]}
|
subnet_dict = {'id': subnet_row[0], 'name': subnet_row[1], 'cidr': subnet_row[2]}
|
||||||
result = {
|
result = {
|
||||||
'subnet': subnet_dict,
|
'subnet': subnet_dict,
|
||||||
@@ -1000,7 +991,7 @@ def register_routes(app, limiter=None):
|
|||||||
conn = get_db_connection(current_app)
|
conn = get_db_connection(current_app)
|
||||||
try:
|
try:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT id, name, cidr, site FROM Subnet')
|
cursor.execute('SELECT id, name, cidr, site, vlan_id FROM Subnet')
|
||||||
subnets = cursor.fetchall()
|
subnets = cursor.fetchall()
|
||||||
sites_subnets = {}
|
sites_subnets = {}
|
||||||
for subnet in subnets:
|
for subnet in subnets:
|
||||||
@@ -1034,6 +1025,7 @@ def register_routes(app, limiter=None):
|
|||||||
'id': subnet[0],
|
'id': subnet[0],
|
||||||
'name': subnet[1],
|
'name': subnet[1],
|
||||||
'cidr': subnet[2],
|
'cidr': subnet[2],
|
||||||
|
'vlan_id': subnet[4],
|
||||||
'utilization': round(utilization_percent, 1)
|
'utilization': round(utilization_percent, 1)
|
||||||
})
|
})
|
||||||
# Cache for 3 hours
|
# Cache for 3 hours
|
||||||
@@ -1500,8 +1492,15 @@ def register_routes(app, limiter=None):
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT id, name, cidr, vlan_id, vlan_description, vlan_notes FROM Subnet WHERE id = %s', (subnet_id,))
|
cursor.execute('SELECT id, name, cidr, vlan_id, vlan_description, vlan_notes FROM Subnet WHERE id = %s', (subnet_id,))
|
||||||
subnet = cursor.fetchone()
|
subnet = cursor.fetchone()
|
||||||
cursor.execute('SELECT id, ip, hostname, notes FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
cursor.execute('''
|
||||||
ip_addresses = cursor.fetchall()
|
SELECT ip.id, ip.ip, ip.hostname, d.id, d.description, ip.notes
|
||||||
|
FROM IPAddress ip
|
||||||
|
LEFT JOIN DeviceIPAddress dia ON ip.id = dia.ip_id
|
||||||
|
LEFT JOIN Device d ON dia.device_id = d.id
|
||||||
|
WHERE ip.subnet_id = %s
|
||||||
|
ORDER BY INET_ATON(ip.ip)
|
||||||
|
''', (subnet_id,))
|
||||||
|
ip_addresses_with_device = cursor.fetchall()
|
||||||
|
|
||||||
# Calculate utilization stats
|
# Calculate utilization stats
|
||||||
cursor.execute('SELECT COUNT(*) FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
cursor.execute('SELECT COUNT(*) FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
||||||
@@ -1534,23 +1533,6 @@ def register_routes(app, limiter=None):
|
|||||||
# Get custom fields for subnet
|
# Get custom fields for subnet
|
||||||
custom_fields = get_custom_fields_for_entity('subnet', subnet_id, conn=conn)
|
custom_fields = get_custom_fields_for_entity('subnet', subnet_id, conn=conn)
|
||||||
|
|
||||||
cursor.execute('SELECT id, name, description FROM Device')
|
|
||||||
devices = cursor.fetchall()
|
|
||||||
device_name_map = {name.lower(): (id, description) for id, name, description in devices}
|
|
||||||
ip_addresses_with_device = []
|
|
||||||
for ip in ip_addresses:
|
|
||||||
ip_id = ip[0]
|
|
||||||
ip_address = ip[1]
|
|
||||||
hostname = ip[2]
|
|
||||||
ip_notes = ip[3] if len(ip) > 3 else None
|
|
||||||
device_id = None
|
|
||||||
device_description = None
|
|
||||||
if hostname:
|
|
||||||
match = device_name_map.get(hostname.lower())
|
|
||||||
if match:
|
|
||||||
device_id, device_description = match
|
|
||||||
ip_addresses_with_device.append((ip_id, ip_address, hostname, device_id, device_description, ip_notes))
|
|
||||||
|
|
||||||
subnet_dict = {
|
subnet_dict = {
|
||||||
'id': subnet[0],
|
'id': subnet[0],
|
||||||
'name': subnet[1],
|
'name': subnet[1],
|
||||||
@@ -3139,24 +3121,15 @@ def register_routes(app, limiter=None):
|
|||||||
subnet = cursor.fetchone()
|
subnet = cursor.fetchone()
|
||||||
if not subnet:
|
if not subnet:
|
||||||
return 'Subnet not found', 404
|
return 'Subnet not found', 404
|
||||||
cursor.execute('SELECT id, ip, hostname, notes FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
cursor.execute('''
|
||||||
ip_addresses = cursor.fetchall()
|
SELECT ip.id, ip.ip, ip.hostname, d.id, d.description, ip.notes
|
||||||
cursor.execute('SELECT id, name, description FROM Device')
|
FROM IPAddress ip
|
||||||
devices = cursor.fetchall()
|
LEFT JOIN DeviceIPAddress dia ON ip.id = dia.ip_id
|
||||||
device_name_map = {name.lower(): (id, description) for id, name, description in devices}
|
LEFT JOIN Device d ON dia.device_id = d.id
|
||||||
ip_addresses_with_device = []
|
WHERE ip.subnet_id = %s
|
||||||
for ip in ip_addresses:
|
ORDER BY INET_ATON(ip.ip)
|
||||||
ip_id = ip[0]
|
''', (subnet_id,))
|
||||||
ip_address = ip[1]
|
ip_addresses_with_device = cursor.fetchall()
|
||||||
hostname = ip[2]
|
|
||||||
ip_notes = ip[3] if len(ip) > 3 else None
|
|
||||||
device_id = None
|
|
||||||
device_description = None
|
|
||||||
if hostname:
|
|
||||||
match = device_name_map.get(hostname.lower())
|
|
||||||
if match:
|
|
||||||
device_id, device_description = match
|
|
||||||
ip_addresses_with_device.append((ip_id, ip_address, hostname, device_id, device_description, ip_notes))
|
|
||||||
output = StringIO()
|
output = StringIO()
|
||||||
writer = csv.writer(output)
|
writer = csv.writer(output)
|
||||||
writer.writerow(['IP Address', 'Hostname', 'Description'])
|
writer.writerow(['IP Address', 'Hostname', 'Description'])
|
||||||
@@ -3761,7 +3734,7 @@ def register_routes(app, limiter=None):
|
|||||||
FROM IPAddress ip
|
FROM IPAddress ip
|
||||||
JOIN Subnet s ON ip.subnet_id = s.id
|
JOIN Subnet s ON ip.subnet_id = s.id
|
||||||
WHERE ip.ip LIKE %s OR ip.hostname LIKE %s OR ip.notes LIKE %s
|
WHERE ip.ip LIKE %s OR ip.hostname LIKE %s OR ip.notes LIKE %s
|
||||||
ORDER BY ip.ip
|
ORDER BY INET_ATON(ip.ip)
|
||||||
''', (search_pattern, search_pattern, search_pattern))
|
''', (search_pattern, search_pattern, search_pattern))
|
||||||
results['ips'] = [{'id': row[0], 'ip': row[1], 'hostname': row[2],
|
results['ips'] = [{'id': row[0], 'ip': row[1], 'hostname': row[2],
|
||||||
'subnet_id': row[3], 'subnet_name': row[4],
|
'subnet_id': row[3], 'subnet_name': row[4],
|
||||||
@@ -4177,7 +4150,7 @@ def register_routes(app, limiter=None):
|
|||||||
LEFT JOIN DeviceIPAddress dia ON ip.id = dia.ip_id
|
LEFT JOIN DeviceIPAddress dia ON ip.id = dia.ip_id
|
||||||
LEFT JOIN Device d ON dia.device_id = d.id
|
LEFT JOIN Device d ON dia.device_id = d.id
|
||||||
WHERE ip.subnet_id = %s
|
WHERE ip.subnet_id = %s
|
||||||
ORDER BY ip.ip
|
ORDER BY INET_ATON(ip.ip)
|
||||||
''', (subnet_id,))
|
''', (subnet_id,))
|
||||||
subnet['ip_addresses'] = cursor.fetchall()
|
subnet['ip_addresses'] = cursor.fetchall()
|
||||||
# Get custom fields
|
# Get custom fields
|
||||||
@@ -5215,7 +5188,7 @@ def register_routes(app, limiter=None):
|
|||||||
FROM DeviceIPAddress dia
|
FROM DeviceIPAddress dia
|
||||||
JOIN IPAddress ip ON dia.ip_id = ip.id
|
JOIN IPAddress ip ON dia.ip_id = ip.id
|
||||||
WHERE dia.device_id = %s
|
WHERE dia.device_id = %s
|
||||||
ORDER BY ip.ip
|
ORDER BY INET_ATON(ip.ip)
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
''', (device['id'],))
|
''', (device['id'],))
|
||||||
ip_result = cursor.fetchone()
|
ip_result = cursor.fetchone()
|
||||||
@@ -5562,19 +5535,19 @@ def register_routes(app, limiter=None):
|
|||||||
writer.writerow([f"Subnet: {subnet[1]} ({subnet[2]})"])
|
writer.writerow([f"Subnet: {subnet[1]} ({subnet[2]})"])
|
||||||
writer.writerow(['IP Address', 'Hostname', 'Description'])
|
writer.writerow(['IP Address', 'Hostname', 'Description'])
|
||||||
|
|
||||||
cursor.execute('SELECT * FROM IPAddress WHERE subnet_id = %s', (subnet_id,))
|
cursor.execute('''
|
||||||
|
SELECT ip.id, ip.ip, ip.hostname, d.id, d.description, ip.notes
|
||||||
|
FROM IPAddress ip
|
||||||
|
LEFT JOIN DeviceIPAddress dia ON ip.id = dia.ip_id
|
||||||
|
LEFT JOIN Device d ON dia.device_id = d.id
|
||||||
|
WHERE ip.subnet_id = %s
|
||||||
|
ORDER BY INET_ATON(ip.ip)
|
||||||
|
''', (subnet_id,))
|
||||||
ip_addresses = cursor.fetchall()
|
ip_addresses = cursor.fetchall()
|
||||||
cursor.execute('SELECT id, name, description FROM Device')
|
|
||||||
devices = cursor.fetchall()
|
|
||||||
device_name_map = {name.lower(): (id, description) for id, name, description in devices}
|
|
||||||
|
|
||||||
for ip in ip_addresses:
|
for ip in ip_addresses:
|
||||||
hostname = ip[2]
|
hostname = ip[2]
|
||||||
device_description = None
|
device_description = ip[4] if len(ip) > 4 else None
|
||||||
if hostname:
|
|
||||||
match = device_name_map.get(hostname.lower())
|
|
||||||
if match:
|
|
||||||
device_description = match[1]
|
|
||||||
writer.writerow([ip[1] or '', hostname or '', device_description or ''])
|
writer.writerow([ip[1] or '', hostname or '', device_description or ''])
|
||||||
|
|
||||||
writer.writerow([]) # Empty row between subnets
|
writer.writerow([]) # Empty row between subnets
|
||||||
|
|||||||
+10
-4
@@ -24,12 +24,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="subnet-list hidden space-y-4 px-2 pb-4">
|
<ul class="subnet-list hidden space-y-4 px-2 pb-4">
|
||||||
{% for subnet in subnets %}
|
{% for subnet in subnets %}
|
||||||
<li class="p-4 bg-gray-300 hover:bg-gray-100 dark:bg-zinc-900 hover:dark:bg-zinc-700 rounded-lg shadow-md flex items-center justify-between">
|
<li class="relative p-4 bg-gray-300 hover:bg-gray-100 dark:bg-zinc-900 hover:dark:bg-zinc-700 rounded-lg shadow-md flex items-center justify-between">
|
||||||
<a href="/subnet/{{ subnet.id }}">
|
<a href="/subnet/{{ subnet.id }}" class="absolute inset-0 z-0 rounded-lg"></a>
|
||||||
|
<div class="pointer-events-none z-10 flex-grow">
|
||||||
<p class="text-gray-900 dark:text-white text-lg font-medium">{{ subnet.name }}</p>
|
<p class="text-gray-900 dark:text-white text-lg font-medium">{{ subnet.name }}</p>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
<p class="text-sm text-gray-800 dark:text-gray-400">{{ subnet.cidr }}</p>
|
<p class="text-sm text-gray-800 dark:text-gray-400">{{ subnet.cidr }}</p>
|
||||||
</a>
|
{% if subnet.vlan_id %}
|
||||||
<button type="button" class="export-csv-btn ml-2 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-600 dark:hover:bg-zinc-500 hover:cursor-pointer flex items-center justify-center rounded-full w-9 h-9" title="Export as CSV" data-subnet-id="{{ subnet.id }}">
|
<span class="px-2 py-0.5 text-xs font-semibold bg-gray-200 dark:bg-zinc-800 rounded-full">VLAN {{ subnet.vlan_id }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button" class="relative z-10 export-csv-btn ml-2 bg-gray-200 hover:bg-gray-400 dark:bg-zinc-600 dark:hover:bg-zinc-500 hover:cursor-pointer flex items-center justify-center rounded-full w-9 h-9 shrink-0" title="Export as CSV" data-subnet-id="{{ subnet.id }}">
|
||||||
<i class="fas fa-file-csv"></i>
|
<i class="fas fa-file-csv"></i>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user