14 Commits

Author SHA1 Message Date
jamie 1fa28590b4 Merge pull request 'fix: 🐛 nav bar items overlap with search bar' (#40) from v1.9.6 into main
Reviewed-on: #40
2026-02-19 19:35:01 +00:00
jamie 30a3ea66d5 fix: 🐛 nav bar items overlap with search bar
Release / release (pull_request) Successful in 29s
Release / Deploy to Kubernetes (pull_request) Has been cancelled
2026-02-19 19:34:43 +00:00
jamie 6f2cfad65f Merge pull request 'v1.9.5' (#38) from v1.9.5 into main
Reviewed-on: #38
2026-01-08 16:24:32 +00:00
jamie 2621d233f9 fix: 🐛 update version display logic to omit 'v' prefix for dev versions
Release / release (pull_request) Successful in 32s
Release / Deploy to Kubernetes (pull_request) Successful in 2s
2026-01-08 16:24:05 +00:00
jamie af4997df5a fix: 🐛 remove leading 'v' from version display in header template 2026-01-08 16:18:26 +00:00
jamie 1980fd04ba Merge pull request 'fix: 🐛 update workflow trigger to workflow_dispatch in dev.yml' (#37) from v1.9.4 into main
Reviewed-on: #37
2026-01-08 16:01:57 +00:00
jamie d06d0c76c2 fix: 🐛 update workflow trigger to workflow_dispatch in dev.yml
Release / release (pull_request) Successful in 32s
Release / Deploy to Kubernetes (pull_request) Successful in 1s
2026-01-08 16:00:55 +00:00
jamie 9244328da8 Merge pull request 'v1.9.3' (#36) from v1.9.3 into main
Dev / build (push) Has been cancelled
Dev / Deploy to Kubernetes (push) Has been cancelled
Reviewed-on: #36
2026-01-08 15:59:14 +00:00
jamie 70489c3dac fix: 🐛 update container image reference in Docker configurations
Dev / build (push) Successful in 2s
Dev / Deploy to Kubernetes (push) Successful in 1s
Release / release (pull_request) Successful in 32s
Release / Deploy to Kubernetes (pull_request) Successful in 1s
2026-01-08 15:57:58 +00:00
jamie 2a3ee1c8af fix: 🐛 update deployment configurations for dev and prod environments
Dev / build (push) Successful in 29s
Dev / Deploy to Kubernetes (push) Successful in 1s
2026-01-08 15:55:54 +00:00
jamie 8a01cb4755 Merge pull request 'ci: 🚀 switch to gitea' (#35) from v1.9.2 into main
Dev / build (push) Successful in 1s
Dev / Deploy to Kubernetes (push) Successful in 2s
Reviewed-on: #35
2026-01-08 15:53:11 +00:00
jamie 9dfea6c795 fix: 🐛 update container image registry in deployment configuration
Dev / build (push) Successful in 2s
Dev / Deploy to Kubernetes (push) Successful in 1s
Release / release (pull_request) Successful in 31s
Release / Deploy to Kubernetes (pull_request) Failing after 2s
2026-01-08 15:48:30 +00:00
jamie 29cb46963c fix: 🐛 update workflow trigger from pull_request to push
Dev / build (push) Successful in 29s
Dev / Deploy to Kubernetes (push) Successful in 6s
2026-01-08 15:44:52 +00:00
jamie ca7c5f77a4 ci: 🚀 switch to gitea
Dev / release (pull_request) Has been skipped
Dev / Deploy to Kubernetes (pull_request) Has been skipped
2026-01-08 15:42:13 +00:00
14 changed files with 187 additions and 139 deletions
+2 -1
View File
@@ -4,7 +4,8 @@ CHANGELOG.md
*.md *.md
# Deployment files # Deployment files
deployment.yml deployment-dev.yml
deployment-prod.yml
run.sh run.sh
Dockerfile Dockerfile
.dockerignore .dockerignore
+34
View File
@@ -0,0 +1,34 @@
name: Dev
on:
workflow_dispatch:
jobs:
build:
runs-on: build-htz-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Build and push Docker image
run: |
docker build -t cr.jdbnet.co.uk/public/ipam:dev \
--build-arg VERSION=dev \
.
docker push cr.jdbnet.co.uk/public/ipam:dev
deploy:
name: Deploy to Kubernetes
needs: release
runs-on: k3s-internal-htz-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Deploy to Kubernetes
run: |
sudo kubectl replace -f deployment-dev.yml --grace-period=60 --force
+60
View File
@@ -0,0 +1,60 @@
name: Release
on:
pull_request:
branches:
- main
types: [closed]
jobs:
release:
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'v')
runs-on: build-htz-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract Version
id: get_version
run: echo "VERSION=${{ github.head_ref }}" >> $GITHUB_OUTPUT
- name: Generate Changelog
id: changelog
uses: https://github.com/metcalfc/changelog-generator@v4.6.2
with:
myToken: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
run: |
VERSION=${{ steps.get_version.outputs.VERSION }}
docker build -t cr.jdbnet.co.uk/public/ipam:$VERSION \
-t cr.jdbnet.co.uk/public/ipam:latest \
--build-arg VERSION=$VERSION \
.
docker push cr.jdbnet.co.uk/public/ipam:$VERSION
docker push cr.jdbnet.co.uk/public/ipam:latest
- name: Create Gitea Release
uses: https://gitea.com/actions/gitea-release-action@v1
with:
tag_name: ${{ steps.get_version.outputs.VERSION }}
name: ${{ steps.get_version.outputs.VERSION }}
body: ${{ steps.changelog.outputs.changelog }}
draft: false
prerelease: false
deploy:
name: Deploy to Kubernetes
needs: release
runs-on: k3s-internal-htz-01
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Deploy to Kubernetes
run: |
sudo kubectl replace -f deployment-prod.yml --grace-period=60 --force
-59
View File
@@ -1,59 +0,0 @@
name: Release Please
on:
push:
branches:
- main
permissions:
contents: write
pull-requests: write
packages: write
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
steps:
- uses: googleapis/release-please-action@v4
id: release
with:
manifest-file: .release-please-manifest.json
config-file: .release-please-config.json
- name: Checkout
if: ${{ steps.release.outputs.release_created }}
uses: actions/checkout@v4
- name: Set up Docker Buildx
if: ${{ steps.release.outputs.release_created }}
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: ${{ steps.release.outputs.release_created }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Read version
if: ${{ steps.release.outputs.release_created }}
id: version
run: |
VERSION=$(cat VERSION)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "VERSION=$VERSION" >> $GITHUB_ENV
- name: Build and push Docker image
if: ${{ steps.release.outputs.release_created }}
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/jdb-net/ipam:${{ env.VERSION }}
ghcr.io/jdb-net/ipam:latest
build-args: |
VERSION=${{ env.VERSION }}
-46
View File
@@ -1,46 +0,0 @@
name: Staging
on:
push:
branches: [ main ]
jobs:
build-and-push:
name: Build and Push
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: |
ghcr.io/jdb-net/ipam:staging
deploy:
name: Deploy to Kubernetes
needs: build-and-push
runs-on: [ k3s-internal-htz-01 ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Apply manifests
run: |
sudo kubectl replace -f deployment.yml --grace-period=60 --force
+3
View File
@@ -1,6 +1,9 @@
FROM python:3.13-slim FROM python:3.13-slim
LABEL org.opencontainers.image.vendor="JDB-NET"
WORKDIR /app WORKDIR /app
COPY . /app COPY . /app
ARG VERSION=unknown
ENV VERSION=${VERSION}
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y curl mariadb-client-compat RUN apt-get update && apt-get install -y curl mariadb-client-compat
RUN rm -rf /var/lib/apt/lists/* RUN rm -rf /var/lib/apt/lists/*
+3 -3
View File
@@ -40,7 +40,7 @@ docker run -d \
-e SECRET_KEY=your_secret_key \ -e SECRET_KEY=your_secret_key \
-e NAME="Your Organisation" \ -e NAME="Your Organisation" \
-e LOGO_PNG="https://example.com/logo.png" \ -e LOGO_PNG="https://example.com/logo.png" \
ghcr.io/jdb-net/ipam:latest cr.jdbnet.co.uk/public/ipam:latest
``` ```
### Docker Compose ### Docker Compose
@@ -48,7 +48,7 @@ docker run -d \
```yaml ```yaml
services: services:
ipam: ipam:
image: ghcr.io/jdb-net/ipam:latest image: cr.jdbnet.co.uk/public/ipam:latest
container_name: ipam container_name: ipam
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -247,7 +247,7 @@ spec:
spec: spec:
containers: containers:
- name: ipam - name: ipam
image: ghcr.io/jdb-net/ipam:latest image: cr.jdbnet.co.uk/public/ipam:latest
ports: ports:
- containerPort: 5000 - containerPort: 5000
env: env:
+1 -8
View File
@@ -27,14 +27,7 @@ limiter = Limiter(
@app.context_processor @app.context_processor
def inject_env_vars(): def inject_env_vars():
version = 'unknown' version = os.environ.get('VERSION', 'unknown')
try:
version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VERSION')
if os.path.exists(version_file):
with open(version_file, 'r') as f:
version = f.read().strip()
except Exception:
pass
# Import has_permission and is_feature_enabled from routes after routes are registered # Import has_permission and is_feature_enabled from routes after routes are registered
from routes import has_permission, is_feature_enabled from routes import has_permission, is_feature_enabled
+1 -1
View File
@@ -15,7 +15,7 @@ spec:
spec: spec:
containers: containers:
- name: ipam - name: ipam
image: ghcr.io/jdb-net/ipam:staging image: cr.jdbnet.co.uk/public/ipam:dev
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 5000 - containerPort: 5000
+64
View File
@@ -0,0 +1,64 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ipam
namespace: ipam
spec:
replicas: 1
selector:
matchLabels:
app: ipam
template:
metadata:
labels:
app: ipam
spec:
containers:
- name: ipam
image: cr.jdbnet.co.uk/public/ipam:latest
imagePullPolicy: Always
ports:
- containerPort: 5000
name: "ipam"
env:
- name: SECRET_KEY
value: "41TbN7v5peFLZPrdwSCc64J3mjmiUk5fkVWsmb2m"
- name: MYSQL_HOST
value: "10.10.25.4"
- name: MYSQL_USER
value: "ipam"
- name: MYSQL_PASSWORD
value: "WXPmo05sGCfjGe"
- name: MYSQL_DATABASE
value: "ipam"
---
apiVersion: v1
kind: Service
metadata:
name: ipam-ingress-service
namespace: ipam
spec:
selector:
app: ipam
ports:
- protocol: TCP
port: 80
targetPort: 5000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ipam-ingress
namespace: ipam
spec:
rules:
- host: ipam.jdb143.uk
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: ipam-ingress-service
port:
number: 80
+5 -9
View File
@@ -2605,7 +2605,7 @@ def register_routes(app, limiter=None):
@app.route('/check_update') @app.route('/check_update')
@login_required @login_required
def check_update(): def check_update():
"""Check for available updates from GitHub (cached for 3 hours)""" """Check for available updates from Gitea (cached for 3 hours)"""
cache_key = 'check_update' cache_key = 'check_update'
# Check cache first # Check cache first
@@ -2614,15 +2614,11 @@ def register_routes(app, limiter=None):
return jsonify(cached_result) return jsonify(cached_result)
try: try:
# Get current version # Get current version from environment
version_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VERSION') current_version = os.environ.get('VERSION', 'unknown').lstrip('v')
current_version = 'unknown'
if os.path.exists(version_file):
with open(version_file, 'r') as f:
current_version = f.read().strip()
# Fetch latest release from GitHub # Fetch latest release from Gitea
response = requests.get('https://api.github.com/repos/JDB-NET/ipam/releases/latest', timeout=5) response = requests.get('https://git.jdbnet.co.uk/api/v1/repos/jamie/ipam/releases/latest', timeout=5)
if response.status_code != 200: if response.status_code != 200:
return jsonify({'error': 'Failed to fetch release information'}), 500 return jsonify({'error': 'Failed to fetch release information'}), 500
+4 -4
View File
@@ -16,12 +16,12 @@ document.addEventListener('DOMContentLoaded', function() {
const compareLink = document.getElementById('toast-compare-link'); const compareLink = document.getElementById('toast-compare-link');
const closeBtn = document.getElementById('toast-close'); const closeBtn = document.getElementById('toast-close');
// Set versions // Set versions (don't add 'v' prefix for dev versions)
currentVersionEl.textContent = 'v' + data.current_version; currentVersionEl.textContent = (data.current_version === 'dev' ? '' : 'v') + data.current_version;
latestVersionEl.textContent = 'v' + data.latest_version; latestVersionEl.textContent = (data.latest_version === 'dev' ? '' : 'v') + data.latest_version;
// Set compare link (current version to latest version) // Set compare link (current version to latest version)
compareLink.href = `https://github.com/JDB-NET/ipam/compare/v${data.current_version}...v${data.latest_version}`; compareLink.href = `https://git.jdbnet.co.uk/jamie/ipam/compare/v${data.current_version}...v${data.latest_version}`;
// Show toast // Show toast
toast.classList.remove('hidden'); toast.classList.remove('hidden');
+1 -1
View File
@@ -1 +1 @@
document.addEventListener("DOMContentLoaded",function(){let t=sessionStorage.getItem("update-toast-dismissed");!t&&fetch("/check_update").then(t=>t.json()).then(t=>{if(t.update_available){let e=document.getElementById("update-toast"),n=document.getElementById("toast-current-version"),s=document.getElementById("toast-latest-version"),a=document.getElementById("toast-compare-link"),o=document.getElementById("toast-close");n.textContent="v"+t.current_version,s.textContent="v"+t.latest_version,a.href=`https://github.com/JDB-NET/ipam/compare/v${t.current_version}...v${t.latest_version}`,e.classList.remove("hidden"),o.addEventListener("click",function(){e.classList.add("hidden"),sessionStorage.setItem("update-toast-dismissed","true")})}}).catch(t=>{console.error("Error checking for updates:",t)})}); document.addEventListener("DOMContentLoaded",function(){let e=sessionStorage.getItem("update-toast-dismissed");!e&&fetch("/check_update").then(e=>e.json()).then(e=>{if(e.update_available){let t=document.getElementById("update-toast"),n=document.getElementById("toast-current-version"),s=document.getElementById("toast-latest-version"),a=document.getElementById("toast-compare-link"),d=document.getElementById("toast-close");n.textContent=("dev"===e.current_version?"":"v")+e.current_version,s.textContent=("dev"===e.latest_version?"":"v")+e.latest_version,a.href=`https://git.jdbnet.co.uk/jamie/ipam/compare/v${e.current_version}...v${e.latest_version}`,t.classList.remove("hidden"),d.addEventListener("click",function(){t.classList.add("hidden"),sessionStorage.setItem("update-toast-dismissed","true")})}}).catch(e=>{console.error("Error checking for updates:",e)})});
+8 -6
View File
@@ -1,22 +1,24 @@
<header class="bg-zinc-800 shadow-md py-3 px-6 flex items-center justify-between relative"> <header class="bg-zinc-800 shadow-md py-3 px-6 relative flex items-center justify-between lg:grid lg:grid-cols-[auto_minmax(18rem,1fr)_auto] lg:gap-6">
<div class="flex items-center space-x-3 flex-shrink-0"> <div class="flex items-center space-x-3 flex-shrink-0">
<a href="/" class="flex items-center space-x-3"> <a href="/" class="flex items-center space-x-3">
<img src="{{ LOGO_PNG }}" alt="Logo" class="h-8 rounded"> <img src="{{ LOGO_PNG }}" alt="Logo" class="h-8 rounded">
<span class="text-2xl font-bold text-white whitespace-nowrap">{{ NAME }} IPAM</span> <span class="text-2xl font-bold text-white whitespace-nowrap">{{ NAME }} IPAM</span>
</a> </a>
<a href="https://github.com/JDB-NET/ipam/releases" target="_blank" rel="noopener noreferrer" class="text-sm font-normal text-gray-300 hover:text-gray-100 -ml-1 mt-3">v{{ VERSION }}</a> <a href="https://git.jdbnet.co.uk/jamie/ipam/releases" target="_blank" rel="noopener noreferrer" class="text-sm font-normal text-gray-300 hover:text-gray-100 -ml-1 mt-3">{{ VERSION }}</a>
</div> </div>
<div class="hidden lg:flex items-center justify-center absolute left-1/2" style="transform: translateX(calc(-50% + 1.5rem));">
<form action="/search" method="GET" class="flex items-center space-x-2"> <div class="hidden lg:flex items-center min-w-0">
<form action="/search" method="GET" class="flex items-center space-x-2 w-full max-w-2xl mx-auto">
<input type="text" name="q" id="search-input" placeholder="Search..." <input type="text" name="q" id="search-input" placeholder="Search..."
class="bg-zinc-700 text-white placeholder-gray-400 px-4 py-2 rounded-md text-base focus:outline-none focus:ring-2 focus:ring-gray-500 w-100" class="bg-zinc-700 text-white placeholder-gray-400 px-4 py-2 rounded-md text-base focus:outline-none focus:ring-2 focus:ring-gray-500 w-full min-w-0"
value="{{ request.args.get('q', '') }}"> value="{{ request.args.get('q', '') }}">
<button type="submit" class="text-gray-200 hover:text-gray-400 hover:cursor-pointer flex-shrink-0"> <button type="submit" class="text-gray-200 hover:text-gray-400 hover:cursor-pointer flex-shrink-0">
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
</button> </button>
</form> </form>
</div> </div>
<nav class="hidden lg:flex items-center space-x-6 flex-shrink-0" id="main-nav">
<nav class="hidden lg:flex items-center space-x-6 flex-shrink-0 whitespace-nowrap justify-self-end" id="main-nav">
{% if has_permission('view_index') %} {% if has_permission('view_index') %}
<a href="/" class="text-gray-200 hover:text-gray-400 font-medium flex items-center gap-2"> <a href="/" class="text-gray-200 hover:text-gray-400 font-medium flex items-center gap-2">
<i class="fas fa-home"></i> <i class="fas fa-home"></i>