mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-11-13 12:23:44 +01:00
Merge remote-tracking branch 'origin/main' into fix/configurable-app-paths
This commit is contained in:
commit
38118c56d3
7 files changed
+130
-12
No files matched your search
@@ -0,0 +1,2 @@
|
|||||||
|
# Shell scripts run inside Linux containers and Git Bash, which fail on CRLF
|
||||||
|
*.sh text eol=lf
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -23,6 +23,14 @@
|
|||||||
|
|
||||||
<img src=".github/img/monthly_view.png" width="18%"></img> <img src=".github/img/yearly.png" width="18%"></img> <img src=".github/img/networth.png" width="18%"></img> <img src=".github/img/calendar.png" width="18%"></img> <img src=".github/img/all_transactions.png" width="18%"></img>
|
<img src=".github/img/monthly_view.png" width="18%"></img> <img src=".github/img/yearly.png" width="18%"></img> <img src=".github/img/networth.png" width="18%"></img> <img src=".github/img/calendar.png" width="18%"></img> <img src=".github/img/all_transactions.png" width="18%"></img>
|
||||||
|
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.simafri.com/en/managed-vps/"><img alt="Simafri" src="./.github/img/simafri-logo.png" height="40" /></a>
|
||||||
|
<br>
|
||||||
|
Demo and site hosting sponsored by <a href="https://www.simafri.com/en/managed-vps/">Simafri</a> (managed VPS)
|
||||||
|
</p>
|
||||||
|
|
||||||
# Why WYGIWYH?
|
# Why WYGIWYH?
|
||||||
Managing money can feel unnecessarily complex, but it doesn’t have to be. WYGIWYH (pronounced "wiggy-wih") is based on a simple principle:
|
Managing money can feel unnecessarily complex, but it doesn’t have to be. WYGIWYH (pronounced "wiggy-wih") is based on a simple principle:
|
||||||
|
|
||||||
@@ -54,7 +62,7 @@ Frustrated by the lack of comprehensive options, I set out to build **WYGIWYH**,
|
|||||||
|
|
||||||
# Demo
|
# Demo
|
||||||
|
|
||||||
You can try WYGIWYH on [wygiwyh-demo.herculino.com](https://wygiwyh-demo.herculino.com/) with the credentials below:
|
You can try WYGIWYH on [demo.wygiwyh.herculino.com](https://demo.wygiwyh.herculino.com/) with the credentials below:
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> E-mail: `demo@demo.com`
|
> E-mail: `demo@demo.com`
|
||||||
@@ -109,6 +117,48 @@ You can now access localhost:OUTBOUND_PORT
|
|||||||
> - If you're planning on running this behind Tailscale or other similar service also add your machine given IP to `DJANGO_ALLOWED_HOSTS`
|
> - If you're planning on running this behind Tailscale or other similar service also add your machine given IP to `DJANGO_ALLOWED_HOSTS`
|
||||||
> - If you're going to use another IP that isn't localhost, add it to `DJANGO_ALLOWED_HOSTS`, without `http://`
|
> - If you're going to use another IP that isn't localhost, add it to `DJANGO_ALLOWED_HOSTS`, without `http://`
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
The development stack runs from `docker-compose.dev.yml`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ docker compose -f docker-compose.dev.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Set `COMPOSE_FILE=docker-compose.dev.yml` in your `.env` to drop the `-f` flag.
|
||||||
|
|
||||||
|
### Working in multiple git worktrees
|
||||||
|
|
||||||
|
You can run one dev stack per [git worktree](https://git-scm.com/docs/git-worktree), side by side. Each worktree gets its own containers, database and volumes, because Compose names them after the worktree's folder.
|
||||||
|
|
||||||
|
A new worktree has no `.env` (it's gitignored). After creating one, run this from the worktree's root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sh docker/dev/worktree.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
It copies `.env` from your main checkout, gives the worktree its own host ports (printed when it finishes) and installs `frontend/node_modules` for tools you run outside Docker. The database starts empty.
|
||||||
|
|
||||||
|
To stop the worktree's containers and delete its volumes before removing it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sh docker/dev/worktree.sh teardown
|
||||||
|
```
|
||||||
|
|
||||||
|
The commands work on Linux, macOS and Windows (Git Bash or WSL). PowerShell and cmd have no `sh`, so on Windows set up git aliases once in your clone; they run through Git's bundled shell from any terminal, and every worktree of the clone shares them:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git config alias.wt-setup '!sh docker/dev/worktree.sh'
|
||||||
|
git config alias.wt-teardown '!sh docker/dev/worktree.sh teardown'
|
||||||
|
```
|
||||||
|
|
||||||
|
Run those two lines in Git Bash or PowerShell; cmd doesn't understand the single quotes.
|
||||||
|
|
||||||
|
Then use `git wt-setup` and `git wt-teardown`. Tools that create worktrees for you, such as Orca, Conductor or Superset, usually have setup and archive hooks: put these commands there.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Ports come from the worktree's folder name, so two worktrees can rarely land on the same ones. If `docker compose up` reports a port in use, change `OUTBOUND_PORT`, `WEBPACK_OUTBOUND_PORT` or `SQL_OUTBOUND_PORT` in that worktree's `.env`.
|
||||||
|
|
||||||
|
|
||||||
## Latest changes
|
## Latest changes
|
||||||
Features are only added to `main` when ready, if you want to run the latest version, you must build from source or use the `:nightly` tag on docker. Keep in mind that there can be undocumented breaking changes.
|
Features are only added to `main` when ready, if you want to run the latest version, you must build from source or use the `:nightly` tag on docker. Keep in mind that there can be undocumented breaking changes.
|
||||||
|
|||||||
+11
-5
@@ -11,7 +11,6 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: ./docker/dev/django/Dockerfile
|
dockerfile: ./docker/dev/django/Dockerfile
|
||||||
image: wygiwyh_dev_server
|
image: wygiwyh_dev_server
|
||||||
container_name: wygiwyh_dev_server
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./app/:/usr/src/app/:z
|
- ./app/:/usr/src/app/:z
|
||||||
- ./frontend/:/usr/src/frontend:z
|
- ./frontend/:/usr/src/frontend:z
|
||||||
@@ -21,6 +20,9 @@ services:
|
|||||||
- "${OUTBOUND_PORT:-8000}:${INTERNAL_PORT:-8000}"
|
- "${OUTBOUND_PORT:-8000}:${INTERNAL_PORT:-8000}"
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
environment:
|
||||||
|
# The browser loads assets straight from vite, so Django must point at the published port
|
||||||
|
- DJANGO_VITE_DEV_SERVER_PORT=${WEBPACK_OUTBOUND_PORT:-5173}
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
- vite
|
- vite
|
||||||
@@ -31,21 +33,20 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: ./docker/dev/vite/Dockerfile
|
dockerfile: ./docker/dev/vite/Dockerfile
|
||||||
image: wygiwyh_dev_node
|
image: wygiwyh_dev_node
|
||||||
container_name: wygiwyh_dev_node
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./frontend/:/usr/src/frontend
|
- ./frontend/:/usr/src/frontend
|
||||||
- ./app/:/usr/src/app/
|
- ./app/:/usr/src/app/
|
||||||
# http://jdlm.info/articles/2016/03/06/lessons-building-node-app-docker.html
|
# http://jdlm.info/articles/2016/03/06/lessons-building-node-app-docker.html
|
||||||
- wygiwyh_node_modules:/usr/src/frontend/node_modules
|
- wygiwyh_node_modules:/usr/src/frontend/node_modules
|
||||||
ports:
|
ports:
|
||||||
- '${WEBPACK_OUTBOUND_PORT}:5173'
|
- '${WEBPACK_OUTBOUND_PORT:-5173}:${WEBPACK_OUTBOUND_PORT:-5173}'
|
||||||
environment:
|
environment:
|
||||||
- WATCHPACK_POLLING=true
|
- WATCHPACK_POLLING=true
|
||||||
|
- VITE_DEV_SERVER_PORT=${WEBPACK_OUTBOUND_PORT:-5173}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:15
|
image: postgres:15
|
||||||
container_name: ${SQL_HOST}
|
|
||||||
volumes:
|
volumes:
|
||||||
- wygiwyh_dev_postgres_data:/var/lib/postgresql/data/
|
- wygiwyh_dev_postgres_data:/var/lib/postgresql/data/
|
||||||
environment:
|
environment:
|
||||||
@@ -53,5 +54,10 @@ services:
|
|||||||
- POSTGRES_PASSWORD=${SQL_PASSWORD}
|
- POSTGRES_PASSWORD=${SQL_PASSWORD}
|
||||||
- POSTGRES_DB=${SQL_DATABASE}
|
- POSTGRES_DB=${SQL_DATABASE}
|
||||||
ports:
|
ports:
|
||||||
- '${SQL_PORT}:5432'
|
- '${SQL_OUTBOUND_PORT:-${SQL_PORT:-5432}}:5432'
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
# Keeps .env files that point SQL_HOST at the old fixed container name working
|
||||||
|
aliases:
|
||||||
|
- ${SQL_HOST:-db}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
Executable
+43
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Prepares or tears down the dev environment of an extra git worktree.
|
||||||
|
# sh docker/dev/worktree.sh copy .env from the main checkout with free host ports, install host node_modules
|
||||||
|
# sh docker/dev/worktree.sh teardown stop this worktree's containers and delete its volumes
|
||||||
|
# Plain POSIX sh: no GNU-only flags, so it runs on Linux, macOS and Git for Windows.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ "${1:-setup}" = "teardown" ]; then
|
||||||
|
# Without .env compose can't find its config, and nothing was ever started here
|
||||||
|
[ -f .env ] || exit 0
|
||||||
|
exec docker compose down -v --remove-orphans
|
||||||
|
fi
|
||||||
|
|
||||||
|
main=$(git worktree list --porcelain | head -n 1 | cut -d ' ' -f 2-)
|
||||||
|
if [ "$main" = "$(git rev-parse --show-toplevel)" ]; then
|
||||||
|
echo "This is the main checkout, nothing to do." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f .env ]; then
|
||||||
|
if [ ! -f "$main/.env" ]; then
|
||||||
|
echo "No .env in the main checkout ($main). Create it from .env.example first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Same folder name -> same offset, so re-creating a worktree keeps its ports
|
||||||
|
off=$(( $(basename "$PWD" | cksum | cut -d ' ' -f 1) % 100 + 1 ))
|
||||||
|
grep -v -E '^(OUTBOUND_PORT|WEBPACK_OUTBOUND_PORT|SQL_OUTBOUND_PORT)=' "$main/.env" > .env || true
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "# Host ports for this worktree, set by docker/dev/worktree.sh"
|
||||||
|
echo "OUTBOUND_PORT=$((18000 + off))"
|
||||||
|
echo "WEBPACK_OUTBOUND_PORT=$((15100 + off))"
|
||||||
|
echo "SQL_OUTBOUND_PORT=$((15400 + off))"
|
||||||
|
} >> .env
|
||||||
|
echo "Created .env: app on http://localhost:$((18000 + off))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For tools run on the host (the vite container keeps its own copy in a volume)
|
||||||
|
if command -v npm > /dev/null 2>&1; then
|
||||||
|
npm ci --prefix frontend --no-audit --no-fund
|
||||||
|
else
|
||||||
|
echo "npm not found, skipping frontend/node_modules." >&2
|
||||||
|
fi
|
||||||
@@ -108,6 +108,23 @@ function isMobile() {
|
|||||||
return isMobileDevice() || isTouchDevice();
|
return isMobileDevice() || isTouchDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AirDatepicker creates a single overlay for all mobile pickers and keeps it in
|
||||||
|
// its module scope, inside a global container on the body. A boosted navigation
|
||||||
|
// swaps the body: AirDatepicker then rebuilds the container, but keeps the old
|
||||||
|
// overlay, which is left detached and can't receive the clicks that close a picker.
|
||||||
|
let mobileOverlay = null;
|
||||||
|
|
||||||
|
function createAirDatepicker(element, opts) {
|
||||||
|
const datepicker = new AirDatepicker(element, opts);
|
||||||
|
mobileOverlay ??= document.querySelector('.air-datepicker-overlay');
|
||||||
|
const container = document.getElementById(AirDatepicker.defaultGlobalContainerId);
|
||||||
|
if (mobileOverlay && !mobileOverlay.isConnected && container) {
|
||||||
|
mobileOverlay.classList.remove('-active-'); // its picker was swapped out while open
|
||||||
|
container.appendChild(mobileOverlay);
|
||||||
|
}
|
||||||
|
return datepicker;
|
||||||
|
}
|
||||||
|
|
||||||
window.DatePicker = async function createDynamicDatePicker(element) {
|
window.DatePicker = async function createDynamicDatePicker(element) {
|
||||||
let todayButton = {
|
let todayButton = {
|
||||||
content: element.dataset.nowButtonTxt,
|
content: element.dataset.nowButtonTxt,
|
||||||
@@ -182,7 +199,7 @@ window.DatePicker = async function createDynamicDatePicker(element) {
|
|||||||
opts["selectedDates"] = [element.dataset.value];
|
opts["selectedDates"] = [element.dataset.value];
|
||||||
opts["startDate"] = [element.dataset.value];
|
opts["startDate"] = [element.dataset.value];
|
||||||
}
|
}
|
||||||
return new AirDatepicker(element, opts);
|
return createAirDatepicker(element, opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.MonthYearPicker = async function createDynamicDatePicker(element) {
|
window.MonthYearPicker = async function createDynamicDatePicker(element) {
|
||||||
@@ -258,7 +275,7 @@ window.MonthYearPicker = async function createDynamicDatePicker(element) {
|
|||||||
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
|
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||||
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
|
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||||
}
|
}
|
||||||
return new AirDatepicker(element, opts);
|
return createAirDatepicker(element, opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.YearPicker = async function createDynamicDatePicker(element) {
|
window.YearPicker = async function createDynamicDatePicker(element) {
|
||||||
@@ -334,5 +351,5 @@ window.YearPicker = async function createDynamicDatePicker(element) {
|
|||||||
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
|
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||||
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
|
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||||
}
|
}
|
||||||
return new AirDatepicker(element, opts);
|
return createAirDatepicker(element, opts);
|
||||||
};
|
};
|
||||||
@@ -644,11 +644,11 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oauthlib"
|
name = "oauthlib"
|
||||||
version = "4.0.0"
|
version = "4.0.1"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7a/d8/a1bcc8ba112a627f8ffbdc212a78ce18d3ac07e91a5ca65d27918eee25a1/oauthlib-4.0.0.tar.gz", hash = "sha256:efb274799819440f95b4ab3b818869f1ce9ae26c5beacba0201d1a1b76b54f86", size = 187232, upload-time = "2026-09-28T06:01:18.77Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/33/17/2bb072de0b990303c985f26869fd47bb179024a051f47e7f9d208eb92f8e/oauthlib-4.0.1.tar.gz", hash = "sha256:3d73febdb6faa00f277c81fb04be7b7586fb96fc3b7f0b9c772dada69a25ede7", size = 191219, upload-time = "2026-10-31T09:52:58.008Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/f4/78229a1066068ca14fc60fb26cf7381cabe4382261392b90e5f9552722d4/oauthlib-4.0.0-py3-none-any.whl", hash = "sha256:624c28c13a0a59cabf9747dfa52af63be3e512a7f2714df16e91b5b3a145e6cd", size = 159715, upload-time = "2026-09-28T06:01:17.008Z" },
|
{ url = "https://files.pythonhosted.org/packages/df/ef/3b8adde1b32a6f540758bff734d3442c621734c83708e5f02b0ba2f43b84/oauthlib-4.0.1-py3-none-any.whl", hash = "sha256:498a298876e996301950a5b053d12c31628bb7b895db03666430c69ce182eb61", size = 161934, upload-time = "2026-10-31T09:52:56.206Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in new issue
Block a user