Compare commits

..

19 Commits

Author SHA1 Message Date
gronod 7f7a91f056 merge branch 'develop' into 'main' - Release v1.7.33
CI / Tests & coverage (push) Successful in 2m53s
Create Release / release (push) Successful in 17s
CI / Security audit (push) Successful in 3m8s
Build and Push Docker Image / build (push) Successful in 1m11s
CI / Swagger Validation & Coverage (push) Successful in 2m23s
2026-05-28 17:42:54 +01:00
gronod 879aee8eea merge branch 'develop' into 'main' - Release v1.7.32 (include markdownlint fix)
Create Release / release (push) Successful in 30s
Build and Push Docker Image / build (push) Successful in 1m24s
CI / Security audit (push) Successful in 2m1s
CI / Swagger Validation & Coverage (push) Successful in 2m16s
CI / Tests & coverage (push) Successful in 2m37s
2026-05-28 16:28:23 +01:00
gronod f8f693e32a merge branch 'develop' into 'main' - Release v1.7.32
Create Release / release (push) Successful in 24s
CI / Security audit (push) Successful in 2m48s
Build and Push Docker Image / build (push) Successful in 2m14s
CI / Swagger Validation & Coverage (push) Successful in 2m54s
CI / Tests & coverage (push) Has been cancelled
2026-05-28 16:25:07 +01:00
gronod c18f5bd26e merge branch 'develop' into 'main' - Release v1.7.31
CI / Tests & coverage (push) Successful in 2m53s
Create Release / release (push) Successful in 29s
Build and Push Docker Image / build (push) Successful in 49s
CI / Security audit (push) Successful in 3m18s
CI / Swagger Validation & Coverage (push) Successful in 1m40s
2026-05-28 08:12:26 +01:00
gronod e726fbe33f merge branch 'develop' into 'main' - Release v1.7.30
Create Release / release (push) Successful in 42s
CI / Security audit (push) Successful in 1m36s
CI / Swagger Validation & Coverage (push) Successful in 3m20s
CI / Tests & coverage (push) Successful in 3m53s
2026-05-28 08:03:41 +01:00
gronod 4107bdf611 merge branch 'develop' into 'main' - Fix CHANGELOG formatting for Release v1.7.30
CI / Security audit (push) Successful in 1m18s
CI / Tests & coverage (push) Successful in 1m55s
CI / Swagger Validation & Coverage (push) Successful in 1m24s
Create Release / release (push) Successful in 14s
2026-05-28 07:02:57 +01:00
gronod 52806d00dc merge branch 'develop' into 'main' - Release v1.7.30
CI / Tests & coverage (push) Successful in 2m9s
Create Release / release (push) Successful in 33s
Build and Push Docker Image / build (push) Successful in 1m3s
CI / Security audit (push) Successful in 2m52s
CI / Swagger Validation & Coverage (push) Successful in 2m4s
2026-05-28 01:39:13 +01:00
gronod dcb77dd27f merge branch 'develop' into 'main' - Release v1.7.29
CI / Security audit (push) Successful in 2m47s
Create Release / release (push) Successful in 40s
CI / Swagger Validation & Coverage (push) Successful in 1m55s
Build and Push Docker Image / build (push) Successful in 1m19s
CI / Tests & coverage (push) Successful in 3m32s
2026-05-27 23:51:12 +01:00
gronod 6c3ffb9b77 merge branch 'develop' into 'main' - Release v1.7.28
Create Release / release (push) Successful in 22s
CI / Swagger Validation & Coverage (push) Successful in 1m52s
Build and Push Docker Image / build (push) Successful in 49s
CI / Security audit (push) Successful in 3m1s
CI / Tests & coverage (push) Successful in 3m38s
2026-05-27 23:26:11 +01:00
gronod 7226404221 merge branch 'develop' into 'main' - Release v1.7.27
Create Release / release (push) Successful in 37s
CI / Swagger Validation & Coverage (push) Successful in 2m5s
CI / Security audit (push) Successful in 2m59s
Build and Push Docker Image / build (push) Successful in 1m39s
CI / Tests & coverage (push) Successful in 3m48s
2026-05-27 23:11:37 +01:00
gronod 0eaa54cf4a merge branch 'develop' into 'main' - Release v1.7.26
Create Release / release (push) Successful in 8s
CI / Security audit (push) Successful in 3m0s
CI / Swagger Validation & Coverage (push) Successful in 1m57s
Build and Push Docker Image / build (push) Successful in 1m26s
CI / Tests & coverage (push) Successful in 3m37s
2026-05-27 22:50:31 +01:00
gronod fd0dc7528d merge branch 'develop' into 'main' - Release v1.7.25
Create Release / release (push) Successful in 23s
CI / Security audit (push) Successful in 1m49s
Build and Push Docker Image / build (push) Successful in 1m50s
CI / Swagger Validation & Coverage (push) Successful in 2m18s
CI / Tests & coverage (push) Successful in 2m30s
2026-05-27 21:43:45 +01:00
gronod c4e584cc3b merge branch 'develop' into 'main' - Release v1.7.24
Create Release / release (push) Successful in 29s
Build and Push Docker Image / build (push) Successful in 57s
CI / Security audit (push) Successful in 3m7s
CI / Swagger Validation & Coverage (push) Successful in 1m49s
CI / Tests & coverage (push) Successful in 3m43s
2026-05-27 19:35:19 +01:00
gronod 610632c4f0 merge branch 'develop' into 'main' - Release v1.7.23
Build and Push Docker Image / build (push) Successful in 1m4s
Create Release / release (push) Successful in 53s
CI / Security audit (push) Successful in 1m42s
CI / Swagger Validation & Coverage (push) Successful in 1m42s
CI / Tests & coverage (push) Successful in 2m21s
2026-05-27 19:16:23 +01:00
gronod 1535a5725a merge branch 'develop' into 'main' - Release v1.7.22
Create Release / release (push) Successful in 29s
Build and Push Docker Image / build (push) Successful in 2m33s
CI / Swagger Validation & Coverage (push) Successful in 1m57s
CI / Security audit (push) Successful in 2m26s
CI / Tests & coverage (push) Successful in 2m55s
2026-05-27 17:44:51 +01:00
gronod 8fb00843ef merge branch 'develop' into 'main' - Release v1.7.21
CI / Security audit (push) Successful in 2m50s
CI / Swagger Validation & Coverage (push) Successful in 3m23s
CI / Tests & coverage (push) Successful in 3m30s
Build and Push Docker Image / build (push) Successful in 46s
Create Release / release (push) Successful in 11s
2026-05-26 15:20:40 +01:00
gronod 6f6aa5b967 merge branch 'develop' into 'main' - Release v1.7.20
Build and Push Docker Image / build (push) Successful in 1m13s
Create Release / release (push) Successful in 1m3s
CI / Security audit (push) Successful in 2m36s
CI / Swagger Validation & Coverage (push) Successful in 3m7s
CI / Tests & coverage (push) Successful in 3m15s
2026-05-26 13:43:33 +01:00
gronod fb68bddedb merge branch 'develop' into 'main' - Update Release v1.7.19 to ignore scratch directory
Build and Push Docker Image / build (push) Successful in 1m8s
Create Release / release (push) Successful in 36s
CI / Tests & coverage (push) Successful in 1m55s
CI / Security audit (push) Successful in 2m12s
CI / Swagger Validation & Coverage (push) Successful in 1m50s
2026-05-25 08:33:16 +01:00
gronod 7d7304637c merge branch 'develop' into 'main' - Release v1.7.19
Create Release / release (push) Successful in 33s
Build and Push Docker Image / build (push) Successful in 2m19s
CI / Security audit (push) Successful in 2m14s
CI / Tests & coverage (push) Successful in 2m21s
CI / Swagger Validation & Coverage (push) Successful in 2m24s
2026-05-25 08:28:28 +01:00
10 changed files with 55 additions and 130 deletions
-10
View File
@@ -4,16 +4,6 @@ All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.7.34] - 2026-05-28
### Fixed
- **Webhook Test Duplicate Error (Issue #71)** — Skipped duplicate/replay protection for `"Test"` event types in Sonarr and Radarr webhook handlers, resolving test button failures. Resolves Gitea Issue [#71](https://git.i3omb.com/Gandalf/sofarr/issues/71).
### Enhanced
- **Unified Tab Header Layout & Typography Consistency (Issue #72)** — Added consistent titles, subtitles, and icons across Active Downloads, Recently Completed, and Requests tab panels, and refactored styling to use a unified flexbox design with CSS variables. Resolves Gitea Issue [#72](https://git.i3omb.com/Gandalf/sofarr/issues/72).
## [1.7.33] - 2026-05-28
### Added
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "sofarr",
"version": "1.7.34",
"version": "1.7.33",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sofarr",
"version": "1.7.34",
"version": "1.7.33",
"license": "MIT",
"dependencies": {
"axios": "^1.6.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "sofarr",
"version": "1.7.34",
"version": "1.7.33",
"description": "A personal media download dashboard that shows your downloads 'so far' while you relax on the sofa waiting for your *arr services to finish",
"main": "server/index.js",
"scripts": {
+6 -18
View File
@@ -170,12 +170,8 @@
<div class="tab-panel" id="tab-downloads">
<div class="downloads-container">
<div class="downloads-header tab-header">
<div class="tab-header-title">
<h2><span class="tab-header-icon">📥</span> Active Downloads</h2>
<p class="tab-header-subtitle">Track and manage your active media downloads in real-time</p>
</div>
<div class="downloads-controls tab-header-controls">
<div class="downloads-header">
<div class="downloads-controls">
<label class="download-client-label" for="download-client-filter">Download client:</label>
<div class="download-client-filter" id="download-client-filter">
<button class="download-client-dropdown-btn" id="download-client-dropdown-btn" type="button" aria-expanded="false">
@@ -204,12 +200,8 @@
<div class="tab-panel hidden" id="tab-requests">
<div class="requests-container">
<div class="requests-header tab-header">
<div class="tab-header-title">
<h2><span class="tab-header-icon">📨</span> Requests</h2>
<p class="tab-header-subtitle">Browse, filter, and track requests synced from Ombi</p>
</div>
<div class="requests-controls tab-header-controls">
<div class="requests-header">
<div class="requests-controls">
<!-- Media Type Filter -->
<div class="request-filter" id="request-type-filter">
<label class="request-filter-label">Type:</label>
@@ -294,12 +286,8 @@
<div class="tab-panel hidden" id="tab-history">
<div class="history-container" id="history-container">
<div class="history-header tab-header">
<div class="tab-header-title">
<h2><span class="tab-header-icon">📜</span> Recently Completed</h2>
<p class="tab-header-subtitle">Review successful imports and troubleshoot failed upgrade attempts</p>
</div>
<div class="history-controls tab-header-controls">
<div class="history-header">
<div class="history-controls">
<label class="history-days-label" for="history-days">Last</label>
<input type="number" id="history-days" class="history-days-input" value="7" min="1" max="90">
<span class="history-days-label">days</span>
+32 -53
View File
@@ -689,61 +689,15 @@ body {
padding: 0;
}
/* Unified Tab Headers (Issue #72) */
.tab-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.tab-header-title {
display: flex;
flex-direction: column;
gap: 4px;
}
.tab-header-title h2 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.tab-header-icon {
font-size: 1.3rem;
display: inline-flex;
align-items: center;
}
.tab-header-subtitle {
margin: 0;
font-size: 0.8rem;
color: var(--text-muted);
}
.tab-header-controls {
/* Downloads header and controls */
.downloads-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.tab-header, .tab-header-title h2, .tab-header-subtitle {
transition: color 0.3s, border-color 0.3s;
}
.downloads-header {
/* Inherits from .tab-header */
}
.downloads-controls {
display: flex;
align-items: center;
@@ -944,7 +898,11 @@ body {
/* ===== Request Filters ===== */
.requests-header {
/* Inherits from .tab-header */
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.requests-controls {
@@ -1118,7 +1076,18 @@ body {
}
.history-header {
/* Inherits from .tab-header */
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.history-header h2 {
margin: 0;
font-size: 1.2rem;
color: var(--text-primary);
flex: 1 1 auto;
}
.history-controls {
@@ -1165,7 +1134,7 @@ body {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 0.85rem;
font-size: 0.82rem;
color: var(--text-secondary);
cursor: pointer;
user-select: none;
@@ -2268,7 +2237,17 @@ body {
}
.requests-header {
/* Inherits from .tab-header */
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.requests-header h2 {
margin: 0;
color: var(--text-primary);
font-size: 1.5rem;
}
.no-requests {
+1 -1
View File
@@ -133,7 +133,7 @@ function createApp({ skipRateLimits = false } = {}) {
* version:
* type: string
* description: sofarr version
* example: "1.7.34"
* example: "1.7.33"
* x-code-samples:
* - lang: curl
* label: cURL
+1 -1
View File
@@ -22,7 +22,7 @@ info:
## SSE Streaming
Real-time updates are available via Server-Sent Events at GET /api/dashboard/stream.
version: 1.7.34
version: 1.7.33
contact:
name: sofarr
license:
+2 -8
View File
@@ -476,10 +476,7 @@ router.post('/sonarr', webhookLimiter, (req, res) => {
// Content-aware replay key components (Issue #62)
const contentId = req.body.downloadId || req.body.series?.id || null;
// Skip replay protection for Test events
if (eventType === "Test") {
logToFile(`[Webhook] Sonarr Test event received — skipping replay protection`);
} else if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) {
if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) {
logToFile(`[Webhook] Sonarr duplicate event ignored: ${eventType} @ ${eventDate} (contentId=${contentId || 'none'})`);
return res.status(200).json({ received: true, duplicate: true });
}
@@ -640,10 +637,7 @@ router.post('/radarr', webhookLimiter, (req, res) => {
// Content-aware replay key components (Issue #62)
const contentId = req.body.downloadId || req.body.movie?.id || null;
// Skip replay protection for Test events
if (eventType === "Test") {
logToFile(`[Webhook] Radarr Test event received — skipping replay protection`);
} else if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) {
if (isReplay(eventType, resolvedInstanceName, eventDate, contentId)) {
logToFile(`[Webhook] Radarr duplicate event ignored: ${eventType} @ ${eventDate} (contentId=${contentId || 'none'})`);
return res.status(200).json({ received: true, duplicate: true });
}
+10 -4
View File
@@ -260,10 +260,19 @@ async function csrfHeaders(app) {
// Environment
// ---------------------------------------------------------------------------
beforeEach(() => {
beforeAll(() => {
process.env.EMBY_URL = EMBY_BASE;
process.env.SONARR_INSTANCES = JSON.stringify([{ id: 'sonarr-1', name: 'Main Sonarr', url: SONARR_BASE, apiKey: 'sk' }]);
process.env.RADARR_INSTANCES = JSON.stringify([{ id: 'radarr-1', name: 'Main Radarr', url: RADARR_BASE, apiKey: 'rk' }]);
});
afterAll(() => {
delete process.env.EMBY_URL;
delete process.env.SONARR_INSTANCES;
delete process.env.RADARR_INSTANCES;
});
beforeEach(() => {
seedEmptyCache();
});
@@ -271,9 +280,6 @@ afterEach(() => {
nock.cleanAll();
invalidatePollCache();
cache.invalidate('emby:users');
delete process.env.EMBY_URL;
delete process.env.SONARR_INSTANCES;
delete process.env.RADARR_INSTANCES;
});
// ---------------------------------------------------------------------------
-32
View File
@@ -387,38 +387,6 @@ describe('Replay protection', () => {
expect(first.body.duplicate).toBeUndefined();
expect(second.body.duplicate).toBeUndefined();
});
it('sonarr: Test events bypass replay protection and are not flagged as duplicates', async () => {
const app = makeApp();
const payload = {
eventType: 'Test',
instanceName: 'Main Sonarr',
date: '2026-05-19T13:00:00.000Z'
};
const first = await postSonarr(app, payload);
expect(first.status).toBe(200);
expect(first.body.duplicate).toBeUndefined();
const second = await postSonarr(app, payload);
expect(second.status).toBe(200);
expect(second.body.duplicate).toBeUndefined();
});
it('radarr: Test events bypass replay protection and are not flagged as duplicates', async () => {
const app = makeApp();
const payload = {
eventType: 'Test',
instanceName: 'Main Radarr',
date: '2026-05-19T13:00:00.000Z'
};
const first = await postRadarr(app, payload);
expect(first.status).toBe(200);
expect(first.body.duplicate).toBeUndefined();
const second = await postRadarr(app, payload);
expect(second.status).toBe(200);
expect(second.body.duplicate).toBeUndefined();
});
});
// ---------------------------------------------------------------------------