Merge pull request #206 from arabcoders/dev
Improve history status display
This commit is contained in:
commit
9427f7b2a0
5 changed files with 81 additions and 50 deletions
14
API.md
14
API.md
|
|
@ -171,21 +171,19 @@ or an error:
|
||||||
**Purpose**: **(Quick Add)** Add a single URL to the download queue via GET.
|
**Purpose**: **(Quick Add)** Add a single URL to the download queue via GET.
|
||||||
|
|
||||||
**Query Parameters**:
|
**Query Parameters**:
|
||||||
- `?url=<video-url>`
|
- `url=<video-url>`
|
||||||
|
- `preset=<preset-name>`
|
||||||
|
|
||||||
**Response**:
|
**Response**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"status": "(ok|error)",
|
"status": true|false, # true if added to the queue. false otherwise.
|
||||||
}
|
"message": "..." # the response message.
|
||||||
```
|
|
||||||
or an error:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "text"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- If `url` is missing, returns `400 Bad Request`.
|
- If `url` is missing, returns `400 Bad Request`.
|
||||||
|
- If `preset` is set and not found, returns `404 Not Found`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,15 @@ Change the the variable `url` and `preset` variables to match your YTPTube insta
|
||||||
|
|
||||||
### iOS Shortcuts
|
### iOS Shortcuts
|
||||||
|
|
||||||
not yet available.
|
You can download [Add To YTPTube](https://www.icloud.com/shortcuts/18b8f70666a04a06aed09424f97ce951) shortcut and use it to send links to your YTPTube instance.
|
||||||
|
You have to edit the shortcut and replace the following:
|
||||||
|
|
||||||
|
- `https://ytp.example.org` with your YTPTube instance.
|
||||||
|
- `username:password` replace this with your own username & password or leave it empty if you don't have authentication enabled.
|
||||||
|
|
||||||
|
This shortcut is powerful, as it's allow you to select your preset on the fly pulled directly from your instance.
|
||||||
|
Combined with the new and powerful presets system, you could add presets for specific websites that need cookies,
|
||||||
|
and use that preset to download directly from your iOS device.
|
||||||
|
|
||||||
## Running behind a reverse proxy
|
## Running behind a reverse proxy
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -614,19 +614,24 @@ class HttpAPI(Common):
|
||||||
|
|
||||||
preset = request.query.get("preset")
|
preset = request.query.get("preset")
|
||||||
if preset:
|
if preset:
|
||||||
exists = Presets.get_instance().get(preset)
|
exists = Presets.get_instance().get(name=preset)
|
||||||
if not exists:
|
if not exists:
|
||||||
return web.json_response(
|
return web.json_response(
|
||||||
data={"error": f"Preset '{preset}' does not exist."}, status=web.HTTPBadRequest.status_code
|
data={"status": False, "message": f"Preset '{preset}' does not exist."},
|
||||||
|
status=web.HTTPBadRequest.status_code,
|
||||||
)
|
)
|
||||||
data["preset"] = preset
|
data["preset"] = preset
|
||||||
|
|
||||||
try:
|
try:
|
||||||
status = await self.add(**self.format_item(data))
|
status = await self.add(**self.format_item(data))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return web.json_response(data={"error": str(e)}, status=web.HTTPBadRequest.status_code)
|
return web.json_response(data={"status": False, "message": str(e)}, status=web.HTTPBadRequest.status_code)
|
||||||
|
|
||||||
return web.json_response(data=status, status=web.HTTPOk.status_code, dumps=self.encoder.encode)
|
return web.json_response(
|
||||||
|
data={"status": status.get("status") == "ok", "message": status.get("msg", "URL added")},
|
||||||
|
status=web.HTTPOk.status_code,
|
||||||
|
dumps=self.encoder.encode,
|
||||||
|
)
|
||||||
|
|
||||||
@route("POST", "api/history")
|
@route("POST", "api/history")
|
||||||
async def history_item_add(self, request: Request) -> Response:
|
async def history_item_add(self, request: Request) -> Response:
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,7 @@ class Presets(metaclass=Singleton):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(self._file, "w") as f:
|
with open(self._file, "w") as f:
|
||||||
json.dump(obj=[preset.serialize() for preset in presets], fp=f, indent=4)
|
json.dump(obj=[preset.serialize() for preset in presets if preset.default is False], fp=f, indent=4)
|
||||||
|
|
||||||
LOG.info(f"Presets saved to '{self._file}'.")
|
LOG.info(f"Presets saved to '{self._file}'.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-header-icon">
|
<div class="card-header-icon">
|
||||||
|
<a v-if="hideThumbnail && 'finished' === item.status" href="#" @click.prevent="playVideo(item)"
|
||||||
|
v-tooltip="'Play video.'">
|
||||||
|
<span class="icon"><i class="fa-solid fa-play" /></span>
|
||||||
|
</a>
|
||||||
<a :href="item.url" class="has-text-primary" v-tooltip="'Copy url.'" @click.prevent="copyText(item.url)">
|
<a :href="item.url" class="has-text-primary" v-tooltip="'Copy url.'" @click.prevent="copyText(item.url)">
|
||||||
<span class="icon"><i class="fa-solid fa-copy" /></span>
|
<span class="icon"><i class="fa-solid fa-copy" /></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -118,11 +122,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="columns is-mobile is-multiline">
|
<div class="columns is-mobile is-multiline">
|
||||||
<div class="column is-12" v-if="item.live_in">
|
|
||||||
<span class="has-text-info">
|
|
||||||
Live stream is scheduled to start at {{ moment(item.live_in).format() }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="column is-12" v-if="item.error">
|
<div class="column is-12" v-if="item.error">
|
||||||
<span class="has-text-danger">{{ item.error }}</span>
|
<span class="has-text-danger">{{ item.error }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -130,23 +129,16 @@
|
||||||
<span class="has-text-danger">{{ item.msg }}</span>
|
<span class="has-text-danger">{{ item.msg }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half-mobile has-text-centered is-text-overflow">
|
<div class="column is-half-mobile has-text-centered is-text-overflow">
|
||||||
<span v-if="!item.live_in && !item.is_live">
|
<span class="icon-text">
|
||||||
<span class="icon-text">
|
<span class="icon" :class="setIconColor(item)"><i :class="setIcon(item)" /></span>
|
||||||
<span class="icon"
|
<span>{{ setStatus(item) }}</span>
|
||||||
:class="{ 'has-text-success': item.status === 'finished', 'has-text-danger': item.status !== 'finished' }">
|
|
||||||
<i :class="setIcon(item)" />
|
|
||||||
</span>
|
|
||||||
<span v-if="item.status == 'finished' && item.is_live">Live Ended</span>
|
|
||||||
<span v-else>{{ ucFirst(item.status) }}</span>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
<span v-else>
|
</div>
|
||||||
<span class="icon-text">
|
<div class="column is-half-mobile has-text-centered is-text-overflow"
|
||||||
<span class="icon has-text-info">
|
v-if="item.live_in && 'not_live' === item.status">
|
||||||
<i class="fa-solid fa-calendar" />
|
<span :date-datetime="item.live_in" class="user-hint"
|
||||||
</span>
|
v-tooltip="'Starts at: ' + moment(item.live_in).format('YYYY-M-DD H:mm Z')">
|
||||||
<span>Live</span>
|
{{ moment(item.live_in).fromNow() }}
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half-mobile has-text-centered is-text-overflow">
|
<div class="column is-half-mobile has-text-centered is-text-overflow">
|
||||||
|
|
@ -155,13 +147,6 @@
|
||||||
{{ moment(item.datetime).fromNow() }}
|
{{ moment(item.datetime).fromNow() }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-half-mobile has-text-centered is-text-overflow"
|
|
||||||
v-if="item.live_in && item.status != 'finished'">
|
|
||||||
<span :date-datetime="item.live_in"
|
|
||||||
v-tooltip="'Starts at: ' + moment(item.live_in).format('YYYY-M-DD H:mm Z')">
|
|
||||||
{{ moment(item.live_in).fromNow() }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="column is-half-mobile has-text-centered is-text-overflow" v-if="item.file_size">
|
<div class="column is-half-mobile has-text-centered is-text-overflow" v-if="item.file_size">
|
||||||
{{ formatBytes(item.file_size) }}
|
{{ formatBytes(item.file_size) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -400,25 +385,61 @@ const clearIncomplete = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const setIcon = item => {
|
const setIcon = item => {
|
||||||
if (item.status === 'finished' && item.is_live) {
|
if ('finished' === item.status) {
|
||||||
return 'fa-solid fa-globe'
|
return item.is_live ? 'fa-solid fa-globe' : 'fa-solid fa-circle-check'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.status === 'finished') {
|
if ('error' === item.status) {
|
||||||
return 'fa-solid fa-circle-check'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.status === 'error') {
|
|
||||||
return 'fa-solid fa-circle-xmark'
|
return 'fa-solid fa-circle-xmark'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.status === 'cancelled') {
|
if ('cancelled' === item.status) {
|
||||||
return 'fa-solid fa-eject'
|
return 'fa-solid fa-eject'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('not_live' === item.status) {
|
||||||
|
return 'fa-solid fa-hourglass-half fa-spin'
|
||||||
|
}
|
||||||
|
|
||||||
return 'fa-solid fa-circle'
|
return 'fa-solid fa-circle'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setIconColor = item => {
|
||||||
|
if (item.status === 'finished') {
|
||||||
|
return 'has-text-success'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('not_live' === item.status) {
|
||||||
|
return 'has-text-info'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('cancelled' === item.status) {
|
||||||
|
return 'has-text-warning'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'has-text-danger'
|
||||||
|
}
|
||||||
|
|
||||||
|
const setStatus = item => {
|
||||||
|
if ('finished' === item.status) {
|
||||||
|
return item.is_live ? 'Live Ended' : 'Completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('error' === item.status) {
|
||||||
|
return 'Error'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('cancelled' === item.status) {
|
||||||
|
return 'User Cancelled'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('not_live' === item.status) {
|
||||||
|
return 'Live Stream'
|
||||||
|
}
|
||||||
|
|
||||||
|
return item.status
|
||||||
|
}
|
||||||
|
|
||||||
const requeueIncomplete = () => {
|
const requeueIncomplete = () => {
|
||||||
if (false === confirm('Are you sure you want to re-queue all incomplete downloads?')) {
|
if (false === confirm('Are you sure you want to re-queue all incomplete downloads?')) {
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue