mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-10 14:50:58 +03:00
Added UI for managing tasks
Added ability to schedule tasks based on timestamp Fixed mismatched types between frontend and openapi yaml Simplified imports for several backend components
This commit is contained in:
@@ -666,11 +666,287 @@ paths:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetDownloadRequest'
|
||||
description: ''
|
||||
description: "Gets a single download using its download_id and session_id. session_id is the device fingerprint. If none was provided at the time of download, then set session_id is 'undeclared'."
|
||||
description: "Gets a single download using its download_id."
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
tags:
|
||||
- downloader
|
||||
/api/pauseDownload:
|
||||
post:
|
||||
summary: Pauses one download
|
||||
operationId: post-api-pause-download
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetDownloadRequest'
|
||||
description: ''
|
||||
description: "Pause a single download using its download_id."
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
tags:
|
||||
- downloader
|
||||
/api/pauseAllDownloads:
|
||||
post:
|
||||
tags:
|
||||
- downloader
|
||||
summary: Pauses all downloads
|
||||
operationId: post-api-pause-all-downloads
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
/api/resumeDownload:
|
||||
post:
|
||||
summary: Resume one download
|
||||
operationId: post-api-resume-download
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetDownloadRequest'
|
||||
description: ''
|
||||
description: "Resume a single download using its download_id."
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
tags:
|
||||
- downloader
|
||||
/api/resumeAllDownloads:
|
||||
post:
|
||||
tags:
|
||||
- downloader
|
||||
summary: Resumes all downloads
|
||||
operationId: post-api-resume-all-downloads
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
/api/restartDownload:
|
||||
post:
|
||||
summary: Restart one download
|
||||
operationId: post-api-restart-download
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetDownloadRequest'
|
||||
description: ''
|
||||
description: "Restart a single download using its download_id."
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
tags:
|
||||
- downloader
|
||||
/api/cancelDownload:
|
||||
post:
|
||||
summary: Cancel one download
|
||||
operationId: post-api-cancel-download
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetDownloadRequest'
|
||||
description: ''
|
||||
description: "Cancel a single download using its download_id."
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
tags:
|
||||
- downloader
|
||||
/api/clearDownload:
|
||||
post:
|
||||
summary: Clear one download
|
||||
operationId: post-api-clear-download
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetDownloadRequest'
|
||||
description: ''
|
||||
description: "Clears a single download from the downloaded list using its download_id."
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
tags:
|
||||
- downloader
|
||||
/api/clearFinishedDownloads:
|
||||
post:
|
||||
tags:
|
||||
- downloader
|
||||
summary: Clear finished downloads
|
||||
operationId: post-api-clear-finished-downloads
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
/api/getTask:
|
||||
post:
|
||||
summary: Get info for one task
|
||||
operationId: post-api-get-task
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetTaskResponse'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetTaskRequest'
|
||||
description: ''
|
||||
description: "Gets a single task using its key."
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
tags:
|
||||
- tasks
|
||||
/api/getTasks:
|
||||
post:
|
||||
tags:
|
||||
- tasks
|
||||
summary: Get tasks
|
||||
operationId: post-api-get-tasks
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetAllTasksResponse'
|
||||
security:
|
||||
- Auth query parameter: []
|
||||
/api/runTask:
|
||||
post:
|
||||
summary: Runs one task
|
||||
operationId: post-api-run-task
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetTaskRequest'
|
||||
/api/confirmTask:
|
||||
post:
|
||||
summary: Confirms a task
|
||||
operationId: post-api-confirm-task
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetTaskRequest'
|
||||
/api/cancelTask:
|
||||
post:
|
||||
summary: Cancels a task
|
||||
operationId: post-api-cancel-task
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetTaskRequest'
|
||||
/api/updateTaskSchedule:
|
||||
post:
|
||||
summary: Updates task schedule
|
||||
operationId: post-api-update-task-schedule
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SuccessObject'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateTaskScheduleRequest'
|
||||
/api/auth/login:
|
||||
post:
|
||||
summary: Login
|
||||
@@ -1231,6 +1507,35 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Download'
|
||||
GetTaskRequest:
|
||||
type: object
|
||||
properties:
|
||||
task_key:
|
||||
type: string
|
||||
required:
|
||||
- task_key
|
||||
UpdateTaskScheduleRequest:
|
||||
type: object
|
||||
properties:
|
||||
task_key:
|
||||
type: string
|
||||
new_schedule:
|
||||
$ref: '#/components/schemas/Schedule'
|
||||
required:
|
||||
- task_key
|
||||
- new_schedule
|
||||
GetTaskResponse:
|
||||
type: object
|
||||
properties:
|
||||
task:
|
||||
$ref: '#/components/schemas/Task'
|
||||
GetAllTasksResponse:
|
||||
type: object
|
||||
properties:
|
||||
tasks:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Task'
|
||||
GetMp3sResponse:
|
||||
required:
|
||||
- mp3s
|
||||
@@ -1506,6 +1811,10 @@ components:
|
||||
type: string
|
||||
playlist_id:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
type:
|
||||
$ref: '#/components/schemas/FileType'
|
||||
DownloadArchiveRequest:
|
||||
required:
|
||||
- sub
|
||||
@@ -1967,6 +2276,58 @@ components:
|
||||
type: string
|
||||
sub_name:
|
||||
type: string
|
||||
Task:
|
||||
required:
|
||||
- key
|
||||
- last_ran
|
||||
- last_confirmed
|
||||
- running
|
||||
- confirming
|
||||
- data
|
||||
- error
|
||||
- schedule
|
||||
type: object
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
last_ran:
|
||||
type: number
|
||||
last_confirmed:
|
||||
type: number
|
||||
running:
|
||||
type: boolean
|
||||
confirming:
|
||||
type: boolean
|
||||
data:
|
||||
type: object
|
||||
error:
|
||||
type: string
|
||||
schedule:
|
||||
type: object
|
||||
Schedule:
|
||||
required:
|
||||
- type
|
||||
- data
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- timestamp
|
||||
- recurring
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
dayOfWeek:
|
||||
type: array
|
||||
items:
|
||||
type: number
|
||||
hour:
|
||||
type: number
|
||||
minute:
|
||||
type: number
|
||||
timestamp:
|
||||
type: number
|
||||
SubscriptionRequestData:
|
||||
required:
|
||||
- id
|
||||
|
||||
@@ -28,6 +28,7 @@ const youtubedl = require('youtube-dl');
|
||||
const logger = require('./logger');
|
||||
const config_api = require('./config.js');
|
||||
const downloader_api = require('./downloader');
|
||||
const tasks_api = require('./tasks');
|
||||
const subscriptions_api = require('./subscriptions');
|
||||
const categories_api = require('./categories');
|
||||
const twitch_api = require('./twitch');
|
||||
@@ -60,9 +61,6 @@ const admin_token = '4241b401-7236-493e-92b5-b72696b9d853';
|
||||
config_api.initialize();
|
||||
db_api.initialize(db, users_db);
|
||||
auth_api.initialize(db_api);
|
||||
downloader_api.initialize(db_api);
|
||||
subscriptions_api.initialize(db_api, downloader_api);
|
||||
categories_api.initialize(db_api);
|
||||
|
||||
// Set some defaults
|
||||
db.defaults(
|
||||
@@ -1878,6 +1876,54 @@ app.post('/api/cancelDownload', optionalJwt, async (req, res) => {
|
||||
res.send({success: success});
|
||||
});
|
||||
|
||||
// tasks
|
||||
|
||||
app.post('/api/getTasks', optionalJwt, async (req, res) => {
|
||||
const tasks = await db_api.getRecords('tasks');
|
||||
for (let task of tasks) {
|
||||
if (task['schedule']) task['next_invocation'] = tasks_api.TASKS[task['key']]['job'].nextInvocation().getTime();
|
||||
}
|
||||
res.send({tasks: tasks});
|
||||
});
|
||||
|
||||
app.post('/api/getTask', optionalJwt, async (req, res) => {
|
||||
const task_key = req.body.task_key;
|
||||
const task = await db_api.getRecord('tasks', {key: task_key});
|
||||
if (task['schedule']) task['next_invocation'] = tasks_api.TASKS[task_key]['job'].nextInvocation().getTime();
|
||||
res.send({task: task});
|
||||
});
|
||||
|
||||
app.post('/api/runTask', optionalJwt, async (req, res) => {
|
||||
const task_key = req.body.task_key;
|
||||
const task = await db_api.getRecord('tasks', {key: task_key});
|
||||
|
||||
let success = true;
|
||||
if (task['running'] || task['confirming']) success = false;
|
||||
else await tasks_api.executeRun(task_key);
|
||||
|
||||
res.send({success: success});
|
||||
});
|
||||
|
||||
app.post('/api/confirmTask', optionalJwt, async (req, res) => {
|
||||
const task_key = req.body.task_key;
|
||||
const task = await db_api.getRecord('tasks', {key: task_key});
|
||||
|
||||
let success = true;
|
||||
if (task['running'] || task['confirming'] || !task['data']) success = false;
|
||||
else await tasks_api.executeConfirm(task_key);
|
||||
|
||||
res.send({success: success});
|
||||
});
|
||||
|
||||
app.post('/api/updateTaskSchedule', optionalJwt, async (req, res) => {
|
||||
const task_key = req.body.task_key;
|
||||
const new_schedule = req.body.new_schedule;
|
||||
|
||||
await tasks_api.updateTaskSchedule(task_key, new_schedule);
|
||||
|
||||
res.send({success: true});
|
||||
});
|
||||
|
||||
// logs management
|
||||
|
||||
app.post('/api/logs', optionalJwt, async function(req, res) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const config_api = require('../config');
|
||||
const consts = require('../consts');
|
||||
const logger = require('../logger');
|
||||
const db_api = require('../db');
|
||||
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { uuid } = require('uuidv4');
|
||||
@@ -12,15 +13,12 @@ var JwtStrategy = require('passport-jwt').Strategy,
|
||||
ExtractJwt = require('passport-jwt').ExtractJwt;
|
||||
|
||||
// other required vars
|
||||
let db_api = null;
|
||||
let SERVER_SECRET = null;
|
||||
let JWT_EXPIRATION = null;
|
||||
let opts = null;
|
||||
let saltRounds = null;
|
||||
|
||||
exports.initialize = function(db_api) {
|
||||
setDB(db_api);
|
||||
|
||||
exports.initialize = function() {
|
||||
/*************************
|
||||
* Authentication module
|
||||
************************/
|
||||
@@ -51,10 +49,6 @@ exports.initialize = function(db_api) {
|
||||
}));
|
||||
}
|
||||
|
||||
function setDB(input_db_api) {
|
||||
db_api = input_db_api;
|
||||
}
|
||||
|
||||
exports.passport = require('passport');
|
||||
|
||||
exports.passport.serializeUser(function(user, done) {
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
const utils = require('./utils');
|
||||
const logger = require('./logger');
|
||||
|
||||
var db_api = null;
|
||||
|
||||
function setDB(input_db_api) { db_api = input_db_api }
|
||||
|
||||
function initialize(input_db_api) {
|
||||
setDB(input_db_api);
|
||||
}
|
||||
|
||||
const db_api = require('./db');
|
||||
/*
|
||||
|
||||
Categories:
|
||||
@@ -137,7 +129,6 @@ function applyCategoryRules(file_json, rules, category_name) {
|
||||
// }
|
||||
|
||||
module.exports = {
|
||||
initialize: initialize,
|
||||
categorize: categorize,
|
||||
getCategories: getCategories,
|
||||
getCategoriesAsPlaylists: getCategoriesAsPlaylists
|
||||
|
||||
@@ -14,26 +14,19 @@ const twitch_api = require('./twitch');
|
||||
const { create } = require('xmlbuilder2');
|
||||
const categories_api = require('./categories');
|
||||
const utils = require('./utils');
|
||||
|
||||
let db_api = null;
|
||||
const db_api = require('./db');
|
||||
|
||||
const mutex = new Mutex();
|
||||
let should_check_downloads = true;
|
||||
|
||||
const archivePath = path.join(__dirname, 'appdata', 'archives');
|
||||
|
||||
function setDB(input_db_api) { db_api = input_db_api }
|
||||
|
||||
exports.initialize = (input_db_api) => {
|
||||
setDB(input_db_api);
|
||||
categories_api.initialize(db_api);
|
||||
if (db_api.database_initialized) {
|
||||
setupDownloads();
|
||||
} else {
|
||||
db_api.database_initialized_bs.subscribe(init => {
|
||||
if (init) setupDownloads();
|
||||
});
|
||||
}
|
||||
if (db_api.database_initialized) {
|
||||
setupDownloads();
|
||||
} else {
|
||||
db_api.database_initialized_bs.subscribe(init => {
|
||||
if (init) setupDownloads();
|
||||
});
|
||||
}
|
||||
|
||||
exports.createDownload = async (url, type, options, user_uid = null, sub_id = null, sub_name = null) => {
|
||||
|
||||
@@ -8,15 +8,8 @@ const logger = require('./logger');
|
||||
|
||||
const debugMode = process.env.YTDL_MODE === 'debug';
|
||||
|
||||
let db_api = null;
|
||||
let downloader_api = null;
|
||||
|
||||
function setDB(input_db_api) { db_api = input_db_api }
|
||||
|
||||
function initialize(input_db_api, input_downloader_api) {
|
||||
setDB(input_db_api);
|
||||
downloader_api = input_downloader_api;
|
||||
}
|
||||
const db_api = require('./db');
|
||||
const downloader_api = require('./downloader');
|
||||
|
||||
async function subscribe(sub, user_uid = null) {
|
||||
const result_obj = {
|
||||
@@ -542,7 +535,6 @@ module.exports = {
|
||||
unsubscribe : unsubscribe,
|
||||
deleteSubscriptionFile : deleteSubscriptionFile,
|
||||
getVideosForSub : getVideosForSub,
|
||||
initialize : initialize,
|
||||
updateSubscriptionPropertyMultiple : updateSubscriptionPropertyMultiple,
|
||||
generateOptionsForSubscriptionDownload: generateOptionsForSubscriptionDownload
|
||||
}
|
||||
|
||||
@@ -31,7 +31,21 @@ const TASKS = {
|
||||
}
|
||||
|
||||
function scheduleJob(task_key, schedule) {
|
||||
return scheduler.scheduleJob(schedule, async () => {
|
||||
// schedule has to be converted from our format to one node-schedule can consume
|
||||
let converted_schedule = null;
|
||||
if (schedule['type'] === 'timestamp') {
|
||||
converted_schedule = new Date(schedule['data']['timestamp']);
|
||||
} else if (schedule['type'] === 'recurring') {
|
||||
const dayOfWeek = schedule['data']['dayOfWeek'] ? schedule['data']['dayOfWeek'] : null;
|
||||
const hour = schedule['data']['hour'] ? schedule['data']['hour'] : null;
|
||||
const minute = schedule['data']['minute'] ? schedule['data']['minute'] : null;
|
||||
converted_schedule = new scheduler.RecurrenceRule(null, null, null, dayOfWeek, hour, minute);
|
||||
} else {
|
||||
logger.error(`Failed to schedule job '${task_key}' as the type '${schedule['type']}' is invalid.`)
|
||||
return null;
|
||||
}
|
||||
|
||||
return scheduler.scheduleJob(converted_schedule, async () => {
|
||||
const task_state = await db_api.getRecord('tasks', {key: task_key});
|
||||
if (task_state['running'] || task_state['confirming']) {
|
||||
logger.verbose(`Skipping running task ${task_state['key']} as it is already in progress.`);
|
||||
@@ -43,15 +57,24 @@ function scheduleJob(task_key, schedule) {
|
||||
});
|
||||
}
|
||||
|
||||
exports.initialize = async () => {
|
||||
if (db_api.database_initialized) {
|
||||
setupTasks();
|
||||
} else {
|
||||
db_api.database_initialized_bs.subscribe(init => {
|
||||
if (init) setupTasks();
|
||||
});
|
||||
}
|
||||
|
||||
const setupTasks = async () => {
|
||||
const tasks_keys = Object.keys(TASKS);
|
||||
for (let i = 0; i < tasks_keys.length; i++) {
|
||||
const task_key = tasks_keys[i];
|
||||
const task_in_db = await db_api.getRecord('tasks', {key: task_key});
|
||||
if (!task_in_db) {
|
||||
// insert task into table if missing
|
||||
// insert task metadata into table if missing
|
||||
await db_api.insertRecordIntoTable('tasks', {
|
||||
key: task_key,
|
||||
title: TASKS[task_key]['title'],
|
||||
last_ran: null,
|
||||
last_confirmed: null,
|
||||
running: false,
|
||||
@@ -85,12 +108,15 @@ exports.executeTask = async (task_key) => {
|
||||
}
|
||||
|
||||
exports.executeRun = async (task_key) => {
|
||||
logger.verbose(`Running task ${task_key}`);
|
||||
await db_api.updateRecord('tasks', {key: task_key}, {running: true});
|
||||
const data = await TASKS[task_key].run();
|
||||
await db_api.updateRecord('tasks', {key: task_key}, {data: data, last_ran: Date.now()/1000, running: false});
|
||||
logger.verbose(`Finished running task ${task_key}`);
|
||||
}
|
||||
|
||||
exports.executeConfirm = async (task_key) => {
|
||||
logger.verbose(`Confirming task ${task_key}`);
|
||||
if (!TASKS[task_key]['confirm']) {
|
||||
return null;
|
||||
}
|
||||
@@ -98,10 +124,12 @@ exports.executeConfirm = async (task_key) => {
|
||||
const task_obj = await db_api.getRecord('tasks', {key: task_key});
|
||||
const data = task_obj['data'];
|
||||
await TASKS[task_key].confirm(data);
|
||||
await db_api.updateRecord('tasks', {key: task_key}, {confirming: false, last_confirmed: Date.now()/1000});
|
||||
await db_api.updateRecord('tasks', {key: task_key}, {confirming: false, last_confirmed: Date.now()/1000, data: null});
|
||||
logger.verbose(`Finished confirming task ${task_key}`);
|
||||
}
|
||||
|
||||
exports.updateTaskSchedule = async (task_key, schedule) => {
|
||||
logger.verbose(`Updating schedule for task ${task_key}`);
|
||||
await db_api.updateRecord('tasks', {key: task_key}, {schedule: schedule});
|
||||
if (TASKS[task_key]['job']) {
|
||||
TASKS[task_key]['job'].cancel();
|
||||
|
||||
@@ -44,6 +44,7 @@ export type { GetAllDownloadsRequest } from './models/GetAllDownloadsRequest';
|
||||
export type { GetAllDownloadsResponse } from './models/GetAllDownloadsResponse';
|
||||
export type { GetAllFilesResponse } from './models/GetAllFilesResponse';
|
||||
export type { GetAllSubscriptionsResponse } from './models/GetAllSubscriptionsResponse';
|
||||
export type { GetAllTasksResponse } from './models/GetAllTasksResponse';
|
||||
export type { GetDownloadRequest } from './models/GetDownloadRequest';
|
||||
export type { GetDownloadResponse } from './models/GetDownloadResponse';
|
||||
export type { GetFileFormatsRequest } from './models/GetFileFormatsRequest';
|
||||
@@ -63,6 +64,8 @@ export type { GetPlaylistsResponse } from './models/GetPlaylistsResponse';
|
||||
export type { GetRolesResponse } from './models/GetRolesResponse';
|
||||
export type { GetSubscriptionRequest } from './models/GetSubscriptionRequest';
|
||||
export type { GetSubscriptionResponse } from './models/GetSubscriptionResponse';
|
||||
export type { GetTaskRequest } from './models/GetTaskRequest';
|
||||
export type { GetTaskResponse } from './models/GetTaskResponse';
|
||||
export type { GetUsersResponse } from './models/GetUsersResponse';
|
||||
export type { IncrementViewCountRequest } from './models/IncrementViewCountRequest';
|
||||
export type { inline_response_200_15 } from './models/inline_response_200_15';
|
||||
@@ -71,6 +74,7 @@ export type { LoginResponse } from './models/LoginResponse';
|
||||
export type { Playlist } from './models/Playlist';
|
||||
export type { RegisterRequest } from './models/RegisterRequest';
|
||||
export type { RegisterResponse } from './models/RegisterResponse';
|
||||
export { Schedule } from './models/Schedule';
|
||||
export type { SetConfigRequest } from './models/SetConfigRequest';
|
||||
export type { SharingToggle } from './models/SharingToggle';
|
||||
export type { SubscribeRequest } from './models/SubscribeRequest';
|
||||
@@ -79,6 +83,7 @@ export type { Subscription } from './models/Subscription';
|
||||
export type { SubscriptionRequestData } from './models/SubscriptionRequestData';
|
||||
export type { SuccessObject } from './models/SuccessObject';
|
||||
export type { TableInfo } from './models/TableInfo';
|
||||
export type { Task } from './models/Task';
|
||||
export type { TestConnectionStringRequest } from './models/TestConnectionStringRequest';
|
||||
export type { TestConnectionStringResponse } from './models/TestConnectionStringResponse';
|
||||
export type { TransferDBRequest } from './models/TransferDBRequest';
|
||||
@@ -93,6 +98,7 @@ export type { UpdateConcurrentStreamResponse } from './models/UpdateConcurrentSt
|
||||
export type { UpdatePlaylistRequest } from './models/UpdatePlaylistRequest';
|
||||
export type { UpdaterStatus } from './models/UpdaterStatus';
|
||||
export type { UpdateServerRequest } from './models/UpdateServerRequest';
|
||||
export type { UpdateTaskScheduleRequest } from './models/UpdateTaskScheduleRequest';
|
||||
export type { UpdateUserRequest } from './models/UpdateUserRequest';
|
||||
export type { User } from './models/User';
|
||||
export { UserPermission } from './models/UserPermission';
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import { FileType } from './FileType';
|
||||
|
||||
export interface DownloadFileRequest {
|
||||
uid?: string;
|
||||
@@ -9,5 +10,5 @@ export interface DownloadFileRequest {
|
||||
sub_id?: string;
|
||||
playlist_id?: string;
|
||||
url?: string;
|
||||
type?: string;
|
||||
type?: FileType;
|
||||
}
|
||||
9
src/api-types/models/GetAllTasksResponse.ts
Normal file
9
src/api-types/models/GetAllTasksResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import { Task } from './Task';
|
||||
|
||||
export interface GetAllTasksResponse {
|
||||
tasks?: Array<Task>;
|
||||
}
|
||||
@@ -15,8 +15,5 @@ export interface GetFullTwitchChatRequest {
|
||||
* User UID
|
||||
*/
|
||||
uuid?: string;
|
||||
/**
|
||||
* Subscription
|
||||
*/
|
||||
sub?: Subscription;
|
||||
}
|
||||
8
src/api-types/models/GetTaskRequest.ts
Normal file
8
src/api-types/models/GetTaskRequest.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
|
||||
export interface GetTaskRequest {
|
||||
task_key: string;
|
||||
}
|
||||
9
src/api-types/models/GetTaskResponse.ts
Normal file
9
src/api-types/models/GetTaskResponse.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import { Task } from './Task';
|
||||
|
||||
export interface GetTaskResponse {
|
||||
task?: Task;
|
||||
}
|
||||
24
src/api-types/models/Schedule.ts
Normal file
24
src/api-types/models/Schedule.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
|
||||
export interface Schedule {
|
||||
type: Schedule.type;
|
||||
data: {
|
||||
dayOfWeek?: Array<number>,
|
||||
hour?: number,
|
||||
minute?: number,
|
||||
timestamp?: number,
|
||||
};
|
||||
}
|
||||
|
||||
export namespace Schedule {
|
||||
|
||||
export enum type {
|
||||
TIMESTAMP = 'timestamp',
|
||||
RECURRING = 'recurring',
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
15
src/api-types/models/Task.ts
Normal file
15
src/api-types/models/Task.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
|
||||
export interface Task {
|
||||
key: string;
|
||||
last_ran: number;
|
||||
last_confirmed: number;
|
||||
running: boolean;
|
||||
confirming: boolean;
|
||||
data: any;
|
||||
error: string;
|
||||
schedule: any;
|
||||
}
|
||||
10
src/api-types/models/UpdateTaskScheduleRequest.ts
Normal file
10
src/api-types/models/UpdateTaskScheduleRequest.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import { Schedule } from './Schedule';
|
||||
|
||||
export interface UpdateTaskScheduleRequest {
|
||||
task_key: string;
|
||||
new_schedule: Schedule;
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { PostsService } from './posts.services';
|
||||
import { LoginComponent } from './components/login/login.component';
|
||||
import { DownloadsComponent } from './components/downloads/downloads.component';
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { TasksComponent } from './components/tasks/tasks.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'home', component: MainComponent, canActivate: [PostsService] },
|
||||
@@ -17,6 +18,7 @@ const routes: Routes = [
|
||||
{ path: 'settings', component: SettingsComponent, canActivate: [PostsService] },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'downloads', component: DownloadsComponent, canActivate: [PostsService] },
|
||||
{ path: 'tasks', component: TasksComponent, canActivate: [PostsService] },
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' }
|
||||
];
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<a *ngIf="postsService.config && postsService.config.Advanced.multi_user_mode && !postsService.isLoggedIn" mat-list-item (click)="sidenav.close()" routerLink='/login'><ng-container i18n="Navigation menu Login Page title">Login</ng-container></a>
|
||||
<a *ngIf="postsService.config && allowSubscriptions && postsService.hasPermission('subscriptions')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/subscriptions'><ng-container i18n="Navigation menu Subscriptions Page title">Subscriptions</ng-container></a>
|
||||
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/downloads'><ng-container i18n="Navigation menu Downloads Page title">Downloads</ng-container></a>
|
||||
<a *ngIf="postsService.config && enableDownloadsManager && postsService.hasPermission('downloads_manager')" mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/tasks'><ng-container i18n="Navigation menu Tasks Page title">Tasks</ng-container></a>
|
||||
<ng-container *ngIf="postsService.config && postsService.hasPermission('settings')">
|
||||
<mat-divider></mat-divider>
|
||||
<a mat-list-item (click)="postsService.sidepanel_mode === 'over' ? sidenav.close() : null" routerLink='/settings'><ng-container i18n="Settings menu label">Settings</ng-container></a>
|
||||
|
||||
@@ -28,6 +28,7 @@ import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||
import { ClipboardModule } from '@angular/cdk/clipboard';
|
||||
import { TextFieldModule } from '@angular/cdk/text-field';
|
||||
@@ -87,6 +88,8 @@ import { LinkifyPipe, SeeMoreComponent } from './components/see-more/see-more.co
|
||||
import { H401Interceptor } from './http.interceptor';
|
||||
import { ConcurrentStreamComponent } from './components/concurrent-stream/concurrent-stream.component';
|
||||
import { SkipAdButtonComponent } from './components/skip-ad-button/skip-ad-button.component';
|
||||
import { TasksComponent } from './components/tasks/tasks.component';
|
||||
import { UpdateTaskScheduleDialogComponent } from './dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component';
|
||||
|
||||
registerLocaleData(es, 'es');
|
||||
|
||||
@@ -135,7 +138,9 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
TwitchChatComponent,
|
||||
SeeMoreComponent,
|
||||
ConcurrentStreamComponent,
|
||||
SkipAdButtonComponent
|
||||
SkipAdButtonComponent,
|
||||
TasksComponent,
|
||||
UpdateTaskScheduleDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -171,6 +176,7 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
MatDatepickerModule,
|
||||
MatChipsModule,
|
||||
DragDropModule,
|
||||
ClipboardModule,
|
||||
|
||||
77
src/app/components/tasks/tasks.component.html
Normal file
77
src/app/components/tasks/tasks.component.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<div [hidden]="!(tasks && tasks.length > 0)">
|
||||
<div style="overflow: hidden;" [ngClass]="'mat-elevation-z8'">
|
||||
<mat-table style="overflow: hidden" matSort [dataSource]="dataSource">
|
||||
<!-- Title Column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Title">Title</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<span class="one-line" [matTooltip]="element.title ? element.title : null">
|
||||
{{element.title}}
|
||||
</span>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Last Ran Column -->
|
||||
<ng-container matColumnDef="last_ran">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Last ran">Last ran</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.last_ran">{{element.last_ran*1000 | date: 'short'}}</ng-container>
|
||||
<ng-container i18n="N/A" *ngIf="!element.last_ran">N/A</ng-container>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Last Confirmed Column -->
|
||||
<ng-container matColumnDef="last_confirmed">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Last confirmed">Last confirmed</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<ng-container *ngIf="element.last_confirmed">{{element.last_confirmed*1000 | date: 'short'}}</ng-container>
|
||||
<ng-container i18n="N/A" *ngIf="!element.last_confirmed">N/A</ng-container>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Status Column -->
|
||||
<ng-container matColumnDef="status">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> <ng-container i18n="Status">Status</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<span *ngIf="element.running || element.confirming"><mat-spinner matTooltip="Busy" i18n-matTooltip="Busy" [diameter]="25"></mat-spinner></span>
|
||||
<span *ngIf="!(element.running || element.confirming) && element.schedule">
|
||||
<ng-container i18n="Scheduled">Scheduled for</ng-container>
|
||||
{{element.next_invocation | date: 'short'}}<mat-icon style="font-size: 16px; text-align: center;" *ngIf="element.schedule.type === 'recurring'">repeat</mat-icon>
|
||||
</span>
|
||||
<span *ngIf="!(element.running || element.confirming) && !element.schedule">
|
||||
<ng-container i18n="Not scheduled">Not scheduled</ng-container>
|
||||
</span>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<mat-header-cell *matHeaderCellDef> <ng-container i18n="Actions">Actions</ng-container> </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element">
|
||||
<div>
|
||||
<ng-container *ngIf="element.data?.uids?.length > 0">
|
||||
<button (click)="confirmTask(element.key)" [disabled]="element.running || element.confirming" mat-stroked-button>
|
||||
<ng-container *ngIf="element.key == 'missing_files_check'" i18n="Clear missing files from DB">Clear missing files from DB:</ng-container>
|
||||
<ng-container *ngIf="element.key == 'duplicate_files_check'" i18n="Clear duplicate files from DB">Clear duplicate files from DB:</ng-container> {{element.data.uids.length}}
|
||||
</button>
|
||||
</ng-container>
|
||||
<button (click)="runTask(element.key)" [disabled]="element.running || element.confirming" mat-icon-button matTooltip="Run" i18n-matTooltip="Run"><mat-icon>play_arrow</mat-icon></button>
|
||||
<button (click)="scheduleTask(element)" mat-icon-button matTooltip="Schedule" i18n-matTooltip="Schedule"><mat-icon>schedule</mat-icon></button>
|
||||
</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator [pageSizeOptions]="[5, 10, 20]"
|
||||
showFirstLastButtons
|
||||
aria-label="Select page of tasks">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="(!tasks || tasks.length === 0) && tasks_retrieved">
|
||||
<h4 style="text-align: center; margin-top: 10px;" i18n="No tasks label">No tasks available!</h4>
|
||||
</div>
|
||||
32
src/app/components/tasks/tasks.component.scss
Normal file
32
src/app/components/tasks/tasks.component.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
mat-header-cell, mat-cell {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.one-line {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.icon-button-spinner {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 6px;
|
||||
}
|
||||
|
||||
.downloads-action-button-div {
|
||||
margin-top: 10px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.rounded-top {
|
||||
border-radius: 16px 16px 0px 0px !important;
|
||||
}
|
||||
|
||||
.rounded-bottom {
|
||||
border-radius: 0px 0px 16px 16px !important;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 16px 16px 16px 16px !important;
|
||||
}
|
||||
25
src/app/components/tasks/tasks.component.spec.ts
Normal file
25
src/app/components/tasks/tasks.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TasksComponent } from './tasks.component';
|
||||
|
||||
describe('TasksComponent', () => {
|
||||
let component: TasksComponent;
|
||||
let fixture: ComponentFixture<TasksComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TasksComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TasksComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
109
src/app/components/tasks/tasks.component.ts
Normal file
109
src/app/components/tasks/tasks.component.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { UpdateTaskScheduleDialogComponent } from 'app/dialogs/update-task-schedule-dialog/update-task-schedule-dialog.component';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tasks',
|
||||
templateUrl: './tasks.component.html',
|
||||
styleUrls: ['./tasks.component.scss']
|
||||
})
|
||||
export class TasksComponent implements OnInit {
|
||||
|
||||
interval_id = null;
|
||||
tasks_check_interval = 1500;
|
||||
tasks = null;
|
||||
tasks_retrieved = false;
|
||||
|
||||
displayedColumns: string[] = ['title', 'last_ran', 'last_confirmed', 'status', 'actions'];
|
||||
dataSource = null;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
constructor(private postsService: PostsService, private dialog: MatDialog) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.postsService.initialized) {
|
||||
this.getTasksRecurring();
|
||||
} else {
|
||||
this.postsService.service_initialized.subscribe(init => {
|
||||
if (init) {
|
||||
this.getTasksRecurring();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.interval_id) { clearInterval(this.interval_id) }
|
||||
}
|
||||
|
||||
getTasksRecurring(): void {
|
||||
this.getTasks();
|
||||
this.interval_id = setInterval(() => {
|
||||
this.getTasks();
|
||||
}, this.tasks_check_interval);
|
||||
}
|
||||
|
||||
getTasks(): void {
|
||||
this.postsService.getTasks().subscribe(res => {
|
||||
if (this.tasks) {
|
||||
if (JSON.stringify(this.tasks) === JSON.stringify(res['tasks'])) return;
|
||||
for (const task of res['tasks']) {
|
||||
const task_index = this.tasks.map(t => t.key).indexOf(task['key']);
|
||||
this.tasks[task_index] = task;
|
||||
}
|
||||
this.dataSource = new MatTableDataSource<Task>(this.tasks);
|
||||
} else {
|
||||
this.tasks = res['tasks'];
|
||||
this.dataSource = new MatTableDataSource<Task>(this.tasks);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
runTask(task_key: string): void {
|
||||
this.postsService.runTask(task_key).subscribe(res => {
|
||||
this.getTasks();
|
||||
});
|
||||
}
|
||||
|
||||
confirmTask(task_key: string): void {
|
||||
this.postsService.confirmTask(task_key).subscribe(res => {
|
||||
this.getTasks();
|
||||
});
|
||||
}
|
||||
|
||||
scheduleTask(task: any): void {
|
||||
// open dialog
|
||||
const dialogRef = this.dialog.open(UpdateTaskScheduleDialogComponent, {
|
||||
data: {
|
||||
task: task
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(schedule => {
|
||||
if (schedule || schedule === null) {
|
||||
this.postsService.updateTaskSchedule(task['key'], schedule).subscribe(res => {
|
||||
this.getTasks();
|
||||
console.log(res);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
key: string;
|
||||
title: string;
|
||||
last_ran: number;
|
||||
last_confirmed: number;
|
||||
running: boolean;
|
||||
confirming: boolean;
|
||||
data: unknown;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<h4 mat-dialog-title><ng-container i18n="Update task schedule">Update task schedule</ng-container></h4>
|
||||
|
||||
<mat-dialog-content>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12 mt-3">
|
||||
<mat-checkbox [(ngModel)]="enabled"><ng-container i18n="Enabled">Enabled</ng-container></mat-checkbox>
|
||||
</div>
|
||||
<div class="col-12 mt-2">
|
||||
<mat-checkbox [(ngModel)]="recurring" [disabled]="!enabled"><ng-container i18n="Recurring">Recurring</ng-container></mat-checkbox>
|
||||
</div>
|
||||
<div class="col-12 mt-2" *ngIf="recurring">
|
||||
<mat-form-field>
|
||||
<mat-select placeholder="Interval" [(ngModel)]="interval" [disabled]="!enabled">
|
||||
<mat-option value="weekly">Weekly</mat-option>
|
||||
<mat-option value="daily">Daily</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div *ngIf="!recurring" class="col-12 mt-2">
|
||||
<mat-form-field>
|
||||
<mat-label>Choose a date</mat-label>
|
||||
<input [(ngModel)]="date" [min]="today" matInput [matDatepicker]="picker" [disabled]="!enabled">
|
||||
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div *ngIf="recurring && interval === 'weekly'" class="col-12 mt-2">
|
||||
<mat-button-toggle-group [(ngModel)]="days_of_week" [multiple]="true" [disabled]="!enabled" aria-label="Week day">
|
||||
<!-- TODO: support translation -->
|
||||
<mat-button-toggle [value]="0">M</mat-button-toggle>
|
||||
<mat-button-toggle [value]="1">T</mat-button-toggle>
|
||||
<mat-button-toggle [value]="2">W</mat-button-toggle>
|
||||
<mat-button-toggle [value]="3">T</mat-button-toggle>
|
||||
<mat-button-toggle [value]="4">F</mat-button-toggle>
|
||||
<mat-button-toggle [value]="5">S</mat-button-toggle>
|
||||
<mat-button-toggle [value]="6">S</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
</div>
|
||||
<div class="col-12 mt-2">
|
||||
<mat-form-field>
|
||||
<mat-label>Time</mat-label>
|
||||
<input type="time" matInput [(ngModel)]="time" [disabled]="!enabled">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<button mat-button mat-dialog-close><ng-container i18n="Update task schedule cancel button">Cancel</ng-container></button>
|
||||
<button mat-button (click)="updateTaskSchedule()"><ng-container i18n="Update button">Update</ng-container></button>
|
||||
</mat-dialog-actions>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UpdateTaskScheduleDialogComponent } from './update-task-schedule-dialog.component';
|
||||
|
||||
describe('UpdateTaskScheduleDialogComponent', () => {
|
||||
let component: UpdateTaskScheduleDialogComponent;
|
||||
let fixture: ComponentFixture<UpdateTaskScheduleDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ UpdateTaskScheduleDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(UpdateTaskScheduleDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { Schedule } from 'api-types';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-update-task-schedule-dialog',
|
||||
templateUrl: './update-task-schedule-dialog.component.html',
|
||||
styleUrls: ['./update-task-schedule-dialog.component.scss']
|
||||
})
|
||||
export class UpdateTaskScheduleDialogComponent implements OnInit {
|
||||
|
||||
enabled = true;
|
||||
recurring = false;
|
||||
days_of_week = [];
|
||||
interval = 'daily';
|
||||
time = null;
|
||||
date = null;
|
||||
today = new Date();
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: any, private dialogRef: MatDialogRef<UpdateTaskScheduleDialogComponent>, private postsService: PostsService) {
|
||||
this.processTask(this.data.task);
|
||||
this.postsService.getTask(this.data.task.key).subscribe(res => {
|
||||
this.processTask(res['task']);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
processTask(task) {
|
||||
if (!task['schedule']) {
|
||||
this.enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const schedule: Schedule = task['schedule'];
|
||||
|
||||
this.recurring = schedule['type'] === Schedule.type.RECURRING;
|
||||
|
||||
if (this.recurring) {
|
||||
this.time = `${schedule['data']['hour']}:${schedule['data']['minute']}`;
|
||||
|
||||
if (schedule['data']['dayOfWeek']) {
|
||||
this.days_of_week = schedule['data']['dayOfWeek'];
|
||||
this.interval = 'weekly';
|
||||
} else {
|
||||
this.interval = 'daily';
|
||||
}
|
||||
} else {
|
||||
const schedule_date = new Date(schedule['data']['timestamp']);
|
||||
this.time = `${schedule_date.getHours()}:${schedule_date.getMinutes()}`
|
||||
this.date = schedule_date;
|
||||
}
|
||||
}
|
||||
|
||||
updateTaskSchedule(): void {
|
||||
if (!this.enabled) {
|
||||
this.dialogRef.close(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.time) {
|
||||
// needs time!
|
||||
}
|
||||
|
||||
const hours = parseInt(this.time.split(':')[0]);
|
||||
const minutes = parseInt(this.time.split(':')[1]);
|
||||
|
||||
const schedule: Schedule = {type: this.recurring ? Schedule.type.RECURRING : Schedule.type.TIMESTAMP, data: null};
|
||||
if (this.recurring) {
|
||||
schedule['data'] = {hour: hours, minute: minutes};
|
||||
if (this.interval === 'weekly') {
|
||||
schedule['data']['dayOfWeek'] = this.days_of_week;
|
||||
}
|
||||
} else {
|
||||
this.date.setHours(hours, minutes);
|
||||
console.log(this.date);
|
||||
schedule['data'] = {timestamp: this.date.getTime()};
|
||||
}
|
||||
this.dialogRef.close(schedule);
|
||||
}
|
||||
}
|
||||
@@ -318,7 +318,7 @@ export class PlayerComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
const type = this.playlist[0].type;
|
||||
const url = this.playlist[0].url;
|
||||
this.downloading = true;
|
||||
this.postsService.downloadFileFromServer(this.uid, this.uuid, this.sub_id, url, type).subscribe(res => {
|
||||
this.postsService.downloadFileFromServer(this.uid, this.uuid, this.sub_id, url, type as FileType).subscribe(res => {
|
||||
this.downloading = false;
|
||||
const blob: Blob = res;
|
||||
saveAs(blob, filename + ext);
|
||||
|
||||
@@ -90,6 +90,9 @@ import {
|
||||
DBInfoResponse,
|
||||
GetFileFormatsRequest,
|
||||
GetFileFormatsResponse,
|
||||
GetTaskRequest,
|
||||
GetTaskResponse,
|
||||
UpdateTaskScheduleRequest,
|
||||
} from '../api-types';
|
||||
import { isoLangs } from './settings/locales_list';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
@@ -351,7 +354,7 @@ export class PostsService implements CanActivate {
|
||||
return this.http.post<GetAllFilesResponse>(this.path + 'getAllFiles', {sort: sort, range: range, text_search: text_search, file_type_filter: file_type_filter}, this.httpOptions);
|
||||
}
|
||||
|
||||
downloadFileFromServer(uid: string, uuid: string = null, sub_id: string = null, url: string = null, type: string = null) {
|
||||
downloadFileFromServer(uid: string, uuid: string = null, sub_id: string = null, url: string = null, type: FileType = null) {
|
||||
const body: DownloadFileRequest = {
|
||||
uid: uid,
|
||||
uuid: uuid,
|
||||
@@ -544,38 +547,67 @@ export class PostsService implements CanActivate {
|
||||
return this.http.post<GetDownloadResponse>(this.path + 'download', body, this.httpOptions);
|
||||
}
|
||||
|
||||
pauseDownload(download_uid) {
|
||||
return this.http.post<SuccessObject>(this.path + 'pauseDownload', {download_uid: download_uid}, this.httpOptions);
|
||||
pauseDownload(download_uid: string) {
|
||||
const body: GetDownloadRequest = {download_uid: download_uid};
|
||||
return this.http.post<SuccessObject>(this.path + 'pauseDownload', body, this.httpOptions);
|
||||
}
|
||||
|
||||
pauseAllDownloads() {
|
||||
return this.http.post<SuccessObject>(this.path + 'pauseAllDownloads', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
resumeDownload(download_uid) {
|
||||
return this.http.post<SuccessObject>(this.path + 'resumeDownload', {download_uid: download_uid}, this.httpOptions);
|
||||
resumeDownload(download_uid: string) {
|
||||
const body: GetDownloadRequest = {download_uid: download_uid};
|
||||
return this.http.post<SuccessObject>(this.path + 'resumeDownload', body, this.httpOptions);
|
||||
}
|
||||
|
||||
resumeAllDownloads() {
|
||||
return this.http.post<SuccessObject>(this.path + 'resumeAllDownloads', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
restartDownload(download_uid) {
|
||||
return this.http.post<SuccessObject>(this.path + 'restartDownload', {download_uid: download_uid}, this.httpOptions);
|
||||
restartDownload(download_uid: string) {
|
||||
const body: GetDownloadRequest = {download_uid: download_uid};
|
||||
return this.http.post<SuccessObject>(this.path + 'restartDownload', body, this.httpOptions);
|
||||
}
|
||||
|
||||
cancelDownload(download_uid) {
|
||||
return this.http.post<SuccessObject>(this.path + 'cancelDownload', {download_uid: download_uid}, this.httpOptions);
|
||||
cancelDownload(download_uid: string) {
|
||||
const body: GetDownloadRequest = {download_uid: download_uid};
|
||||
return this.http.post<SuccessObject>(this.path + 'cancelDownload', body, this.httpOptions);
|
||||
}
|
||||
|
||||
clearDownload(download_uid) {
|
||||
return this.http.post<SuccessObject>(this.path + 'clearDownload', {download_uid: download_uid}, this.httpOptions);
|
||||
clearDownload(download_uid: string) {
|
||||
const body: GetDownloadRequest = {download_uid: download_uid};
|
||||
return this.http.post<SuccessObject>(this.path + 'clearDownload', body, this.httpOptions);
|
||||
}
|
||||
|
||||
clearFinishedDownloads() {
|
||||
return this.http.post<SuccessObject>(this.path + 'clearFinishedDownloads', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
getTasks() {
|
||||
return this.http.post<SuccessObject>(this.path + 'getTasks', {}, this.httpOptions);
|
||||
}
|
||||
|
||||
getTask(task_key) {
|
||||
const body: GetTaskRequest = {task_key: task_key};
|
||||
return this.http.post<GetTaskResponse>(this.path + 'getTask', body, this.httpOptions);
|
||||
}
|
||||
|
||||
runTask(task_key) {
|
||||
const body: GetTaskRequest = {task_key: task_key};
|
||||
return this.http.post<SuccessObject>(this.path + 'runTask', body, this.httpOptions);
|
||||
}
|
||||
|
||||
confirmTask(task_key) {
|
||||
const body: GetTaskRequest = {task_key: task_key};
|
||||
return this.http.post<SuccessObject>(this.path + 'confirmTask', body, this.httpOptions);
|
||||
}
|
||||
|
||||
updateTaskSchedule(task_key, schedule) {
|
||||
const body: UpdateTaskScheduleRequest = {task_key: task_key, new_schedule: schedule};
|
||||
return this.http.post<SuccessObject>(this.path + 'updateTaskSchedule', body, this.httpOptions);
|
||||
}
|
||||
|
||||
getVersionInfo() {
|
||||
return this.http.get<VersionInfoResponse>(this.path + 'versionInfo', this.httpOptions);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user