2 Commits

Author SHA1 Message Date
57a4883931 Seed branding logo from bundled assets 2026-01-25 18:01:54 +13:00
6ba41b854b Tidy request sync controls 2026-01-25 17:52:33 +13:00
6 changed files with 41 additions and 10 deletions

View File

@@ -1 +1 @@
251260445 251260501

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -11,6 +11,9 @@ router = APIRouter(prefix="/branding", tags=["branding"])
_BRANDING_DIR = os.path.join(os.getcwd(), "data", "branding") _BRANDING_DIR = os.path.join(os.getcwd(), "data", "branding")
_LOGO_PATH = os.path.join(_BRANDING_DIR, "logo.png") _LOGO_PATH = os.path.join(_BRANDING_DIR, "logo.png")
_FAVICON_PATH = os.path.join(_BRANDING_DIR, "favicon.ico") _FAVICON_PATH = os.path.join(_BRANDING_DIR, "favicon.ico")
_BUNDLED_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "assets", "branding"))
_BUNDLED_LOGO_PATH = os.path.join(_BUNDLED_DIR, "logo.png")
_BUNDLED_FAVICON_PATH = os.path.join(_BUNDLED_DIR, "favicon.ico")
def _ensure_branding_dir() -> None: def _ensure_branding_dir() -> None:
@@ -41,6 +44,18 @@ def _ensure_default_branding() -> None:
if os.path.exists(_LOGO_PATH) and os.path.exists(_FAVICON_PATH): if os.path.exists(_LOGO_PATH) and os.path.exists(_FAVICON_PATH):
return return
_ensure_branding_dir() _ensure_branding_dir()
if not os.path.exists(_LOGO_PATH) and os.path.exists(_BUNDLED_LOGO_PATH):
try:
with open(_BUNDLED_LOGO_PATH, "rb") as source, open(_LOGO_PATH, "wb") as target:
target.write(source.read())
except OSError:
pass
if not os.path.exists(_FAVICON_PATH) and os.path.exists(_BUNDLED_FAVICON_PATH):
try:
with open(_BUNDLED_FAVICON_PATH, "rb") as source, open(_FAVICON_PATH, "wb") as target:
target.write(source.read())
except OSError:
pass
if not os.path.exists(_LOGO_PATH): if not os.path.exists(_LOGO_PATH):
image = Image.new("RGBA", (300, 300), (12, 18, 28, 255)) image = Image.new("RGBA", (300, 300), (12, 18, 28, 255))
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)

View File

@@ -714,7 +714,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
.map((sectionGroup) => ( .map((sectionGroup) => (
<section key={sectionGroup.key} className="admin-section"> <section key={sectionGroup.key} className="admin-section">
<div className="section-header"> <div className="section-header">
<h2>{sectionGroup.title}</h2> <h2>{sectionGroup.key === 'requests' ? 'Sync controls' : sectionGroup.title}</h2>
{sectionGroup.key === 'sonarr' && ( {sectionGroup.key === 'sonarr' && (
<button type="button" onClick={() => loadOptions('sonarr')}> <button type="button" onClick={() => loadOptions('sonarr')}>
Refresh Sonarr options Refresh Sonarr options
@@ -737,17 +737,22 @@ export default function SettingsPage({ section }: SettingsPageProps) {
</button> </button>
) : null} ) : null}
{showRequestsExtras && sectionGroup.key === 'requests' && ( {showRequestsExtras && sectionGroup.key === 'requests' && (
<div className="sync-actions-block">
<div className="sync-actions"> <div className="sync-actions">
<button type="button" onClick={syncRequests}> <button type="button" onClick={syncRequests}>
Full refresh Full refresh (all requests)
</button> </button>
<button type="button" className="ghost-button" onClick={syncRequestsDelta}> <button type="button" className="ghost-button" onClick={syncRequestsDelta}>
Quick refresh (new changes) Quick refresh (delta changes)
</button> </button>
</div> </div>
<div className="meta sync-note">
Full refresh reloads the entire list. Quick refresh only checks recent changes.
</div>
</div>
)} )}
</div> </div>
{SECTION_DESCRIPTIONS[sectionGroup.key] && ( {SECTION_DESCRIPTIONS[sectionGroup.key] && !settingsSection && (
<p className="section-subtitle">{SECTION_DESCRIPTIONS[sectionGroup.key]}</p> <p className="section-subtitle">{SECTION_DESCRIPTIONS[sectionGroup.key]}</p>
)} )}
{sectionGroup.key === 'sonarr' && sonarrError && ( {sectionGroup.key === 'sonarr' && sonarrError && (

View File

@@ -1047,6 +1047,17 @@ button span {
flex-wrap: wrap; flex-wrap: wrap;
} }
.sync-actions-block {
display: grid;
gap: 6px;
justify-items: end;
text-align: right;
}
.sync-note {
margin-top: 0;
}
.section-header button { .section-header button {
background: rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.08);
color: var(--ink); color: var(--ink);