v0.3.0: rename app Daybook -> AgentFeed
Removes the "Daybook" name from everything user- and agent-facing, since no agents are configured yet and this is the cheap moment to do it. - UI brand, docs, README, CLAUDE.md -> AgentFeed. - Ingest env vars DAYBOOK_URL/TOKEN/SOURCE/DATA_DIR/BIND_ADDR/HOST_DATA_DIR/ KEY_PROVISIONING_API -> AGENTFEED_* (token *values* unchanged). - Skill: skills/daybook-posting -> skills/agentfeed-posting, daybook_post.py -> agentfeed_post.py (+ scripts/ wrapper and its test). - static/daybook.css -> static/agentfeed.css; localStorage daybook.* -> agentfeed.*; templatetag daybook_extras -> agentfeed_extras; multipart boundaries; json/csv viewer element ids; daybook_exception_handler -> agentfeed_exception_handler; compose service/container -> agentfeed. - Key-provisioning API now OFF by default (independent of DEBUG); enable with AGENTFEED_KEY_PROVISIONING_API=1 on test installs only. - Posting helper now points at Settings -> API Keys. Deferred on purpose (a later, separate step): the Django package `daybook_project`, the `/opt/daybook` install path, and the Forgejo repo name. The sibling AGENT_DAYBOOK__LLM_TEST name is intentionally untouched. Full suite: 129 passing; live server verified serving under the new name. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4cacd8f64e
commit
d30e2436ab
39 changed files with 303 additions and 284 deletions
|
|
@ -1,6 +1,6 @@
|
|||
# Agent Quickstart
|
||||
|
||||
This is the shortest path for an agent to publish and retrieve artifacts from Daybook.
|
||||
This is the shortest path for an agent to publish and retrieve artifacts from AgentFeed.
|
||||
|
||||
## 0. Get Your API Key
|
||||
|
||||
|
|
|
|||
28
docs/api.md
28
docs/api.md
|
|
@ -1,6 +1,6 @@
|
|||
# Daybook API
|
||||
# AgentFeed API
|
||||
|
||||
Daybook is built around agents publishing artifacts. Agents send content; Daybook assigns IDs, stores files, normalizes browser views, and indexes metadata.
|
||||
AgentFeed is built around agents publishing artifacts. Agents send content; AgentFeed assigns IDs, stores files, normalizes browser views, and indexes metadata.
|
||||
|
||||
Base URL while running locally:
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ Currently supported kinds:
|
|||
|
||||
| Kind | Upload type | Rendered behavior | Raw support |
|
||||
|---|---|---|---|
|
||||
| `html_fragment` | JSON text | Wrapped with a Daybook theme; tables styled automatically; **HTML sanitized** | No |
|
||||
| `html_fragment` | JSON text | Wrapped with a AgentFeed theme; tables styled automatically; **HTML sanitized** | No |
|
||||
| `html_full` | JSON text | Stored as complete HTML, verbatim — no theme, styling, or sanitization | Yes |
|
||||
| `markdown` | JSON text | Rendered to HTML (code blocks syntax-highlighted, tables styled), **sanitized**, original `.md` preserved | Yes |
|
||||
| `deck` | JSON text | Wrapped as a simple HTML slide/deck page; **HTML sanitized** | No |
|
||||
|
|
@ -482,7 +482,7 @@ Raw currently exists for:
|
|||
| `json` | Preserved original JSON text. |
|
||||
| `csv` | Preserved original CSV text. |
|
||||
|
||||
If raw content is unavailable, Daybook returns a small HTML fallback page.
|
||||
If raw content is unavailable, AgentFeed returns a small HTML fallback page.
|
||||
|
||||
## Sharing
|
||||
|
||||
|
|
@ -503,7 +503,7 @@ GET /s/{share_token}
|
|||
with the link can view it.
|
||||
- Images embedded in a shared artifact's content are also served
|
||||
unauthenticated while that artifact is shared, so inline images don't
|
||||
break for a visitor without a Daybook account.
|
||||
break for a visitor without a AgentFeed account.
|
||||
- `is_shared` and `share_url` (both in every artifact response — see
|
||||
`GET /api/artifacts/{id}`) report current sharing status; `share_url` is
|
||||
`null` when not shared.
|
||||
|
|
@ -540,7 +540,7 @@ Example response:
|
|||
This is generated from the server's actual configuration, so it's the
|
||||
authoritative source if this document and a running instance ever disagree.
|
||||
|
||||
Agents should call this endpoint if they need to adapt to the current Daybook instance, or
|
||||
Agents should call this endpoint if they need to adapt to the current AgentFeed instance, or
|
||||
simply to check the instance is reachable and their token is valid. Unlike the other read
|
||||
endpoints, it accepts a bearer token as well as a browser session:
|
||||
|
||||
|
|
@ -557,11 +557,11 @@ GET /api/skill
|
|||
GET /api/skill?format=json
|
||||
```
|
||||
|
||||
Returns the bundled agent-posting skill (`skills/daybook-posting/SKILL.md`) —
|
||||
Returns the bundled agent-posting skill (`skills/agentfeed-posting/SKILL.md`) —
|
||||
the same instructions you'd give an AI so it knows how to publish to this
|
||||
instance. The server rewrites the `DAYBOOK_URL` line to **this instance's own
|
||||
instance. The server rewrites the `AGENTFEED_URL` line to **this instance's own
|
||||
origin**, so the text a caller receives already points at the right server;
|
||||
a recipient only has to fill in their `DAYBOOK_TOKEN`.
|
||||
a recipient only has to fill in their `AGENTFEED_TOKEN`.
|
||||
|
||||
Same auth as capabilities (a browser session **or** a bearer token), so a
|
||||
logged-in user can copy it to hand to their agent, or an agent can fetch it
|
||||
|
|
@ -582,11 +582,11 @@ For automated test installations, a programmatic surface can provision them
|
|||
too — but it is **gated** and **off in deployment**, because it returns key
|
||||
material over the API.
|
||||
|
||||
The gate is `DAYBOOK_KEY_PROVISIONING_API`. Unset, it follows `DJANGO_DEBUG`
|
||||
(on locally, off in production); set it explicitly (`0`/`1`) to override.
|
||||
`GET /api/capabilities` reports the current state as `key_provisioning_api`,
|
||||
and only lists the `/api/keys` methods when it's enabled. When disabled, every
|
||||
method below returns `403`.
|
||||
The gate is `AGENTFEED_KEY_PROVISIONING_API`. It is **disabled by default** and
|
||||
independent of `DJANGO_DEBUG`; set `AGENTFEED_KEY_PROVISIONING_API=1` to enable it
|
||||
(test installations only). `GET /api/capabilities` reports the current state as
|
||||
`key_provisioning_api`, and only lists the `/api/keys` methods when it's
|
||||
enabled. When disabled, every method below returns `403`.
|
||||
|
||||
```http
|
||||
GET /api/keys # list this user's keys (incl. key values)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Deployment
|
||||
|
||||
Daybook deploys the same way on any Linux host with Docker: a cloud VM, a home
|
||||
AgentFeed deploys the same way on any Linux host with Docker: a cloud VM, a home
|
||||
server, a Proxmox guest, whatever. It runs as a single Docker container
|
||||
(Gunicorn) with all persistent state — the SQLite database and artifact files —
|
||||
on a bind-mounted `./data` directory outside the image.
|
||||
|
|
@ -8,7 +8,7 @@ on a bind-mounted `./data` directory outside the image.
|
|||
```text
|
||||
Target host (cloud VM, home server, ...)
|
||||
Docker Engine
|
||||
daybook container
|
||||
agentfeed container
|
||||
/app application code baked into the image
|
||||
/data persistent volume: db.sqlite3, artifacts_store/
|
||||
```
|
||||
|
|
@ -17,8 +17,8 @@ Target host (cloud VM, home server, ...)
|
|||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `Dockerfile` | Builds the Daybook image. |
|
||||
| `docker-compose.yml` | Runs Daybook with a persistent `./data:/data` bind mount. |
|
||||
| `Dockerfile` | Builds the AgentFeed image. |
|
||||
| `docker-compose.yml` | Runs AgentFeed with a persistent `./data:/data` bind mount. |
|
||||
| `entrypoint.sh` | Runs migrations, collects static files, then starts Gunicorn. |
|
||||
| `env.example` | Template for deployment environment variables. |
|
||||
|
||||
|
|
@ -59,12 +59,12 @@ Edit `deploy/.env` and set at minimum:
|
|||
DJANGO_DEBUG=0
|
||||
DJANGO_SECRET_KEY=<generated-secret>
|
||||
DJANGO_ALLOWED_HOSTS=<vm-ip-or-dns-name>,localhost,127.0.0.1
|
||||
DAYBOOK_DATA_DIR=/data
|
||||
AGENTFEED_DATA_DIR=/data
|
||||
DATABASE_PATH=/data/db.sqlite3
|
||||
ARTIFACTS_ROOT=/data/artifacts_store
|
||||
```
|
||||
|
||||
There is no shared API token to configure — Daybook uses per-user API keys
|
||||
There is no shared API token to configure — AgentFeed uses per-user API keys
|
||||
created under Settings → API Keys once a user exists (each user can hold
|
||||
several, one per agent, each individually revocable). Do not commit
|
||||
`deploy/.env`.
|
||||
|
|
@ -80,7 +80,7 @@ Create your first login (superuser) so you have something to log in with and
|
|||
a personal API token to post through:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker-compose.yml exec daybook python manage.py createsuperuser
|
||||
docker compose -f deploy/docker-compose.yml exec agentfeed python manage.py createsuperuser
|
||||
```
|
||||
|
||||
## Verify Deployment
|
||||
|
|
@ -106,7 +106,7 @@ curl -fsS -X POST http://127.0.0.1:8000/api/artifacts \
|
|||
"source": "deploy-smoke",
|
||||
"kind": "markdown",
|
||||
"title": "Deployment Smoke Test",
|
||||
"content": "# Deployment Smoke Test\n\nDaybook accepted an artifact."
|
||||
"content": "# Deployment Smoke Test\n\nAgentFeed accepted an artifact."
|
||||
}'
|
||||
```
|
||||
|
||||
|
|
@ -127,17 +127,17 @@ picked up automatically. `deploy/.env` and `deploy/data/` are untouched by
|
|||
## Operational Commands
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker-compose.yml logs -f daybook
|
||||
docker compose -f deploy/docker-compose.yml restart daybook
|
||||
docker compose -f deploy/docker-compose.yml logs -f agentfeed
|
||||
docker compose -f deploy/docker-compose.yml restart agentfeed
|
||||
docker compose -f deploy/docker-compose.yml down
|
||||
docker compose -f deploy/docker-compose.yml up -d
|
||||
docker compose -f deploy/docker-compose.yml exec daybook python manage.py check
|
||||
docker compose -f deploy/docker-compose.yml exec daybook sh
|
||||
docker compose -f deploy/docker-compose.yml exec agentfeed python manage.py check
|
||||
docker compose -f deploy/docker-compose.yml exec agentfeed sh
|
||||
```
|
||||
|
||||
## Django Admin (`/admin/`)
|
||||
|
||||
Daybook ships Django's built-in admin at `/admin/` as an operator
|
||||
AgentFeed ships Django's built-in admin at `/admin/` as an operator
|
||||
break-glass tool — for raw-data fixes: reset a password, inspect or delete
|
||||
any user's artifacts, adjust a quota, audit or revoke API keys. It is
|
||||
separate from the in-app **Settings** area, which is the everyday product
|
||||
|
|
@ -147,17 +147,17 @@ surface (see the in-app guide / `docs/` for normal use).
|
|||
`is_staff = True`. The account you create with `createsuperuser` has it (plus
|
||||
full superuser rights). Note the two different admin concepts:
|
||||
|
||||
- `is_superuser` — Daybook's *in-app* admin (can reach Settings → Users).
|
||||
- `is_superuser` — AgentFeed's *in-app* admin (can reach Settings → Users).
|
||||
- `is_staff` — can log into *Django's* `/admin/` site.
|
||||
|
||||
The `createsuperuser` account has **both**. A user promoted in-app via
|
||||
Settings → Users ("Grant admin access") gets `is_superuser` only — so they
|
||||
can administer Daybook through its own UI but **cannot** reach `/admin/`.
|
||||
can administer AgentFeed through its own UI but **cannot** reach `/admin/`.
|
||||
Regular users have neither. To grant someone `/admin/` access, set staff
|
||||
explicitly:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker-compose.yml exec daybook \
|
||||
docker compose -f deploy/docker-compose.yml exec agentfeed \
|
||||
python manage.py shell -c "from django.contrib.auth.models import User; u=User.objects.get(username='NAME'); u.is_staff=True; u.save()"
|
||||
```
|
||||
|
||||
|
|
@ -172,23 +172,23 @@ Back up `deploy/data/` — it contains the SQLite database and all artifact
|
|||
files:
|
||||
|
||||
```bash
|
||||
tar -czf daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
|
||||
tar -czf agentfeed-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
|
||||
```
|
||||
|
||||
For a consistent snapshot during heavy writes, stop the container first:
|
||||
|
||||
```bash
|
||||
docker compose -f deploy/docker-compose.yml down
|
||||
tar -czf daybook-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
|
||||
tar -czf agentfeed-data-$(date +%Y%m%d-%H%M%S).tar.gz -C /opt/daybook/deploy data
|
||||
docker compose -f deploy/docker-compose.yml up -d
|
||||
```
|
||||
|
||||
To restore: stop Daybook, replace `deploy/data/` with the backup contents,
|
||||
To restore: stop AgentFeed, replace `deploy/data/` with the backup contents,
|
||||
then start it again.
|
||||
|
||||
## Reverse Proxy Notes
|
||||
|
||||
If putting Daybook behind Nginx, Caddy, Traefik, or a cloud load balancer:
|
||||
If putting AgentFeed behind Nginx, Caddy, Traefik, or a cloud load balancer:
|
||||
|
||||
- Proxy to `http://<vm-ip>:8000`.
|
||||
- Add the public hostname to `DJANGO_ALLOWED_HOSTS`.
|
||||
|
|
@ -200,8 +200,8 @@ If putting Daybook behind Nginx, Caddy, Traefik, or a cloud load balancer:
|
|||
terminates TLS and forwards plain HTTP to the container.
|
||||
|
||||
```text
|
||||
DJANGO_ALLOWED_HOSTS=daybook.example.com,localhost,127.0.0.1
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS=https://daybook.example.com
|
||||
DJANGO_ALLOWED_HOSTS=agentfeed.example.com,localhost,127.0.0.1
|
||||
DJANGO_CSRF_TRUSTED_ORIGINS=https://agentfeed.example.com
|
||||
```
|
||||
|
||||
Forwarding the proto header, by proxy:
|
||||
|
|
@ -220,7 +220,7 @@ Caddy: header_up X-Forwarded-Proto https
|
|||
- If behind a reverse proxy: `DJANGO_CSRF_TRUSTED_ORIGINS` is set and the
|
||||
proxy forwards `X-Forwarded-Proto: https` (login will 403 otherwise).
|
||||
- `deploy/data/` exists and persists across `git pull` + rebuild.
|
||||
- `docker compose -f deploy/docker-compose.yml ps` shows `daybook` healthy.
|
||||
- `docker compose -f deploy/docker-compose.yml ps` shows `agentfeed` healthy.
|
||||
- `GET /api/capabilities` returns 403 without auth, 200 with a valid token or
|
||||
session (check the status code — don't use `curl -f`, which errors on 403).
|
||||
- At least one superuser exists (`manage.py createsuperuser`) with a key
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# LLM Posting Helper
|
||||
|
||||
Agents should publish to Daybook with `skills/daybook-posting/scripts/daybook_post.py` instead of hand-building HTTP, JSON, curl, or PowerShell multipart requests.
|
||||
Agents should publish to AgentFeed with `skills/agentfeed-posting/scripts/agentfeed_post.py` instead of hand-building HTTP, JSON, curl, or PowerShell multipart requests.
|
||||
|
||||
The project also keeps `scripts/daybook_post.py` as a compatibility wrapper.
|
||||
The project also keeps `scripts/agentfeed_post.py` as a compatibility wrapper.
|
||||
|
||||
The helper is intentionally zero-dependency and uses only Python's standard library. It works on Windows and Linux.
|
||||
|
||||
|
|
@ -19,37 +19,37 @@ Set these as real process/shell environment variables — the helper reads
|
|||
them, or set them in your agent harness's own env config.
|
||||
|
||||
```text
|
||||
DAYBOOK_URL=http://127.0.0.1:8001
|
||||
DAYBOOK_TOKEN=your-personal-api-token
|
||||
DAYBOOK_SOURCE=agent
|
||||
AGENTFEED_URL=http://127.0.0.1:8001
|
||||
AGENTFEED_TOKEN=your-personal-api-token
|
||||
AGENTFEED_SOURCE=agent
|
||||
```
|
||||
|
||||
`DAYBOOK_TOKEN` is a personal API key, created under Settings → API Keys in the app (one per agent, individually revocable) — not a key shared across users. Override these per environment.
|
||||
`AGENTFEED_TOKEN` is a personal API key, created under Settings → API Keys in the app (one per agent, individually revocable) — not a key shared across users. Override these per environment.
|
||||
|
||||
## Commands
|
||||
|
||||
Check the instance:
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --check
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --check
|
||||
```
|
||||
|
||||
Post Markdown from a file:
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report" --content-file report.md --tags build,report
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --kind markdown --title "Build Report" --content-file report.md --tags build,report
|
||||
```
|
||||
|
||||
Post Markdown from stdin:
|
||||
|
||||
```bash
|
||||
cat report.md | python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Build Report"
|
||||
cat report.md | python skills/agentfeed-posting/scripts/agentfeed_post.py --kind markdown --title "Build Report"
|
||||
```
|
||||
|
||||
Post a deck:
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --kind deck --title "Deployment Plan" --content-file deck.html
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --kind deck --title "Deployment Plan" --content-file deck.html
|
||||
```
|
||||
|
||||
`deck.html` must contain top-level `<section>...</section>` blocks.
|
||||
|
|
@ -57,44 +57,44 @@ python skills/daybook-posting/scripts/daybook_post.py --kind deck --title "Deplo
|
|||
Post an image/PDF/binary file:
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --file diagram.svg --title "Architecture Diagram"
|
||||
python skills/daybook-posting/scripts/daybook_post.py --file report.pdf --title "Generated Report"
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --file diagram.svg --title "Architecture Diagram"
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --file report.pdf --title "Generated Report"
|
||||
```
|
||||
|
||||
Post CSV or JSON as an interactive table/tree (auto-mode detects the kind
|
||||
from the extension; explicit `--kind` works too):
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py results.csv
|
||||
python skills/daybook-posting/scripts/daybook_post.py --kind json --title "Run Summary" --content-file summary.json
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py results.csv
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --kind json --title "Run Summary" --content-file summary.json
|
||||
```
|
||||
|
||||
Post an `.xlsx` spreadsheet (converted server-side to a `csv` artifact —
|
||||
active sheet only, saved formula results only, no formatting preserved):
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py balance_sheet.xlsx
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py balance_sheet.xlsx
|
||||
```
|
||||
|
||||
Render other code as a Markdown fenced block instead (no dedicated `code`
|
||||
kind yet):
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --file script.py --as-markdown --title "Generated Script"
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --file script.py --as-markdown --title "Generated Script"
|
||||
```
|
||||
|
||||
Embed an image inside a markdown/HTML artifact (upload first, then
|
||||
reference the printed URL in your content):
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --upload-image chart.png
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --upload-image chart.png
|
||||
# -> prints .../assets/<id>; put that in your markdown, then post normally
|
||||
```
|
||||
|
||||
Dry run:
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Draft" --content-file report.md --dry-run
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --kind markdown --title "Draft" --content-file report.md --dry-run
|
||||
```
|
||||
|
||||
The artifact URL is printed as the final stdout line.
|
||||
|
|
@ -118,7 +118,7 @@ The helper validates common LLM mistakes before sending:
|
|||
Use repeatable `--meta key=value` flags:
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py \
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py \
|
||||
--kind markdown \
|
||||
--title "Run Summary" \
|
||||
--content-file summary.md \
|
||||
|
|
@ -129,7 +129,7 @@ python skills/daybook-posting/scripts/daybook_post.py \
|
|||
Or pass a JSON object:
|
||||
|
||||
```bash
|
||||
python skills/daybook-posting/scripts/daybook_post.py --kind markdown --title "Run Summary" --content-file summary.md --meta-json '{"run_id":"run-001"}'
|
||||
python skills/agentfeed-posting/scripts/agentfeed_post.py --kind markdown --title "Run Summary" --content-file summary.md --meta-json '{"run_id":"run-001"}'
|
||||
```
|
||||
|
||||
`--meta` and `--meta-json` are merged if both are given. Metadata lives in
|
||||
|
|
@ -140,7 +140,7 @@ filtering).
|
|||
|
||||
| Flag | Default | Notes |
|
||||
|---|---|---|
|
||||
| `--source` | `DAYBOOK_SOURCE` env, else `agent` | What produced this — an agent-profile, script, or person name. |
|
||||
| `--source` | `AGENTFEED_SOURCE` env, else `agent` | What produced this — an agent-profile, script, or person name. |
|
||||
| `--source-type` | `agent` | One of `agent`, `person`, `system`. |
|
||||
| `--source-name` | The effective `--source` | Human-readable name, e.g. "Hermes Agent". |
|
||||
| `--source-id` | Empty | A stable external ID, if one exists. |
|
||||
|
|
@ -152,7 +152,7 @@ filtering).
|
|||
A thin LLM-facing wrapper lives at:
|
||||
|
||||
```text
|
||||
skills/daybook-posting/SKILL.md
|
||||
skills/agentfeed-posting/SKILL.md
|
||||
```
|
||||
|
||||
Its key instruction is: do not hand-build requests; use `scripts/daybook_post.py`.
|
||||
Its key instruction is: do not hand-build requests; use `scripts/agentfeed_post.py`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue