{"id":49495072,"url":"https://github.com/chhanz/atop-web","last_synced_at":"2026-05-01T09:01:08.145Z","repository":{"id":354968721,"uuid":"1225387475","full_name":"chhanz/atop-web","owner":"chhanz","description":"Web dashboard for atop rawlog analysis: direct binary parsing (v2.7 / v2.12 tested), interactive charts, and LLM-assisted Q\u0026A with tool-use. No preprocessing, no CDN dependencies, runs in a single Docker container.","archived":false,"fork":false,"pushed_at":"2026-05-01T06:58:59.000Z","size":245,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-01T09:00:54.678Z","etag":null,"topics":["atop","bedrock","chartjs","fastapi","linux-kernel","llm","monitoring","performance-analysis","self-hosted"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chhanz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-30T08:20:15.000Z","updated_at":"2026-05-01T06:59:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/chhanz/atop-web","commit_stats":null,"previous_names":["chhanz/atop-web"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/chhanz/atop-web","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chhanz%2Fatop-web","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chhanz%2Fatop-web/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chhanz%2Fatop-web/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chhanz%2Fatop-web/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chhanz","download_url":"https://codeload.github.com/chhanz/atop-web/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chhanz%2Fatop-web/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32490815,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["atop","bedrock","chartjs","fastapi","linux-kernel","llm","monitoring","performance-analysis","self-hosted"],"created_at":"2026-05-01T09:01:06.853Z","updated_at":"2026-05-01T09:01:08.125Z","avatar_url":"https://github.com/chhanz.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# atop-web\n\nWeb based visualization and analysis tool for atop rawlog files.\n\natop-web reads atop binary rawlog files directly (no `atop -P` preprocessing\nrequired) and renders system and process level metrics in a browser. It targets\nusers who want to inspect atop captures without learning the curses interface.\n\n## Features\n\n- Direct parsing of atop binary rawlog via `dissect.cstruct`\n- Server side file browser over the mounted log directory for one click\n  analysis of rawlog files already on disk\n- Ad hoc upload of arbitrary rawlog files for quick investigations\n- FastAPI backend exposing JSON endpoints for samples, processes, and summary\n- Single page frontend using Chart.js for time series charts\n- Process (tstat) drill down: click a time point to see per process CPU, memory,\n  disk, and network counters\n- Runs entirely in Docker, no host Python required\n\n## Project layout\n\n```\natop_web/\n  main.py              FastAPI application entry point\n  parser/              rawlog binary parser\n    layouts/           CDEF definitions for rawlog structures\n    reader.py          header + sample iteration\n    decompress.py      zlib helpers\n  api/routes/          REST endpoints (upload, samples, processes, summary)\n  static/              index.html, app.js, style.css\ntests/                 pytest suite\n```\n\nA future `atop_web/llm/` package is planned for Phase 2 to add optional\nassistant style analysis. Phase 1 is self contained and does not depend on it.\n\n## Quick start\n\nThe project runs only via Docker. Do not invoke `python`, `pip`, or `venv` on\nthe host.\n\n### Run from the prebuilt image (recommended)\n\nA prebuilt image is published to GitHub Container Registry:\n\n```\ndocker pull ghcr.io/chhanz/atop-web:latest\n\ndocker run -d --name atop-web \\\n  -p 8000:8000 \\\n  -v /var/log/atop:/app/logs:ro \\\n  ghcr.io/chhanz/atop-web:latest\n```\n\nAvailable tags: `latest`, `v0.2.0`, `v0.1.0`. Image is ~200MB, runs on x86_64.\n\nThen open `http://localhost:8000`. Files under `/var/log/atop` on the host\nappear in the \"Server log directory\" panel.\n\n### Build the image\n\n```\ndocker compose build\n```\n\n### Run the tests\n\n```\ndocker compose run --rm test\n```\n\n### Start the server\n\n```\ndocker compose up -d\n```\n\n## Primary workflow: open a file already on the server\n\nThe expected production setup is that atop writes rawlog files under\n`/var/log/atop` on the host, and the container bind mounts that directory read\nonly. The web UI then lists those files and opens them with a single click, no\nupload required.\n\n1. `atop -w /var/log/atop/atop_$(date +%Y%m%d)` runs on the host (either\n   directly or via the standard atop service) to produce rawlog files.\n2. `docker compose up -d` starts atop-web with the mount in place.\n3. Open the UI. The top bar shows a \"Server log directory\" panel listing the\n   files in `/var/log/atop`, newest first. Click any file to load it.\n\nUnder the hood the picker is backed by:\n\n- `GET  /api/files`         returns the filtered, sorted list of candidate files\n- `POST /api/files/parse`   opens a file by name, returns a session id\n\nPath traversal is blocked: names must match `[A-Za-z0-9._-]+`, and the resolved\nreal path must stay inside `ATOP_LOG_DIR` after symlink resolution.\n\nIf `ATOP_LOG_DIR` points at a missing directory, the panel hides itself and the\nUI falls back to the upload form.\n\n## Secondary workflow: ad hoc upload\n\nThe top bar also hosts an \"Upload ad hoc\" form for analyzing a rawlog file that\nis not on the server (for example one copied from another machine). Uploads\nare held in memory for the duration of the process only; restart the container\nto drop all sessions.\n\n### Running behind a reverse proxy (sub path deployment)\n\natop-web is designed to run at the origin root (for example\n`http://host:8000/`) or under an arbitrary sub path (for example\n`https://docs.example.com/atop/`) without code changes.\n\n**How it works.** The reverse proxy strips the external prefix before\nforwarding the request, so the application always sees paths starting at\n`/` internally. `ATOP_ROOT_PATH` only controls the value injected into the\nHTML `\u003cbase href\u003e`, which tells the browser to resolve relative URLs\n(`static/...`, `api/...`) against the external prefix so they travel back\nthrough the proxy on the next hop.\n\nDeployment checklist:\n\n1. Strip the prefix at the proxy. With nginx that means `proxy_pass` with a\n   trailing slash; with Caddy `handle_path`; with Traefik a `stripPrefix`\n   middleware.\n2. Forward the usual `X-Forwarded-*` headers.\n3. Set `ATOP_ROOT_PATH=/your-prefix` on the `atop-web` container.\n\nDo **not** set FastAPI's `root_path` on top of a stripping proxy. That would\nmake the application expect the prefix twice and break the static mount.\n\n#### nginx\n\n```nginx\nlocation /atop/ {\n    proxy_pass http://atop-web:8000/;          # trailing slash strips prefix\n    proxy_set_header Host $host;\n    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n}\n```\n\nContainer: `ATOP_ROOT_PATH=/atop`.\n\n#### Caddy\n\n```\ndocs.example.com {\n    handle_path /atop/* {\n        reverse_proxy atop-web:8000\n    }\n}\n```\n\n`handle_path` rewrites the request so atop-web sees `/...` with no prefix.\nContainer: `ATOP_ROOT_PATH=/atop`.\n\n#### Traefik (Docker labels)\n\n```yaml\nservices:\n  atop-web:\n    environment:\n      ATOP_ROOT_PATH: /atop\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.atop.rule=Host(`docs.example.com`) \u0026\u0026 PathPrefix(`/atop`)\"\n      - \"traefik.http.routers.atop.entrypoints=web\"\n      - \"traefik.http.routers.atop.middlewares=atop-stripprefix\"\n      - \"traefik.http.middlewares.atop-stripprefix.stripprefix.prefixes=/atop\"\n      - \"traefik.http.services.atop.loadbalancer.server.port=8000\"\n```\n\nThe shipped `docker-compose.yml` already contains this layout, plus a\n`mkdocs-auth@file` middleware entry used on the author's deployment; remove\nthat middleware if you do not have an equivalent file provider setup.\n\n### Local development (no reverse proxy)\n\nThe `atop-web` service does not publish any host ports by default. To hit\nthe app directly without a proxy, uncomment the `ports:` block in\n`docker-compose.yml`, leave `ATOP_ROOT_PATH` unset (or set it to an empty\nstring), and run:\n\n```\nHOST_PORT=18000 docker compose up -d atop-web\n```\n\nThen open http://localhost:18000/ in a browser.\n\n## Configuration\n\n| Variable          | Default          | Purpose                                                        |\n| ----------------- | ---------------- | -------------------------------------------------------------- |\n| `ATOP_LOG_DIR`    | `/var/log/atop`  | Directory containing atop rawlog files; drives the server file browser. Leave empty or set to a non existent path to disable the browser and rely on uploads only. |\n| `ATOP_ROOT_PATH`  | `\"\"`             | External URL prefix when running behind a prefix stripping proxy (injected as HTML `\u003cbase href\u003e`; do not include a trailing slash) |\n| `HOST_PORT`       | `8000`           | Host port for the optional local debug mapping                 |\n\nLeave `ATOP_ROOT_PATH` empty for root deployment. Set it to the external\nprefix (for example `/atop`) when running behind a reverse proxy that strips\nthat prefix before forwarding; see the reverse proxy section above for the\nfull configuration.\n\n## API\n\n| Method | Path                | Description                                         |\n| ------ | ------------------- | --------------------------------------------------- |\n| GET    | `/api/files`        | List candidate rawlog files in `ATOP_LOG_DIR`       |\n| POST   | `/api/files/parse`  | Parse a server file by name, returns a session id   |\n| POST   | `/api/upload`       | Upload a rawlog file, returns a session id          |\n| GET    | `/api/dashboard`    | Summary + charts + processes in one fan-out payload |\n| GET    | `/api/samples`      | Time series for CPU, memory, disk, network          |\n| GET    | `/api/processes`    | Process (tstat) list for a specific sample time     |\n| GET    | `/api/summary`      | Hostname, kernel, sample count, time range, etc.    |\n\nThe `/api/samples`, `/api/processes` and `/api/summary` endpoints accept a\n`session` query parameter returned by the two parse entry points.\n\n## Performance notes\n\n- Large rawlog files (hundreds of MB) are decoded lazily through an offset\n  index and an mmap over the file, so the parser keeps Python heap usage flat\n  regardless of the capture size. Set `ATOP_LAZY=0` to fall back to the\n  pre-Phase-22 eager decoder if the lazy path hits a regression.\n- The chart endpoints (`/api/samples/system_*`) downsample to one sample per\n  minute on windows longer than a minute. Sub-minute windows keep full\n  per-sample resolution for tooltip accuracy.\n- `/api/dashboard` is the fast path the frontend uses for first paint and\n  filter changes: one fetch serves the summary, all four charts and the\n  process table together. Responses are cached per session for\n  `ATOP_RESPONSE_CACHE_TTL` seconds (default 300s, cap 32 entries via\n  `ATOP_RESPONSE_CACHE_MAX`). The same-range refresh latency drops from\n  several seconds to milliseconds inside the TTL window.\n\n## Compatibility\n\natop-web targets the rawlog format produced by current atop releases. The\nbinary layout is defined in `atop_web/parser/layouts/*.cdef`; adding support\nfor a different rawlog revision is a matter of adding a new CDEF file. The\nparser is an independent implementation and makes no claim of compatibility\nwith other third party tooling.\n\n## License\n\nApache License 2.0.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchhanz%2Fatop-web","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchhanz%2Fatop-web","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchhanz%2Fatop-web/lists"}