Merge pull request #172 from arabcoders/dev

Add Basic mode for WebUI
This commit is contained in:
Abdulmohsen 2025-01-17 15:55:51 +03:00 committed by GitHub
commit 084def9346
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 88 additions and 38 deletions

View file

@ -19,6 +19,7 @@ YTPTube started as a fork of [meTube](https://github.com/alexta69/metube), Since
* Queue multiple URLs separated by comma. * Queue multiple URLs separated by comma.
* Basic Authentication support. * Basic Authentication support.
* Support for curl_cffi, see [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#impersonation) * Support for curl_cffi, see [yt-dlp documentation](https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#impersonation)
* Support for both advanced and basic mode for WebUI.
### Tips ### Tips
Your `yt-dlp` config should include the following options for optimal working conditions. Your `yt-dlp` config should include the following options for optimal working conditions.
@ -94,6 +95,8 @@ Certain values can be set via environment variables, using the `-e` parameter on
* __YTP_UI_UPDATE_TITLE__: Whether to update the title of the page with the current stats. Defaults to `true`. * __YTP_UI_UPDATE_TITLE__: Whether to update the title of the page with the current stats. Defaults to `true`.
* __YTP_PIP_PACKAGES__: a space separated list of pip packages to install. Defaults to `""`, you can also use `{config_path}/pip.txt` to install the packages. * __YTP_PIP_PACKAGES__: a space separated list of pip packages to install. Defaults to `""`, you can also use `{config_path}/pip.txt` to install the packages.
* __YTP_PIP_IGNORE_UPDATES__: Do not update the custom pip packages. Defaults to `false`. * __YTP_PIP_IGNORE_UPDATES__: Do not update the custom pip packages. Defaults to `false`.
* __YTP_BASIC_MODE__: Whether to run WebUI in basic mode. Defaults to `false`. In basic mode, A minimal UI will be shown, the majority of the features will be disabled.
* __YTP_DEFAULT_PRESET__: The default preset to use for the download. Defaults to `default`.
## Running behind a reverse proxy ## Running behind a reverse proxy
@ -341,6 +344,27 @@ As this is a simple basic authentication, if your browser doesn't show the promp
`http://username:password@your_ytptube_url:port` `http://username:password@your_ytptube_url:port`
## Basic mode
What does the basic mode do? it hides the the following features from the WebUI.
### Header
It disables the `Check cookies`, `Console`, `Tasks` and `Add` buttons.
### Add form
Disables everything except the `URL` and `Add` button. the default preset `YTP_DEFAULT_PRESET` will be used. The folder will be
the root download path `YTP_DOWNLOAD_PATH`.
The add form will always be visible and un-collapsible.
### Queue & History
Disables the `Information` button.
# Social contact # Social contact
If you have short or quick questions, you are free to join my [discord server](https://discord.gg/G3GpVR8xpb) and ask If you have short or quick questions, you are free to join my [discord server](https://discord.gg/G3GpVR8xpb) and ask

View file

@ -69,10 +69,14 @@ class Config:
ytdlp_version: str = YTDLP_VERSION ytdlp_version: str = YTDLP_VERSION
started: int = 0 started: int = 0
ignore_ui: bool = False ignore_ui: bool = False
basic_mode: bool = False
presets: list = [ presets: list = [
{"name": "default", "format": "default"}, {"name": "default", "format": "default"},
] ]
default_preset: str = "default"
_manual_vars: tuple = ( _manual_vars: tuple = (
"temp_path", "temp_path",
"config_path", "config_path",
@ -108,6 +112,7 @@ class Config:
"ignore_ui", "ignore_ui",
"ui_update_title", "ui_update_title",
"pip_ignore_updates", "pip_ignore_updates",
"basic_mode",
) )
_frontend_vars: tuple = ( _frontend_vars: tuple = (
@ -122,6 +127,8 @@ class Config:
"remove_files", "remove_files",
"ui_update_title", "ui_update_title",
"max_workers", "max_workers",
"basic_mode",
"default_preset",
) )
@staticmethod @staticmethod
@ -286,6 +293,9 @@ class Config:
if self.auth_password and self.auth_username: if self.auth_password and self.auth_username:
LOG.warning(f"Basic authentication enabled with username '{self.auth_username}'.") LOG.warning(f"Basic authentication enabled with username '{self.auth_username}'.")
if self.basic_mode:
LOG.info("The frontend is running in basic mode.")
self.started = time.time() self.started = time.time()
def _getAttributes(self) -> dict: def _getAttributes(self) -> dict:

View file

@ -203,7 +203,7 @@
</span> </span>
</a> </a>
</div> </div>
<div class="column is-half-mobile" v-if="item.url"> <div class="column is-half-mobile" v-if="item.url && !config.app.basic_mode">
<button class="button is-info is-fullwidth" @click="emitter('getInfo', item.url)"> <button class="button is-info is-fullwidth" @click="emitter('getInfo', item.url)">
<span class="icon-text is-block"> <span class="icon-text is-block">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>

View file

@ -12,7 +12,7 @@
</span> </span>
</div> </div>
</div> </div>
<div class="column is-4-tablet is-12-mobile"> <div class="column is-4-tablet is-12-mobile" v-if="!config.app.basic_mode">
<div class="field has-addons"> <div class="field has-addons">
<div class="control"> <div class="control">
<a href="#" class="button is-static">Preset</a> <a href="#" class="button is-static">Preset</a>
@ -28,7 +28,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="column is-6-tablet is-12-mobile"> <div class="column is-6-tablet is-12-mobile" v-if="!config.app.basic_mode">
<div class="field has-addons" v-tooltip="'Folder relative to ' + config.app.download_path"> <div class="field has-addons" v-tooltip="'Folder relative to ' + config.app.download_path">
<div class="control"> <div class="control">
<a href="#" class="button is-static">Save in</a> <a href="#" class="button is-static">Save in</a>
@ -47,7 +47,7 @@
<span>Add</span> <span>Add</span>
</button> </button>
</div> </div>
<div class="column"> <div class="column" v-if="!config.app.basic_mode">
<button type="submit" class="button is-info" @click="showAdvanced = !showAdvanced" <button type="submit" class="button is-info" @click="showAdvanced = !showAdvanced"
:class="{ 'is-loading': !socket.isConnected }" :disabled="!socket.isConnected"> :class="{ 'is-loading': !socket.isConnected }" :disabled="!socket.isConnected">
<span class="icon"><i class="fa-solid fa-cog" /></span> <span class="icon"><i class="fa-solid fa-cog" /></span>
@ -55,7 +55,7 @@
</button> </button>
</div> </div>
</div> </div>
<div class="columns is-multiline is-mobile" v-if="showAdvanced"> <div class="columns is-multiline is-mobile" v-if="showAdvanced && !config.app.basic_mode">
<div class="column is-12"> <div class="column is-12">
<div class="field"> <div class="field">
<label class="label is-inline" for="output_format" <label class="label is-inline" for="output_format"
@ -139,11 +139,11 @@
import { useStorage } from '@vueuse/core' import { useStorage } from '@vueuse/core'
const emitter = defineEmits(['getInfo']) const emitter = defineEmits(['getInfo'])
const config = useConfigStore(); const config = useConfigStore()
const socket = useSocketStore(); const socket = useSocketStore()
const toast = useToast(); const toast = useToast()
const selectedPreset = useStorage('selectedPreset', '') const selectedPreset = useStorage('selectedPreset', config.app.default_preset)
const ytdlpConfig = useStorage('ytdlp_config', '') const ytdlpConfig = useStorage('ytdlp_config', '')
const ytdlpCookies = useStorage('ytdlp_cookies', '') const ytdlpCookies = useStorage('ytdlp_cookies', '')
const output_template = useStorage('output_template', null) const output_template = useStorage('output_template', null)
@ -153,36 +153,36 @@ const showAdvanced = useStorage('show_advanced', false)
const addInProgress = ref(false) const addInProgress = ref(false)
const addDownload = () => { const addDownload = () => {
addInProgress.value = true; addInProgress.value = true
url.value.split(',').forEach(url => { url.value.split(',').forEach(url => {
if (!url.trim()) { if (!url.trim()) {
return; return
} }
socket.emit('add_url', { socket.emit('add_url', {
url: url, url: url,
preset: selectedPreset.value, preset: config.app.basic_mode ? config.app.default_preset : selectedPreset.value,
folder: downloadPath.value, folder: config.app.basic_mode ? null : downloadPath.value,
ytdlp_config: ytdlpConfig.value, ytdlp_config: config.app.basic_mode ? '' : ytdlpConfig.value,
ytdlp_cookies: ytdlpCookies.value, ytdlp_cookies: config.app.basic_mode ? '' : ytdlpCookies.value,
output_template: output_template.value, output_template: config.app.basic_mode ? null : output_template.value,
}); })
}); })
} }
const resetConfig = () => { const resetConfig = () => {
if (true !== confirm('Reset your local configuration?')) { if (true !== confirm('Reset your local configuration?')) {
return; return
} }
selectedPreset.value = 'default'; selectedPreset.value = config.app.default_preset.value
ytdlpConfig.value = ''; ytdlpConfig.value = ''
ytdlpCookies.value = ''; ytdlpCookies.value = ''
output_template.value = null; output_template.value = null
url.value = null; url.value = null
downloadPath.value = null; downloadPath.value = null
showAdvanced.value = false; showAdvanced.value = false
toast.success('Local configuration has been reset.'); toast.success('Local configuration has been reset.')
} }
const statusHandler = async data => { const statusHandler = async data => {
@ -195,13 +195,13 @@ const statusHandler = async data => {
return return
} }
url.value = ''; url.value = ''
} }
onMounted(() => { onMounted(() => {
socket.on('status', statusHandler) socket.on('status', statusHandler)
if ('' === selectedPreset.value) { if ('' === selectedPreset.value) {
selectedPreset.value = 'default' selectedPreset.value = config.app.default_preset.value
} }
}) })
onUnmounted(() => socket.off('status', statusHandler)) onUnmounted(() => socket.off('status', statusHandler))

View file

@ -102,7 +102,7 @@
</span> </span>
</button> </button>
</div> </div>
<div class="column is-half-mobile" v-if="item.url"> <div class="column is-half-mobile" v-if="item.url && !config.app.basic_mode">
<button class="button is-info is-fullwidth" @click="emitter('getInfo', item.url)"> <button class="button is-info is-fullwidth" @click="emitter('getInfo', item.url)">
<span class="icon-text is-block"> <span class="icon-text is-block">
<span class="icon"><i class="fa-solid fa-info" /></span> <span class="icon"><i class="fa-solid fa-info" /></span>

View file

@ -12,14 +12,14 @@
</div> </div>
<div class="navbar-end is-flex"> <div class="navbar-end is-flex">
<div class="navbar-item" v-if="socket.isConnected && config.app.has_cookies"> <div class="navbar-item" v-if="socket.isConnected && config.app.has_cookies && !config.app.basic_mode">
<button class="button is-dark" @click="checkCookies" v-tooltip="'Check youtube cookies status.'" <button class="button is-dark" @click="checkCookies" v-tooltip="'Check youtube cookies status.'"
:disabled="isChecking"> :disabled="isChecking">
<span class="icon has-text-info"><i class="fas fa-cookie"></i></span> <span class="icon has-text-info"><i class="fas fa-cookie"></i></span>
</button> </button>
</div> </div>
<div class="navbar-item" v-if="socket.isConnected"> <div class="navbar-item" v-if="socket.isConnected && !config.app.basic_mode">
<button class="button is-dark" @click="pauseDownload" v-if="false === config.paused" <button class="button is-dark" @click="pauseDownload" v-if="false === config.paused"
v-tooltip="'Pause non-active downloads.'"> v-tooltip="'Pause non-active downloads.'">
<span class="icon has-text-warning"><i class="fas fa-pause"></i></span> <span class="icon has-text-warning"><i class="fas fa-pause"></i></span>
@ -29,20 +29,20 @@
</button> </button>
</div> </div>
<div class="navbar-item"> <div class="navbar-item" v-if="!config.app.basic_mode">
<NuxtLink class="button is-dark has-tooltip-bottom" to="/console" v-tooltip.bottom="'Terminal'"> <NuxtLink class="button is-dark has-tooltip-bottom" to="/console" v-tooltip.bottom="'Terminal'">
<span class="icon"><i class="fa-solid fa-terminal" /></span> <span class="icon"><i class="fa-solid fa-terminal" /></span>
</NuxtLink> </NuxtLink>
</div> </div>
<div class="navbar-item"> <div class="navbar-item" v-if="!config.app.basic_mode">
<button v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom" <button v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom"
@click="config.showForm = !config.showForm"> @click="config.showForm = !config.showForm">
<span class="icon"><i class="fa-solid fa-plus" /></span> <span class="icon"><i class="fa-solid fa-plus" /></span>
</button> </button>
</div> </div>
<div class="navbar-item" v-if="config.tasks.length > 0" v-tooltip.bottom="'Tasks'"> <div class="navbar-item" v-if="!config.app.basic_mode && config.tasks.length > 0" v-tooltip.bottom="'Tasks'">
<NuxtLink class="button is-dark has-tooltip-bottom" to="/tasks"> <NuxtLink class="button is-dark has-tooltip-bottom" to="/tasks">
<span class="icon"><i class="fa-solid fa-tasks" /></span> <span class="icon"><i class="fa-solid fa-tasks" /></span>
</NuxtLink> </NuxtLink>
@ -67,7 +67,7 @@
</div> </div>
</nav> </nav>
<NewDownload v-if="config.showForm" @getInfo="url => get_info = url" /> <NewDownload v-if="config.showForm || config.app.basic_mode" @getInfo="url => get_info = url" />
<NuxtPage @getInfo="url => get_info = url" /> <NuxtPage @getInfo="url => get_info = url" />
<GetInfo v-if="get_info" :link="get_info" @closeModel="get_info = ''" /> <GetInfo v-if="get_info" :link="get_info" @closeModel="get_info = ''" />

View file

@ -65,7 +65,7 @@ const command = ref('')
const terminal_window = ref() const terminal_window = ref()
const command_input = ref() const command_input = ref()
const isLoading = ref(false) const isLoading = ref(false)
const config = useConfigStore()
const socket = useSocketStore() const socket = useSocketStore()
watch(() => isLoading.value, async value => { watch(() => isLoading.value, async value => {
@ -149,6 +149,13 @@ const writer = s => {
const loader = () => isLoading.value = false const loader = () => isLoading.value = false
watch(() => config.app.basic_mode, async () => {
if (!config.app.basic_mode) {
return
}
await navigateTo('/')
})
onMounted(async () => { onMounted(async () => {
window.addEventListener('resize', reSizeTerminal); window.addEventListener('resize', reSizeTerminal);
focusInput() focusInput()

View file

@ -64,4 +64,11 @@ div.table-container {
import moment from 'moment' import moment from 'moment'
import { parseExpression } from 'cron-parser' import { parseExpression } from 'cron-parser'
const config = useConfigStore() const config = useConfigStore()
watch(() => config.app.basic_mode, async () => {
if (!config.app.basic_mode) {
return
}
await navigateTo('/')
})
</script> </script>

View file

@ -14,10 +14,12 @@ const CONFIG_KEYS = {
url_host: '', url_host: '',
url_prefix: '', url_prefix: '',
has_cookies: false, has_cookies: false,
basic_mode: true,
default_preset: 'default',
}, },
presets: [ presets: [
{ {
'name': 'Default', 'name': 'default',
'format': 'default' 'format': 'default'
} }
], ],