Compare commits

...

5 Commits

Author SHA1 Message Date
Daniel Volz 318f63657b docs: add Pushover and improve push notification documentation (#27)
- Add Pushover to supported services list in UI
- Add Gotify to supported services list
- Add URL placeholder with examples (ntfy, pushover)
- Add link to shoutrrr.dev for all available services
- Change input type from 'url' to 'text' (shoutrrr URLs aren't HTTP URLs)
- Add comprehensive Push Notifications section to README
- Include URL examples for ntfy, Pushover, Gotify, Discord, Telegram

Closes feature request for Pushover support.
2026-01-16 21:05:40 +01:00
Daniel Volz 718157e472 fix: clean up Export/Import UI (#26)
- Fix tooltip visibility (overflow: visible for export card)
- Remove unnecessary 'Include sensitive data' checkbox
- Always export all data including notification URLs
- Remove unused CSS styles for checkbox and warning
2026-01-16 20:50:29 +01:00
Daniel Volz f00f11aa55 docs: add rule to never release without explicit permission (#25) 2026-01-16 20:35:25 +01:00
Daniel Volz 4081e03970 fix(ui): improve Export/Import section layout (#24)
* fix(ui): improve Export/Import section layout and styling

- Redesign as two-column card layout with icons
- Remove CAPSLOCK from labels
- Add proper descriptions for export and import sections
- Improve checkbox and button styling
- Make responsive for mobile

* fix(ui): clean up Export/Import section design

- Remove ugly folder icons
- Replace hint text box with info tooltip on title
- Cleaner h3 styling with uppercase letters
- Better visual hierarchy
2026-01-16 20:32:39 +01:00
Daniel Volz 9cfbf89d46 fix: correct release workflow to find previous tag (#23) 2026-01-16 20:06:29 +01:00
8 changed files with 187 additions and 58 deletions
+1
View File
@@ -3,6 +3,7 @@
## General Rules
- **English is the primary language**: All code, comments, documentation, commit messages, PR descriptions, and GitHub releases MUST be written in English. The user may communicate in German, but all project artifacts must be in English.
- **NEVER release without explicit permission**: Do NOT create tags, releases, or version bumps unless the user explicitly asks for it. Always wait for explicit confirmation before any release action.
- **No temporary files**: Delete temporary scripts/files immediately after use. Do not commit temporary debug scripts, test files, or one-off utilities to the repository.
- **Clean workspace**: Always clean up after yourself. If you create a file for a specific task, delete it once done.
+17 -3
View File
@@ -16,11 +16,25 @@ jobs:
with:
fetch-depth: 0
- name: Get previous tag
id: prev_tag
run: |
# Get all tags sorted by version, find the one before current
CURRENT_TAG=${GITHUB_REF#refs/tags/}
PREV_TAG=$(git tag --sort=-v:refname | grep -A1 "^${CURRENT_TAG}$" | tail -1)
# If no previous tag found (first release), use empty
if [ "$PREV_TAG" = "$CURRENT_TAG" ]; then
PREV_TAG=""
fi
echo "previous_tag=$PREV_TAG" >> $GITHUB_OUTPUT
echo "Current tag: $CURRENT_TAG, Previous tag: $PREV_TAG"
- name: Generate changelog
id: changelog
run: |
# Get previous tag
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
PREV_TAG="${{ steps.prev_tag.outputs.previous_tag }}"
if [ -z "$PREV_TAG" ]; then
# First release - get all commits
@@ -37,6 +51,6 @@ jobs:
uses: softprops/action-gh-release@v1
with:
body_path: changelog.txt
generate_release_notes: true
generate_release_notes: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+49 -1
View File
@@ -62,7 +62,7 @@
### Notifications
- Email via SMTP
- Push notifications via ntfy, Gotify, Telegram, Discord (Shoutrrr)
- Push notifications via ntfy, Pushover, Gotify, Telegram, Discord & more ([Shoutrrr](https://containrrr.dev/shoutrrr/))
- Supports both stock warnings and intake reminders
### Privacy & Security
@@ -148,6 +148,54 @@ Generate secrets with: `openssl rand -hex 32`
| `REMINDER_MINUTES_BEFORE` | `15` | Minutes before intake to send reminder |
| `EXPIRY_WARNING_DAYS` | `30` | Days before expiry to show warning |
### Push Notifications (Shoutrrr)
MedAssist uses [Shoutrrr](https://containrrr.dev/shoutrrr/) for push notifications, supporting many services with a single URL format.
**Supported services:** ntfy, Pushover, Gotify, Discord, Telegram, Slack, Matrix, and [many more](https://containrrr.dev/shoutrrr/v0.8/services/overview/).
Configure push notifications in Settings → Push, or set defaults via environment variables:
| Variable | Default | Description |
|----------|---------|-------------|
| `DEFAULT_SHOUTRRR_ENABLED` | `false` | Enable push notifications by default |
| `DEFAULT_SHOUTRRR_URL` | — | Shoutrrr URL (see examples below) |
| `DEFAULT_SHOUTRRR_STOCK_REMINDERS` | `true` | Send stock warnings via push |
| `DEFAULT_SHOUTRRR_INTAKE_REMINDERS` | `true` | Send intake reminders via push |
#### URL Examples
**ntfy** (free, self-hostable):
```
ntfy://ntfy.sh/your-topic
ntfy://user:password@your-server.com/topic
```
**Pushover** (free app for iOS/Android):
```
pushover://shoutrrr:API_TOKEN@USER_KEY/
```
Get your keys at [pushover.net](https://pushover.net/):
- **User Key**: Shown on your dashboard (top right)
- **API Token**: Create an application → copy the API Token
**Gotify** (self-hosted):
```
gotify://your-server.com/TOKEN
```
**Discord**:
```
discord://TOKEN@WEBHOOK_ID
```
**Telegram**:
```
telegram://TOKEN@telegram?chats=CHAT_ID
```
For all services and options, see the [Shoutrrr documentation](https://containrrr.dev/shoutrrr/v0.8/services/overview/).
# Development
```bash
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "medassist-ng-frontend",
"version": "1.0.2",
"version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "medassist-ng-frontend",
"version": "1.0.2",
"version": "1.1.0",
"dependencies": {
"i18next": "^24.2.2",
"i18next-browser-languagedetector": "^8.0.4",
+20 -42
View File
@@ -354,7 +354,7 @@ function AppContent() {
// Export/Import state
const [exporting, setExporting] = useState(false);
const [importing, setImporting] = useState(false);
const [includeSensitiveData, setIncludeSensitiveData] = useState(false);
const [showImportConfirm, setShowImportConfirm] = useState(false);
const [pendingImportData, setPendingImportData] = useState<any>(null);
// Collapsed days state (manually collapsed days are persisted)
@@ -788,7 +788,7 @@ function AppContent() {
async function handleExport() {
setExporting(true);
try {
const res = await fetch(`/api/export?includeSensitive=${includeSensitiveData}`, {
const res = await fetch('/api/export?includeSensitive=true', {
credentials: "include",
});
if (!res.ok) throw new Error("Export failed");
@@ -2243,12 +2243,12 @@ function AppContent() {
<span className="field-label">{t('settings.push.url')}</span>
<div className="input-with-tooltip">
<input
type="url"
type="text"
value={settings.shoutrrrUrl}
onChange={(e) => setSettings({ ...settings, shoutrrrUrl: e.target.value })}
placeholder="https://ntfy.sh/your-topic"
placeholder={t('settings.push.urlPlaceholder')}
/>
<span className="info-tooltip" data-tooltip={t('settings.push.supports')}></span>
<span className="info-tooltip" data-tooltip={`${t('settings.push.supports')}\n\n${t('settings.push.docsLink')}`}></span>
</div>
</label>
</div>
@@ -2412,60 +2412,38 @@ function AppContent() {
{/* Export/Import Section */}
<article className="card">
<div className="card-head">
<h2>{t('exportImport.title')}</h2>
<h2>
{t('exportImport.title')}
<span className="info-tooltip" data-tooltip={t('exportImport.description')}></span>
</h2>
</div>
<div className="setting-section">
<p className="hint-text" style={{marginBottom: "16px"}}>{t('exportImport.description')}</p>
{/* Export */}
<div className="setting-group" style={{marginBottom: "24px"}}>
<div className="export-controls">
<label className="checkbox-label" style={{marginBottom: "12px", display: "flex", alignItems: "center", gap: "8px"}}>
<input
type="checkbox"
checked={includeSensitiveData}
onChange={(e) => setIncludeSensitiveData(e.target.checked)}
/>
<span>{t('exportImport.includeSensitive')}</span>
</label>
{includeSensitiveData && (
<p className="hint-text warning-text" style={{marginBottom: "12px", color: "var(--warning)", fontSize: "0.85rem"}}>
{t('exportImport.sensitiveWarning')}
</p>
)}
<div className="export-import-grid">
{/* Export */}
<div className="export-import-card">
<h3>{t('exportImport.exportTitle')}</h3>
<p className="export-import-desc">{t('exportImport.exportDesc')}</p>
<button
type="button"
className="secondary"
onClick={handleExport}
disabled={exporting}
style={{marginRight: "12px"}}
>
{exporting ? t('exportImport.exporting') : t('exportImport.export')}
</button>
</div>
</div>
{/* Import */}
<div className="setting-group">
<div className="import-controls">
<label className="secondary" style={{
cursor: "pointer",
display: "inline-block",
padding: "0.7rem 1.25rem",
borderRadius: "var(--btn-radius)",
background: "var(--bg-tertiary)",
color: "var(--text-primary)",
border: "1px solid var(--border-secondary)",
fontWeight: 600,
fontSize: "0.9rem"
}}>
{/* Import */}
<div className="export-import-card">
<h3>{t('exportImport.importTitle')}</h3>
<p className="export-import-desc">{t('exportImport.importDesc')}</p>
<label className="export-import-file-btn">
{importing ? t('exportImport.importing') : t('exportImport.import')}
<input
type="file"
accept=".json,application/json"
onChange={handleImportFileSelect}
disabled={importing}
style={{display: "none"}}
/>
</label>
</div>
+11 -5
View File
@@ -173,7 +173,9 @@
},
"push": {
"url": "URL",
"supports": "Unterstützt ntfy, Discord, Telegram, Slack"
"urlPlaceholder": "ntfy://topic oder pushover://:token@userkey/",
"supports": "Unterstützt ntfy, Pushover, Gotify, Discord, Telegram, Slack & mehr",
"docsLink": "Siehe shoutrrr.dev für alle Services"
},
"schedule": {
"title": "Erinnerungsplan",
@@ -351,14 +353,18 @@
},
"exportImport": {
"title": "Daten Export / Import",
"description": "Exportiere deine Daten zur Sicherung oder Übertragung auf ein anderes Gerät. Import ersetzt ALLE deine bestehenden Daten.",
"description": "Sichere deine Daten oder übertrage sie auf ein anderes Gerät.",
"exportTitle": "Export",
"exportDesc": "Lade alle deine Daten als JSON-Datei herunter.",
"importTitle": "Import",
"importDesc": "Stelle Daten aus einer Sicherung wieder her. Dies ersetzt alle bestehenden Daten.",
"export": "Daten exportieren",
"exporting": "Exportiere...",
"import": "Daten importieren",
"import": "Datei auswählen",
"importing": "Importiere...",
"selectFile": "Datei auswählen",
"includeSensitive": "Sensible Daten einschließen",
"sensitiveWarning": "Warnung: Dies fügt Benachrichtigungs-URLs (können Passwörter enthalten) im Klartext in die Exportdatei ein.",
"includeSensitive": "Sensible Daten einschließen (Benachrichtigungs-URLs)",
"sensitiveWarning": "Benachrichtigungs-URLs können Passwörter enthalten und werden im Klartext gespeichert.",
"confirmImport": "Alle Daten ersetzen?",
"confirmImportMessage": "Dies löscht dauerhaft alle deine aktuellen Medikamente, Einnahmehistorie, Einstellungen und Teilen-Links und ersetzt sie durch die importierten Daten.",
"confirmImportWarning": "Diese Aktion kann nicht rückgängig gemacht werden!",
+11 -5
View File
@@ -175,7 +175,9 @@
},
"push": {
"url": "URL",
"supports": "Supports ntfy, Discord, Telegram, Slack"
"urlPlaceholder": "ntfy://topic or pushover://:token@userkey/",
"supports": "Supports ntfy, Pushover, Gotify, Discord, Telegram, Slack & more",
"docsLink": "See shoutrrr.dev for all services"
},
"schedule": {
"title": "Reminder Schedule",
@@ -353,14 +355,18 @@
},
"exportImport": {
"title": "Data Export / Import",
"description": "Export your data for backup or transfer to another device. Import will replace ALL your existing data.",
"description": "Backup your data or transfer it to another device.",
"exportTitle": "Export",
"exportDesc": "Download all your data as a JSON file.",
"importTitle": "Import",
"importDesc": "Restore data from a backup file. This will replace all existing data.",
"export": "Export Data",
"exporting": "Exporting...",
"import": "Import Data",
"import": "Select File",
"importing": "Importing...",
"selectFile": "Select File",
"includeSensitive": "Include sensitive data",
"sensitiveWarning": "Warning: This will include notification URLs (may contain passwords) in plain text in the export file.",
"includeSensitive": "Include sensitive data (notification URLs)",
"sensitiveWarning": "Notification URLs may contain passwords and will be stored in plain text.",
"confirmImport": "Replace All Data?",
"confirmImportMessage": "This will permanently delete all your current medications, dose history, settings, and share links, then replace them with the imported data.",
"confirmImportWarning": "This action cannot be undone!",
+76
View File
@@ -3977,3 +3977,79 @@ h3 .reminder-icon.info-tooltip {
margin-top: 0.5rem;
width: 100%;
}
/* Export/Import Section */
.card:has(.export-import-grid) {
overflow: visible;
}
.card:has(.export-import-grid) .card-head {
overflow: visible;
}
.export-import-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
}
@media (max-width: 640px) {
.export-import-grid {
grid-template-columns: 1fr;
}
}
.export-import-card {
background: var(--bg-tertiary);
border: 1px solid var(--border-secondary);
border-radius: var(--card-radius);
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.export-import-card h3 {
margin: 0;
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.export-import-desc {
color: var(--text-secondary);
font-size: 0.85rem;
margin: 0;
line-height: 1.5;
flex: 1;
}
.export-import-card button,
.export-import-file-btn {
margin-top: auto;
align-self: flex-start;
}
.export-import-file-btn {
cursor: pointer;
display: inline-block;
padding: 0.7rem 1.25rem;
border-radius: var(--btn-radius);
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-secondary);
font-weight: 600;
font-size: 0.9rem;
transition: all 0.2s ease;
}
.export-import-file-btn:hover {
background: var(--bg-hover);
border-color: var(--border-primary);
}
.export-import-file-btn input {
display: none;
}