Archived
Polish KarlDNS production UI and docs
This commit is contained in:
1 parent
ca92dc211a
commit
5beee0e5c6
61 files changed
+1566
-523
No files matched your search
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_OUTPUT = ROOT / "router_dyndns" / "assets" / "bootstrap-subset.css"
|
||||
SCAN_PATHS = [
|
||||
ROOT / "router_dyndns" / "web",
|
||||
ROOT / "router_dyndns" / "routers",
|
||||
]
|
||||
|
||||
CSS = r"""
|
||||
:root{--bs-body-color:#000;--bs-body-bg:#fff;--bs-border-color:#e5e5e5;--bs-secondary-color:#737373}[data-bs-theme="dark"]{--bs-body-color:#f5f5f5;--bs-body-bg:#0f0f0f;--bs-border-color:rgba(255,255,255,.16);--bs-secondary-color:#a3a3a3}html{-webkit-text-size-adjust:100%}body{margin:0}button,input,select{margin:0;font:inherit}button,select{text-transform:none}button:not(:disabled),[type="button"]:not(:disabled),[type="submit"]:not(:disabled){cursor:pointer}a{color:inherit}img,svg{vertical-align:middle}code,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}pre{margin:0}.container{width:100%;margin-right:auto;margin-left:auto;padding-right:12px;padding-left:12px}.row{display:flex;flex-wrap:wrap;margin-right:calc(-.5 * var(--bs-gutter-x,0));margin-left:calc(-.5 * var(--bs-gutter-x,0));margin-top:calc(-1 * var(--bs-gutter-y,0))}.row>*{width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x,0) * .5);padding-left:calc(var(--bs-gutter-x,0) * .5);margin-top:var(--bs-gutter-y,0)}.col-6{flex:0 0 auto;width:50%}.col-12{flex:0 0 auto;width:100%}@media (min-width:768px){.col-md-6{flex:0 0 auto;width:50%}.align-items-md-center{align-items:center!important}.flex-md-row{flex-direction:row!important}}@media (min-width:992px){.col-lg-3{flex:0 0 auto;width:25%}.col-lg-5{flex:0 0 auto;width:41.666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.333333%}.flex-lg-row{flex-direction:row!important}}@media (min-width:1200px){.col-xl-3{flex:0 0 auto;width:25%}}.g-3{--bs-gutter-x:16px;--bs-gutter-y:16px}.g-4{--bs-gutter-x:24px;--bs-gutter-y:24px}.d-flex{display:flex!important}.d-grid{display:grid!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-wrap{flex-wrap:wrap!important}.flex-shrink-0{flex-shrink:0!important}.align-items-center{align-items:center!important}.align-items-start{align-items:flex-start!important}.align-items-stretch{align-items:stretch!important}.align-self-start{align-self:flex-start!important}.justify-content-between{justify-content:space-between!important}.justify-content-center{justify-content:center!important}.justify-content-end{justify-content:flex-end!important}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.gap-sm-3{gap:16px!important}}.gap-2{gap:8px!important}.gap-3{gap:16px!important}.gap-4{gap:24px!important}.m-0{margin:0!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.ms-auto{margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-3{margin-top:16px!important;margin-bottom:16px!important}.mt-1{margin-top:4px!important}.mt-2{margin-top:8px!important}.mt-3{margin-top:16px!important}.mt-4{margin-top:24px!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:4px!important}.mb-2{margin-bottom:8px!important}.mb-3{margin-bottom:16px!important}.mb-4{margin-bottom:24px!important}.p-0{padding:0!important}.p-4{padding:24px!important}.px-0{padding-right:0!important;padding-left:0!important}.px-2{padding-right:8px!important;padding-left:8px!important}.px-3{padding-right:16px!important;padding-left:16px!important}.py-2{padding-top:8px!important;padding-bottom:8px!important}.w-100{width:100%!important}.h-100{height:100%!important}.text-center{text-align:center!important}.text-start{text-align:left!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-secondary{color:var(--bs-secondary-color)!important}.text-danger{color:var(--rp-danger)!important}.small{font-size:.875em!important}.h5{font-size:1.25rem}.opacity-75{opacity:.75!important}.rounded{border-radius:12px!important}.rounded-0{border-radius:0!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:9999px!important}.border{border:1px solid var(--bs-border-color)!important}.border-top{border-top:1px solid var(--bs-border-color)!important}.border-start-0{border-left:0!important}.border-end-0{border-right:0!important}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:5px;padding-bottom:5px;margin-right:16px;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-text{padding-top:8px;padding-bottom:8px}.nav-link{display:block;padding:8px;text-decoration:none}.btn{--bs-btn-color:#000;--bs-btn-bg:transparent;--bs-btn-border-color:transparent;--bs-btn-hover-color:var(--bs-btn-color);--bs-btn-hover-bg:var(--bs-btn-bg);--bs-btn-hover-border-color:var(--bs-btn-border-color);--bs-btn-active-color:var(--bs-btn-hover-color);--bs-btn-active-bg:var(--bs-btn-hover-bg);--bs-btn-active-border-color:var(--bs-btn-hover-border-color);color:var(--bs-btn-color);background-color:var(--bs-btn-bg);border:1px solid var(--bLine truncated
|
||||
""".strip()
|
||||
|
||||
SUPPORTED_DYNAMIC_CLASSES = {"active", "disabled", "page-item", "page-link"}
|
||||
|
||||
|
||||
def _class_names_from_css(css: str) -> set[str]:
|
||||
return set(re.findall(r"\.([_a-zA-Z][_a-zA-Z0-9-]*)", css))
|
||||
|
||||
|
||||
def _class_names_from_templates() -> set[str]:
|
||||
class_names: set[str] = set()
|
||||
for scan_path in SCAN_PATHS:
|
||||
for path in scan_path.glob("*.py"):
|
||||
text = path.read_text()
|
||||
for match in re.finditer(r"class=[\"']([^\"']+)[\"']", text):
|
||||
for class_name in match.group(1).split():
|
||||
if "{" not in class_name and "}" not in class_name:
|
||||
class_names.add(class_name)
|
||||
return class_names
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Build and validate the tiny KarlDNS Bootstrap compatibility subset.")
|
||||
parser.add_argument("--output", type=Path, default=DEFAULT_OUTPUT)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
output = args.output if args.output.is_absolute() else ROOT / args.output
|
||||
app_styles = (ROOT / "router_dyndns" / "web" / "styles.py").read_text()
|
||||
supported = _class_names_from_css(CSS) | _class_names_from_css(app_styles) | SUPPORTED_DYNAMIC_CLASSES
|
||||
unsupported = sorted(_class_names_from_templates() - supported)
|
||||
if unsupported:
|
||||
unsupported_text = "\n".join(f"- {class_name}" for class_name in unsupported)
|
||||
raise SystemExit(f"Unsupported Bootstrap-style classes in templates:\n{unsupported_text}")
|
||||
output.write_text(CSS + "\n")
|
||||
print(f"Wrote {output.relative_to(ROOT)} ({output.stat().st_size} bytes)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -22,12 +22,12 @@ Usage:
|
||||
|
||||
Options:
|
||||
--base-url URL KarlDNS server URL. Default: https://karldns.de
|
||||
--raw-base-url URL Script download base URL. Default: GitHub raw main branch
|
||||
--every MINUTES Cron interval, 1-60. Default: 5
|
||||
--raw-base-url URL Updater script download URL
|
||||
--every MINUTES Update interval, 1-60. Default: 5
|
||||
--mode MODE auto, system, or user. Default: auto
|
||||
--update-url URL Existing private /u/<secret> update URL
|
||||
--hostname NAME Optional display hostname for output/config
|
||||
--dashboard-url URL Optional private dashboard URL for output/config
|
||||
--update-url URL Existing private update URL
|
||||
--hostname NAME Hostname to show after install
|
||||
--dashboard-url URL Private dashboard URL to show after install
|
||||
--ipv4 VALUE auto, off, or a static IPv4. Default: auto
|
||||
--ipv6 VALUE auto, off, or a static IPv6. Default: auto
|
||||
--no-cron Install files only, do not configure cron
|
||||
@@ -202,7 +202,7 @@ case "$KARLDNS_INSTALL_MODE" in
|
||||
esac
|
||||
|
||||
if [ "$KARLDNS_INSTALL_MODE" = "system" ] && [ "$(id -u)" -ne 0 ]; then
|
||||
echo "System install needs root. Re-run with sudo or set KARLDNS_INSTALL_MODE=user." >&2
|
||||
echo "System install needs root. Re-run with sudo or use --mode user." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -215,7 +215,7 @@ if [ -z "$KARLDNS_UPDATE_URL" ]; then
|
||||
fi
|
||||
|
||||
if [ -z "$KARLDNS_UPDATE_URL" ]; then
|
||||
echo "KarlDNS installer: could not get an update URL" >&2
|
||||
echo "KarlDNS installer: could not create an update URL" >&2
|
||||
exit 1
|
||||
fi
|
||||
case "$KARLDNS_UPDATE_URL" in
|
||||
@@ -230,10 +230,10 @@ fi
|
||||
|
||||
echo "KarlDNS installed."
|
||||
echo "Hostname: ${KARLDNS_HOSTNAME:-already configured}"
|
||||
echo "Dashboard: ${KARLDNS_MANAGEMENT_URL:-use your existing private status page}"
|
||||
echo "Dashboard: ${KARLDNS_MANAGEMENT_URL:-use your existing private dashboard}"
|
||||
echo "Mode: $KARLDNS_INSTALL_MODE"
|
||||
if [ "$KARLDNS_SKIP_CRON" = "0" ]; then
|
||||
echo "Interval: every $KARLDNS_INTERVAL_MINUTES minute(s)"
|
||||
echo "Interval: every $KARLDNS_INTERVAL_MINUTES minutes"
|
||||
else
|
||||
echo "Cron: skipped"
|
||||
fi
|
||||
|
||||
@@ -8,7 +8,7 @@ if [ -r "$CONFIG_FILE" ]; then
|
||||
. "$CONFIG_FILE"
|
||||
fi
|
||||
|
||||
: "${KARLDNS_UPDATE_URL:?KARLDNS_UPDATE_URL is required in $CONFIG_FILE or the environment}"
|
||||
: "${KARLDNS_UPDATE_URL:?Set KARLDNS_UPDATE_URL in $CONFIG_FILE or the environment}"
|
||||
KARLDNS_BASE_URL="${KARLDNS_BASE_URL:-https://karldns.de}"
|
||||
KARLDNS_LOCK_FILE="${KARLDNS_LOCK_FILE:-/tmp/karldns-updater.lock}"
|
||||
KARLDNS_CACHE_FILE="${KARLDNS_CACHE_FILE:-${XDG_CACHE_HOME:-$HOME/.cache}/karldns/last-ip}"
|
||||
@@ -49,8 +49,8 @@ elif [ "$KARLDNS_IPV6" != "off" ]; then
|
||||
ipv6="$KARLDNS_IPV6"
|
||||
fi
|
||||
|
||||
case "$ipv4" in *" "*) echo "KarlDNS: invalid IPv4 discovery result" >&2; exit 1 ;; esac
|
||||
case "$ipv6" in *" "*) echo "KarlDNS: invalid IPv6 discovery result" >&2; exit 1 ;; esac
|
||||
case "$ipv4" in *" "*) echo "KarlDNS: could not read a valid IPv4 address" >&2; exit 1 ;; esac
|
||||
case "$ipv6" in *" "*) echo "KarlDNS: could not read a valid IPv6 address" >&2; exit 1 ;; esac
|
||||
public_ip="${ipv4}|${ipv6}"
|
||||
has_discovered_ip=0
|
||||
if [ -n "$ipv4" ] || [ -n "$ipv6" ]; then
|
||||
@@ -87,7 +87,7 @@ case "$response" in
|
||||
echo "KarlDNS: $response"
|
||||
;;
|
||||
*)
|
||||
echo "KarlDNS: update failed: $response" >&2
|
||||
echo "KarlDNS: update failed. Server said: $response" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -13,7 +13,7 @@ Usage:
|
||||
|
||||
Options:
|
||||
--mode MODE auto, system, or user. Default: auto
|
||||
--delete-config Also delete the private updater config and last-IP cache
|
||||
--delete-config Also delete saved private URLs and the last-IP cache
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
}
|
||||
@@ -80,5 +80,5 @@ fi
|
||||
|
||||
echo "KarlDNS uninstalled from $KARLDNS_INSTALL_MODE mode."
|
||||
if [ "$KARLDNS_DELETE_CONFIG" = "0" ]; then
|
||||
echo "Private config was kept. Re-run with --delete-config to remove it."
|
||||
echo "Saved private URLs were kept. Re-run with --delete-config to remove them."
|
||||
fi
|
||||
Reference in new issue
Block a user