diff --git a/.github/workflows/docker_dashboard.yaml b/.github/workflows/docker_dashboard.yaml deleted file mode 100644 index a201b74..0000000 --- a/.github/workflows/docker_dashboard.yaml +++ /dev/null @@ -1,34 +0,0 @@ -name: Publish dashboard Image - -on: - push: - branches: - - 'main' - paths: - - 'dashboard/*' - tags: - - 'dashboard-*' - -jobs: - dashboard: - name: Publish dashboard Image - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v3.4.0 - - name: Publish dashboard Image - uses: openzim/docker-publish-action@v10 - with: - image-name: offspot/dashboard - on-master: dev - build-args: - VERSION={tag} - tag-pattern: /^dashboard-([0-9.]+)$/ - restrict-to: offspot/container-images - platforms: | - linux/amd64 - linux/arm64 - context: dashboard - registries: ghcr.io - credentials: - GHCRIO_USERNAME=${{ secrets.GHCR_USERNAME }} - GHCRIO_TOKEN=${{ secrets.GHCR_TOKEN }} diff --git a/dashboard/.gitignore b/dashboard/.gitignore deleted file mode 100644 index 36d2079..0000000 --- a/dashboard/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -assets/fontawesome -/assets/dashboard.css -/*.yaml -/download -/download.html -/index.html diff --git a/dashboard/Dockerfile b/dashboard/Dockerfile deleted file mode 100644 index d9af489..0000000 --- a/dashboard/Dockerfile +++ /dev/null @@ -1,72 +0,0 @@ -FROM alpine:3.19 -LABEL org.opencontainers.image.source https://github.com/offspot/container-images - -RUN \ - apk add --no-cache curl dumb-init yaml python3 lighttpd \ - # FontAwesome font - && curl -L -O https://use.fontawesome.com/releases/v6.5.1/fontawesome-free-6.5.1-web.zip \ - && unzip -o fontawesome-free-6.5.1-web.zip \ - fontawesome-free-6.5.1-web/css/* \ - fontawesome-free-6.5.1-web/webfonts/* \ - -d /var/www/assets/ \ - && mv /var/www/assets/fontawesome-free-6.5.1-web /var/www/assets/fontawesome \ - && rm -f fontawesome-free-6.5.1-web.zip \ - # python dependencies - && python3 -m venv /usr/local/bin/gen-home_env \ - && /usr/local/bin/gen-home_env/bin/pip3 install --no-cache-dir -U pip \ - && /usr/local/bin/gen-home_env/bin/pip3 install \ - --no-cache-dir \ - Jinja2==3.1.2 PyYAML==6.0.1 humanfriendly==10.0 iso639-lang==2.2.3 libzim==3.4.0 \ - # install tailwind CSS cli - && curl -L -o /usr/local/bin/tailwindcss https://github.com/tailwindlabs/tailwindcss/releases/download/v3.4.3/tailwindcss-linux-arm64 \ - && chmod +x /usr/local/bin/tailwindcss \ - && apk del curl - -ENV FQDN "generic.hotspot" -ENV NAME "My Hotspot" -# path in which to find code (templates) -ENV SRC_DIR "/src" -# path to packages YAML file -ENV PACKAGES_PATH "/src/home.yaml" -# path to write home HTML and assets file -ENV DEST_DIR "/var/www" -# folder storing ZIM files. unless DONT_UPDATE_PACKAGES, ZimPackages not in the folder -# will be removed (disabled) from packages.yaml -# discovered ZIM (not in YAML) will be added -ENV ZIM_DIR "/data/zims" -# set to skip packages.yaml update on start (reading ZIM_PATH folder) -ENV DONT_UPDATE_PACKAGES "" - -# templates to write ZIM Package links to reader and ZIM downloads. -# Available patterns (to be replaced): `{fqdn}`, `{zim_name}`, `{zim_filename}` -ENV KIWIX_READER_LINK_TPL "//kiwix.{fqdn}/viewer#{zim_name}" -ENV KIWIX_DOWNLOAD_LINK_TPL "//zim-download.{fqdn}/{zim_filename}" - -# WARN: this break apk but saves a lot of space -# it's OK on prod but comment it during dev if you need packages -RUN apk del apk-tools ca-certificates-bundle - -COPY gen-home.py refresh-zims.py tailwind.config.js /src/ -COPY templates /src/templates -COPY assets /var/www/assets -COPY fallback.html /var/www/fallback.html -COPY home.yaml /src/ -COPY lighttpd.conf /etc/lighttpd/ -COPY entrypoint.sh /usr/local/bin/ - -WORKDIR /src - -RUN \ - # gen final CSS - tailwindcss -i /var/www/assets/dashboard-tailwind-src.css -o /var/www/assets/dashboard.css \ - && rm -f /var/www/assets/dashboard-tailwind-src.css \ - && rm -f /usr/local/bin/tailwindcss \ - # store python bytecode in image - && /usr/local/bin/gen-home_env/bin/python3 -m compileall /src/gen-home.py /src/refresh-zims.py && mv /src/__pycache__/*.pyc /usr/local/bin/gen-home_env/lib/ - -WORKDIR /var/www - -HEALTHCHECK --interval=10s --timeout=2s CMD ["/bin/ls", "/tmp/.ready"] -ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/local/bin/entrypoint.sh"] -CMD ["lighttpd", "-D", "-f", "/etc/lighttpd/lighttpd.conf"] - diff --git a/dashboard/README.md b/dashboard/README.md deleted file mode 100644 index f168efe..0000000 --- a/dashboard/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# dashboard - -Caddy-based file server serving static file with home-page generated on startup - -## Configuration - -Configuration is done via a YAML file describing the contents that should be exposed. - -| Variable | Usage | -| ---------------------- | ------------------------------------------------------------------------------------------ | -| `metadata` | A dict containing hompage-level metadata | -| **`metadata.name`** | Hotspot name | -| **`metadata.fqdn`** | Domain name of the hotspot. Used to build links | -| `packages` | A list of `package` dicts describing each content package | -| **`package.title`** | Main name of content | -| `package.kind` | Kind of package, to adjust display and filters | -| `package.description` | Short description of the content | -| `package.languages` | List of ISO-639-3 language code the content relates to | -| `package.tags` | List of Tag strings helping users guess what to expect from content | -| `package.icon` | Base64 encoded 48x48px PNG image | -| **`package.url`** | URL to access the content on the hotspot. Accepts some variables | -| `package.download.url` | URL to download the content from (for hotspot users) | -| `package.download.size`| Size of the download file to download. Informative only | - -Main goal of this tool being linking to content, `package.url` accepts different helpers which are rewritten automatically: - -- `{fqdn}` is replaced by the content of `metadata.fqdn`. -- `{service-fqdn}` is replaced by the service's subdomain. Ex: `{kiwix-fqdn}` may become `kiwix.myname.hotspot`. -- URLs not starting with a scheme (`urllib.parse.urlparse`) will be prefixed with `//`. -- `//` is the only scheme-less prefix allowed. -- Use `http://` or `https://` to enforce protocol. -- You can use external links (at your own risk of it not working) -- Other protocols alike. - -### Sample - -```yaml ---- -metadata: - name: My Hotspot - fqdn: renaud.Hotspot -packages: - kind: zim - title: ArchWiki - description: Arch Linux documentation - languages: - - eng - tags: - - archlinux - url: //{kiwix-fqdn}/archlinux_en_all_maxi_2022-04 - download: - url: //{zimdl-fqdn}/archlinux_en_all_maxi_2022-04.zim - size: 32M -``` - -In addition, the `DEBUG` environ can be set to enable Caddy debug output. \ No newline at end of file diff --git a/dashboard/assets/close-down-orange.png b/dashboard/assets/close-down-orange.png deleted file mode 100644 index f8aae17..0000000 Binary files a/dashboard/assets/close-down-orange.png and /dev/null differ diff --git a/dashboard/assets/dashboard-tailwind-src.css b/dashboard/assets/dashboard-tailwind-src.css deleted file mode 100644 index 3ef9fea..0000000 --- a/dashboard/assets/dashboard-tailwind-src.css +++ /dev/null @@ -1,169 +0,0 @@ -/* Tailwind source file in dashboard-tailwind-src.css - -To be compiled using `npx tailwindcss` -eg: npx tailwindcss -i ./assets/dashboard-tailwind-src.css -o ./assets/dashboard.css -*/ -@tailwind base; -@tailwind components; -@tailwind utilities; - -:root { - --rounded-btn: 0.5rem; -} - -/*START: daisyui select (adapted; need to reimpl in tw) */ -select { - font-family: inherit; - font-feature-settings: inherit; - font-variation-settings: inherit; - font-size: 100%; - font-weight: inherit; - line-height: inherit; - color: inherit; - margin: 0; - padding: 0; - padding-right: 0px; - padding-left: 0px; - - text-transform: none; -} - -.select { - display: inline-flex; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - height: 3rem; - min-height: 3rem; - padding-left: 1rem; - padding-right: 2.5rem; - font-size: 0.875rem; - line-height: 1.25rem; - line-height: 2; - border-radius: var(--rounded-btn, 0.5rem); - border-width: 1px; - border-color: transparent; - --tw-bg-opacity: 1; - background-color: oklch(1 0 0); - background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%); - background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%); - background-size: 4px 4px, 4px 4px; - background-repeat: no-repeat; -} - -[dir="rtl"] .select { - background-position: - calc(0% + 12px) calc(1px + 50%), - calc(0% + 16px) calc(1px + 50%); -} - - -.max-w-xs { - max-width: 20rem; -} -.select-sm { - height: 2rem; - min-height: 2rem; - padding-left: 0.75rem; - padding-right: 2rem; - font-size: 0.875rem; - line-height: 2rem; -} -.select-bordered { - border-color: var(oklch(var(--bc)/0.2)); -} - -/*END: daisyui select */ - -@layer components { - .kiwix-home-btn { - @apply hover:bg-kwordergrey-600 hover:text-white bg-kwordergrey-500 text-kwwhite; - } - .kiwix-home-btn-active { - @apply border-white text-kwordergrey-500 bg-white drop-shadow-md hover:text-kwordergrey-500 hover:bg-white hover:cursor-default; - } - - .kiwix-download-btn { - @apply hover:bg-kworange-800 hover:text-white bg-kworange-500 text-kwwhite; - } - .kiwix-download-btn-active { - @apply border-white text-kworange-100 bg-white drop-shadow-md hover:text-kworange-100 hover:bg-white hover:cursor-default; - } - - .kiwix-order-dir-btn { - @apply mr-1 last:mr-0 cursor-pointer p-0 text-[0.9em] leading-[1.1em] h-[1.7em] w-[1.7em] rounded-md drop-shadow-none; - } - - .kiwix-sort-btn { - @apply mr-0 last:mr-0 cursor-pointer box-border px-[2em] py-0 text-[0.9em] leading-[1.1em] h-[2em] rounded-2xl min-w-[3em] md:min-w-[6em] relative z-40 pr-[1.8em] drop-shadow-none; - } - .kiwix-sort-btn-active { - @apply z-50 pr-[2em] hover:cursor-default; - } - - .kiwix-download-filter-label { - @apply text-white; - } - - .kiwix-reader-platform-btn { - @apply cursor-pointer border-white border bg-kwordergrey-500 md:bg-kwordergrey-500 md:bg-kwreaderbggrey text-white text-sm rounded-xl px-3 py-1 sm:me-1 lg:me-3 last:me-0 hover:bg-kwordergrey-600 md:hover:bg-kwreaderhover hover:text-white hover:border-white; - } - - .kiwix-reader-platform-btn-active { - @apply text-kwreaderbggrey bg-white drop-shadow-md hover:text-kwreaderbggrey hover:bg-white hover:border-white hover:cursor-default; - } - - .kiwix-home-innerglow { - box-shadow: inset 0 0 1em #a6b2bd; - } - - .kiwix-download-innerglow { - box-shadow: inset 0 0 1em #c60; - } - - .kiwix-mini-download-open-outerglow { - box-shadow: 0 0 1em 0.5em #c60; - } - - .kiwix-mini-download-close-uppershadow { - box-shadow: #f39325 0 -0.4rem 0.9rem 0; - } - - .kiwix-mini-download-open-downshadow { - box-shadow: inset 0 0.3em 0.3em #c60; - } - - .bg-center-top { - background-position-x: center; - background-position-y: top; - } - - .content-max-w { - @apply sm:max-w-screen-sm md:max-w-screen-md lg:max-w-screen-md xl:max-w-screen-lg 2xl:max-w-screen-xl; - } - - .content-w { - @apply sm:w-[640px] md:w-[768px] lg:w-[1024px] xl:w-[1280px] 2xl:w-[1536px] w-[90%] mx-auto; - } - - .mini-height-card-90 { - height: calc((90vw /8.79) * 4.69); - } - .mini-height-card-60 { - height: calc((60vw /8.79) * 4.69); - } - .inherit { - position: inherit; - } - .hide-after-2:nth-child(3), .hide-after-2:nth-child(4) { - @apply hidden; - } -} - -body { - font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif; -} diff --git a/dashboard/assets/dashboard.js b/dashboard/assets/dashboard.js deleted file mode 100644 index e639f87..0000000 --- a/dashboard/assets/dashboard.js +++ /dev/null @@ -1,251 +0,0 @@ -// matches polyfill -this.Element && function(ElementPrototype) { - ElementPrototype.matches = ElementPrototype.matches || - ElementPrototype.matchesSelector || - ElementPrototype.webkitMatchesSelector || - ElementPrototype.msMatchesSelector || - function(selector) { - var node = this, nodes = (node.parentNode || node.document).querySelectorAll(selector), i = -1; - while (nodes[++i] && nodes[i] != node); - return !!nodes[i]; - } -}(Element.prototype); -// closest polyfill -this.Element && function(ElementPrototype) { - ElementPrototype.closest = ElementPrototype.closest || - function(selector) { - var el = this; - while (el.matches && !el.matches(selector)) el = el.parentNode; - return el.matches ? el : null; - } -}(Element.prototype); - -// helper for enabling IE 8 event bindings -function addEvent(el, type, handler) { - if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler); -} - -function hasClass(el, className) { - return el.classList ? el.classList.contains(className) : new RegExp('\\b'+ className+'\\b').test(el.className); -} - -function addClass(el, className) { - if (el.classList) el.classList.add(className); - else if (!hasClass(el, className)) el.className += ' ' + className; -} - -function removeClass(el, className) { - if (el.classList) el.classList.remove(className); - else el.className = el.className.replace(new RegExp('\\b'+ className+'\\b', 'g'), ''); -} - -function toggleClass(el, className) { - if (hasClass(el, className)) - removeClass(el, className); - else - addClass(el, className); -} - -// live binding helper using matchesSelector -function live(selector, event, callback, context) { - addEvent(context || document, event, function(e) { - var found, el = e.target || e.srcElement; - while (el && el.matches && el !== context && !(found = el.matches(selector))) el = el.parentElement; - if (found) callback.call(undefined, el, e); - }); -} - -function getCookie(name) { - var v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)'); - return v ? v[2] : null; -} - -function setCookie(name, value, deleteAfter) { - document.cookie = name + '=' + value + ';path=/;max-age=' + deleteAfter; -} - -function deleteCookie(name) { setCookie(name, '', -1); } - -const Filtering = class { - order_by = 'name'; - order_dir = 'desc'; - only_lang = ''; - only_category = ''; - - constructor(order_by, order_dir, only_lang, only_category) { - this.order_by = order_by || this.order_by; - this.order_dir = order_dir || this.order_dir; - this.only_lang = only_lang || this.only_lang; - this.only_category = only_category || this.only_category; - } - - toString() { - return `order_by=${this.order_by}, order_dir=${this.order_dir}, only_lang=${this.only_lang}, only_category=${this.only_category}` - } - - sortByNameAsc(a, b) { - a = a.getAttribute('data-name').toLowerCase(); - b = b.getAttribute('data-name').toLowerCase(); - if (a == b) - return 0; - return a > b ? -1 : 1; - } - - sortByNameDesc(a, b) { - a = a.getAttribute('data-name').toLowerCase(); - b = b.getAttribute('data-name').toLowerCase(); - if (a == b) - return 0; - return a < b ? -1 : 1; - } - - sortBySizeAsc(a, b) { - a = parseInt(a.getAttribute('data-size')); - b = parseInt(b.getAttribute('data-size')); - if (a == b) - return 0; - return a < b ? -1 : 1; - } - - sortBySizeDesc(a, b) { - a = parseInt(a.getAttribute('data-size')); - b = parseInt(b.getAttribute('data-size')); - if (a == b) - return 0; - return a > b ? -1 : 1; - } - - sortByDOMOrder(a, b) { - return 0; - } - - getSortFunction() { - if (this.order_by == 'name') - return (this.order_dir == 'asc') ? this.sortByNameAsc : this.sortByNameDesc; - if (this.order_by == 'size') - return (this.order_dir == 'asc') ? this.sortBySizeAsc : this.sortBySizeDesc; - return this.sortByDOMOrder; - } - - render(withSorting) { - const elems = document.getElementById('entries').getElementsByClassName('hotspot-entry'); - for (var i=0; i - - - - - - diff --git a/dashboard/assets/hotspot-logo.png b/dashboard/assets/hotspot-logo.png deleted file mode 100644 index 78ac7a3..0000000 Binary files a/dashboard/assets/hotspot-logo.png and /dev/null differ diff --git a/dashboard/assets/kiwix-bird-white.png b/dashboard/assets/kiwix-bird-white.png deleted file mode 100644 index 02e65f5..0000000 Binary files a/dashboard/assets/kiwix-bird-white.png and /dev/null differ diff --git a/dashboard/assets/kiwix-bird-white.svg b/dashboard/assets/kiwix-bird-white.svg deleted file mode 100644 index ab0a938..0000000 --- a/dashboard/assets/kiwix-bird-white.svg +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - diff --git a/dashboard/assets/open-down-orange.png b/dashboard/assets/open-down-orange.png deleted file mode 100644 index 532f6ac..0000000 Binary files a/dashboard/assets/open-down-orange.png and /dev/null differ diff --git a/dashboard/entrypoint.sh b/dashboard/entrypoint.sh deleted file mode 100755 index 93c904f..0000000 --- a/dashboard/entrypoint.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -set -e - -VENV=/usr/local/bin/gen-home_env - -# refresh ZIM packages collection (maybe) -$VENV/bin/python3 $VENV/lib/refresh-zims.cpython-311.pyc - -# generate homepage from collection -$VENV/bin/python3 $VENV/lib/gen-home.cpython-311.pyc - -touch /tmp/.ready - -exec "$@" diff --git a/dashboard/fallback.html b/dashboard/fallback.html deleted file mode 100644 index 60b7e56..0000000 --- a/dashboard/fallback.html +++ /dev/null @@ -1,7 +0,0 @@ - - -Error - -

Error!

-

Homepage was not generated. Try restarting hotspot.

- diff --git a/dashboard/gen-home.py b/dashboard/gen-home.py deleted file mode 100755 index a38675a..0000000 --- a/dashboard/gen-home.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/env python3 - -""" gen-home: generate static home page from Packages YAML - - - Reads Packages YAML from `PACKAGES_PATH` - - Prepares an HTML output with templates defined in `SRC_DIR`/templates - - Writes and index.html in `DEST_DIR` - - Optionally (`DEBUG`) outputs index to stdout as well - - Dependencies: - - PyYAML - - Jinja2 - - humanfriendly -""" - -import os -import pathlib -import re -import traceback -import urllib.parse -from collections.abc import Iterable - -import humanfriendly -import iso639 -from jinja2 import Environment, FileSystemLoader, select_autoescape -import yaml - -try: - from yaml import CSafeLoader as SafeLoader -except ImportError: - # we don't NEED cython ext but it's faster so use it if avail. - from yaml import SafeLoader - - -src_dir = pathlib.Path(os.getenv("SRC_DIR", "/src")).expanduser().resolve() -packages_path = ( - pathlib.Path(os.getenv("PACKAGES_PATH", "home.yaml")).expanduser().resolve() -) -dest_dir = pathlib.Path(os.getenv("DEST_DIR", "/var/www")).expanduser().resolve() -templates_dir = src_dir.joinpath("templates") -env = Environment( - loader=FileSystemLoader(templates_dir), autoescape=select_autoescape() -) - - -def format_fsize(size: str | int) -> str: - one_gib = 2**30 - one_mib = 2**20 - hundred_mib = one_gib / 10 - - def round_to(size: int, scale: int) -> int: - return (size // scale) * scale - - if not str(size).isdigit(): - size = humanfriendly.parse_size(str(size)) - size = int(size) - - if size > one_gib and size >= 100 * one_gib: - size = round_to(size, one_gib) - elif size > one_gib: - size = size = round_to(size, hundred_mib) - else: - size = round_to(size, one_mib) - try: - return humanfriendly.format_size( - int(size), keep_width=False, binary=True - ).replace("iB", "B") - except Exception: - return str(size) - - -env.filters["fsize"] = format_fsize - - -def normalize(url: str) -> str: - if not url.strip(): - return "" - uri = urllib.parse.urlparse(url) - if not uri.scheme and not url.startswith("//"): - url = f"//{url}" - - url = re.sub(r"{([a-z]+)-fqdn}", r"\1.{fqdn}", url) - url = url.replace("{fqdn}", Conf.fqdn) - return url - - -class Conf: - debug: bool = bool(os.getenv("DEBUG", False)) - fqdn: str = "" - name: str = "" - footer_note: str = "" - - @classmethod - def from_doc(cls, document): - for key in ("name", "fqdn", "footer_note"): - setattr(cls, key, document.get(key, "--")) - - @classmethod - def to_dict(cls): - return { - key: getattr(cls, key) for key in ("debug", "fqdn", "name", "footer_note") - } - - -class Link(dict): - MANDATORY_FIELDS = ["name", "url"] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self["url"] = normalize(self.get("url", "")) - - -class Reader(dict): - MANDATORY_FIELDS = ["platform", "url", "size"] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self["url"] = normalize(self.get("url", "")) - - @property - def name(self) -> str: - return { - "windows": "Windows", - "android": "Android", - "macos": "macOS", - "linux": "Linux", - }.get(self["platform"].lower(), self["platform"]) - - @property - def icon(self) -> str: - return { - "windows": "windows", - "android": "android", - "macos": "apple", - "linux": "linux", - }.get(self["platform"].lower(), "robot") - - -class Package(dict): - MANDATORY_FIELDS = ["title", "url"] - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self["url"] = normalize(self.get("url", "")) - try: - self["download"]["url"] = normalize(self["download"]["url"]) - except KeyError: - ... - - @property - def tags(self) -> list[str]: - return [tag for tag in self.get("tags", []) if tag and not tag.startswith("_")] - - @property - def private_tags(self) -> list[str]: - return [tag for tag in self.get("tags", []) if tag.startswith("_")] - - @property - def visible(self): - if self.get("disabled", False): - return False - try: - return all([self[key] for key in self.MANDATORY_FIELDS]) - except KeyError: - return False - - @property - def langs(self) -> list[str]: - return [lang[:2] for lang in self.get("languages", [])] - - -def gen_home(fpath: pathlib.Path): - try: - document = yaml.load(fpath.read_text(), Loader=SafeLoader) - except Exception as exc: - print("[CRITICAL] unable to read home YAML document, using fallback homepage") - traceback.print_exception(exc) - return - - Conf.from_doc(document.get("metadata", {})) - context = Conf.to_dict() - context["packages"] = list( - filter(lambda p: p.visible, [Package(**item) for item in document["packages"]]) - ) - context["languages"] = {} - context["categories"] = set() - for package in context["packages"]: - for lang in package.get("languages", []): - context["languages"][lang] = iso639.Lang(lang).name - for tag in package.get("tags", []): - if tag.startswith("_category:"): - package["category"] = tag.split(":", 1)[-1] - context["categories"].add(package["category"]) - context["categories"] = sorted(context["categories"]) - context["readers"] = [Reader(**item) for item in document.get("readers", [])] - context["links"] = [Link(**item) for item in document.get("links", [])] - - try: - with open(dest_dir / "index.html", "w") as fh: - context["page"] = "home" - fh.write(env.get_template("home.html").render(**context)) - - with open(dest_dir / "download.html", "w") as fh: - context["page"] = "download" - fh.write(env.get_template("download.html").render(**context)) - except Exception as exc: - print("[CRITICAL] unable to gen homepage, using fallback") - traceback.print_exception(exc) - return - - print("Generated homepage") - - -if __name__ == "__main__": - gen_home(packages_path) - - if Conf.debug: - with open(dest_dir / "index.html", "r") as fh: - print(fh.read()) diff --git a/dashboard/home.yaml b/dashboard/home.yaml deleted file mode 100644 index 8672e20..0000000 --- a/dashboard/home.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -metadata: - name: Not Configured - fqdn: default.hotspot -packages: [] diff --git a/dashboard/lighttpd.conf b/dashboard/lighttpd.conf deleted file mode 100644 index 3a43905..0000000 --- a/dashboard/lighttpd.conf +++ /dev/null @@ -1,5 +0,0 @@ -server.document-root = "/var/www/" -index-file.names = ( "index.html", "fallback.html" ) - -server.modules += ("mod_alias") -alias.url += ( "/download" => "/var/www/download.html" ) diff --git a/dashboard/refresh-zims.py b/dashboard/refresh-zims.py deleted file mode 100644 index aaf757f..0000000 --- a/dashboard/refresh-zims.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 - -""" refresh-zims: update YAML packages list using disk-discovered ZIM files - - This is an optional feature, yet it is enabled by default. - Set `DONT_UPDATE_PACKAGES` environ to disable - - Once enabled, this loops over ZIM files present on disk (at `ZIM_DIR` path) - and updates the Packages YAML (at `PACKAGES_PATH`) accordingly: - - if the package (using its ident) was in YAML: keep YAML entry - - if the ZIM was not in YAML: add entry using in-ZIM info - - entries in YAML for which ZIM is missing are disabled but kept. - - Dependencies: - - PyYAML - - libzim -""" -import base64 -import os -import pathlib -import traceback -from typing import Any - -import yaml -from libzim.reader import Archive - -try: - from yaml import CDumper as Dumper - from yaml import CSafeLoader as SafeLoader -except ImportError: - # we don't NEED cython ext but it's faster so use it if avail. - from yaml import Dumper, SafeLoader - -packages_path = ( - pathlib.Path(os.getenv("PACKAGES_PATH", "home.yaml")).expanduser().resolve() -) -zims_dir = pathlib.Path(os.getenv("ZIM_DIR", "/src")) -dont_update = bool(os.getenv("DONT_UPDATE_PACKAGES", "")) - -kiwix_reader_link_tpl = os.getenv("KIWIX_READER_LINK_TPL", "//kiwix.{fqdn}/{zim_name}") -kiwix_download_link_tpl = os.getenv( - "KIWIX_DOWNLOAD_LINK_TPL", "//zim-download.{fqdn}/{zim_filename}" -) - - -def get_metadata(archive: Archive, name: str) -> str: - if name not in archive.metadata_keys: - return "" - return archive.get_metadata(name).decode("UTF-8") - - -def get_kiwix_url(template: str, fqdn: str, name: str, filename: str) -> str: - return ( - template.replace("{fqdn}", fqdn) - .replace("{zim_name}", name) - .replace("{zim_filename}", filename) - ) - - -def get_entry_for( - fpath: pathlib.Path, document_metadata: dict[str, str] -) -> dict[str, Any]: - zim = Archive(fpath) - publisher = get_metadata(zim, "Publisher") - name = get_metadata(zim, "Name") - flavour = get_metadata(zim, "Flavour") - ident = f"{publisher}:{name}:{flavour}" - icon = None - if zim.has_illustration and 48 in zim.get_illustration_sizes(): - icon = base64.b64encode(bytes(zim.get_illustration_item(48).content)).decode( - "ASCII" - ) - return { - "kind": "zim", - "ident": ident, - "title": get_metadata(zim, "Title"), - "description": get_metadata(zim, "Description"), - "languages": get_metadata(zim, "Language").split(",") or ["eng"], - "tags": get_metadata(zim, "Tags").split(";"), - "url": get_kiwix_url( - template=kiwix_reader_link_tpl, - fqdn=document_metadata["fqdn"], - name=name, - filename=fpath.name, - ), - "download": { - "url": get_kiwix_url( - template=kiwix_download_link_tpl, - fqdn=document_metadata["fqdn"], - name=name, - filename=fpath.name, - ), - "size": fpath.stat().st_size, - }, - "icon": icon, - } - - -def refresh_zims( - packages_path: pathlib.Path, zims_dir: pathlib.Path, debug: bool | None = False -): - print(f"refreshing ZIMs from {zims_dir=}") - try: - document = yaml.load(packages_path.read_text(), Loader=SafeLoader) - document["packages"] - document["metadata"] - document["metadata"]["fqdn"] - document["metadata"]["name"] - except Exception as exc: - print("[CRITICAL] unable to read home YAML document, skiping") - traceback.print_exception(exc) - return - - # copy list of packages from YAML - conf_packages = { - package.get("ident", ""): package for package in document["packages"] - } - - # get packages from what's on the filesystem - fs_packages = {} - for zim_fpath in zims_dir.glob("*.zim"): - try: - package = get_entry_for(zim_fpath, document["metadata"]) - except Exception as exc: - print(f"Failed to read from {zim_fpath}, skiping ({exc})") - continue - else: - fs_packages[package["ident"]] = package - - # now let's start from scratch - document["packages"] = [] - - # first, adding files in config, in their original order, disabling missing ones - for ident, package in conf_packages.items(): - # make sure we keep non-ZIM packages - if ident in fs_packages or package.get("kind", "") != "zim": - package.pop("disabled", None) - else: - package["disabled"] = True - document["packages"].append(package) - - # second, add all the fs ones, but not those already in conf - for ident, package in fs_packages.items(): - # dont add twice, was already handled - if ident in conf_packages: - continue - document["packages"].append(package) - - try: - packages_path.write_text(yaml.dump(document, Dumper=Dumper)) - except Exception as exc: - print("[CRITICAL] unable to update(save) home YAML document, skiping") - traceback.print_exception(exc) - return - - -if __name__ == "__main__": - if not dont_update and zims_dir.exists() and zims_dir.is_dir(): - refresh_zims( - packages_path=packages_path, - zims_dir=zims_dir, - debug=bool(os.getenv("DEBUG", False)), - ) - else: - print(f"Not refreshing ZIMs for {dont_update=}, {zims_dir=}") diff --git a/dashboard/tailwind.config.js b/dashboard/tailwind.config.js deleted file mode 100644 index c2a026f..0000000 --- a/dashboard/tailwind.config.js +++ /dev/null @@ -1,68 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["./templates/**/*.{html,js}"], - safelist: [ - 'kiwix-home-btn', - 'kiwix-home-btn-active', - 'kiwix-download-btn', - 'kiwix-download-btn-active', - 'kiwix-order-dir-btn', - 'kiwix-sort-btn', - 'kiwix-sort-btn-active', - {pattern: /kiwix-.+/}, - ], - theme: { - extend: { - screens: { - 'xxs': {'min': '376px', 'max': '439px'}, - 'xs': {'min': '450px', 'max': '639px'}, - }, - colors: { - kwwhite: '#eef2f6', - kwbgwhitehover: '#f7f8fa', - kwbggrey: '#eef2f6', - kwfiltersgrey: { - DEFAULT: '#dbe1e9', - 400: '#dae0e8', - 500: '#d6dce4', - 600: '#d3d9e1', - 700: '#cfd5dd', - 800: '#cbd1d9', - 900: '#ccd3da', - }, - kwordergrey: { - 100: '#a6b2bd', - 500: '#6f8291', - 600: '#92a6b9', - }, - kwtagbggrey: '#a8aba2', - kwreaderbggrey: '#777777', - kwreaderhover: '#4c4c4c', - kwbordergrey: '#c3c5c6', - kwtextblack: '#231f20', - kwtextgrey: '#585b5d', - kwtextgreyn: '#707272', - 'kworange': { - DEFAULT: '#f39325', - 100: '#f79433', - 200: '#f39033', - 300: '#f08e33', - 400: '#ed8b33', - 500: '#cc6600', - 600: '#dc7d2c', - 700: '#da7c2c', - 800: '#d7711d', - 900: '#d77336', - } - }, - gridTemplateColumns: { - '1m': 'repeat(1, max-content)', - '1f': 'minmax(100%, 100%)', - '2m': 'repeat(2, max-content)', - '3m': 'repeat(3, max-content)', - '4m': 'repeat(4, max-content)', - '5m': 'repeat(5, max-content)', - } - } - }, -} diff --git a/dashboard/templates/base.html b/dashboard/templates/base.html deleted file mode 100644 index 946b29b..0000000 --- a/dashboard/templates/base.html +++ /dev/null @@ -1,292 +0,0 @@ - - - - - - {{ name }} - - - - - - - - - - -
- -
- - {% if page == 'home' %} -
- -
- -
-
- - - -
-
-
- {% endif %} - - -{% block entries %} -{% endblock %} - - - {% if page == 'download' %} -
- -
- -
-
- - - -
-
- -
-
- {% endif %} - - - - diff --git a/dashboard/templates/download.html b/dashboard/templates/download.html deleted file mode 100644 index 54f7e4d..0000000 --- a/dashboard/templates/download.html +++ /dev/null @@ -1,312 +0,0 @@ -{% extends "base.html" %} - -{% block entries %} -
- -
-

DOWNLOADS

-
- - {% if readers %} - - {% endif %} - - {% for package in packages %} - {% if package.download and package.download.size %} -
- -
- -
-
-

{{ package.title|truncate(30, killwords=True, end="…") }}

-
- -
- {{ package.download.size|fsize }} -
-
-
- {% if package.langs %} - {% for lang in package.langs %} - {% if loop.index0 < 2 %} - {{ lang }} - {% endif %} - {% endfor %} - {% endif %} -
-
- - - -
-
- - - -
-
- -
- {% endif %} - {% else %} -

No content packages 🙁

- {% endfor %} -
-{% endblock %} diff --git a/dashboard/templates/home.html b/dashboard/templates/home.html deleted file mode 100644 index 6f9912b..0000000 --- a/dashboard/templates/home.html +++ /dev/null @@ -1,221 +0,0 @@ -{% extends "base.html" %} - -{% block entries %} -
- {% for package in packages %} -
- - -
-
- {% if package.langs %} -
- {% for lang in package.langs %} - {% if loop.index0 <= 2 and lang != 'mu' %} -
{{ lang }}
- {% endif %} - {% endfor %} -
- {% endif %} -
-
-

{{ package.description|default("-")|truncate(80, killwords=False, end="…") }}

-
-
- -
-
{% if package.download and package.download.size %}{{ package.download.size|fsize }}{% endif %}
-
{% if package.tags %}{% for tag in package.tags %}{% if loop.index0 <= 1 %}{{ tag }}{% endif %}{% endfor %}{% endif %}
-
-
- {% else %} -

No content packages 🙁

- {% endfor %} -
-{% endblock %}