diff --git a/README.md b/README.md index d12ff91..28b1de8 100644 --- a/README.md +++ b/README.md @@ -173,15 +173,11 @@ prism_api_key: "Bearer YOUR_API_KEY_HERE" Reboot your Home Assistant system and you'll then be able to send Signal notifications to yourself by using this notify prism action. -## API Reference +## Sending Notifications -### Send Notification +Send notifications via HTTP POST to `/{appName}`: -#### POST /{appName} - -Send a notification to a specific app. Messages are routed based on your app configuration in the web UI. - -JSON format: +**JSON format:** ```bash curl -X POST http://localhost:8080/my-app \ @@ -190,7 +186,7 @@ curl -X POST http://localhost:8080/my-app \ -d '{"title": "Alert", "message": "Something happened"}' ``` -Plain text (ntfy-compatible): +**Plain text (ntfy-compatible):** ```bash curl -X POST http://localhost:8080/my-app \ @@ -198,52 +194,7 @@ curl -X POST http://localhost:8080/my-app \ -d "Simple message text" ``` -**App Routing:** -- Configure which integration(s) receive messages from each app via the web UI -- Apps can route to Signal, Telegram, WebPush, or multiple destinations -- Special apps like "Proton Mail" are created automatically - -### WebPush/Webhook Management - -#### POST /api/v1/webpush/app - -Register or update a WebPush subscription or plain webhook. - -Encrypted WebPush (all crypto fields required): - -```bash -curl -X POST http://localhost:8080/api/v1/webpush/app \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "appName": "my-app", - "pushEndpoint": "https://updates.push.services.mozilla.org/...", - "p256dh": "base64-encoded-key", - "auth": "base64-encoded-auth", - "vapidPrivateKey": "base64-encoded-vapid-key" - }' -``` - -Plain HTTP webhook (no encryption): - -```bash -curl -X POST http://localhost:8080/api/v1/webpush/app \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "appName": "my-app", - "pushEndpoint": "https://your-server.com/webhook" - }' -``` - -#### DELETE /api/v1/webpush/app/{appName} - -Unregister a WebPush subscription (clears WebPush settings, reverts to Signal). - -```bash -curl -X DELETE http://localhost:8080/api/v1/webpush/app/my-app \ - -H "Authorization: Bearer YOUR_API_KEY" -``` +Messages are routed based on your app's subscriptions configured in the web UI. Apps can have multiple subscriptions (Signal + WebPush + Telegram) and will receive notifications on all configured channels. ## Monitoring diff --git a/VERSION b/VERSION index 9325c3c..60a2d3e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 \ No newline at end of file +0.4.0 \ No newline at end of file diff --git a/public/index.css b/public/index.css index 79abcd8..2daf747 100644 --- a/public/index.css +++ b/public/index.css @@ -148,7 +148,6 @@ details[open] > .card-header::before { } /* Tooltips */ -.channel-badge:has(.tooltip), .integration-status:has(.tooltip) { cursor: help; } @@ -172,7 +171,7 @@ details[open] > .card-header::before { transition: opacity 0.2s; pointer-events: none; z-index: 10; - box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.2); + box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.3); } .tooltip::after { @@ -232,18 +231,11 @@ details[open] > .card-header::before { flex: 1; display: flex; flex-direction: column; - gap: 0.25rem; + gap: 0.5rem; } .app-name { - font-size: 1.1em; -} - -.app-channel { - display: flex; - align-items: center; - gap: 0.5rem; - font-size: 0.9em; + font-size: 1.2em; } .channel-badge { @@ -252,26 +244,69 @@ details[open] > .card-header::before { font-size: 0.85em; font-weight: 500; position: relative; + border: none; + background: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.25rem; + min-width: 5rem; } -.channel-signal { +.badge-active { + cursor: pointer; + transition: filter 0.2s ease; +} + +.badge-active:hover { + filter: brightness(0.9); +} + +.badge-active.htmx-request { + opacity: 0.6; + cursor: wait; + pointer-events: none; +} + +.badge-inactive { + cursor: pointer; + border: 0.0625rem dashed currentColor; + transition: filter 0.2s ease; +} + +.badge-inactive:hover { + filter: brightness(1.2); +} + +.badge-inactive.htmx-request { + opacity: 0.6; + cursor: wait; + pointer-events: none; +} + +.channel-signal.badge-active { background: var(--accent); color: var(--text-on-color); } -.channel-webpush { +.channel-signal.badge-inactive { + color: var(--accent); + background: transparent; +} + +.channel-webpush.badge-active { background: var(--success); color: var(--text-on-color); } -.channel-telegram { - background: #0088cc; +.channel-telegram.badge-active { + background: var(--accent); color: var(--text-on-color); } -.app-detail { - color: var(--text-secondary); - font-size: 0.85em; +.channel-telegram.badge-inactive { + color: var(--accent); + background: transparent; } .app-actions { @@ -280,20 +315,6 @@ details[open] > .card-header::before { align-items: center; } -.channel-form { - display: inline; -} - -.channel-select { - padding: 0.375rem 0.5rem; - background: var(--bg-secondary); - color: var(--text-primary); - border: 0.0625rem solid var(--border-color); - border-radius: 0.25rem; - cursor: pointer; - font-size: 0.9em; -} - .btn-delete { padding: 0.375rem 0.75rem; background: var(--error); @@ -309,6 +330,23 @@ details[open] > .card-header::before { filter: brightness(0.85); } +.btn-delete-sub { + background: none; + border: none; + color: inherit; + font-size: 1.1em; + font-weight: bold; + cursor: pointer; + padding: 0 0.25rem; + margin-left: 0.25rem; + opacity: 0.7; + transition: opacity 0.2s ease; +} + +.btn-delete-sub:hover { + opacity: 1; +} + /* Loading Spinner */ .loading { color: var(--text-secondary); @@ -401,8 +439,9 @@ details[open] > .integration-header::before { } .integration-status.unlinked { - background: var(--text-secondary); - color: var(--text-on-color); + background: var(--bg-secondary); + color: var(--text-primary); + border: 0.0625rem solid var(--border-color); } .link-instructions { @@ -477,8 +516,9 @@ details[open] > .integration-header::before { } .btn-secondary { - background: var(--text-secondary); - color: var(--text-on-color); + background: var(--bg-secondary); + color: var(--text-primary); + border: 0.0625rem solid var(--border-color); } .btn-danger { @@ -533,3 +573,73 @@ details[open] > .integration-header::before { height: auto; display: block; } + +/* Toast Notifications */ +#toast-container { + position: fixed; + bottom: 1.5rem; + right: 1.5rem; + z-index: 9999; + display: flex; + flex-direction: column; + gap: 0.5rem; + pointer-events: none; +} + +.toast { + background: var(--bg-secondary); + color: var(--text-primary); + padding: 0.875rem 1.25rem; + border-radius: 0.375rem; + box-shadow: + 0 0.25rem 0.5rem rgba(0, 0, 0, 0.1), + 0 0 0 0.0625rem var(--border-color); + min-width: 15rem; + max-width: 25rem; + pointer-events: auto; + animation: toastSlideIn 0.3s ease; + display: flex; + align-items: center; + gap: 0.75rem; +} + +.toast.success { + background: var(--success); + color: var(--text-on-color); +} + +.toast.error { + background: var(--error); + color: var(--text-on-color); +} + +.toast.info { + background: var(--accent); + color: var(--text-on-color); +} + +.toast.hiding { + animation: toastSlideOut 0.3s ease forwards; +} + +@keyframes toastSlideIn { + from { + transform: translateX(calc(100% + 1.5rem)); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes toastSlideOut { + from { + transform: translateX(0); + opacity: 1; + } + to { + transform: translateX(calc(100% + 1.5rem)); + opacity: 0; + } +} diff --git a/public/index.html b/public/index.html index 40c1e34..e59a004 100644 --- a/public/index.html +++ b/public/index.html @@ -10,12 +10,13 @@ +