Compare commits

..

2 Commits

Author SHA1 Message Date
d1184bf939 removed unused import 2024-11-15 14:22:59 +01:00
a39b526d64 editable templates 2024-11-15 11:29:13 +01:00
13 changed files with 1125 additions and 754 deletions

View File

@@ -1,7 +1,3 @@
> [!NOTE]
> A poll is up to decide the future of yt-dlp-web-ui frontend! If you're interested you can take part.
> https://github.com/marcopeocchi/yt-dlp-web-ui/discussions/223
# yt-dlp Web UI # yt-dlp Web UI
A not so terrible web ui for yt-dlp. A not so terrible web ui for yt-dlp.
@@ -155,8 +151,6 @@ Usage yt-dlp-webui:
session file path (default ".") session file path (default ".")
-user string -user string
Username required for auth Username required for auth
-web string
frontend web resources path
``` ```
### Config file ### Config file
@@ -186,7 +180,7 @@ password: my_random_secret
queue_size: 4 # min. 2 queue_size: 4 # min. 2
# [optional] Full path to the yt-dlp (default: "yt-dlp") # [optional] Full path to the yt-dlp (default: "yt-dlp")
#downloaderPath: /usr/local/bin/yt-dlp downloaderPath: /usr/local/bin/yt-dlp
# [optional] Enable file based logging with rotation (default: false) # [optional] Enable file based logging with rotation (default: false)
#enable_file_logging: false #enable_file_logging: false
@@ -199,9 +193,6 @@ queue_size: 4 # min. 2
# [optional] Path where the sqlite database will be created/opened (default: "./local.db") # [optional] Path where the sqlite database will be created/opened (default: "./local.db")
#local_database_path #local_database_path
# [optional] Path where a custom frontend will be loaded (instead of the embedded one)
#frontend_path: ./web/solid-frontend
``` ```
### Systemd integration ### Systemd integration
@@ -267,22 +258,6 @@ It is **planned** to also expose a **gRPC** server.
For more information open an issue on GitHub and I will provide more info ASAP. For more information open an issue on GitHub and I will provide more info ASAP.
## Custom frontend
To load a custom frontend you need to specify its path either in the config file ([see config file](#config-file)) or via flags.
The frontend needs to follow this structure:
```
path/to/my/frontend
├── assets
│ ├── js-chunk-1.js (example)
│ ├── js-chunk-2.js (example)
│ ├── style.css (example)
└── index.html
```
`assets` is where the resources will be loaded.
`index.html` is the entrypoint.
## Nix ## Nix
This repo adds support for Nix(OS) in various ways through a `flake-parts` flake. This repo adds support for Nix(OS) in various ways through a `flake-parts` flake.
For more info, please refer to the [official documentation](https://nixos.org/learn/). For more info, please refer to the [official documentation](https://nixos.org/learn/).

View File

@@ -1,27 +0,0 @@
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://app:3033;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
client_max_body_size 20000m;
proxy_connect_timeout 5000;
proxy_send_timeout 5000;
proxy_read_timeout 5000;
send_timeout 5000;
}
}

View File

@@ -1,15 +0,0 @@
services:
app:
image: marcobaobao/yt-dlp-webui
volumes:
- ./downloads:/downloads
restart: unless-stopped
nginx:
image: nginx:alpine
restart: unless-stopped
volumes:
- ./app.conf:/etc/nginx/conf.d/app.conf
depends_on:
- app
ports:
- 80:80

View File

@@ -1,6 +1,6 @@
{ {
"name": "yt-dlp-webui", "name": "yt-dlp-webui",
"version": "3.2.3", "version": "3.2.2",
"description": "Frontend compontent of yt-dlp-webui", "description": "Frontend compontent of yt-dlp-webui",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
@@ -10,29 +10,30 @@
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.11.5",
"@fontsource/roboto": "^5.0.13", "@fontsource/roboto": "^5.0.13",
"@fontsource/roboto-mono": "^5.0.18", "@fontsource/roboto-mono": "^5.0.18",
"@mui/icons-material": "^6.2.0", "@mui/icons-material": "^5.15.16",
"@mui/material": "^6.2.0", "@mui/material": "^5.15.16",
"fp-ts": "^2.16.5", "fp-ts": "^2.16.5",
"react": "^19.0.0", "react": "^18.3.1",
"react-dom": "^19.0.0", "react-dom": "^18.3.1",
"react-router-dom": "^6.23.1", "react-router-dom": "^6.23.1",
"react-virtuoso": "^4.7.11", "react-virtuoso": "^4.7.11",
"jotai": "^2.10.3", "jotai": "^2.10.2",
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },
"devDependencies": { "devDependencies": {
"@modyfi/vite-plugin-yaml": "^1.1.0", "@modyfi/vite-plugin-yaml": "^1.1.0",
"@types/node": "^20.14.2", "@types/node": "^20.14.2",
"@types/react": "^19.0.1", "@types/react": "^18.3.3",
"@types/react-dom": "^19.0.2", "@types/react-dom": "^18.2.18",
"@types/react-helmet": "^6.1.11", "@types/react-helmet": "^6.1.11",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-react-swc": "^3.7.2", "@vitejs/plugin-react-swc": "^3.7.0",
"typescript": "^5.7.2", "million": "^3.1.11",
"vite": "^6.0.3" "typescript": "^5.4.5",
"vite": "^5.2.11"
} }
} }

1666
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,8 +22,8 @@ export const filenameTemplateState = atomWithStorage(
localStorage.getItem('lastFilenameTemplate') ?? '' localStorage.getItem('lastFilenameTemplate') ?? ''
) )
export const downloadTemplateState = atom<Promise<string>>(async (get) => export const downloadTemplateState = atom<string>((get) =>
`${get(customArgsState)} ${await get(cookiesTemplateState)}` `${get(customArgsState)} ${get(cookiesTemplateState)}`
.replace(/ +/g, ' ') .replace(/ +/g, ' ')
.trim() .trim()
) )

View File

@@ -2,6 +2,7 @@ import { FileUpload } from '@mui/icons-material'
import CloseIcon from '@mui/icons-material/Close' import CloseIcon from '@mui/icons-material/Close'
import { import {
Autocomplete, Autocomplete,
Backdrop,
Box, Box,
Button, Button,
Checkbox, Checkbox,
@@ -20,7 +21,6 @@ import Slide from '@mui/material/Slide'
import Toolbar from '@mui/material/Toolbar' import Toolbar from '@mui/material/Toolbar'
import Typography from '@mui/material/Typography' import Typography from '@mui/material/Typography'
import { TransitionProps } from '@mui/material/transitions' import { TransitionProps } from '@mui/material/transitions'
import { useAtom, useAtomValue } from 'jotai'
import { import {
FC, FC,
Suspense, Suspense,
@@ -30,22 +30,18 @@ import {
useState, useState,
useTransition useTransition
} from 'react' } from 'react'
import { import { customArgsState, downloadTemplateState, filenameTemplateState, savedTemplatesState } from '../atoms/downloadTemplate'
customArgsState,
downloadTemplateState,
filenameTemplateState,
savedTemplatesState
} from '../atoms/downloadTemplate'
import { settingsState } from '../atoms/settings' import { settingsState } from '../atoms/settings'
import { availableDownloadPathsState, connectedState } from '../atoms/status' import { availableDownloadPathsState, connectedState } from '../atoms/status'
import FormatsGrid from '../components/FormatsGrid' import FormatsGrid from '../components/FormatsGrid'
import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC' import { useRPC } from '../hooks/useRPC'
import type { DLMetadata } from '../types' import type { DLMetadata } from '../types'
import { toFormatArgs } from '../utils' import { toFormatArgs } from '../utils'
import ExtraDownloadOptions from './ExtraDownloadOptions' import ExtraDownloadOptions from './ExtraDownloadOptions'
import { useToast } from '../hooks/toast'
import LoadingBackdrop from './LoadingBackdrop' import LoadingBackdrop from './LoadingBackdrop'
import { useAtom, useAtomValue } from 'jotai'
const Transition = forwardRef(function Transition( const Transition = forwardRef(function Transition(
props: TransitionProps & { props: TransitionProps & {

View File

@@ -1,33 +0,0 @@
import { Button, CircularProgress } from '@mui/material'
import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import { useState } from 'react'
import { useToast } from '../hooks/toast'
const UpdateBinaryButton: React.FC = () => {
const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast()
const [isLoading, setIsLoading] = useState(false)
const updateBinary = () => {
setIsLoading(true)
client
.updateExecutable()
.then(() => pushMessage(i18n.t('toastUpdated'), 'success'))
.then(() => setIsLoading(false))
}
return (
<Button
variant="contained"
endIcon={isLoading ? <CircularProgress size={16} color='secondary' /> : <></>}
onClick={updateBinary}
>
{i18n.t('updateBinButton')}
</Button>
)
}
export default UpdateBinaryButton

View File

@@ -191,11 +191,4 @@ export class RPCClient {
params: [] params: []
}) })
} }
public updateExecutable() {
return this.sendHTTP({
method: 'Service.UpdateExecutable',
params: []
})
}
} }

View File

@@ -1,4 +1,5 @@
import { import {
Button,
Checkbox, Checkbox,
Container, Container,
FormControl, FormControl,
@@ -17,7 +18,6 @@ import {
Typography, Typography,
capitalize capitalize
} from '@mui/material' } from '@mui/material'
import { useAtom } from 'jotai'
import { Suspense, useEffect, useMemo, useState } from 'react' import { Suspense, useEffect, useMemo, useState } from 'react'
import { import {
Subject, Subject,
@@ -44,10 +44,11 @@ import {
themeState themeState
} from '../atoms/settings' } from '../atoms/settings'
import CookiesTextField from '../components/CookiesTextField' import CookiesTextField from '../components/CookiesTextField'
import UpdateBinaryButton from '../components/UpdateBinaryButton'
import { useToast } from '../hooks/toast' import { useToast } from '../hooks/toast'
import { useI18n } from '../hooks/useI18n' import { useI18n } from '../hooks/useI18n'
import { useRPC } from '../hooks/useRPC'
import { validateDomain, validateIP } from '../utils' import { validateDomain, validateIP } from '../utils'
import { useAtom } from 'jotai'
// NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS // NEED ABSOLUTELY TO BE SPLIT IN MULTIPLE COMPONENTS
export default function Settings() { export default function Settings() {
@@ -71,6 +72,7 @@ export default function Settings() {
const [invalidIP, setInvalidIP] = useState(false) const [invalidIP, setInvalidIP] = useState(false)
const { i18n } = useI18n() const { i18n } = useI18n()
const { client } = useRPC()
const { pushMessage } = useToast() const { pushMessage } = useToast()
@@ -138,6 +140,13 @@ export default function Settings() {
setTheme(event.target.value as Theme) setTheme(event.target.value as Theme)
} }
/**
* Updates yt-dlp binary via RPC
*/
const updateBinary = () => {
client.updateExecutable().then(() => pushMessage(i18n.t('toastUpdated'), 'success'))
}
return ( return (
<Container maxWidth="xl" sx={{ mt: 4, mb: 8 }}> <Container maxWidth="xl" sx={{ mt: 4, mb: 8 }}>
<Paper <Paper
@@ -343,8 +352,14 @@ export default function Settings() {
</Suspense> </Suspense>
</Grid> </Grid>
<Grid> <Grid>
<Stack direction="row" sx={{ pt: 2 }}> <Stack direction="row">
<UpdateBinaryButton /> <Button
sx={{ mr: 1, mt: 3 }}
variant="contained"
onClick={() => updateBinary()}
>
{i18n.t('updateBinButton')}
</Button>
</Stack> </Stack>
</Grid> </Grid>
</Paper> </Paper>

View File

@@ -1,10 +1,12 @@
import react from '@vitejs/plugin-react-swc' import react from '@vitejs/plugin-react-swc'
import million from 'million/compiler'
import ViteYaml from '@modyfi/vite-plugin-yaml' import ViteYaml from '@modyfi/vite-plugin-yaml'
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
export default defineConfig(() => { export default defineConfig(() => {
return { return {
plugins: [ plugins: [
million.vite({ auto: true }),
react(), react(),
ViteYaml(), ViteYaml(),
], ],

View File

@@ -23,7 +23,6 @@ var (
downloaderPath string downloaderPath string
sessionFilePath string sessionFilePath string
localDatabasePath string localDatabasePath string
frontendPath string
requireAuth bool requireAuth bool
username string username string
@@ -53,7 +52,6 @@ func init() {
flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path") flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path")
flag.StringVar(&sessionFilePath, "session", ".", "session file path") flag.StringVar(&sessionFilePath, "session", ".", "session file path")
flag.StringVar(&localDatabasePath, "db", "local.db", "local database path") flag.StringVar(&localDatabasePath, "db", "local.db", "local database path")
flag.StringVar(&frontendPath, "web", "", "frontend web resources path")
flag.BoolVar(&enableFileLogging, "fl", false, "enable outputting logs to a file") flag.BoolVar(&enableFileLogging, "fl", false, "enable outputting logs to a file")
flag.StringVar(&logFile, "lf", "yt-dlp-webui.log", "set log file location") flag.StringVar(&logFile, "lf", "yt-dlp-webui.log", "set log file location")
@@ -71,10 +69,6 @@ func main() {
log.Fatalln(err) log.Fatalln(err)
} }
if frontendPath != "" {
frontend = os.DirFS(frontendPath)
}
c := config.Instance() c := config.Instance()
{ {

View File

@@ -22,13 +22,13 @@ type Config struct {
QueueSize int `yaml:"queue_size"` QueueSize int `yaml:"queue_size"`
LocalDatabasePath string `yaml:"local_database_path"` LocalDatabasePath string `yaml:"local_database_path"`
SessionFilePath string `yaml:"session_file_path"` SessionFilePath string `yaml:"session_file_path"`
path string // private path string
UseOpenId bool `yaml:"use_openid"` UseOpenId bool `yaml:"use_openid"`
OpenIdProviderURL string `yaml:"openid_provider_url"` OpenIdProviderURL string `yaml:"openid_provider_url"`
OpenIdClientId string `yaml:"openid_client_id"` OpenIdClientId string `yaml:"openid_client_id"`
OpenIdClientSecret string `yaml:"openid_client_secret"` OpenIdClientSecret string `yaml:"openid_client_secret"`
OpenIdRedirectURL string `yaml:"openid_redirect_url"` OpenIdRedirectURL string `yaml:"openid_redirect_url"`
FrontendPath string `yaml:"frontend_path"`
} }
var ( var (