diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 95f9808..0000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -exclude = .git, .venv, output, static/icons -max-line-length = 160 diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml deleted file mode 100644 index 5cf26be..0000000 --- a/.forgejo/workflows/ci.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: CI - -on: - push: - pull_request: - workflow_dispatch: - -jobs: - ci: - name: Lint, test, and build - # This label must match your Forgejo runner's label - runs-on: docker - # Use a clean Debian container so tools are predictable - container: debian:stable-slim - env: - PYTHONDONTWRITEBYTECODE: "1" - PIP_DISABLE_PIP_VERSION_CHECK: "1" - UV_SYSTEM_PYTHON: "1" - steps: - - name: Install build tooling - run: | - set -euo pipefail - apt-get update - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - git ca-certificates python3 python3-venv python3-pip python3-setuptools \ - python3-wheel sqlite3 - update-ca-certificates || true - - - name: Checkout repository (manual) - run: | - set -euo pipefail - if [ -f Makefile ] || [ -d .git ]; then - echo "Repository present in workspace; skipping clone" - exit 0 - fi - REMOTE_URL="${CI_REPOSITORY_URL:-}" - if [ -z "$REMOTE_URL" ]; then - if [ -n "${GITHUB_SERVER_URL:-}" ] && [ -n "${GITHUB_REPOSITORY:-}" ]; then - REMOTE_URL="${GITHUB_SERVER_URL%/}/${GITHUB_REPOSITORY}.git" - elif [ -n "${GITHUB_REPOSITORY:-}" ]; then - REMOTE_URL="https://git.jordanwages.com/${GITHUB_REPOSITORY}.git" - else - echo "Unable to determine repository URL from CI environment" >&2 - exit 1 - fi - fi - AUTH_URL="$REMOTE_URL" - if [ -n "${GITHUB_TOKEN:-}" ]; then - ACTOR="${GITHUB_ACTOR:-oauth2}" - AUTH_URL=$(printf '%s' "$REMOTE_URL" | sed -E "s#^https://#https://${ACTOR}:${GITHUB_TOKEN}@#") - fi - echo "Cloning from: $REMOTE_URL" - if ! git clone --depth 1 "$AUTH_URL" .; then - echo "Auth clone failed; trying anonymous clone..." >&2 - git clone --depth 1 "$REMOTE_URL" . - fi - if [ -n "${GITHUB_SHA:-}" ]; then - git fetch --depth 1 origin "$GITHUB_SHA" || true - git checkout -q "$GITHUB_SHA" || true - elif [ -n "${GITHUB_REF_NAME:-}" ]; then - git fetch --depth 1 origin "$GITHUB_REF_NAME" || true - git checkout -q "$GITHUB_REF_NAME" || true - fi - - - name: Set up venv and install deps - run: | - set -euo pipefail - # Prefer persistent cache if runner provides /cache - USE_CACHE=0 - if [ -d /cache ] && [ -w /cache ]; then - export PIP_CACHE_DIR=/cache/pip - mkdir -p "$PIP_CACHE_DIR" - REQ_HASH=$(sha256sum requirements.txt | awk '{print $1}') - PYVER=$(python3 -c 'import sys;print(".".join(map(str, sys.version_info[:2])))') - CACHE_VENV="/cache/venv-${REQ_HASH}-py${PYVER}" - if [ ! -f "$CACHE_VENV/bin/activate" ]; then - echo "Preparing cached virtualenv: $CACHE_VENV" - rm -rf "$CACHE_VENV" || true - python3 -m venv "$CACHE_VENV" - fi - ln -sfn "$CACHE_VENV" .venv - USE_CACHE=1 - else - # Fallback to local venv - python3 -m venv .venv - fi - - # If the link didn't produce an activate file, fallback to local venv - if [ ! -f .venv/bin/activate ]; then - echo "Cached venv missing; creating local .venv" - rm -f .venv - python3 -m venv .venv - USE_CACHE=0 - fi - - . .venv/bin/activate - python -m pip install --upgrade pip - if [ "$USE_CACHE" = "1" ]; then - # Ensure required packages are present; pip will use cache - pip install -r requirements.txt pytest || pip install -r requirements.txt pytest - else - pip install -r requirements.txt pytest - fi - - - name: Format check (black) - run: | - . .venv/bin/activate - black --check . - - - name: Lint (flake8) - run: | - . .venv/bin/activate - flake8 . - - - name: Run tests (pytest) - run: | - . .venv/bin/activate - export PYTHONPATH="$(pwd)${PYTHONPATH:+:$PYTHONPATH}" - pytest -q --maxfail=1 - - - name: Build sample reports (no artifact upload) - run: | - set -euo pipefail - . .venv/bin/activate - python - <<'PY' - import sqlite3, pathlib - db = pathlib.Path('database/ngxstat.db') - db.parent.mkdir(parents=True, exist_ok=True) - conn = sqlite3.connect(db) - cur = conn.cursor() - cur.execute('''CREATE TABLE IF NOT EXISTS logs ( - id INTEGER PRIMARY KEY, - ip TEXT, - host TEXT, - time TEXT, - request TEXT, - status INTEGER, - bytes_sent INTEGER, - referer TEXT, - user_agent TEXT, - cache_status TEXT - )''') - cur.execute("INSERT INTO logs (ip, host, time, request, status, bytes_sent, referer, user_agent, cache_status) VALUES ('127.0.0.1','example.com','2024-01-01 10:00:00','GET / HTTP/1.1',200,100,'-','curl','MISS')") - cur.execute("INSERT INTO logs (ip, host, time, request, status, bytes_sent, referer, user_agent, cache_status) VALUES ('127.0.0.1','example.com','2024-01-01 10:05:00','GET /about HTTP/1.1',200,100,'-','curl','MISS')") - conn.commit(); conn.close() - PY - python scripts/generate_reports.py global - python scripts/generate_reports.py hourly - python scripts/generate_reports.py index - tar -czf ngxstat-reports.tar.gz -C output . - echo "Built sample reports archive: ngxstat-reports.tar.gz" diff --git a/AGENTS.md b/AGENTS.md index 7e7d3c5..4cdfa62 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,9 +24,6 @@ This document outlines general practices and expectations for AI agents assistin The `run-import.sh` script can initialize this environment automatically. Always activate the virtual environment before running scripts or tests. -* Before committing code run `black` for consistent formatting and execute - the test suite with `pytest`. All tests should pass. - * Dependency management: Use `requirements.txt` or `pip-tools` * Use standard libraries where feasible (e.g., `sqlite3`, `argparse`, `datetime`) * Adopt `typer` for CLI command interface (if CLI ergonomics matter) @@ -92,14 +89,6 @@ ngxstat/ If uncertain, the agent should prompt the human for clarification before making architectural assumptions. -## Testing - -Use `pytest` for automated tests. Run the suite from an activated virtual environment and ensure all tests pass before committing: - -```bash -pytest -q -``` - --- ## Future Capabilities @@ -117,4 +106,3 @@ As the project matures, agents may also: * **2025-07-17**: Initial version by Jordan + ChatGPT * **2025-07-17**: Expanded virtual environment usage guidance - diff --git a/README.md b/README.md index ac601fc..d206658 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,11 @@ # ngxstat +Per-domain Nginx log analytics with hybrid static reports and live insights. -`ngxstat` is a lightweight log analytics toolkit for Nginx. It imports access -logs into an SQLite database and renders static dashboards so you can explore -per-domain metrics without running a heavy backend service. +## Generating Reports -## Requirements +Use the `generate_reports.py` script to build aggregated JSON and HTML snippet files from `database/ngxstat.db`. -* Python 3.10+ -* Access to the Nginx log files (default: `/var/log/nginx`) - -The helper scripts create a virtual environment on first run, but you can also -set one up manually: +Create a virtual environment and install dependencies: ```bash python3 -m venv .venv @@ -18,95 +13,105 @@ source .venv/bin/activate pip install -r requirements.txt ``` +Then run one or more of the interval commands: + +```bash +python scripts/generate_reports.py hourly +python scripts/generate_reports.py daily +python scripts/generate_reports.py weekly +python scripts/generate_reports.py monthly +``` + +Each command accepts optional flags to generate per-domain reports. Use +`--domain ` to limit output to a specific domain or `--all-domains` +to generate a subdirectory for every domain found in the database: + +```bash +# Hourly reports for example.com only +python scripts/generate_reports.py hourly --domain example.com + +# Weekly reports for all domains individually +python scripts/generate_reports.py weekly --all-domains +``` + +Reports are written under the `output/` directory. Each command updates the corresponding `.json` file and writes one HTML snippet per report. These snippets are loaded dynamically by the main dashboard using Chart.js and DataTables. + +### Configuring Reports + +Report queries are defined in `reports.yml`. Each entry specifies the `name`, +optional `label` and `chart` type, and a SQL `query` that must return `bucket` +and `value` columns. The special token `{bucket}` is replaced with the +appropriate SQLite `strftime` expression for each interval (hourly, daily, +weekly or monthly) so that a single definition works across all durations. +When `generate_reports.py` runs, every definition is executed for the requested +interval and creates `output//.json` plus a small HTML snippet +`output//.html` used by the dashboard. + +Example snippet: + +```yaml +- name: hits + chart: bar + query: | + SELECT {bucket} AS bucket, + COUNT(*) AS value + FROM logs + GROUP BY bucket + ORDER BY bucket +``` + +Add or modify entries in `reports.yml` to tailor the generated metrics. + ## Importing Logs -Run the importer to ingest new log entries into `database/ngxstat.db`: +Use the `run-import.sh` script to set up the Python environment if needed and import the latest Nginx log entries into `database/ngxstat.db`. ```bash ./run-import.sh ``` -Rotated logs are processed in order and only entries newer than the last -imported timestamp are added. +This script is suitable for cron jobs as it creates the virtual environment on first run, installs dependencies and reuses the environment on subsequent runs. -## Generating Reports +The importer handles rotated logs in order from oldest to newest so entries are +processed exactly once. If you rerun the script, it only ingests records with a +timestamp newer than the latest one already stored in the database, preventing +duplicates. -To build the HTML dashboard and JSON data files use `run-reports.sh` which runs -all intervals in one go: +## Cron Report Generation + +Use the `run-reports.sh` script to run all report intervals in one step. The script sets up the Python environment the same way as `run-import.sh`, making it convenient for automation via cron. ```bash ./run-reports.sh ``` -The script calls `scripts/generate_reports.py` internally to create hourly, -daily, weekly and monthly reports, then writes analysis JSON files used by the -"Analysis" tab. Per-domain reports are written under `output/domains/` -alongside the aggregate data. Open `output/index.html` in a browser to view the -dashboard. +Running this script will create or update the hourly, daily, weekly and monthly reports under `output/`. It also detects all unique domains found in the database and writes per-domain reports to `output/domains//` alongside the aggregate data. After generation, open `output/index.html` in your browser to browse the reports. -If you prefer to run individual commands you can invoke the generator directly: +## Serving Reports with Nginx -```bash -python scripts/generate_reports.py hourly -python scripts/generate_reports.py daily --all-domains -``` - -## Analysis Helpers - -`run-analysis.sh` executes additional utilities that examine the database for -missing domains, caching opportunities and potential threats. The JSON output is -saved under `output/analysis` and appears in the "Analysis" tab. The -`run-reports.sh` script also generates these JSON files as part of the build. - -## UX Controls - -The dashboard defaults to a 7‑day window for time series. Your view preferences -persist locally in the browser under the `ngxstat-state-v2` key. Use the -"Reset view" button to clear saved state and restore defaults. - -```bash -./run-analysis.sh -``` - -## Serving the Reports - -The generated files are static. You can serve them with a simple Nginx block: +To expose the generated HTML dashboards and JSON files over HTTP you can use a +simple Nginx server block. Point the `root` directive to the repository's +`output/` directory and optionally restrict access to your local network. ```nginx server { listen 80; server_name example.com; + + # Path to the generated reports root /path/to/ngxstat/output; location / { try_files $uri $uri/ =404; } + + # Allow access only from private networks + allow 192.0.0.0/8; + allow 10.0.0.0/8; + deny all; } ``` -Restrict access if the reports should not be public. +With this configuration the generated static files are served directly by +Nginx while connections outside of `192.*` and `10.*` are denied. -## Running Tests - -Install the development dependencies and execute the suite with `pytest`: - -```bash -pip install -r requirements.txt -pytest -q -``` - -All tests must pass before submitting changes. - -## Acknowledgements - -ngxstat uses the following third‑party resources: - -* [Chart.js](https://www.chartjs.org/) for charts -* [DataTables](https://datatables.net/) and [jQuery](https://jquery.com/) for table views -* [Bulma CSS](https://bulma.io/) for styling -* Icons from [Free CC0 Icons](https://cc0-icons.jonh.eu/) by Jon Hicks (CC0 / MIT) -* [Typer](https://typer.tiangolo.com/) for the command-line interface -* [Jinja2](https://palletsprojects.com/p/jinja/) for templating - -The project is licensed under the GPLv3. Icon assets remain in the public domain -via the CC0 license. diff --git a/reports.yml b/reports.yml index 709d686..1a6f5ef 100644 --- a/reports.yml +++ b/reports.yml @@ -1,37 +1,28 @@ - name: hits label: Hits - icon: pulse chart: line - bucket: time_bucket - bucket_label: Time query: | - SELECT {bucket} AS time_bucket, + SELECT {bucket} AS bucket, COUNT(*) AS value FROM logs - GROUP BY time_bucket - ORDER BY time_bucket + GROUP BY bucket + ORDER BY bucket - name: error_rate label: Error Rate (%) - icon: file-alert chart: line - bucket: time_bucket - bucket_label: Time query: | - SELECT {bucket} AS time_bucket, + SELECT {bucket} AS bucket, SUM(CASE WHEN status BETWEEN 400 AND 599 THEN 1 ELSE 0 END) * 100.0 / COUNT(*) AS value FROM logs - GROUP BY time_bucket - ORDER BY time_bucket + GROUP BY bucket + ORDER BY bucket - name: cache_status_breakdown label: Cache Status - icon: archive chart: polarArea - bucket: cache_status - bucket_label: Cache Status query: | - SELECT cache_status AS cache_status, + SELECT cache_status AS bucket, COUNT(*) AS value FROM logs GROUP BY cache_status @@ -46,168 +37,78 @@ - name: domain_traffic label: Top Domains - icon: globe chart: table - top_n: 50 per_domain: false - bucket: domain - bucket_label: Domain query: | - SELECT host AS domain, + SELECT host AS bucket, COUNT(*) AS value FROM logs - GROUP BY domain + GROUP BY host ORDER BY value DESC - name: bytes_sent label: Bytes Sent - icon: upload chart: line - bucket: time_bucket - bucket_label: Time query: | - SELECT {bucket} AS time_bucket, + SELECT {bucket} AS bucket, SUM(bytes_sent) AS value FROM logs - GROUP BY time_bucket - ORDER BY time_bucket + GROUP BY bucket + ORDER BY bucket - name: top_paths label: Top Paths - icon: map chart: table - top_n: 50 - buckets: - - domain - - path - bucket_label: - - Domain - - Path query: | - WITH paths AS ( - SELECT host AS domain, - substr(substr(request, instr(request, ' ') + 1), 1, + SELECT path AS bucket, + COUNT(*) AS value + FROM ( + SELECT substr(substr(request, instr(request, ' ') + 1), 1, instr(substr(request, instr(request, ' ') + 1), ' ') - 1) AS path FROM logs - ), ranked AS ( - SELECT domain, path, COUNT(*) AS value, - ROW_NUMBER() OVER (PARTITION BY domain ORDER BY COUNT(*) DESC) AS rn - FROM paths - GROUP BY domain, path ) - SELECT domain, path, value - FROM ranked - WHERE rn <= 20 - ORDER BY domain, value DESC + GROUP BY path + ORDER BY value DESC + LIMIT 20 - name: user_agents label: User Agents - icon: user chart: table - top_n: 50 - buckets: - - domain - - user_agent - bucket_label: - - Domain - - User Agent query: | - WITH ua AS ( - SELECT host AS domain, user_agent - FROM logs - ), ranked AS ( - SELECT domain, user_agent, COUNT(*) AS value, - ROW_NUMBER() OVER (PARTITION BY domain ORDER BY COUNT(*) DESC) AS rn - FROM ua - GROUP BY domain, user_agent - ) - SELECT domain, user_agent, value - FROM ranked - WHERE rn <= 20 - ORDER BY domain, value DESC + SELECT user_agent AS bucket, + COUNT(*) AS value + FROM logs + GROUP BY user_agent + ORDER BY value DESC + LIMIT 20 - name: referrers label: Referrers - icon: link chart: table - top_n: 50 - buckets: - - domain - - referrer - bucket_label: - - Domain - - Referrer query: | - WITH ref AS ( - SELECT host AS domain, referer AS referrer - FROM logs - ), ranked AS ( - SELECT domain, referrer, COUNT(*) AS value, - ROW_NUMBER() OVER (PARTITION BY domain ORDER BY COUNT(*) DESC) AS rn - FROM ref - GROUP BY domain, referrer - ) - SELECT domain, referrer, value - FROM ranked - WHERE rn <= 20 - ORDER BY domain, value DESC + SELECT referer AS bucket, + COUNT(*) AS value + FROM logs + GROUP BY referer + ORDER BY value DESC + LIMIT 20 - name: status_distribution label: HTTP Statuses - icon: server chart: pie - bucket: status_group - bucket_label: Status query: | SELECT CASE WHEN status BETWEEN 200 AND 299 THEN '2xx' WHEN status BETWEEN 300 AND 399 THEN '3xx' WHEN status BETWEEN 400 AND 499 THEN '4xx' ELSE '5xx' - END AS status_group, + END AS bucket, COUNT(*) AS value FROM logs - GROUP BY status_group - ORDER BY status_group + GROUP BY bucket + ORDER BY bucket colors: - "#48c78e" - "#209cee" - "#ffdd57" - "#f14668" - -# New time-series: status classes over time (stacked) -- name: status_classes_timeseries - label: Status Classes Over Time - icon: server - chart: stackedBar - bucket: time_bucket - bucket_label: Time - stacked: true - query: | - SELECT {bucket} AS time_bucket, - SUM(CASE WHEN status BETWEEN 200 AND 299 THEN 1 ELSE 0 END) AS "2xx", - SUM(CASE WHEN status BETWEEN 300 AND 399 THEN 1 ELSE 0 END) AS "3xx", - SUM(CASE WHEN status BETWEEN 400 AND 499 THEN 1 ELSE 0 END) AS "4xx", - SUM(CASE WHEN status BETWEEN 500 AND 599 THEN 1 ELSE 0 END) AS "5xx", - COUNT(*) AS total - FROM logs - GROUP BY time_bucket - ORDER BY time_bucket - -# New time-series: cache status over time (compact Hit/Miss; exclude '-' by default) -- name: cache_status_timeseries - label: Cache Status Over Time - icon: archive - chart: stackedBar - bucket: time_bucket - bucket_label: Time - stacked: true - exclude_values: ["-"] - query: | - SELECT {bucket} AS time_bucket, - SUM(CASE WHEN cache_status = 'HIT' THEN 1 ELSE 0 END) AS hit, - SUM(CASE WHEN cache_status = 'MISS' THEN 1 ELSE 0 END) AS miss, - COUNT(*) AS total - FROM logs - GROUP BY time_bucket - ORDER BY time_bucket diff --git a/run-analysis.sh b/run-analysis.sh deleted file mode 100755 index 4149b9a..0000000 --- a/run-analysis.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Prevent concurrent executions of this script. -LOCK_FILE="/tmp/$(basename "$0").lock" -if [ -e "$LOCK_FILE" ]; then - echo "[WARN] $(basename "$0") is already running (lock file present)." >&2 - exit 0 -fi -touch "$LOCK_FILE" -trap 'rm -f "$LOCK_FILE"' EXIT - -# Ensure virtual environment exists -if [ ! -d ".venv" ]; then - echo "[INFO] Creating virtual environment..." - python3 -m venv .venv - source .venv/bin/activate - echo "[INFO] Installing dependencies..." - pip install --upgrade pip - if [ -f requirements.txt ]; then - pip install -r requirements.txt - else - echo "[WARN] requirements.txt not found, skipping." - fi -else - echo "[INFO] Activating virtual environment..." - source .venv/bin/activate -fi - -# Run analysis helpers -echo "[INFO] Checking for missing domains..." -python -m scripts.analyze check-missing-domains - -echo "[INFO] Suggesting cache improvements..." -python -m scripts.analyze suggest-cache - -echo "[INFO] Detecting threats..." -python -m scripts.analyze detect-threats - -# Deactivate to keep cron environment clean -if type deactivate >/dev/null 2>&1; then - deactivate -fi diff --git a/run-import.sh b/run-import.sh index 3c79d35..22b4b31 100755 --- a/run-import.sh +++ b/run-import.sh @@ -1,17 +1,6 @@ #!/usr/bin/env bash set -e -# Prevent multiple simultaneous runs by using a lock file specific to this -# script. If the lock already exists, assume another instance is running and -# exit gracefully. -LOCK_FILE="/tmp/$(basename "$0").lock" -if [ -e "$LOCK_FILE" ]; then - echo "[WARN] $(basename "$0") is already running (lock file present)." >&2 - exit 0 -fi -touch "$LOCK_FILE" -trap 'rm -f "$LOCK_FILE"' EXIT - # Ensure virtual environment exists if [ ! -d ".venv" ]; then echo "[INFO] Creating virtual environment..." diff --git a/run-reports.sh b/run-reports.sh index f7cffba..a0d718f 100755 --- a/run-reports.sh +++ b/run-reports.sh @@ -1,15 +1,6 @@ #!/usr/bin/env bash set -e -# Prevent concurrent executions of this script. -LOCK_FILE="/tmp/$(basename "$0").lock" -if [ -e "$LOCK_FILE" ]; then - echo "[WARN] $(basename "$0") is already running (lock file present)." >&2 - exit 0 -fi -touch "$LOCK_FILE" -trap 'rm -f "$LOCK_FILE"' EXIT - # Ensure virtual environment exists if [ ! -d ".venv" ]; then echo "[INFO] Creating virtual environment..." @@ -29,25 +20,21 @@ fi # Generate reports for all domains combined echo "[INFO] Generating aggregate reports..." -python -m scripts.generate_reports hourly -python -m scripts.generate_reports daily -python -m scripts.generate_reports weekly -python -m scripts.generate_reports monthly -python -m scripts.generate_reports global +python scripts/generate_reports.py hourly +python scripts/generate_reports.py daily +python scripts/generate_reports.py weekly +python scripts/generate_reports.py monthly +python scripts/generate_reports.py global # Generate reports for each individual domain echo "[INFO] Generating per-domain reports..." -python -m scripts.generate_reports hourly --all-domains -python -m scripts.generate_reports daily --all-domains -python -m scripts.generate_reports weekly --all-domains -python -m scripts.generate_reports monthly --all-domains - -# Generate analysis JSON -echo "[INFO] Generating analysis files..." -python -m scripts.generate_reports analysis +python scripts/generate_reports.py hourly --all-domains +python scripts/generate_reports.py daily --all-domains +python scripts/generate_reports.py weekly --all-domains +python scripts/generate_reports.py monthly --all-domains # Generate root index -python -m scripts.generate_reports index +python scripts/generate_reports.py index # Deactivate to keep cron environment clean if type deactivate >/dev/null 2>&1; then diff --git a/scripts/__init__.py b/scripts/__init__.py deleted file mode 100644 index f4c57a1..0000000 --- a/scripts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"Utility package for ngxstat scripts" diff --git a/scripts/analyze.py b/scripts/analyze.py deleted file mode 100644 index 9f49978..0000000 --- a/scripts/analyze.py +++ /dev/null @@ -1,358 +0,0 @@ -#!/usr/bin/env python3 -"""Utility helpers for ad-hoc log analysis. - -This module exposes small helper functions to inspect the ``ngxstat`` SQLite -database. The intent is to allow quick queries from the command line or other -scripts without rewriting SQL each time. - -Examples --------- -To list all domains present in the database:: - - python scripts/analyze.py domains - -The CLI is powered by :mod:`typer` and currently only offers a couple of -commands. More analysis routines can be added over time. -""" -from __future__ import annotations - -import sqlite3 -from pathlib import Path -from typing import List, Optional, Set -from datetime import datetime, timedelta - -import json - -import typer - -from scripts import nginx_config # noqa: F401 # imported for side effects/usage - -DB_PATH = Path("database/ngxstat.db") -ANALYSIS_DIR = Path("output/analysis") - -app = typer.Typer(help="Ad-hoc statistics queries") - - -def _connect() -> sqlite3.Connection: - """Return a new SQLite connection to :data:`DB_PATH`.""" - return sqlite3.connect(DB_PATH) - - -def load_domains_from_db() -> List[str]: - """Return a sorted list of distinct domains from the ``logs`` table.""" - conn = _connect() - cur = conn.cursor() - cur.execute("SELECT DISTINCT host FROM logs ORDER BY host") - domains = [row[0] for row in cur.fetchall()] - conn.close() - return domains - - -def get_hit_count(domain: Optional[str] = None) -> int: - """Return total request count. - - Parameters - ---------- - domain: - Optional domain to filter on. If ``None`` the count includes all logs. - """ - conn = _connect() - cur = conn.cursor() - if domain: - cur.execute("SELECT COUNT(*) FROM logs WHERE host = ?", (domain,)) - else: - cur.execute("SELECT COUNT(*) FROM logs") - count = cur.fetchone()[0] or 0 - conn.close() - return count - - -def get_cache_ratio(domain: Optional[str] = None) -> float: - """Return the percentage of requests served from cache.""" - conn = _connect() - cur = conn.cursor() - if domain: - cur.execute( - "SELECT SUM(CASE WHEN cache_status = 'HIT' THEN 1 ELSE 0 END) * 1.0 / " - "COUNT(*) FROM logs WHERE host = ?", - (domain,), - ) - else: - cur.execute( - "SELECT SUM(CASE WHEN cache_status = 'HIT' THEN 1 ELSE 0 END) * 1.0 / " - "COUNT(*) FROM logs" - ) - result = cur.fetchone()[0] - conn.close() - return float(result or 0.0) - - -@app.command() -def domains() -> None: - """Print the list of domains discovered in the database.""" - for d in load_domains_from_db(): - typer.echo(d) - - -@app.command() -def hits(domain: Optional[str] = typer.Option(None, help="Filter by domain")) -> None: - """Show request count.""" - count = get_hit_count(domain) - if domain: - typer.echo(f"{domain}: {count} hits") - else: - typer.echo(f"Total hits: {count}") - - -@app.command("cache-ratio") -def cache_ratio_cmd( - domain: Optional[str] = typer.Option(None, help="Filter by domain") -) -> None: - """Display cache hit ratio as a percentage.""" - ratio = get_cache_ratio(domain) * 100 - if domain: - typer.echo(f"{domain}: {ratio:.2f}% cached") - else: - typer.echo(f"Cache hit ratio: {ratio:.2f}%") - - -@app.command("check-missing-domains") -def check_missing_domains( - json_output: bool = typer.Option( - False, "--json", help="Output missing domains as JSON" - ) -) -> None: - """Show domains present in the database but absent from Nginx config.""" - try: - from scripts.generate_reports import _get_domains as _db_domains - except Exception: # pragma: no cover - fallback if import fails - _db_domains = load_domains_from_db - - if not isinstance(json_output, bool): - json_output = False - - db_domains = set(_db_domains()) - - paths = nginx_config.discover_configs() - servers = nginx_config.parse_servers(paths) - config_domains: Set[str] = set() - for server in servers: - names = server.get("server_name", "") - for name in names.split(): - if name: - config_domains.add(name) - - missing = sorted(db_domains - config_domains) - - ANALYSIS_DIR.mkdir(parents=True, exist_ok=True) - out_path = ANALYSIS_DIR / "missing_domains.json" - out_path.write_text(json.dumps(missing, indent=2)) - - if json_output: - typer.echo(json.dumps(missing)) - else: - for d in missing: - typer.echo(d) - - -def suggest_cache( - threshold: int = 10, - json_output: bool = False, -) -> None: - """Suggest domain/path pairs that could benefit from caching. - - Paths with at least ``threshold`` ``MISS`` entries are shown for domains - whose server blocks lack a ``proxy_cache`` directive. - """ - - # Discover domains without explicit proxy_cache - paths = nginx_config.discover_configs() - servers = nginx_config.parse_servers(paths) - no_cache: Set[str] = set() - for server in servers: - if "proxy_cache" in server: - continue - for name in server.get("server_name", "").split(): - if name: - no_cache.add(name) - - conn = _connect() - cur = conn.cursor() - cur.execute( - """ - SELECT host, - substr(request, instr(request, ' ')+1, - instr(request, ' HTTP') - instr(request, ' ') - 1) AS path, - COUNT(*) AS miss_count - FROM logs - WHERE cache_status = 'MISS' - GROUP BY host, path - HAVING miss_count >= ? - ORDER BY miss_count DESC - """, - (int(threshold),), - ) - - rows = [r for r in cur.fetchall() if r[0] in no_cache] - conn.close() - - result = [ - {"host": host, "path": path, "misses": count} for host, path, count in rows - ] - - ANALYSIS_DIR.mkdir(parents=True, exist_ok=True) - out_path = ANALYSIS_DIR / "cache_suggestions.json" - out_path.write_text(json.dumps(result, indent=2)) - - if json_output: - typer.echo(json.dumps(result)) - else: - for item in result: - typer.echo(f"{item['host']} {item['path']} {item['misses']}") - -@app.command("suggest-cache") -def suggest_cache_cli( - threshold: int = typer.Option(10, help="Minimum number of MISS entries to report"), - json_output: bool = typer.Option(False, "--json", help="Output results as JSON"), -) -> None: - """CLI wrapper for suggest_cache.""" - suggest_cache(threshold=threshold, json_output=json_output) - - -def detect_threats( - hours: int = 1, - ip_threshold: int = 100, -) -> None: - """Detect potential security threats from recent logs.""" - - conn = _connect() - cur = conn.cursor() - - cur.execute("SELECT MAX(time) FROM logs") - row = cur.fetchone() - if not row or not row[0]: - typer.echo("No logs found") - conn.close() - return - - max_dt = datetime.strptime(row[0], "%Y-%m-%d %H:%M:%S") - recent_end = max_dt - recent_start = recent_end - timedelta(hours=int(hours)) - prev_start = recent_start - timedelta(hours=int(hours)) - prev_end = recent_start - - fmt = "%Y-%m-%d %H:%M:%S" - recent_start_s = recent_start.strftime(fmt) - recent_end_s = recent_end.strftime(fmt) - prev_start_s = prev_start.strftime(fmt) - prev_end_s = prev_end.strftime(fmt) - - cur.execute( - """ - SELECT host, - SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END) AS errors, - COUNT(*) AS total - FROM logs - WHERE time >= ? AND time < ? - GROUP BY host - """, - (recent_start_s, recent_end_s), - ) - recent_rows = {r[0]: (r[1], r[2]) for r in cur.fetchall()} - - cur.execute( - """ - SELECT host, - SUM(CASE WHEN status >= 400 THEN 1 ELSE 0 END) AS errors, - COUNT(*) AS total - FROM logs - WHERE time >= ? AND time < ? - GROUP BY host - """, - (prev_start_s, prev_end_s), - ) - prev_rows = {r[0]: (r[1], r[2]) for r in cur.fetchall()} - - error_spikes = [] - for host in set(recent_rows) | set(prev_rows): - r_err, r_total = recent_rows.get(host, (0, 0)) - p_err, p_total = prev_rows.get(host, (0, 0)) - r_rate = r_err * 100.0 / r_total if r_total else 0.0 - p_rate = p_err * 100.0 / p_total if p_total else 0.0 - if r_rate >= 10 and r_rate >= p_rate * 2: - error_spikes.append( - { - "host": host, - "recent_error_rate": round(r_rate, 2), - "previous_error_rate": round(p_rate, 2), - } - ) - - cur.execute( - """ - SELECT DISTINCT user_agent FROM logs - WHERE time >= ? AND time < ? - """, - (prev_start_s, prev_end_s), - ) - prev_agents = {r[0] for r in cur.fetchall()} - - cur.execute( - """ - SELECT user_agent, COUNT(*) AS c - FROM logs - WHERE time >= ? AND time < ? - GROUP BY user_agent - HAVING c >= 10 - """, - (recent_start_s, recent_end_s), - ) - suspicious_agents = [ - {"user_agent": ua, "requests": cnt} - for ua, cnt in cur.fetchall() - if ua not in prev_agents - ] - - cur.execute( - """ - SELECT ip, COUNT(*) AS c - FROM logs - WHERE time >= ? AND time < ? - GROUP BY ip - HAVING c >= ? - ORDER BY c DESC - """, - (recent_start_s, recent_end_s, ip_threshold), - ) - high_ip_requests = [{"ip": ip, "requests": cnt} for ip, cnt in cur.fetchall()] - - conn.close() - - report = { - "time_range": { - "recent_start": recent_start_s, - "recent_end": recent_end_s, - "previous_start": prev_start_s, - "previous_end": prev_end_s, - }, - "error_spikes": error_spikes, - "suspicious_agents": suspicious_agents, - "high_ip_requests": high_ip_requests, - } - - ANALYSIS_DIR.mkdir(parents=True, exist_ok=True) - out_path = ANALYSIS_DIR / "threat_report.json" - out_path.write_text(json.dumps(report, indent=2)) - typer.echo(json.dumps(report)) - -@app.command("detect-threats") -def detect_threats_cli( - hours: int = typer.Option(1, help="Number of recent hours to analyze"), - ip_threshold: int = typer.Option(100, help="Requests from a single IP to flag"), -) -> None: - """CLI wrapper for detect_threats.""" - detect_threats(hours=hours, ip_threshold=ip_threshold) - - -if __name__ == "__main__": - app() diff --git a/scripts/download_icons.py b/scripts/download_icons.py deleted file mode 100644 index 6f4675a..0000000 --- a/scripts/download_icons.py +++ /dev/null @@ -1,28 +0,0 @@ -import json -from urllib.request import urlopen, Request -from pathlib import Path - -ICON_LIST_URL = "https://cc0-icons.jonh.eu/icons.json" -BASE_URL = "https://cc0-icons.jonh.eu/" - -OUTPUT_DIR = Path(__file__).resolve().parent.parent / "static" / "icons" - - -def main() -> None: - OUTPUT_DIR.mkdir(parents=True, exist_ok=True) - req = Request(ICON_LIST_URL, headers={"User-Agent": "Mozilla/5.0"}) - with urlopen(req) as resp: - data = json.load(resp) - icons = data.get("icons", []) - for icon in icons: - slug = icon.get("slug") - url = BASE_URL + icon.get("url") - path = OUTPUT_DIR / f"{slug}.svg" - req = Request(url, headers={"User-Agent": "Mozilla/5.0"}) - with urlopen(req) as resp: - path.write_bytes(resp.read()) - print(f"Downloaded {len(icons)} icons to {OUTPUT_DIR}") - - -if __name__ == "__main__": - main() diff --git a/scripts/generate_reports.py b/scripts/generate_reports.py index d3c2f8a..ae7a3a7 100644 --- a/scripts/generate_reports.py +++ b/scripts/generate_reports.py @@ -1,27 +1,17 @@ import json -import sys import sqlite3 from pathlib import Path -import shutil from typing import List, Dict, Optional -from datetime import datetime, timezone -import time import yaml import typer from jinja2 import Environment, FileSystemLoader -# Ensure project root is importable when running as a script (python scripts/generate_reports.py) -PROJECT_ROOT = Path(__file__).resolve().parent.parent -if str(PROJECT_ROOT) not in sys.path: - sys.path.insert(0, str(PROJECT_ROOT)) - DB_PATH = Path("database/ngxstat.db") OUTPUT_DIR = Path("output") TEMPLATE_DIR = Path("templates") REPORT_CONFIG = Path("reports.yml") -GENERATED_MARKER = OUTPUT_DIR / "generated.txt" # Mapping of interval names to SQLite strftime formats. These strings are # substituted into report queries whenever the special ``{bucket}`` token is @@ -37,19 +27,6 @@ INTERVAL_FORMATS = { app = typer.Typer(help="Generate aggregated log reports") -@app.callback() -def _cli_callback(ctx: typer.Context) -> None: - """Register post-command hook to note generation time.""" - - def _write_marker() -> None: - OUTPUT_DIR.mkdir(parents=True, exist_ok=True) - # Use timezone-aware UTC to avoid deprecation warnings and ambiguity - timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") - GENERATED_MARKER.write_text(f"{timestamp}\n") - - ctx.call_on_close(_write_marker) - - def _get_domains() -> List[str]: """Return a sorted list of unique domains from the logs table.""" conn = sqlite3.connect(DB_PATH) @@ -77,20 +54,6 @@ def _save_json(path: Path, data: List[Dict]) -> None: path.write_text(json.dumps(data, indent=2)) -def _copy_icons() -> None: - """Copy vendored icons and scripts to the output directory.""" - src_dir = Path("static/icons") - dst_dir = OUTPUT_DIR / "icons" - if src_dir.is_dir(): - dst_dir.mkdir(parents=True, exist_ok=True) - for icon in src_dir.glob("*.svg"): - shutil.copy(icon, dst_dir / icon.name) - - js_src = Path("static/chartManager.js") - if js_src.is_file(): - shutil.copy(js_src, OUTPUT_DIR / js_src.name) - - def _render_snippet(report: Dict, out_dir: Path) -> None: """Render a single report snippet to ``.html`` inside ``out_dir``.""" env = Environment(loader=FileSystemLoader(TEMPLATE_DIR)) @@ -99,9 +62,7 @@ def _render_snippet(report: Dict, out_dir: Path) -> None: snippet_path.write_text(template.render(report=report)) -def _write_stats( - generated_at: Optional[str] = None, generation_seconds: Optional[float] = None -) -> None: +def _write_stats() -> None: """Query basic dataset stats and write them to ``output/global/stats.json``.""" conn = sqlite3.connect(DB_PATH) cur = conn.cursor() @@ -125,10 +86,6 @@ def _write_stats( "end_date": end_date, "unique_domains": unique_domains, } - if generated_at: - stats["generated_at"] = generated_at - if generation_seconds is not None: - stats["generation_seconds"] = generation_seconds out_path = OUTPUT_DIR / "global" / "stats.json" _save_json(out_path, stats) @@ -149,8 +106,6 @@ def _generate_interval(interval: str, domain: Optional[str] = None) -> None: typer.echo("No report definitions found") return - _copy_icons() - bucket = _bucket_expr(interval) conn = sqlite3.connect(DB_PATH) @@ -185,16 +140,6 @@ def _generate_interval(interval: str, domain: Optional[str] = None) -> None: name = definition["name"] query = definition["query"].replace("{bucket}", bucket) query = query.replace("FROM logs", "FROM logs_view") - # Apply top_n limit for tables (performance-friendly), if configured - top_n = definition.get("top_n") - chart_type = definition.get("chart", "line") - if top_n and chart_type == "table": - try: - n = int(top_n) - if "LIMIT" not in query.upper(): - query = f"{query}\nLIMIT {n}" - except Exception: - pass cur.execute(query) rows = cur.fetchall() headers = [c[0] for c in cur.description] @@ -208,38 +153,15 @@ def _generate_interval(interval: str, domain: Optional[str] = None) -> None: "json": f"{name}.json", "html": f"{name}.html", } - if "icon" in definition: - entry["icon"] = definition["icon"] - if "bucket" in definition: - entry["bucket"] = definition["bucket"] - if "buckets" in definition: - entry["buckets"] = definition["buckets"] - if "bucket_label" in definition: - entry["bucket_label"] = definition["bucket_label"] if "color" in definition: entry["color"] = definition["color"] if "colors" in definition: entry["colors"] = definition["colors"] - # Optional UX metadata passthrough for frontend-only transforms - for key in ( - "windows_supported", - "window_default", - "group_others_threshold", - "exclude_values", - "top_n", - "stacked", - "palette", - ): - if key in definition: - entry[key] = definition[key] _render_snippet(entry, out_dir) report_list.append(entry) _save_json(out_dir / "reports.json", report_list) - if domain: - typer.echo(f"Generated {interval} reports for {domain}") - else: - typer.echo(f"Generated {interval} reports") + typer.echo(f"Generated {interval} reports") def _generate_all_domains(interval: str) -> None: @@ -250,10 +172,12 @@ def _generate_all_domains(interval: str) -> None: def _generate_root_index() -> None: """Render the top-level index listing all intervals and domains.""" - _copy_icons() - intervals = sorted( - [name for name in INTERVAL_FORMATS if (OUTPUT_DIR / name).is_dir()] - ) + intervals = [ + p.name + for p in OUTPUT_DIR.iterdir() + if p.is_dir() and p.name.lower() not in {"domains", "global"} + ] + intervals.sort() domains_dir = OUTPUT_DIR / "domains" domains: List[str] = [] @@ -277,12 +201,6 @@ def _generate_global() -> None: typer.echo("No report definitions found") return - start_time = time.time() - # Use timezone-aware UTC for generated_at (string remains unchanged format) - generated_at = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") - - _copy_icons() - conn = sqlite3.connect(DB_PATH) cur = conn.cursor() @@ -296,16 +214,6 @@ def _generate_global() -> None: name = definition["name"] query = definition["query"] - # Apply top_n limit for tables (performance-friendly), if configured - top_n = definition.get("top_n") - chart_type = definition.get("chart", "line") - if top_n and chart_type == "table": - try: - n = int(top_n) - if "LIMIT" not in query.upper(): - query = f"{query}\nLIMIT {n}" - except Exception: - pass cur.execute(query) rows = cur.fetchall() headers = [c[0] for c in cur.description] @@ -319,67 +227,18 @@ def _generate_global() -> None: "json": f"{name}.json", "html": f"{name}.html", } - if "icon" in definition: - entry["icon"] = definition["icon"] - if "bucket" in definition: - entry["bucket"] = definition["bucket"] - if "buckets" in definition: - entry["buckets"] = definition["buckets"] - if "bucket_label" in definition: - entry["bucket_label"] = definition["bucket_label"] if "color" in definition: entry["color"] = definition["color"] if "colors" in definition: entry["colors"] = definition["colors"] - # Optional UX metadata passthrough for frontend-only transforms - for key in ( - "windows_supported", - "window_default", - "group_others_threshold", - "exclude_values", - "top_n", - "stacked", - "palette", - ): - if key in definition: - entry[key] = definition[key] _render_snippet(entry, out_dir) report_list.append(entry) _save_json(out_dir / "reports.json", report_list) - elapsed = round(time.time() - start_time, 2) - _write_stats(generated_at, elapsed) + _write_stats() typer.echo("Generated global reports") -def _generate_analysis() -> None: - """Generate analysis JSON files consumed by the Analysis tab.""" - try: - # Import lazily to avoid circulars and keep dependencies optional - from scripts import analyze - except Exception as exc: # pragma: no cover - defensive - typer.echo(f"Failed to import analysis module: {exc}") - return - - # Ensure output root and icons present for parity - _copy_icons() - - # These commands write JSON files under output/analysis/ - try: - analyze.check_missing_domains(json_output=True) - except Exception as exc: # pragma: no cover - continue best-effort - typer.echo(f"check_missing_domains failed: {exc}") - try: - analyze.suggest_cache(json_output=True) - except Exception as exc: # pragma: no cover - typer.echo(f"suggest_cache failed: {exc}") - try: - analyze.detect_threats() - except Exception as exc: # pragma: no cover - typer.echo(f"detect_threats failed: {exc}") - typer.echo("Generated analysis JSON files") - - @app.command() def hourly( domain: Optional[str] = typer.Option( @@ -450,12 +309,6 @@ def global_reports() -> None: _generate_global() -@app.command() -def analysis() -> None: - """Generate analysis JSON files for the Analysis tab.""" - _generate_analysis() - - @app.command() def index() -> None: """Generate the root index page linking all reports.""" diff --git a/scripts/init_db.py b/scripts/init_db.py index b9ea07d..f378b5c 100644 --- a/scripts/init_db.py +++ b/scripts/init_db.py @@ -61,9 +61,7 @@ try: suffix = match.group(1) number = int(suffix.lstrip(".")) if suffix else 0 log_files.append((number, os.path.join(LOG_DIR, f))) - log_files = [ - path for _, path in sorted(log_files, key=lambda x: x[0], reverse=True) - ] + log_files = [path for _, path in sorted(log_files, key=lambda x: x[0], reverse=True)] except FileNotFoundError: print(f"[ERROR] Log directory not found: {LOG_DIR}") exit(1) diff --git a/scripts/nginx_config.py b/scripts/nginx_config.py deleted file mode 100644 index bc585a7..0000000 --- a/scripts/nginx_config.py +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env python3 -"""Utilities for discovering and parsing Nginx configuration files. - -This module provides helper functions to locate Nginx configuration files and -extract key details from ``server`` blocks. Typical usage:: - - from scripts.nginx_config import discover_configs, parse_servers - - files = discover_configs() - servers = parse_servers(files) - for s in servers: - print(s.get("server_name"), s.get("listen")) - -The functions intentionally tolerate missing or unreadable files and will simply -skip over them. -""" - -import os -import re -from pathlib import Path -from typing import Dict, List, Set - -DEFAULT_PATHS = [ - "/etc/nginx/nginx.conf", - "/usr/local/etc/nginx/nginx.conf", -] - -INCLUDE_RE = re.compile(r"^\s*include\s+(.*?);", re.MULTILINE) -SERVER_RE = re.compile(r"server\s*{(.*?)}", re.DOTALL) -DIRECTIVE_RE = re.compile(r"^\s*(\S+)\s+(.*?);", re.MULTILINE) - - -def discover_configs() -> Set[Path]: - """Return a set of all config files reachable from :data:`DEFAULT_PATHS`.""" - - found: Set[Path] = set() - queue = [Path(p) for p in DEFAULT_PATHS] - - while queue: - path = queue.pop() - if path in found: - continue - if not path.exists(): - continue - try: - text = path.read_text() - except OSError: - continue - found.add(path) - for pattern in INCLUDE_RE.findall(text): - pattern = os.path.expanduser(pattern.strip()) - if os.path.isabs(pattern): - # ``Path.glob`` does not allow absolute patterns, so we - # anchor at the filesystem root and remove the leading - # separator. - base = Path(os.sep) - glob_iter = base.glob(pattern.lstrip(os.sep)) - else: - glob_iter = path.parent.glob(pattern) - for included in glob_iter: - if included.is_file() and included not in found: - queue.append(included) - return found - - -def parse_servers(paths: Set[Path]) -> List[Dict[str, str]]: - """Parse ``server`` blocks from the given files. - - Parameters - ---------- - paths: - Iterable of configuration file paths. - """ - - servers: List[Dict[str, str]] = [] - for p in paths: - try: - text = Path(p).read_text() - except OSError: - continue - for block in SERVER_RE.findall(text): - directives: Dict[str, List[str]] = {} - for name, value in DIRECTIVE_RE.findall(block): - directives.setdefault(name, []).append(value.strip()) - entry: Dict[str, str] = {} - if "server_name" in directives: - entry["server_name"] = " ".join(directives["server_name"]) - if "listen" in directives: - entry["listen"] = " ".join(directives["listen"]) - if "proxy_cache" in directives: - entry["proxy_cache"] = " ".join(directives["proxy_cache"]) - if "root" in directives: - entry["root"] = " ".join(directives["root"]) - servers.append(entry) - return servers diff --git a/setup.sh b/setup.sh deleted file mode 100755 index be5087c..0000000 --- a/setup.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Default schedules -import_sched="*/5 * * * *" -report_sched="0 * * * *" -analysis_sched="0 0 * * *" -remove=false - -usage() { - echo "Usage: $0 [--import CRON] [--reports CRON] [--analysis CRON] [--remove]" -} - -while [ $# -gt 0 ]; do - case "$1" in - --import) - import_sched="$2"; shift 2;; - --reports) - report_sched="$2"; shift 2;; - --analysis) - analysis_sched="$2"; shift 2;; - --remove) - remove=true; shift;; - -h|--help) - usage; exit 0;; - *) - usage; exit 1;; - esac -done - -repo_dir="$(cd "$(dirname "$0")" && pwd)" - -if [ "$remove" = true ]; then - tmp=$(mktemp) - sudo crontab -l 2>/dev/null | grep -v "# ngxstat import" | grep -v "# ngxstat reports" | grep -v "# ngxstat analysis" > "$tmp" || true - sudo crontab "$tmp" - rm -f "$tmp" - echo "[INFO] Removed ngxstat cron entries" - exit 0 -fi - -cron_entries="${import_sched} cd ${repo_dir} && ./run-import.sh # ngxstat import\n${report_sched} cd ${repo_dir} && ./run-reports.sh # ngxstat reports\n${analysis_sched} cd ${repo_dir} && ./run-analysis.sh # ngxstat analysis" - -( sudo crontab -l 2>/dev/null; echo -e "$cron_entries" ) | sudo crontab - - -echo "[INFO] Installed ngxstat cron entries" diff --git a/static/chartManager.js b/static/chartManager.js deleted file mode 100644 index 2f14f4f..0000000 --- a/static/chartManager.js +++ /dev/null @@ -1,109 +0,0 @@ -export let currentLoad = null; -const loadInfo = new Map(); - -export function newLoad(container) { - if (currentLoad) { - abortLoad(currentLoad); - } - reset(container); - const controller = new AbortController(); - const token = { controller, charts: new Map() }; - loadInfo.set(token, token); - currentLoad = token; - return token; -} - -export function abortLoad(token) { - const info = loadInfo.get(token); - if (!info) return; - info.controller.abort(); - info.charts.forEach(chart => { - try { - chart.destroy(); - } catch (e) {} - }); - loadInfo.delete(token); - if (currentLoad === token) { - currentLoad = null; - } -} - -export function registerChart(token, id, chart) { - const info = loadInfo.get(token); - if (info) { - info.charts.set(id, chart); - } else { - chart.destroy(); - } -} - -export function reset(container) { - if (!container) return; - container.querySelectorAll('canvas').forEach(c => { - const chart = Chart.getChart(c); - if (chart) { - chart.destroy(); - } - }); - container.innerHTML = ''; -} - -// ---- Lightweight client-side data helpers ---- - -// Slice last N rows from a time-ordered array -export function sliceWindow(data, n) { - if (!Array.isArray(data) || n === undefined || n === null) return data; - if (n === 'all') return data; - const count = Number(n); - if (!Number.isFinite(count) || count <= 0) return data; - return data.slice(-count); -} - -// Exclude rows whose value in key is in excluded list -export function excludeValues(data, key, excluded = []) { - if (!excluded || excluded.length === 0) return data; - const set = new Set(excluded); - return data.filter(row => !set.has(row[key])); -} - -// Compute percentages for categorical distributions (valueKey default 'value') -export function toPercent(data, valueKey = 'value') { - const total = data.reduce((s, r) => s + (Number(r[valueKey]) || 0), 0); - if (total <= 0) return data.map(r => ({ ...r })); - return data.map(r => ({ ...r, [valueKey]: (Number(r[valueKey]) || 0) * 100 / total })); -} - -// Group categories with share < threshold into an 'Other' bucket. -export function groupOthers(data, bucketKey, valueKey = 'value', threshold = 0.03, otherLabel = 'Other') { - if (!Array.isArray(data) || data.length === 0) return data; - const total = data.reduce((s, r) => s + (Number(r[valueKey]) || 0), 0); - if (total <= 0) return data; - const major = []; - let other = 0; - for (const r of data) { - const v = Number(r[valueKey]) || 0; - if (total && v / total < threshold) { - other += v; - } else { - major.push({ ...r }); - } - } - if (other > 0) major.push({ [bucketKey]: otherLabel, [valueKey]: other }); - return major; -} - -// Simple moving average over numeric array -export function movingAverage(series, span = 3) { - const n = Math.max(1, Number(span) || 1); - const out = []; - for (let i = 0; i < series.length; i++) { - const start = Math.max(0, i - n + 1); - let sum = 0, cnt = 0; - for (let j = start; j <= i; j++) { - const v = Number(series[j]); - if (Number.isFinite(v)) { sum += v; cnt++; } - } - out.push(cnt ? sum / cnt : null); - } - return out; -} diff --git a/static/icons/achievement.svg b/static/icons/achievement.svg deleted file mode 100644 index a45ed68..0000000 --- a/static/icons/achievement.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/align-center.svg b/static/icons/align-center.svg deleted file mode 100644 index 63f0ce2..0000000 --- a/static/icons/align-center.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/align-left.svg b/static/icons/align-left.svg deleted file mode 100644 index 1e9144d..0000000 --- a/static/icons/align-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/align-right.svg b/static/icons/align-right.svg deleted file mode 100644 index d854828..0000000 --- a/static/icons/align-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/app-marketplace.svg b/static/icons/app-marketplace.svg deleted file mode 100644 index e833972..0000000 --- a/static/icons/app-marketplace.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/archive.svg b/static/icons/archive.svg deleted file mode 100644 index e8df12a..0000000 --- a/static/icons/archive.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-diagonal-2.svg b/static/icons/arrow-double-diagonal-2.svg deleted file mode 100644 index 7a32329..0000000 --- a/static/icons/arrow-double-diagonal-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-diagonal-3.svg b/static/icons/arrow-double-diagonal-3.svg deleted file mode 100644 index 3c86255..0000000 --- a/static/icons/arrow-double-diagonal-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-diagonal-4.svg b/static/icons/arrow-double-diagonal-4.svg deleted file mode 100644 index 9c8beaa..0000000 --- a/static/icons/arrow-double-diagonal-4.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-diagonal.svg b/static/icons/arrow-double-diagonal.svg deleted file mode 100644 index f3c93f4..0000000 --- a/static/icons/arrow-double-diagonal.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-horizontal-2.svg b/static/icons/arrow-double-horizontal-2.svg deleted file mode 100644 index f8be44b..0000000 --- a/static/icons/arrow-double-horizontal-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-horizontal.svg b/static/icons/arrow-double-horizontal.svg deleted file mode 100644 index d06fc55..0000000 --- a/static/icons/arrow-double-horizontal.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-vertical-2.svg b/static/icons/arrow-double-vertical-2.svg deleted file mode 100644 index 5f81dba..0000000 --- a/static/icons/arrow-double-vertical-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-double-vertical.svg b/static/icons/arrow-double-vertical.svg deleted file mode 100644 index ffd66ce..0000000 --- a/static/icons/arrow-double-vertical.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-down-2.svg b/static/icons/arrow-down-2.svg deleted file mode 100644 index 9bb9519..0000000 --- a/static/icons/arrow-down-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-down-left-2.svg b/static/icons/arrow-down-left-2.svg deleted file mode 100644 index c7e116f..0000000 --- a/static/icons/arrow-down-left-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-down-left.svg b/static/icons/arrow-down-left.svg deleted file mode 100644 index edbc82c..0000000 --- a/static/icons/arrow-down-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-down-right-2.svg b/static/icons/arrow-down-right-2.svg deleted file mode 100644 index 5f59d55..0000000 --- a/static/icons/arrow-down-right-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-down-right.svg b/static/icons/arrow-down-right.svg deleted file mode 100644 index e0ca232..0000000 --- a/static/icons/arrow-down-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-down-up.svg b/static/icons/arrow-down-up.svg deleted file mode 100644 index 6bab77f..0000000 --- a/static/icons/arrow-down-up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-down.svg b/static/icons/arrow-down.svg deleted file mode 100644 index 317b68a..0000000 --- a/static/icons/arrow-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-left-2.svg b/static/icons/arrow-left-2.svg deleted file mode 100644 index 09ecec9..0000000 --- a/static/icons/arrow-left-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-left-right.svg b/static/icons/arrow-left-right.svg deleted file mode 100644 index 3915efb..0000000 --- a/static/icons/arrow-left-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-left.svg b/static/icons/arrow-left.svg deleted file mode 100644 index eb1b95f..0000000 --- a/static/icons/arrow-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-right-2.svg b/static/icons/arrow-right-2.svg deleted file mode 100644 index 5647c5b..0000000 --- a/static/icons/arrow-right-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-right-left.svg b/static/icons/arrow-right-left.svg deleted file mode 100644 index b3dbbc2..0000000 --- a/static/icons/arrow-right-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-right.svg b/static/icons/arrow-right.svg deleted file mode 100644 index 98fd9e8..0000000 --- a/static/icons/arrow-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-up-2.svg b/static/icons/arrow-up-2.svg deleted file mode 100644 index ef31915..0000000 --- a/static/icons/arrow-up-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-up-down.svg b/static/icons/arrow-up-down.svg deleted file mode 100644 index e83a86f..0000000 --- a/static/icons/arrow-up-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-up-left-2.svg b/static/icons/arrow-up-left-2.svg deleted file mode 100644 index e869fba..0000000 --- a/static/icons/arrow-up-left-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-up-left.svg b/static/icons/arrow-up-left.svg deleted file mode 100644 index 820469c..0000000 --- a/static/icons/arrow-up-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-up-right-2.svg b/static/icons/arrow-up-right-2.svg deleted file mode 100644 index 5e4bff7..0000000 --- a/static/icons/arrow-up-right-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-up-right.svg b/static/icons/arrow-up-right.svg deleted file mode 100644 index 6c3ee2e..0000000 --- a/static/icons/arrow-up-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrow-up.svg b/static/icons/arrow-up.svg deleted file mode 100644 index c6e18b7..0000000 --- a/static/icons/arrow-up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/arrows-diagonal-out.svg b/static/icons/arrows-diagonal-out.svg deleted file mode 100644 index 4840df3..0000000 --- a/static/icons/arrows-diagonal-out.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/average.svg b/static/icons/average.svg deleted file mode 100644 index b427612..0000000 --- a/static/icons/average.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/bag-2.svg b/static/icons/bag-2.svg deleted file mode 100644 index 18e8261..0000000 --- a/static/icons/bag-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/bag-4.svg b/static/icons/bag-4.svg deleted file mode 100644 index 2614b52..0000000 --- a/static/icons/bag-4.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/bag-lock.svg b/static/icons/bag-lock.svg deleted file mode 100644 index f7e9ece..0000000 --- a/static/icons/bag-lock.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/static/icons/bag-plus.svg b/static/icons/bag-plus.svg deleted file mode 100644 index 6bb5934..0000000 --- a/static/icons/bag-plus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/bag-work.svg b/static/icons/bag-work.svg deleted file mode 100644 index 51247c2..0000000 --- a/static/icons/bag-work.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/bag.svg b/static/icons/bag.svg deleted file mode 100644 index 92d74b7..0000000 --- a/static/icons/bag.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/bolt.svg b/static/icons/bolt.svg deleted file mode 100644 index fc64532..0000000 --- a/static/icons/bolt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/bookmark.svg b/static/icons/bookmark.svg deleted file mode 100644 index 813568e..0000000 --- a/static/icons/bookmark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/burger-menu.svg b/static/icons/burger-menu.svg deleted file mode 100644 index 3b6b197..0000000 --- a/static/icons/burger-menu.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/calendar-2.svg b/static/icons/calendar-2.svg deleted file mode 100644 index 83fdf9f..0000000 --- a/static/icons/calendar-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/calendar.svg b/static/icons/calendar.svg deleted file mode 100644 index ec49336..0000000 --- a/static/icons/calendar.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/check-circle.svg b/static/icons/check-circle.svg deleted file mode 100644 index 12c6187..0000000 --- a/static/icons/check-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/check-square.svg b/static/icons/check-square.svg deleted file mode 100644 index 6208802..0000000 --- a/static/icons/check-square.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/check.svg b/static/icons/check.svg deleted file mode 100644 index 71b981c..0000000 --- a/static/icons/check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevron-down-circle.svg b/static/icons/chevron-down-circle.svg deleted file mode 100644 index 6eb3fe5..0000000 --- a/static/icons/chevron-down-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/chevron-down.svg b/static/icons/chevron-down.svg deleted file mode 100644 index 72235ec..0000000 --- a/static/icons/chevron-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevron-left-circle.svg b/static/icons/chevron-left-circle.svg deleted file mode 100644 index 87719e4..0000000 --- a/static/icons/chevron-left-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/chevron-left.svg b/static/icons/chevron-left.svg deleted file mode 100644 index 774ee4b..0000000 --- a/static/icons/chevron-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevron-right-circle.svg b/static/icons/chevron-right-circle.svg deleted file mode 100644 index 98d5421..0000000 --- a/static/icons/chevron-right-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/chevron-right.svg b/static/icons/chevron-right.svg deleted file mode 100644 index 19d783a..0000000 --- a/static/icons/chevron-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevron-up-circle.svg b/static/icons/chevron-up-circle.svg deleted file mode 100644 index 750f239..0000000 --- a/static/icons/chevron-up-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/chevron-up.svg b/static/icons/chevron-up.svg deleted file mode 100644 index 6257f70..0000000 --- a/static/icons/chevron-up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-down.svg b/static/icons/chevrons-down.svg deleted file mode 100644 index 8c7a4ae..0000000 --- a/static/icons/chevrons-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-left.svg b/static/icons/chevrons-left.svg deleted file mode 100644 index 45ebf78..0000000 --- a/static/icons/chevrons-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-right.svg b/static/icons/chevrons-right.svg deleted file mode 100644 index 2b3aea1..0000000 --- a/static/icons/chevrons-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-triple-down.svg b/static/icons/chevrons-triple-down.svg deleted file mode 100644 index ed4dc03..0000000 --- a/static/icons/chevrons-triple-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-triple-left.svg b/static/icons/chevrons-triple-left.svg deleted file mode 100644 index 3df85fe..0000000 --- a/static/icons/chevrons-triple-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-triple-right.svg b/static/icons/chevrons-triple-right.svg deleted file mode 100644 index aac50bf..0000000 --- a/static/icons/chevrons-triple-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-triple-up.svg b/static/icons/chevrons-triple-up.svg deleted file mode 100644 index 0ef890b..0000000 --- a/static/icons/chevrons-triple-up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/chevrons-up.svg b/static/icons/chevrons-up.svg deleted file mode 100644 index 867f61b..0000000 --- a/static/icons/chevrons-up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/circle-dot.svg b/static/icons/circle-dot.svg deleted file mode 100644 index e380e09..0000000 --- a/static/icons/circle-dot.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/circle-dots.svg b/static/icons/circle-dots.svg deleted file mode 100644 index 6275a98..0000000 --- a/static/icons/circle-dots.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/circle-slash.svg b/static/icons/circle-slash.svg deleted file mode 100644 index 861352d..0000000 --- a/static/icons/circle-slash.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/circle.svg b/static/icons/circle.svg deleted file mode 100644 index 84f078d..0000000 --- a/static/icons/circle.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/clipboard-alert.svg b/static/icons/clipboard-alert.svg deleted file mode 100644 index 3cfae40..0000000 --- a/static/icons/clipboard-alert.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/clipboard-chart.svg b/static/icons/clipboard-chart.svg deleted file mode 100644 index 9805ca0..0000000 --- a/static/icons/clipboard-chart.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/clipboard-check.svg b/static/icons/clipboard-check.svg deleted file mode 100644 index 4515c5e..0000000 --- a/static/icons/clipboard-check.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/clipboard-copy.svg b/static/icons/clipboard-copy.svg deleted file mode 100644 index d59fc74..0000000 --- a/static/icons/clipboard-copy.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/clipboard-data.svg b/static/icons/clipboard-data.svg deleted file mode 100644 index aebd8f5..0000000 --- a/static/icons/clipboard-data.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/clipboard-download.svg b/static/icons/clipboard-download.svg deleted file mode 100644 index bcb349e..0000000 --- a/static/icons/clipboard-download.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/clipboard-line-chart.svg b/static/icons/clipboard-line-chart.svg deleted file mode 100644 index a9d28a7..0000000 --- a/static/icons/clipboard-line-chart.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/static/icons/clipboard-upload.svg b/static/icons/clipboard-upload.svg deleted file mode 100644 index 434b4b8..0000000 --- a/static/icons/clipboard-upload.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/clock-2.svg b/static/icons/clock-2.svg deleted file mode 100644 index 2b15f8b..0000000 --- a/static/icons/clock-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/clock.svg b/static/icons/clock.svg deleted file mode 100644 index 88df7eb..0000000 --- a/static/icons/clock.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/cloud-download.svg b/static/icons/cloud-download.svg deleted file mode 100644 index f284c8f..0000000 --- a/static/icons/cloud-download.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/cloud-upload.svg b/static/icons/cloud-upload.svg deleted file mode 100644 index 2a22e27..0000000 --- a/static/icons/cloud-upload.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/cloud.svg b/static/icons/cloud.svg deleted file mode 100644 index a84e416..0000000 --- a/static/icons/cloud.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/code-2.svg b/static/icons/code-2.svg deleted file mode 100644 index dbb6f8e..0000000 --- a/static/icons/code-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/code.svg b/static/icons/code.svg deleted file mode 100644 index 47d6c90..0000000 --- a/static/icons/code.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/command-line.svg b/static/icons/command-line.svg deleted file mode 100644 index d7c4a03..0000000 --- a/static/icons/command-line.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/content.svg b/static/icons/content.svg deleted file mode 100644 index 21105d0..0000000 --- a/static/icons/content.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/copy.svg b/static/icons/copy.svg deleted file mode 100644 index 867f6cc..0000000 --- a/static/icons/copy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/credit-card.svg b/static/icons/credit-card.svg deleted file mode 100644 index 758948d..0000000 --- a/static/icons/credit-card.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/database.svg b/static/icons/database.svg deleted file mode 100644 index dd2463d..0000000 --- a/static/icons/database.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/dialog.svg b/static/icons/dialog.svg deleted file mode 100644 index 9d761b8..0000000 --- a/static/icons/dialog.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/document-edit-2.svg b/static/icons/document-edit-2.svg deleted file mode 100644 index a4e567b..0000000 --- a/static/icons/document-edit-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/document-edit.svg b/static/icons/document-edit.svg deleted file mode 100644 index 8f685c8..0000000 --- a/static/icons/document-edit.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/document-minus.svg b/static/icons/document-minus.svg deleted file mode 100644 index 94dd4d8..0000000 --- a/static/icons/document-minus.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/document-shield.svg b/static/icons/document-shield.svg deleted file mode 100644 index c5732d4..0000000 --- a/static/icons/document-shield.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/dots-horizontal.svg b/static/icons/dots-horizontal.svg deleted file mode 100644 index f8b9c23..0000000 --- a/static/icons/dots-horizontal.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/download-2.svg b/static/icons/download-2.svg deleted file mode 100644 index 2884115..0000000 --- a/static/icons/download-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/download-3.svg b/static/icons/download-3.svg deleted file mode 100644 index f496817..0000000 --- a/static/icons/download-3.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/download.svg b/static/icons/download.svg deleted file mode 100644 index 595f3a5..0000000 --- a/static/icons/download.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/drop.svg b/static/icons/drop.svg deleted file mode 100644 index eb0a474..0000000 --- a/static/icons/drop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/east.svg b/static/icons/east.svg deleted file mode 100644 index 6082fd9..0000000 --- a/static/icons/east.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/equals.svg b/static/icons/equals.svg deleted file mode 100644 index 08896d9..0000000 --- a/static/icons/equals.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/eye.svg b/static/icons/eye.svg deleted file mode 100644 index 2f1f62f..0000000 --- a/static/icons/eye.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-alert.svg b/static/icons/file-alert.svg deleted file mode 100644 index bf5105b..0000000 --- a/static/icons/file-alert.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-archive.svg b/static/icons/file-archive.svg deleted file mode 100644 index 1f76ed0..0000000 --- a/static/icons/file-archive.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-broken.svg b/static/icons/file-broken.svg deleted file mode 100644 index 2f9804f..0000000 --- a/static/icons/file-broken.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-certificate-2.svg b/static/icons/file-certificate-2.svg deleted file mode 100644 index bfc06f5..0000000 --- a/static/icons/file-certificate-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-certificate.svg b/static/icons/file-certificate.svg deleted file mode 100644 index 429da44..0000000 --- a/static/icons/file-certificate.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-chart.svg b/static/icons/file-chart.svg deleted file mode 100644 index 20a4a70..0000000 --- a/static/icons/file-chart.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-copy.svg b/static/icons/file-copy.svg deleted file mode 100644 index d7d469e..0000000 --- a/static/icons/file-copy.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-download.svg b/static/icons/file-download.svg deleted file mode 100644 index 36e1e61..0000000 --- a/static/icons/file-download.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-key.svg b/static/icons/file-key.svg deleted file mode 100644 index c3e679a..0000000 --- a/static/icons/file-key.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-lock.svg b/static/icons/file-lock.svg deleted file mode 100644 index c4e5965..0000000 --- a/static/icons/file-lock.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-program.svg b/static/icons/file-program.svg deleted file mode 100644 index a9ed60f..0000000 --- a/static/icons/file-program.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-search.svg b/static/icons/file-search.svg deleted file mode 100644 index 357706c..0000000 --- a/static/icons/file-search.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-signed.svg b/static/icons/file-signed.svg deleted file mode 100644 index f3502c6..0000000 --- a/static/icons/file-signed.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-text.svg b/static/icons/file-text.svg deleted file mode 100644 index ce116ff..0000000 --- a/static/icons/file-text.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file-x.svg b/static/icons/file-x.svg deleted file mode 100644 index 6335c6f..0000000 --- a/static/icons/file-x.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/file.svg b/static/icons/file.svg deleted file mode 100644 index 0b7d0d5..0000000 --- a/static/icons/file.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/filter-asc-2.svg b/static/icons/filter-asc-2.svg deleted file mode 100644 index 8ba8e96..0000000 --- a/static/icons/filter-asc-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/filter-asc-3.svg b/static/icons/filter-asc-3.svg deleted file mode 100644 index 58add88..0000000 --- a/static/icons/filter-asc-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/filter-asc.svg b/static/icons/filter-asc.svg deleted file mode 100644 index 1561654..0000000 --- a/static/icons/filter-asc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/filter-desc-2.svg b/static/icons/filter-desc-2.svg deleted file mode 100644 index de1f326..0000000 --- a/static/icons/filter-desc-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/filter-desc-3.svg b/static/icons/filter-desc-3.svg deleted file mode 100644 index 01c08ac..0000000 --- a/static/icons/filter-desc-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/filter-desc.svg b/static/icons/filter-desc.svg deleted file mode 100644 index 77ddd9e..0000000 --- a/static/icons/filter-desc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/flag-2.svg b/static/icons/flag-2.svg deleted file mode 100644 index 34aca99..0000000 --- a/static/icons/flag-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/flag-3.svg b/static/icons/flag-3.svg deleted file mode 100644 index ef18dc3..0000000 --- a/static/icons/flag-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/flag.svg b/static/icons/flag.svg deleted file mode 100644 index d8a4bf9..0000000 --- a/static/icons/flag.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/folder.svg b/static/icons/folder.svg deleted file mode 100644 index 3a69c2e..0000000 --- a/static/icons/folder.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/static/icons/forbidden.svg b/static/icons/forbidden.svg deleted file mode 100644 index 1e0da82..0000000 --- a/static/icons/forbidden.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/forward.svg b/static/icons/forward.svg deleted file mode 100644 index ac0969b..0000000 --- a/static/icons/forward.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/globe-2.svg b/static/icons/globe-2.svg deleted file mode 100644 index 08d9809..0000000 --- a/static/icons/globe-2.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/static/icons/globe.svg b/static/icons/globe.svg deleted file mode 100644 index eb59d66..0000000 --- a/static/icons/globe.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/static/icons/heart-2.svg b/static/icons/heart-2.svg deleted file mode 100644 index 4ea1f6e..0000000 --- a/static/icons/heart-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/heart.svg b/static/icons/heart.svg deleted file mode 100644 index 240d3fc..0000000 --- a/static/icons/heart.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/home.svg b/static/icons/home.svg deleted file mode 100644 index 082fe2d..0000000 --- a/static/icons/home.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/inbox.svg b/static/icons/inbox.svg deleted file mode 100644 index bb70339..0000000 --- a/static/icons/inbox.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/key.svg b/static/icons/key.svg deleted file mode 100644 index 1e13032..0000000 --- a/static/icons/key.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/link.svg b/static/icons/link.svg deleted file mode 100644 index 42d6be5..0000000 --- a/static/icons/link.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/location-pin.svg b/static/icons/location-pin.svg deleted file mode 100644 index 4462ab6..0000000 --- a/static/icons/location-pin.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/lock-open.svg b/static/icons/lock-open.svg deleted file mode 100644 index b2a448e..0000000 --- a/static/icons/lock-open.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/lock.svg b/static/icons/lock.svg deleted file mode 100644 index dcd3d94..0000000 --- a/static/icons/lock.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/login.svg b/static/icons/login.svg deleted file mode 100644 index fc30ad6..0000000 --- a/static/icons/login.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/logout.svg b/static/icons/logout.svg deleted file mode 100644 index 8b9e27d..0000000 --- a/static/icons/logout.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/magnifier-2.svg b/static/icons/magnifier-2.svg deleted file mode 100644 index e2c4593..0000000 --- a/static/icons/magnifier-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/magnifier-zoom-minus.svg b/static/icons/magnifier-zoom-minus.svg deleted file mode 100644 index 66e415b..0000000 --- a/static/icons/magnifier-zoom-minus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/magnifier-zoom-plus.svg b/static/icons/magnifier-zoom-plus.svg deleted file mode 100644 index 3be8d7d..0000000 --- a/static/icons/magnifier-zoom-plus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/magnifier.svg b/static/icons/magnifier.svg deleted file mode 100644 index 0b9507b..0000000 --- a/static/icons/magnifier.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/mail.svg b/static/icons/mail.svg deleted file mode 100644 index 5508bb5..0000000 --- a/static/icons/mail.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/map.svg b/static/icons/map.svg deleted file mode 100644 index 31c5fd8..0000000 --- a/static/icons/map.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/message-check.svg b/static/icons/message-check.svg deleted file mode 100644 index 68bec13..0000000 --- a/static/icons/message-check.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/message-info.svg b/static/icons/message-info.svg deleted file mode 100644 index 66db685..0000000 --- a/static/icons/message-info.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/message.svg b/static/icons/message.svg deleted file mode 100644 index d53bd41..0000000 --- a/static/icons/message.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/minus.svg b/static/icons/minus.svg deleted file mode 100644 index 43054e2..0000000 --- a/static/icons/minus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/monitor.svg b/static/icons/monitor.svg deleted file mode 100644 index 6745a74..0000000 --- a/static/icons/monitor.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/moon-star.svg b/static/icons/moon-star.svg deleted file mode 100644 index e5accb4..0000000 --- a/static/icons/moon-star.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/moon.svg b/static/icons/moon.svg deleted file mode 100644 index 7b3ad0b..0000000 --- a/static/icons/moon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/mouse-pointer.svg b/static/icons/mouse-pointer.svg deleted file mode 100644 index 5bd051f..0000000 --- a/static/icons/mouse-pointer.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/new-tab-2.svg b/static/icons/new-tab-2.svg deleted file mode 100644 index 1529217..0000000 --- a/static/icons/new-tab-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/new-tab.svg b/static/icons/new-tab.svg deleted file mode 100644 index 64d63cb..0000000 --- a/static/icons/new-tab.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/north.svg b/static/icons/north.svg deleted file mode 100644 index dd9ac10..0000000 --- a/static/icons/north.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/notification.svg b/static/icons/notification.svg deleted file mode 100644 index 219835c..0000000 --- a/static/icons/notification.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/pen-writing-3.svg b/static/icons/pen-writing-3.svg deleted file mode 100644 index 8fcba73..0000000 --- a/static/icons/pen-writing-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/pen-writing.svg b/static/icons/pen-writing.svg deleted file mode 100644 index 7bc2eac..0000000 --- a/static/icons/pen-writing.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/pen.svg b/static/icons/pen.svg deleted file mode 100644 index 991bfd7..0000000 --- a/static/icons/pen.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/pin-flag.svg b/static/icons/pin-flag.svg deleted file mode 100644 index 52586e4..0000000 --- a/static/icons/pin-flag.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/pin-goal.svg b/static/icons/pin-goal.svg deleted file mode 100644 index 24222c3..0000000 --- a/static/icons/pin-goal.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/play.svg b/static/icons/play.svg deleted file mode 100644 index 9cb22e3..0000000 --- a/static/icons/play.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/plus.svg b/static/icons/plus.svg deleted file mode 100644 index e665b91..0000000 --- a/static/icons/plus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/pulse.svg b/static/icons/pulse.svg deleted file mode 100644 index 087e57a..0000000 --- a/static/icons/pulse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/reply.svg b/static/icons/reply.svg deleted file mode 100644 index fdf9414..0000000 --- a/static/icons/reply.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/send-2.svg b/static/icons/send-2.svg deleted file mode 100644 index bd05f41..0000000 --- a/static/icons/send-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/send.svg b/static/icons/send.svg deleted file mode 100644 index 1bb2f3a..0000000 --- a/static/icons/send.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/server.svg b/static/icons/server.svg deleted file mode 100644 index 0a1c0a7..0000000 --- a/static/icons/server.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/settings-2.svg b/static/icons/settings-2.svg deleted file mode 100644 index ffc0c11..0000000 --- a/static/icons/settings-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/settings-3.svg b/static/icons/settings-3.svg deleted file mode 100644 index ce5be79..0000000 --- a/static/icons/settings-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/settings-4.svg b/static/icons/settings-4.svg deleted file mode 100644 index 3827d6a..0000000 --- a/static/icons/settings-4.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/static/icons/settings.svg b/static/icons/settings.svg deleted file mode 100644 index 2015b2d..0000000 --- a/static/icons/settings.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/share-2.svg b/static/icons/share-2.svg deleted file mode 100644 index eabd15c..0000000 --- a/static/icons/share-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/share.svg b/static/icons/share.svg deleted file mode 100644 index 6820704..0000000 --- a/static/icons/share.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/shield-disabled.svg b/static/icons/shield-disabled.svg deleted file mode 100644 index 5b25f59..0000000 --- a/static/icons/shield-disabled.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/static/icons/shield-key.svg b/static/icons/shield-key.svg deleted file mode 100644 index 8d27536..0000000 --- a/static/icons/shield-key.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/shield-lock.svg b/static/icons/shield-lock.svg deleted file mode 100644 index bc4a33a..0000000 --- a/static/icons/shield-lock.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/shield-user.svg b/static/icons/shield-user.svg deleted file mode 100644 index ca73611..0000000 --- a/static/icons/shield-user.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/shield-x.svg b/static/icons/shield-x.svg deleted file mode 100644 index d040044..0000000 --- a/static/icons/shield-x.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/shield.svg b/static/icons/shield.svg deleted file mode 100644 index 5d5621f..0000000 --- a/static/icons/shield.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/south.svg b/static/icons/south.svg deleted file mode 100644 index 4238228..0000000 --- a/static/icons/south.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/square.svg b/static/icons/square.svg deleted file mode 100644 index cc5e940..0000000 --- a/static/icons/square.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/sun.svg b/static/icons/sun.svg deleted file mode 100644 index d292182..0000000 --- a/static/icons/sun.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/tag.svg b/static/icons/tag.svg deleted file mode 100644 index 1bd6bee..0000000 --- a/static/icons/tag.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/terminal.svg b/static/icons/terminal.svg deleted file mode 100644 index 67d48be..0000000 --- a/static/icons/terminal.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/test-tube.svg b/static/icons/test-tube.svg deleted file mode 100644 index 3b8ce6b..0000000 --- a/static/icons/test-tube.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/translate.svg b/static/icons/translate.svg deleted file mode 100644 index 76eebf5..0000000 --- a/static/icons/translate.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/trash-2.svg b/static/icons/trash-2.svg deleted file mode 100644 index 58e9b72..0000000 --- a/static/icons/trash-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/trash-alert.svg b/static/icons/trash-alert.svg deleted file mode 100644 index e56eca9..0000000 --- a/static/icons/trash-alert.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/trash.svg b/static/icons/trash.svg deleted file mode 100644 index c236be7..0000000 --- a/static/icons/trash.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/trend-down.svg b/static/icons/trend-down.svg deleted file mode 100644 index eeb26c6..0000000 --- a/static/icons/trend-down.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/trend-flatish.svg b/static/icons/trend-flatish.svg deleted file mode 100644 index 39c6c30..0000000 --- a/static/icons/trend-flatish.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/trend-up.svg b/static/icons/trend-up.svg deleted file mode 100644 index 9b90cac..0000000 --- a/static/icons/trend-up.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/upload-2.svg b/static/icons/upload-2.svg deleted file mode 100644 index eed5c4a..0000000 --- a/static/icons/upload-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/upload.svg b/static/icons/upload.svg deleted file mode 100644 index 09e7ce4..0000000 --- a/static/icons/upload.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/user-circle.svg b/static/icons/user-circle.svg deleted file mode 100644 index 9f4bb63..0000000 --- a/static/icons/user-circle.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/static/icons/user.svg b/static/icons/user.svg deleted file mode 100644 index 5cdf24e..0000000 --- a/static/icons/user.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/video-camera.svg b/static/icons/video-camera.svg deleted file mode 100644 index 0d2bd30..0000000 --- a/static/icons/video-camera.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/west.svg b/static/icons/west.svg deleted file mode 100644 index 8b5ea0b..0000000 --- a/static/icons/west.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/static/icons/x.svg b/static/icons/x.svg deleted file mode 100644 index b209221..0000000 --- a/static/icons/x.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/templates/index.html b/templates/index.html index a5de3db..98e76ac 100644 --- a/templates/index.html +++ b/templates/index.html @@ -12,15 +12,13 @@ -
- +
- - - - - - -
- +
-
+
-

Recent

+

Overview

Total logs: -

Date range: - to -

Unique domains: -

-

Last generated: -

-

Generation time: - seconds

- -
-