From 8f774ba8f04bdcc501fce38c53690e36c8d0f927 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Sun, 26 Oct 2025 18:00:18 +1100 Subject: [PATCH] feat: Added tools page --- blueprints/blog.py | 4 + blueprints/template.py | 9 ++ data/blog/Software_I_Use.md | 2 + data/tools.json | 164 ++++++++++++++++++++++++++++ server.py | 9 +- templates/assets/css/styles.min.css | 2 +- templates/sitemap.xml | 3 + templates/tools.html | 148 +++++++++++++++++++++++++ tools.py | 5 + 9 files changed, 341 insertions(+), 5 deletions(-) create mode 100644 blueprints/template.py create mode 100644 data/tools.json create mode 100644 templates/tools.html diff --git a/blueprints/blog.py b/blueprints/blog.py index a3103d0..26a2306 100644 --- a/blueprints/blog.py +++ b/blueprints/blog.py @@ -10,6 +10,10 @@ blog_bp = Blueprint('blog', __name__) def list_page_files(): blog_pages = os.listdir("data/blog") + # Sort pages by modified time, newest first + blog_pages.sort( + key=lambda x: os.path.getmtime(os.path.join("data/blog", x)), reverse=True) + # Remove .md extension blog_pages = [page.removesuffix(".md") for page in blog_pages if page.endswith(".md")] diff --git a/blueprints/template.py b/blueprints/template.py new file mode 100644 index 0000000..beb6da9 --- /dev/null +++ b/blueprints/template.py @@ -0,0 +1,9 @@ +from flask import Blueprint, request +from tools import json_response + +template_bp = Blueprint('template', __name__) + + +@template_bp.route("/") +def index(): + return json_response(request, "Success", 200) \ No newline at end of file diff --git a/data/blog/Software_I_Use.md b/data/blog/Software_I_Use.md index bd5e614..4025d14 100644 --- a/data/blog/Software_I_Use.md +++ b/data/blog/Software_I_Use.md @@ -1,7 +1,9 @@ G'day, Just thought it might be useful to write down some of the software I use regularly. I've no clue if you'll find any useful :) +For a more complete list, check out [/tools](/tools) +
## Overview OS: Arch Linux | Because it is quick to update and has all the latest tools I can play with DE: Hyprland | Feel free to check out my dotfiles if you're interested diff --git a/data/tools.json b/data/tools.json new file mode 100644 index 0000000..32642f6 --- /dev/null +++ b/data/tools.json @@ -0,0 +1,164 @@ +[ + { + "name":"Obsidian", + "type":"Desktop Applications", + "url":"https://obsidian.md/", + "description":"Note taking app that stores everything in Markdown files" + }, + { + "name": "Alacritty", + "type": "Desktop Applications", + "url": "https://alacritty.org/", + "description": "A cross-platform, GPU-accelerated terminal emulator" + }, + { + "name": "Brave", + "type": "Desktop Applications", + "url": "https://brave.com/", + "description": "Privacy-focused web browser" + }, + { + "name": "VSCode", + "type": "Desktop Applications", + "url": "https://code.visualstudio.com/", + "description": "Source-code editor developed by Microsoft" + }, + { + "name": "Zellij", + "type": "terminal", + "url": "https://zellij.dev/", + "description": "A terminal workspace and multiplexer" + }, + { + "name": "Fx", + "type": "terminal", + "url": "https://fx.wtf/", + "description": "A command-line JSON viewer and processor", + "demo": "" + }, + { + "name": "Zoxide", + "type": "terminal", + "url": "https://github.com/ajeetdsouza/zoxide", + "description": "cd but with fuzzy matching and other cool features", + "demo": "" + }, + { + "name": "Atuin", + "type": "terminal", + "url": "https://atuin.sh/", + "description": "A next-generation shell history manager", + "demo": "" + }, + { + "name": "Tmate", + "type": "terminal", + "url": "https://tmate.io/", + "description": "Instant terminal sharing", + "demo": "" + }, + { + "name": "Eza", + "type": "terminal", + "url": "https://eza.rocks/", + "description": "A modern replacement for 'ls'", + "demo": "" + }, + { + "name": "Bat", + "type": "terminal", + "url": "https://github.com/sharkdp/bat", + "description": "A cat clone with syntax highlighting and Git integration", + "demo": "" + }, + { + "name": "Oh My Zsh", + "type": "terminal", + "url": "https://ohmyz.sh/", + "description": "A delightful community-driven framework for managing your Zsh configuration" + }, + { + "name": "Proxmox", + "type": "Server Management", + "url": "https://www.proxmox.com/en", + "description": "Open-source server virtualization management solution" + }, + { + "name": "Portainer", + "type": "Server Management", + "url": "https://www.portainer.io/", + "description": "Lightweight management UI which allows you to easily manage your Docker containers" + }, + { + "name": "Coolify", + "type": "Server Management", + "url": "https://coolify.io/", + "description": "An open-source self-hosted Heroku alternative" + }, + { + "name": "OpnSense", + "type": "Server Management", + "url": "https://opnsense.org/", + "description": "Open source, easy-to-use and easy-to-build FreeBSD based firewall and routing platform" + }, + { + "name": "Nginx Proxy Manager", + "type": "Server Management", + "url": "https://nginxproxymanager.com/", + "description": "A powerful yet easy to use web interface for managing Nginx proxy hosts" + }, + { + "name": "Tailscale", + "type": "Server Management", + "url": "https://tailscale.com/", + "description": "A zero-config VPN that just works" + }, + { + "name": "Authentik", + "type": "Self-Hosting Services", + "url": "https://goauthentik.io/", + "description": "An open-source identity provider focused on flexibility and ease of use" + }, + { + "name": "Uptime Kuma", + "type": "Self-Hosting Services", + "url": "https://uptime.kuma.pet/", + "description": "A fancy self-hosted monitoring tool" + }, + { + "name": "Gitea", + "type": "Self-Hosting Services", + "url": "https://about.gitea.com/", + "description": "A painless self-hosted Git service" + }, + { + "name": "Nextcloud", + "type": "Self-Hosting Services", + "url": "https://nextcloud.com/", + "description": "A suite of client-server software for creating and using file hosting services" + }, + { + "name": "Umami", + "type": "Self-Hosting Services", + "url": "https://umami.is/", + "description": "A simple, fast, privacy-focused alternative to Google Analytics" + }, + { + "name": "PhotoPrism", + "type": "Self-Hosting Services", + "url": "https://photoprism.app/", + "description": "AI-powered app for browsing, organizing & sharing your photo collection" + }, + { + "name": "FreeScout", + "type": "Self-Hosting Services", + "url": "https://freescout.net/", + "description": "Self hosted email dashboard" + }, + { + "name": "Vaultwarden", + "type": "Miscellaneous", + "url": "https://github.com/dani-garcia/vaultwarden", + "description": "Password manager server implementation compatible with Bitwarden clients" + } +] \ No newline at end of file diff --git a/server.py b/server.py index 3fd1e57..37ced1c 100644 --- a/server.py +++ b/server.py @@ -25,7 +25,7 @@ from blueprints.wellknown import wk_bp from blueprints.api import api_bp from blueprints.podcast import podcast_bp from blueprints.acme import acme_bp -from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getGitCommit, isDev, getHandshakeScript +from tools import isCurl, isCrawler, getAddress, getFilePath, error_response, getClientIP, json_response, getGitCommit, isDev, getHandshakeScript, get_tools_data app = Flask(__name__) CORS(app) @@ -399,8 +399,6 @@ def index(): return resp # region Donate - - @app.route("/donate") def donate(): coinList = os.listdir(".well-known/wallets") @@ -563,7 +561,6 @@ def qrcodee(data): # endregion - @app.route("/supersecretpath") def supersecretpath(): ascii_art = "" @@ -689,6 +686,10 @@ def resume_pdf(): return send_file("data/resume.pdf") return error_response(request, message="Resume not found") +@app.route("/tools") +def tools(): + return render_template("tools.html", tools=get_tools_data()) + # endregion # region Error Catching diff --git a/templates/assets/css/styles.min.css b/templates/assets/css/styles.min.css index 3597ccf..3e252b8 100644 --- a/templates/assets/css/styles.min.css +++ b/templates/assets/css/styles.min.css @@ -1 +1 @@ -:root,[data-bs-theme=light]{--bs-primary:#6E0E9C;--bs-primary-rgb:110,14,156;--bs-primary-text-emphasis:#2C063E;--bs-primary-bg-subtle:#E2CFEB;--bs-primary-border-subtle:#C59FD7;--bs-link-color:#6E0E9C;--bs-link-color-rgb:110,14,156;--bs-link-hover-color:#a41685;--bs-link-hover-color-rgb:164,22,133}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5E0C85;--bs-btn-hover-border-color:#580B7D;--bs-btn-focus-shadow-rgb:233,219,240;--bs-btn-active-color:#fff;--bs-btn-active-bg:#580B7D;--bs-btn-active-border-color:#530B75;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6E0E9C;--bs-btn-disabled-border-color:#6E0E9C}.btn-outline-primary{--bs-btn-color:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-focus-shadow-rgb:110,14,156;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6E0E9C;--bs-btn-hover-border-color:#6E0E9C;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6E0E9C;--bs-btn-active-border-color:#6E0E9C;--bs-btn-disabled-color:#6E0E9C;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6E0E9C}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}@media (min-width:992px){.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}} \ No newline at end of file +:root,[data-bs-theme=light]{--bs-primary:#6E0E9C;--bs-primary-rgb:110,14,156;--bs-primary-text-emphasis:#2C063E;--bs-primary-bg-subtle:#E2CFEB;--bs-primary-border-subtle:#C59FD7;--bs-link-color:#6E0E9C;--bs-link-color-rgb:110,14,156;--bs-link-hover-color:#a41685;--bs-link-hover-color-rgb:164,22,133}.btn-primary{--bs-btn-color:#fff;--bs-btn-bg:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#5E0C85;--bs-btn-hover-border-color:#580B7D;--bs-btn-focus-shadow-rgb:233,219,240;--bs-btn-active-color:#fff;--bs-btn-active-bg:#580B7D;--bs-btn-active-border-color:#530B75;--bs-btn-disabled-color:#fff;--bs-btn-disabled-bg:#6E0E9C;--bs-btn-disabled-border-color:#6E0E9C}.btn-outline-primary{--bs-btn-color:#6E0E9C;--bs-btn-border-color:#6E0E9C;--bs-btn-focus-shadow-rgb:110,14,156;--bs-btn-hover-color:#fff;--bs-btn-hover-bg:#6E0E9C;--bs-btn-hover-border-color:#6E0E9C;--bs-btn-active-color:#fff;--bs-btn-active-bg:#6E0E9C;--bs-btn-active-border-color:#6E0E9C;--bs-btn-disabled-color:#6E0E9C;--bs-btn-disabled-bg:transparent;--bs-btn-disabled-border-color:#6E0E9C}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}@media (min-width:992px){.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}} \ No newline at end of file diff --git a/templates/sitemap.xml b/templates/sitemap.xml index ddf86b4..0a226f7 100644 --- a/templates/sitemap.xml +++ b/templates/sitemap.xml @@ -96,4 +96,7 @@ https://nathan.woodburn.au/resume + + https://nathan.woodburn.au/tools + \ No newline at end of file diff --git a/templates/tools.html b/templates/tools.html new file mode 100644 index 0000000..0745827 --- /dev/null +++ b/templates/tools.html @@ -0,0 +1,148 @@ + + + + + + + Tools | Nathan.Woodburn/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

Tools

+

Here is a list of applications, tools and services I use regularly.

+
+
+
+
+
+
+
{% for type, tools_in_type in tools | groupby('type') %} +

{{ type }}

+
+ {% for tool in tools_in_type %} +
+
+
+

{{tool.name}}

+

{{ tool.description }}

+
{% if tool.demo %}{% endif %}{{tool.name}} Website
+
+
+
+ {% endfor %} +
+ +{% for tool in tools_in_type %} +{% if tool.demo %} + +{% endif %} +{% endfor %} +{% endfor %} + +
+
+ {{handshake_scripts | safe}} + + + + + + + \ No newline at end of file diff --git a/tools.py b/tools.py index 56b355d..61a1e63 100644 --- a/tools.py +++ b/tools.py @@ -5,6 +5,7 @@ import datetime from typing import Optional, Dict, Union, Tuple import re from dateutil.parser import parse +import json # HTTP status codes HTTP_OK = 200 @@ -250,3 +251,7 @@ def parse_date(date_groups: list[str]) -> str | None: except (ValueError, TypeError): return None + +def get_tools_data(): + with open("data/tools.json", "r") as f: + return json.load(f) \ No newline at end of file