Skip to content

Commit 586c60a

Browse files
authored
Merge pull request #418 from DialmasterOrg/dev
Release
2 parents 247f063 + 6a5e10a commit 586c60a

File tree

25 files changed

+5466
-9608
lines changed

25 files changed

+5466
-9608
lines changed

.github/workflows/claude.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Claude Code Review
2+
3+
permissions:
4+
contents: write
5+
pull-requests: write
6+
issues: write
7+
id-token: write
8+
9+
on:
10+
# Automatic review when PRs target dev branch
11+
pull_request:
12+
types: [opened, synchronize]
13+
branches:
14+
- dev
15+
16+
# Manual @claude triggers in comments
17+
issue_comment:
18+
types: [created]
19+
pull_request_review_comment:
20+
types: [created]
21+
22+
jobs:
23+
# Automatic review for PRs targeting dev
24+
auto-review:
25+
if: github.event_name == 'pull_request'
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- uses: anthropics/claude-code-action@v1
31+
with:
32+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
33+
prompt: "/review"
34+
claude_args: "--max-turns 5"
35+
36+
# Manual @claude responses - restricted to authorized users
37+
manual-claude:
38+
if: |
39+
(github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
40+
contains(github.event.comment.body, '@claude') &&
41+
(
42+
github.event.comment.author_association == 'OWNER' ||
43+
github.event.comment.author_association == 'MEMBER' ||
44+
github.event.comment.author_association == 'COLLABORATOR'
45+
)
46+
runs-on: ubuntu-latest
47+
steps:
48+
- uses: actions/checkout@v4
49+
50+
- uses: anthropics/claude-code-action@v1
51+
with:
52+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
53+
claude_args: "--max-turns 10"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,6 @@ docker-compose.override.*
5151
# Default video download directory and README, all other files excluded
5252
downloads/*
5353
!downloads/README.md
54+
55+
# Backup archive location
56+
backups/

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ Or remove/comment out the `YOUTARR_IMAGE` line in your `.env` file to use the de
109109
- [External Database](docs/platforms/external-db.md) - Using existing MariaDB/MySQL
110110

111111
### Advanced Topics
112+
- [Backup & Restore](docs/BACKUP_RESTORE.md) - Backup your configuration and database, restore to new systems
112113
- [Database Management](docs/DATABASE.md) - Database configuration and maintenance
113114
- [Docker Configuration](docs/DOCKER.md) - Advanced Docker settings
114115
- [Development Guide](docs/DEVELOPMENT.md) - Contributing and development setup

client/package-lock.json

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,10 @@
6262
"src/**/*.{ts,tsx,js,jsx}",
6363
"!src/types/**"
6464
]
65+
},
66+
"overrides": {
67+
"jsdom": {
68+
"form-data": "^3.0.4"
69+
}
6570
}
6671
}

client/src/App.tsx

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import './App.css';
22
import packageJson from '../package.json';
3-
import React, { useState, useEffect, useRef, useMemo } from 'react';
3+
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
44
import toplogo from './Youtarr_text.png';
55
import axios from 'axios';
66
import {
@@ -34,6 +34,7 @@ import { ThemeProvider } from '@mui/material/styles';
3434
import CloseIcon from '@mui/icons-material/Close';
3535
import MenuIcon from '@mui/icons-material/Menu';
3636
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
37+
import Tooltip from '@mui/material/Tooltip';
3738
import SettingsIcon from '@mui/icons-material/Settings';
3839
import SubscriptionsIcon from '@mui/icons-material/Subscriptions';
3940
import DownloadIcon from '@mui/icons-material/Download';
@@ -55,6 +56,7 @@ import { useConfig } from './hooks/useConfig';
5556
import ErrorBoundary from './components/ErrorBoundary';
5657
import DatabaseErrorOverlay from './components/DatabaseErrorOverlay';
5758
import { lightTheme, darkTheme } from './theme';
59+
import { YTDLP_UPDATED_EVENT } from './components/Configuration/hooks/useYtDlpUpdate';
5860

5961
// Event name for database error detection
6062
const DB_ERROR_EVENT = 'db-error-detected';
@@ -76,6 +78,8 @@ function AppContent() {
7678
const [dbErrors, setDbErrors] = useState<string[]>([]);
7779
const [dbRecovered, setDbRecovered] = useState(false);
7880
const [countdown, setCountdown] = useState(15);
81+
const [ytDlpUpdateAvailable, setYtDlpUpdateAvailable] = useState(false);
82+
const [ytDlpLatestVersion, setYtDlpLatestVersion] = useState('');
7983
const location = useLocation();
8084
const theme = useTheme();
8185
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
@@ -359,6 +363,53 @@ function AppContent() {
359363
});
360364
}, []);
361365

366+
const fetchYtDlpVersionInfo = useCallback(() => {
367+
if (!token) {
368+
setYtDlpUpdateAvailable(false);
369+
setYtDlpLatestVersion('');
370+
return;
371+
}
372+
373+
fetch('/api/ytdlp/latest-version', {
374+
headers: { 'x-access-token': token },
375+
})
376+
.then((response) => {
377+
if (response.ok) {
378+
return response.json();
379+
}
380+
throw new Error('Failed to fetch yt-dlp version');
381+
})
382+
.then((data) => {
383+
setYtDlpUpdateAvailable(data.updateAvailable || false);
384+
setYtDlpLatestVersion(data.latestVersion || '');
385+
if (data.currentVersion) {
386+
setYtDlpVersion(data.currentVersion);
387+
}
388+
})
389+
.catch((err) => {
390+
console.error('Failed to fetch yt-dlp version info:', err);
391+
setYtDlpUpdateAvailable(false);
392+
setYtDlpLatestVersion('');
393+
});
394+
}, [token]);
395+
396+
useEffect(() => {
397+
fetchYtDlpVersionInfo();
398+
}, [fetchYtDlpVersionInfo]);
399+
400+
// Listen for yt-dlp update events to refresh version display
401+
useEffect(() => {
402+
const handleYtDlpUpdated = () => {
403+
fetchYtDlpVersionInfo();
404+
};
405+
406+
window.addEventListener(YTDLP_UPDATED_EVENT, handleYtDlpUpdated);
407+
408+
return () => {
409+
window.removeEventListener(YTDLP_UPDATED_EVENT, handleYtDlpUpdated);
410+
};
411+
}, [fetchYtDlpVersionInfo]);
412+
362413
return (
363414
<ThemeProvider theme={selectedTheme}>
364415
<CssBaseline />
@@ -450,13 +501,28 @@ function AppContent() {
450501
{clientVersion}
451502
</Typography>
452503
{ytDlpVersion && (
453-
<Typography
454-
fontSize='x-small'
455-
color={'textSecondary'}
456-
style={{ opacity: 0.7 }}
457-
>
458-
yt-dlp: {ytDlpVersion}
459-
</Typography>
504+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
505+
{ytDlpUpdateAvailable && (
506+
<Tooltip title={`yt-dlp update available (${ytDlpLatestVersion}). Go to Configuration to update.`}>
507+
<IconButton
508+
component={Link}
509+
to="/configuration"
510+
size="small"
511+
aria-label={`yt-dlp update available (${ytDlpLatestVersion}). Click to go to Configuration.`}
512+
sx={{ p: 0.25 }}
513+
>
514+
<WarningAmberIcon sx={{ fontSize: 14, color: 'warning.main' }} />
515+
</IconButton>
516+
</Tooltip>
517+
)}
518+
<Typography
519+
fontSize='x-small'
520+
color={'textSecondary'}
521+
style={{ opacity: 0.7 }}
522+
>
523+
yt-dlp: {ytDlpVersion}
524+
</Typography>
525+
</Box>
460526
)}
461527
</Box>
462528
{/* This is the matching invisible IconButton */}

client/src/components/Configuration.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
usePlexConnection,
2828
useConfigSave,
2929
} from './Configuration/hooks';
30+
import { useYtDlpUpdate } from './Configuration/hooks/useYtDlpUpdate';
3031
import { useStorageStatus } from '../hooks/useStorageStatus';
3132
import { useConfig } from '../hooks/useConfig';
3233
import { TRACKABLE_CONFIG_KEYS } from '../config/configSchema';
@@ -129,6 +130,34 @@ function Configuration({ token }: ConfigurationProps) {
129130
checkPlexConnection,
130131
});
131132

133+
const {
134+
versionInfo: ytDlpVersionInfo,
135+
updateStatus: ytDlpUpdateStatus,
136+
errorMessage: ytDlpErrorMessage,
137+
successMessage: ytDlpSuccessMessage,
138+
performUpdate: performYtDlpUpdate,
139+
clearMessages: clearYtDlpMessages,
140+
} = useYtDlpUpdate(token);
141+
142+
// Show yt-dlp update snackbar messages
143+
React.useEffect(() => {
144+
if (ytDlpErrorMessage) {
145+
setSnackbar({
146+
open: true,
147+
message: ytDlpErrorMessage,
148+
severity: 'error',
149+
});
150+
clearYtDlpMessages();
151+
} else if (ytDlpSuccessMessage) {
152+
setSnackbar({
153+
open: true,
154+
message: ytDlpSuccessMessage,
155+
severity: 'success',
156+
});
157+
clearYtDlpMessages();
158+
}
159+
}, [ytDlpErrorMessage, ytDlpSuccessMessage, clearYtDlpMessages]);
160+
132161
const handleOpenConfirmDialog = () => {
133162
setOpenConfirmDialog(true);
134163
};
@@ -200,6 +229,9 @@ function Configuration({ token }: ConfigurationProps) {
200229
onConfigChange={handleConfigChange}
201230
onMobileTooltipClick={setMobileTooltip}
202231
token={token}
232+
ytDlpVersionInfo={ytDlpVersionInfo}
233+
ytDlpUpdateStatus={ytDlpUpdateStatus}
234+
onYtDlpUpdate={performYtDlpUpdate}
203235
/>
204236

205237
<PlexIntegrationSection

0 commit comments

Comments
 (0)