updates
This commit is contained in:
parent
c61fd133d8
commit
cf8f971785
10 changed files with 183 additions and 53 deletions
|
|
@ -342,6 +342,9 @@ class Notifier:
|
|||
async def error(self, dl: dict, message: str):
|
||||
await self.emit('error', (dl, message))
|
||||
|
||||
async def warning(self, message: str):
|
||||
await self.emit('error', message)
|
||||
|
||||
async def emit(self, event: str, data):
|
||||
tasks = []
|
||||
tasks.append(self.sio.emit(event, self.serializer.encode(data)))
|
||||
|
|
|
|||
43
app/main.py
43
app/main.py
|
|
@ -688,6 +688,49 @@ class Main:
|
|||
})
|
||||
await self.sio.emit('cli_close', {'exitcode': -1})
|
||||
|
||||
@self.sio.event()
|
||||
async def add_url(sid, post: dict):
|
||||
url: str = post.get('url')
|
||||
quality: str = post.get('quality')
|
||||
|
||||
if not url:
|
||||
self.notifier.warning('No URL provided.')
|
||||
return
|
||||
|
||||
format: str = post.get('format')
|
||||
folder: str = post.get('folder')
|
||||
ytdlp_cookies: str = post.get('ytdlp_cookies')
|
||||
ytdlp_config: dict = post.get('ytdlp_config')
|
||||
output_template: str = post.get('output_template')
|
||||
if ytdlp_config is None:
|
||||
ytdlp_config = {}
|
||||
|
||||
status = await self.add(
|
||||
url=url,
|
||||
quality=quality,
|
||||
format=format,
|
||||
folder=folder,
|
||||
ytdlp_cookies=ytdlp_cookies,
|
||||
ytdlp_config=ytdlp_config,
|
||||
output_template=output_template
|
||||
)
|
||||
await self.sio.emit('status', self.serializer.encode(status))
|
||||
|
||||
@self.sio.event
|
||||
async def cancel_items(sid, post: dict):
|
||||
ids = post.get('ids')
|
||||
identifier: str = post.get('identifier')
|
||||
|
||||
if not ids:
|
||||
await self.notifier.warning('Invalid request.')
|
||||
return
|
||||
|
||||
status: dict[str, str] = {}
|
||||
status = await self.queue.cancel(ids)
|
||||
status.update({'identifier': identifier})
|
||||
|
||||
await self.sio.emit('cancel_items', self.serializer.encode(status))
|
||||
|
||||
@self.sio.event()
|
||||
async def archive_item(sid, data):
|
||||
if not isinstance(data, dict) or 'url' not in data or not self.config.keep_archive:
|
||||
|
|
|
|||
|
|
@ -188,3 +188,22 @@ hr {
|
|||
background-color: #087363;
|
||||
}
|
||||
}
|
||||
|
||||
.vue-notification {
|
||||
background: unset;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.vue-notification-wrapper {
|
||||
cursor: pointer;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<span class="icon">
|
||||
<i class="fas" :class="showQueue ? 'fa-solid fa-arrow-up' : 'fa-solid fa-arrow-down'" />
|
||||
</span>
|
||||
<span>Queue <span v-if="hasQueuedItems">({{ getTotal }})</span></span>
|
||||
<span>Queue <span v-if="hasQueuedItems">({{ stateStore.count('queue') }})</span></span>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
<LazyLoader :unrender="true" :min-height="265" class="column is-6" v-for="item in stateStore.queue"
|
||||
<LazyLoader :unrender="true" :min-height="265" class="column is-6" v-for="item in stateStore.history"
|
||||
:key="item._id">
|
||||
<div class="card">
|
||||
<header class="card-header has-tooltip" v-tooltip="item.title">
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
<i class="fas" :class="setIcon(item)" />
|
||||
</span>
|
||||
<span v-if="item.status == 'downloading' && item.is_live">Live Streaming</span>
|
||||
<span v-else>{{ capitalize(item.status) }}</span>
|
||||
<span v-else>{{ ucFirst(item.status) }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="column is-half-mobile has-text-centered">
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
</div>
|
||||
<div class="columns is-multiline is-mobile">
|
||||
<div class="column is-half-mobile">
|
||||
<button class="button is-danger is-fullwidth" @click="confirmDelete(item);">
|
||||
<button class="button is-danger is-fullwidth" @click="confirmCancel(item);">
|
||||
<span class="icon-text is-block">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-trash-can" />
|
||||
|
|
@ -123,12 +123,10 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, ref, watch, computed } from 'vue';
|
||||
import moment from "moment";
|
||||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import LazyLoader from './LazyLoader'
|
||||
|
||||
const emit = defineEmits(['deleteItem']);
|
||||
import LazyLoader from '~/components/LazyLoader'
|
||||
import { ucFirst } from '~/utils/index'
|
||||
|
||||
const config = useConfigStore();
|
||||
const stateStore = useStateStore();
|
||||
|
|
@ -137,6 +135,7 @@ const socket = useSocketStore();
|
|||
const selectedElms = ref([]);
|
||||
const masterSelectAll = ref(false);
|
||||
const showQueue = useStorage('showQueue', true)
|
||||
const actionId = ref(null);
|
||||
|
||||
watch(masterSelectAll, (value) => {
|
||||
for (const key in stateStore.queue) {
|
||||
|
|
@ -150,8 +149,8 @@ watch(masterSelectAll, (value) => {
|
|||
})
|
||||
|
||||
const hasSelected = computed(() => selectedElms.value.length > 0)
|
||||
const hasQueuedItems = computed(() => stateStore.count('queue') > 0)
|
||||
const getTotal = computed(() => stateStore.count('queue'));
|
||||
const hasQueuedItems = computed(() => stateStore.count('history') > 0)
|
||||
const getTotal = computed(() => stateStore.count('history'));
|
||||
|
||||
const setIcon = item => {
|
||||
if (item.status === 'downloading' && item.is_live) {
|
||||
|
|
@ -219,14 +218,74 @@ const updateProgress = (item) => {
|
|||
return string;
|
||||
}
|
||||
|
||||
const confirmDelete = (item) => {
|
||||
if (!confirm(`Are you sure you want to delete (${item.title})?`)) {
|
||||
const confirmCancel = item => {
|
||||
if (true !== confirm(`Are you sure you want to cancel (${item.title})?`)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
emit('deleteItem', 'queue', item._id);
|
||||
stateStore.remove('queue', item._id);
|
||||
deleteItem('queue', item._id);
|
||||
return true;
|
||||
}
|
||||
|
||||
const deleteItem = (type, item) => {
|
||||
const items = []
|
||||
|
||||
if (typeof item === 'object') {
|
||||
for (const key in item) {
|
||||
items.push(item[key]);
|
||||
}
|
||||
} else {
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
if (items.length < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionId.value = makeId();
|
||||
|
||||
socket.emit('cancel_items', {
|
||||
identifier: actionId.value,
|
||||
ids: items,
|
||||
});
|
||||
}
|
||||
|
||||
const cancelHandler = async ev => {
|
||||
if (!actionId.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.parse(ev)
|
||||
|
||||
if (data.identifier !== actionId.value) {
|
||||
console.log('Invalid action identifier', data);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('error' === data.status) {
|
||||
notification('error', 'Failed to cancel item/s.');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in items) {
|
||||
const itemId = items[key];
|
||||
if (itemId in json && json[itemId] === 'ok') {
|
||||
if (true === stateStore.has('history', itemId)) {
|
||||
stateStore.remove('history', itemId);
|
||||
}
|
||||
if (true === stateStore.has('queue', itemId)) {
|
||||
notification('info', 'Download canceled: ' + ag(stateStore.get(itemId), 'title'));
|
||||
stateStore.remove('queue', itemId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
socket.on('cancel_items', cancelHandler)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.off('cancel_items', cancelHandler)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -13,25 +13,26 @@
|
|||
<div class="navbar-end is-flex">
|
||||
<div class="navbar-item">
|
||||
<NuxtLink class="button is-dark has-tooltip-bottom" to="/console">
|
||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||
<span>Terminal</span>
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||
<span>Terminal</span>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="navbar-item">
|
||||
<button v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom"
|
||||
@click="config.showForm = !config.showForm">
|
||||
<span class="icon"><i class="fa-solid fa-plus" /></span>
|
||||
<span>Add</span>
|
||||
</button>
|
||||
<NuxtLink v-tooltip.bottom="'Toggle Add Form'" class="button is-dark has-tooltip-bottom" to="/add">
|
||||
<span class="icon-text">
|
||||
<span class="icon"><i class="fa-solid fa-plus" /></span>
|
||||
<span>Add</span>
|
||||
</span>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="navbar-item" v-if="config.tasks.length > 0">
|
||||
<button v-tooltip.bottom="'Toggle Tasks'" class="button is-dark has-tooltip-bottom"
|
||||
@click="config.showTasks = !config.showTasks">
|
||||
<span class="icon">
|
||||
<i class="fa-solid fa-tasks" />
|
||||
</span>
|
||||
<span class="icon"><i class="fa-solid fa-tasks" /></span>
|
||||
<span class="is-hidden-mobile">Tasks</span>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -46,6 +47,7 @@
|
|||
<span class="icon"><i class="fas fa-moon"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="navbar-item">
|
||||
<button class="button is-dark" @click="reloadPage">
|
||||
<span class="icon"><i class="fas fa-refresh"></i></span>
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ const addInProgress = ref(false)
|
|||
|
||||
const addDownload = () => {
|
||||
addInProgress.value = true;
|
||||
emits('addItem', {
|
||||
socket.emit('add_url', {
|
||||
url: url.value,
|
||||
format: selectedFormat.value,
|
||||
quality: selectedQuality.value,
|
||||
|
|
@ -187,4 +187,23 @@ bus.on((event, data) => {
|
|||
addInProgress.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
const statusHandler = async data => {
|
||||
const { status, msg } = JSON.parse(data)
|
||||
|
||||
addInProgress.value = false
|
||||
|
||||
console.log(data)
|
||||
|
||||
if ('error' === status) {
|
||||
notification('error', 'Add error', msg, 5000)
|
||||
return
|
||||
}
|
||||
|
||||
notification('success', 'Add success', msg, 3000)
|
||||
await navigateTo('/')
|
||||
}
|
||||
|
||||
onMounted(() => socket.on('status', statusHandler))
|
||||
onUnmounted(() => socket.off('status', statusHandler))
|
||||
</script>
|
||||
|
|
@ -135,7 +135,6 @@ const focusInput = () => {
|
|||
command_input.value.focus()
|
||||
}
|
||||
|
||||
onUnmounted(() => window.removeEventListener('resize', reSizeTerminal));
|
||||
|
||||
const writer = s => {
|
||||
if (!terminal.value) {
|
||||
|
|
@ -154,4 +153,10 @@ onMounted(async () => {
|
|||
socket.on('cli_close', loader)
|
||||
socket.on('cli_output', writer)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
socket.off('cli_close', loader)
|
||||
socket.off('cli_output', writer)
|
||||
window.removeEventListener('resize', reSizeTerminal)
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,28 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<NewDownload v-if="config.showForm" />
|
||||
<Queue />
|
||||
<History />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const bus = useEventBus('item_added', 'show_form', 'task_edit')
|
||||
const config = useConfigStore()
|
||||
const socket = useSocketStore()
|
||||
|
||||
bus.on((event, data) => {
|
||||
console.log({ e: event, d: data })
|
||||
|
||||
if (!['show_form'].includes(event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ('show_form' === event) {
|
||||
addForm.value = true;
|
||||
setTimeout(() => bus.emit('task_edit', data), 500);
|
||||
}
|
||||
})
|
||||
|
||||
useHead({ title: 'Index' })
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { io } from "socket.io-client";
|
||||
import { notification, ag } from "~/utils/index"
|
||||
|
||||
export const useSocketStore = defineStore('socket', () => {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
|
|
@ -29,13 +30,13 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
|
||||
socket.value.on('added', stream => {
|
||||
const item = JSON.parse(stream);
|
||||
stateStore.add('queue', item);
|
||||
toast.success(`Item queued successfully: ${stateStore.get('queue', item._id)?.title}`);
|
||||
stateStore.add('queue', item._id, item);
|
||||
notification('success', `Item queued successfully: ${ag(stateStore.get('queue', item._id, {}), 'title')}`);
|
||||
});
|
||||
|
||||
socket.value.on('error', stream => {
|
||||
const [item, error] = JSON.parse(stream);
|
||||
toast.error(`${item?.id}: Error: ${error}`);
|
||||
notification('error', `${item?.id}: Error: ${error}`);
|
||||
});
|
||||
|
||||
socket.value.on('completed', stream => {
|
||||
|
|
@ -54,7 +55,7 @@ export const useSocketStore = defineStore('socket', () => {
|
|||
return
|
||||
}
|
||||
|
||||
toast.info('Download canceled: ' + stateStore.get('queue', id)?.title);
|
||||
notification('info', `Download canceled: ${ag(stateStore.get('queue', id, {}), 'title')}`);
|
||||
|
||||
if (true === stateStore.has('queue', id)) {
|
||||
stateStore.remove('queue', id);
|
||||
|
|
|
|||
|
|
@ -126,9 +126,6 @@ const notification = (type, title, text, duration = 3000) => {
|
|||
if (3000 === duration) {
|
||||
duration = 10000
|
||||
}
|
||||
if ('crit' === notificationType) {
|
||||
log_error(`${title} ${text}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue