initial code to support adding tasks via WebGUI

This commit is contained in:
ArabCoders 2023-12-05 21:34:32 +03:00
parent 4c6059b81c
commit bbc57113b6
5 changed files with 168 additions and 36 deletions

View file

@ -107,9 +107,21 @@ class Main:
f'YTPTube v{self.config.version} - listening on http://{self.config.host}:{self.config.port}'),
)
async def connect(self, sid, environ):
await self.sio.emit('all', self.serializer.encode(self.dqueue.get()), to=sid)
await self.sio.emit('configuration', self.serializer.encode(self.config), to=sid)
async def connect(self, sid, _):
data: dict = {
**self.dqueue.get(),
"config": self.config,
"tasks": [],
}
if os.path.exists(os.path.join(self.config.config_path, 'tasks.json')):
try:
with open(os.path.join(self.config.config_path, 'tasks.json'), 'r') as f:
data['tasks'] = json.load(f)
except Exception as e:
pass
await self.sio.emit('initial_data', self.serializer.encode(data), to=sid)
def addTasks(self):
tasks_file: str = os.path.join(self.config.config_path, 'tasks.json')
@ -118,8 +130,13 @@ class Main:
f'No tasks file found at {tasks_file}. Skipping Tasks.')
return
with open(tasks_file, 'r') as f:
tasks = json.load(f)
try:
with open(tasks_file, 'r') as f:
tasks = json.load(f)
except Exception as e:
logging.error(
f'Could not load tasks file [{tasks_file}]. Error message [{str(e)}]. Skipping Tasks.')
return
for task in tasks:
if not task.get('url'):
@ -188,6 +205,22 @@ class Main:
return web.Response(text=self.serializer.encode(status))
@self.routes.get(self.config.url_prefix + 'tasks')
async def tasks(_: Request) -> Response:
tasks_file: str = os.path.join(
self.config.config_path, 'tasks.json')
if not os.path.exists(tasks_file):
return web.json_response({"error": "No tasks defined."}, status=404)
try:
with open(tasks_file, 'r') as f:
tasks = json.load(f)
except Exception as e:
return web.json_response({"error": str(e)}, status=500)
return web.json_response(tasks)
@self.routes.post(self.config.url_prefix + 'add_batch')
async def add_batch(request: Request) -> Response:
status = {}

View file

@ -1,6 +1,7 @@
<template>
<PageHeader :config="config" @toggleForm="addForm = !addForm" />
<PageHeader :config="config" @toggleForm="addForm = !addForm" @toggleTasks="showTasks = !showTasks" />
<formAdd v-if="addForm" :config="config" @addItem="addItem" />
<pageTasks v-if="showTasks" :tasks="config.tasks" @removeTask="deleteTask" />
<DownloadingList :config="config" :queue="downloading" @deleteItem="deleteItem" />
<PageCompleted :config="config" :completed="completed" @deleteItem="deleteItem" @addItem="addItem"
@playItem="playItem" />
@ -20,6 +21,7 @@
import { onMounted, reactive, ref } from 'vue'
import PageHeader from './components/Page-Header'
import formAdd from './components/Form-Add'
import pageTasks from './components/Page-Tasks'
import DownloadingList from './components/Page-Downloading'
import PageCompleted from './components/Page-Completed'
import PageFooter from './components/Page-Footer'
@ -34,12 +36,14 @@ const bus = useEventBus('item_added');
const config = reactive({
isConnected: false,
app: {},
tasks: [],
});
const downloading = reactive({});
const completed = reactive({});
const video_link = ref('');
const addForm = useStorage('addForm', true)
const showTasks = useStorage('showTasks', false)
onMounted(() => {
const socket = io(process.env.VUE_APP_BASE_URL, {
@ -49,19 +53,17 @@ onMounted(() => {
socket.on('connect', () => config.isConnected = true);
socket.on('disconnect', () => config.isConnected = false);
socket.on('all', stream => {
socket.on('initial_data', stream => {
const initialData = JSON.parse(stream);
for (const key in initialData) {
const element = initialData[key];
if (key === 'queue') {
for (const id in element) {
downloading[id] = element[id];
}
} else if (key === 'done') {
for (const id in element) {
completed[id] = element[id];
}
}
config.app = initialData['config'];
config.tasks = initialData['tasks'];
for (const id in initialData['queue']) {
downloading[id] = initialData['done'][id];
}
for (const id in initialData['done']) {
completed[id] = initialData['done'][id];
}
});
@ -104,10 +106,6 @@ onMounted(() => {
data.deleting = dl?.deleting;
downloading[data._id] = data;
});
socket.on('configuration', stream => {
config.app = JSON.parse(stream);
});
});
const deleteItem = (type, item) => {
@ -172,6 +170,14 @@ const playItem = (item) => {
video_link.value = config.app.url_host + config.app.url_prefix + baseDir + encodeURIComponent(item.filename);
};
const deleteTask = (indexNumber, item) => {
if (!confirm(`Are you sure you want to delete this task ${item?.name || item.url}?`)) {
return;
}
config.tasks = config.tasks.filter((_, index) => {
return index !== indexNumber;
});
}
</script>

View file

@ -1,26 +1,22 @@
<template>
<nav class="navbar is-mobile is-dark">
<div class="navbar-brand pl-5">
<a class="navbar-item" href="#">
<a class="navbar-item has-tooltip-bottom" :class="config.isConnected ? 'has-text-success' : 'has-text-danger'"
:data-tooltip="config.isConnected ? 'Connected' : 'Connecting'" href="javascript:void(0);">
<b>YTPTube</b>
</a>
</div>
<div class="navbar-end">
<div class="navbar-item">
<button class="button is-dark has-tooltip-bottom" :data-tooltip="config.isConnected ? 'Connected' : 'Connecting'">
<span class="icon-text" :class="config.isConnected ? 'has-text-success' : 'has-text-danger'">
<span class="icon">
<font-awesome-icon :icon="config.isConnected ? 'fa-solid fa-wifi' : 'fa-solid fa-signal'" />
</span>
</span>
</button>
</div>
<div class="navbar-item">
<button data-tooltip="Show/Hide Add Form" class="button is-dark has-tooltip-bottom" @click="$emit('toggleForm')">
<font-awesome-icon icon="fa-solid fa-plus" />
</button>
</div>
<div class="navbar-item">
<button data-tooltip="Show/Hide Tasks" class="button is-dark has-tooltip-bottom" @click="$emit('toggleTasks')">
<font-awesome-icon icon="fa-solid fa-tasks" />
</button>
</div>
<div class="navbar-item">
<button data-tooltip="Switch to Light theme" class="button is-dark has-tooltip-bottom"
@click="selectedTheme = 'light'" v-if="selectedTheme == 'dark'">🌞</button>
@ -37,7 +33,7 @@ import { useStorage } from '@vueuse/core'
const selectedTheme = useStorage('theme', (() => window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')());
defineEmits(['toggleForm'])
defineEmits(['toggleForm', 'toggleTasks'])
defineProps({
config: {
@ -115,6 +111,7 @@ watch(selectedTheme, (value) => {
display: flex;
padding: 0;
}
.navbar-menu {
flex-grow: 1;
flex-shrink: 0;

View file

@ -0,0 +1,96 @@
<template>
<div class="mt-3 is-unselectable">
<p class="title is-3">
<span class="icon-text">
<span class="icon">
<font-awesome-icon icon="fa-solid fa-tasks" />
</span>
<span>Tasks</span>
</span>
</p>
<p class="subtitle is-4 has-text-danger">
This section is not working yet.
</p>
</div>
<div class="columns is-multiline">
<div class="column is-12 has-text-right">
<button class="button is-primary" @click="$emit('task_new')">
<span class="icon-text">
<span class="icon">
<font-awesome-icon icon="fa-solid fa-plus" />
</span>
<span>New Task</span>
</span>
</button>
</div>
<div class="column is-12">
<div class="table-container">
<table class="table is-bordered is-narrow is-hoverable is-striped is-fullwidth is-fixed">
<thead class="has-text-centered">
<tr>
<th class="has-text-centered" width="20%">Name</th>
<th class="has-text-centered" width="50%">URL</th>
<th class="has-text-centered" width="20%">Timer</th>
<th class="has-text-centered" width="10%">Options</th>
</tr>
</thead>
<tbody>
<tr v-for="item, key in tasks" :key="key">
<td class="is-text-overflow has-text-centered" v-if="item.name">{{ item.name }}</td>
<td class="has-text-centered" v-else>Not set</td>
<td class="is-text-overflow">{{ item.url }}</td>
<td class="has-text-centered" v-if="item.timer">{{ item.timer }}</td>
<td class="has-text-centered" v-else>once an hour</td>
<td class="has-text-centered">
<div class="field is-grouped">
<div class="control is-expanded">
<button class="button is-small is-danger" @click="$emit('removeTask', key, item)"
data-tooltip="Remove task">
<span class="icon">
<font-awesome-icon icon="fa-solid fa-trash" />
</span>
</button>
</div>
<div class="control is-expanded">
<button class="button is-small is-info" data-tooltip="Modify task">
<span class="icon">
<font-awesome-icon icon="fa-solid fa-cog" />
</span>
</button>
</div>
</div>
</td>
</tr>
<tr v-if="tasks.length < 1">
<td colspan="4" class="has-text-centered has-text-danger">
No tasks are defined.
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue'
defineEmits(['task_new', 'task_add', 'removeTask', 'tasks_updated'])
defineProps({
tasks: {
type: Array,
required: true
}
})
</script>
<style scoped>
table.is-fixed {
table-layout: fixed;
}
</style>

View file

@ -5,7 +5,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import {
faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare,
faSpinner, faWifi, faSignal, faArrowUp, faArrowDown,
faSpinner, faArrowUp, faArrowDown, faTasks
} from '@fortawesome/free-solid-svg-icons'
import { faSquare, faSquareCheck } from '@fortawesome/free-regular-svg-icons'
@ -17,7 +17,7 @@ import './assets/css/style.css'
import '@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css';
library.add(faCog, faTrash, faLink, faPlus, faTrashCan, faCircleXmark, faCircleCheck, faRotateRight, faDownload, faUpRightFromSquare,
faSquare, faSquareCheck, faSpinner, faWifi, faSignal, faArrowUp, faArrowDown,)
faSquare, faSquareCheck, faSpinner, faArrowUp, faArrowDown, faTasks,)
const app = createApp(App);
app.config.globalProperties.capitalize = s => s && s[0].toUpperCase() + s.slice(1);