push latest changes
This commit is contained in:
parent
7523a19d1f
commit
cb2b5c2c2b
63 changed files with 3158 additions and 1552 deletions
|
@ -10,21 +10,21 @@
|
|||
{{ bootstrap.load_css() }}
|
||||
<link rel="stylesheet" href="{{url_for('static', filename='theme.css')}}">
|
||||
{% endblock %}
|
||||
<link rel="shortcut icon" type="image/svg" href="{{url_for('static',filename='icon.svg')}}"/>
|
||||
<title>MediaDash</title>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% block navbar %}
|
||||
<nav class="navbar sticky-top navbar-expand-lg navbar-dark" style="background-color: #222;">
|
||||
<a class="navbar-brand" href="/">MediaDash</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar_main" aria-controls="navbar_main" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
{% if request.path!=url_for("login") %}
|
||||
<img src="{{url_for('static',filename='icon.svg')}}" width=40 height=40/>
|
||||
{% endif %}
|
||||
<div class="collapse navbar-collapse" id="navbar_main">
|
||||
{{nav.left_nav.render(renderer='bootstrap4')}}
|
||||
{{nav.right_nav.render(renderer='bootstrap4')}}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<div class={{"container-fluid" if fluid else "container"}}>
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<h1>
|
||||
<a href="{{config.APP_CONFIG.portainer_url}}">Portainer</a>
|
||||
</h1>
|
||||
<table class="table table-sm">
|
||||
<table class="table table-sm table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
|
@ -42,7 +42,7 @@
|
|||
{% set label = container.Labels["com.docker.compose.service"] %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{url_for('containers',container_id=container.Id)}}">
|
||||
<a href="{{url_for('containers.details',container_id=container.Id)}}">
|
||||
{{container.Labels["com.docker.compose.project"]}}/{{container.Labels["com.docker.compose.service"]}}
|
||||
</a>
|
||||
</td>
|
||||
|
|
8
templates/error.html
Normal file
8
templates/error.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'utils.html' import custom_render_form_row,make_tabs %}
|
||||
|
||||
{% block app_content %}
|
||||
<div class="container" style="max-width: 30% !important;">
|
||||
<h1>Oops</h1>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -41,62 +41,89 @@
|
|||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<h3>Movies</h3>
|
||||
<table class="table table-sm">
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>In Cinemas</th>
|
||||
<th>Digital Release</th>
|
||||
</tr>
|
||||
{% for movie in data.calendar.movies %}
|
||||
{% if movie.isAvailable and movie.hasFile %}
|
||||
{% set row_class = "bg-success" %}
|
||||
{% set row_attrs = "bg-success" %}
|
||||
{% elif movie.isAvailable and not movie.hasFile %}
|
||||
{% set row_class = "bg-danger" %}
|
||||
{% set row_attrs = "bg-danger" %}
|
||||
{% elif not movie.isAvailable and movie.hasFile %}
|
||||
{% set row_class = "bg-primary" %}
|
||||
{% set row_attrs = "bg-primary" %}
|
||||
{% elif not movie.isAvailable and not movie.hasFile %}
|
||||
{% set row_class = "bg-info" %}
|
||||
{% set row_attrs = "bg-info" %}
|
||||
{% endif %}
|
||||
<tr class={{row_class}}>
|
||||
<tr class={{row_attrs}}>
|
||||
<td>
|
||||
<a href="{{urljoin(config.APP_CONFIG.radarr_url,'movie/'+movie.titleSlug)}}" style="color: #eee; text-decoration: underline;">
|
||||
<a title="{{movie.overview}}" href="{{urljoin(config.APP_CONFIG.radarr_url,'movie/'+movie.titleSlug)}}" style="color: #eee; text-decoration: underline;">
|
||||
{{movie.title}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{movie.status}}</td>
|
||||
<td>{{movie.inCinemas|fromiso|ago_dt_utc_human(rnd=0)}}</td>
|
||||
<td>{{movie.digitalRelease|fromiso|ago_dt_utc_human(rnd=0)}}</td>
|
||||
{% if movie.digitalRelease %}
|
||||
<td>{{movie.digitalRelease|fromiso|ago_dt_utc_human(rnd=0)}}</td>
|
||||
{% else %}
|
||||
<td>Unknown</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<h3>Episodes</h3>
|
||||
|
||||
<table class="table table-sm">
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<th>Season | Episode Number</th>
|
||||
<th>Show</th>
|
||||
<th>Title</th>
|
||||
<th>Status</th>
|
||||
<th>Air Date</th>
|
||||
</tr>
|
||||
{% for entry in data.calendar.episodes %}
|
||||
{% if entry.episode.hasAired and entry.episode.hasFile %}
|
||||
{% set row_class = "bg-success" %}
|
||||
{% elif entry.episode.hasAired and not entry.episode.hasFile %}
|
||||
{% set row_class = "bg-danger" %}
|
||||
{% elif not entry.episode.hasAired and entry.episode.hasFile %}
|
||||
{% set row_class = "bg-primary" %}
|
||||
{% elif not entry.episode.hasAired and not entry.episode.hasFile %}
|
||||
{% set row_class = "bg-info" %}
|
||||
{% endif %}
|
||||
<tr class={{row_class}}>
|
||||
<td>{{entry.episode.seasonNumber}} | {{entry.episode.episodeNumber}}</td>
|
||||
<td>
|
||||
<a href="{{urljoin(config.APP_CONFIG.sonarr_url,'series/'+entry.series.titleSlug)}}" style="color: #eee; text-decoration: underline;">
|
||||
{{entry.series.title}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{entry.episode.title}}</td>
|
||||
<td>{{entry.episode.airDateUtc|fromiso|ago_dt_utc_human(rnd=0)}}</td>
|
||||
</tr>
|
||||
{% if entry.details %}
|
||||
{% set details = entry.details[0] %}
|
||||
{% endif %}
|
||||
{% if entry.episode.hasAired and entry.episode.hasFile %}
|
||||
{% set row_attrs = {"class":"bg-success"} %}
|
||||
{% elif entry.episode.hasAired and not entry.episode.hasFile and details %}
|
||||
{% set row_attrs = {"style":"background-color: green !important"} %}
|
||||
{% elif entry.episode.hasAired and not entry.episode.hasFile %}
|
||||
{% set row_attrs = {"class":"bg-danger"} %}
|
||||
{% elif not entry.episode.hasAired and entry.episode.hasFile %}
|
||||
{% set row_attrs = {"class":"bg-primary"} %}
|
||||
{% elif not entry.episode.hasAired and not entry.episode.hasFile %}
|
||||
{% set row_attrs = {"class":"bg-info"} %}
|
||||
{% endif %}
|
||||
<tr {{row_attrs|xmlattr}}>
|
||||
<td>{{entry.episode.seasonNumber}} | {{entry.episode.episodeNumber}}</td>
|
||||
<td>
|
||||
<a href="{{urljoin(config.APP_CONFIG.sonarr_url,'series/'+entry.series.titleSlug)}}" style="color: #eee; text-decoration: underline;">
|
||||
{{entry.series.title}}
|
||||
</a>
|
||||
</td>
|
||||
<td title="{{entry.episode.overview}}">{{entry.episode.title}}</td>
|
||||
<td>
|
||||
{% if details %}
|
||||
{% set details = entry.details[0] %}
|
||||
{% set dl_prc =((details.size-details.sizeleft)/details.size)*100|round(2) %}
|
||||
{{details.status}} ({{dl_prc|round(2)}} %)
|
||||
{% elif row_attrs.class=="bg-success" %}
|
||||
downloaded
|
||||
{% elif row_attrs.class=="bg-danger" %}
|
||||
not downloaded
|
||||
{% elif row_attrs.class=="bg-primary" %}
|
||||
leaked?
|
||||
{% elif row_attrs.class=="bg-info" %}
|
||||
not aired
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{entry.episode.airDateUtc|fromiso|ago_dt_utc_human(rnd=0)}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
|
@ -109,7 +136,7 @@
|
|||
<h2>No Data available!</h2>
|
||||
{% else %}
|
||||
{% set tabs = [] %}
|
||||
{% do tabs.append(("Upcoming",[upcoming(data)])) %}
|
||||
{% do tabs.append(("Schedule",[upcoming(data)])) %}
|
||||
{% for row in data.images %}
|
||||
{% if row[0] is string %}
|
||||
{% set title=row[0] %}
|
||||
|
|
20
templates/jellyfin/details.html
Normal file
20
templates/jellyfin/details.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'utils.html' import custom_render_form_row,make_tabs %}
|
||||
{% from 'bootstrap/utils.html' import render_icon %}
|
||||
{% from 'bootstrap/form.html' import render_form, render_field, render_form_row %}
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
<h2><a href={{info.LocalAddress}}>Jellyfin</a> v{{info.Version}}</h2>
|
||||
{% if status.HasUpdateAvailable %}
|
||||
<h3>Update available</h3>
|
||||
{% endif %}
|
||||
{% if status.HasPendingRestart %}
|
||||
<h3>Restart pending</h3>
|
||||
{% endif %}
|
||||
|
||||
<img src={{cfg.jellyfin_url}}/Items/{{item.Id}}/Images/Art>
|
||||
|
||||
<pre>{{item|pformat}}</pre>
|
||||
|
||||
{% endblock %}
|
|
@ -3,119 +3,80 @@
|
|||
{% from 'bootstrap/utils.html' import render_icon %}
|
||||
{% from 'bootstrap/form.html' import render_form, render_field, render_form_row %}
|
||||
|
||||
{% macro make_row(title,items) %}
|
||||
<div class="d-flex flex-wrap">
|
||||
{% for item in items %}
|
||||
{{item|safe}}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro make_tabs(tabs) %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
|
||||
{% for (label,_) in tabs %}
|
||||
{% set slug = (label|slugify) %}
|
||||
{% if not (loop.first and loop.last) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{'active' if loop.first}}" id="nav-{{slug}}-tab" data-toggle="pill" href="#pills-{{slug}}" role="tab" aria-controls="pills-{{slug}}" aria-selected="{{loop.first}}">
|
||||
{{label}}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="searchResults">
|
||||
{% for (label,items) in tabs %}
|
||||
{% set slug = (label|slugify) %}
|
||||
<div class="tab-pane fade {{'show active' if loop.first}}" id="pills-{{slug}}" role="tabpanel" aria-labelledby="nav-{{slug}}-tab">
|
||||
{{make_row(label,items)}}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro make_table(items) %}
|
||||
<table class="table table-sm table-bordered">
|
||||
{% for item in items|sort(attribute="Name")%}
|
||||
<tr>
|
||||
<td><a href="{{url_for('jellyfin.details',item_id=item.Id)}}">{{item.Name}}</a> ({{item.ProductionYear}})</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endmacro %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1><a href={{info.LocalAddress}}>Jellyfin</a> v{{info.Version}}</h1>
|
||||
{% if status.HasUpdateAvailable %}
|
||||
<h3>Update available</h3>
|
||||
{% endif %}
|
||||
{% if status.HasPendingRestart %}
|
||||
<h3>Restart pending</h3>
|
||||
{% endif %}
|
||||
|
||||
<h2><a href={{jellyfin.info.LocalAddress}}>Jellyfin</a> v{{jellyfin.info.Version}}</h2>
|
||||
<h3>Library statistics</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<h4>Active Streams</h4>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Episode</th>
|
||||
<th>Show</th>
|
||||
<th>Language</th>
|
||||
<th>User</th>
|
||||
<th>Device</th>
|
||||
<th>Mode</th>
|
||||
</tr>
|
||||
{% for session in jellyfin.sessions %}
|
||||
{% if "NowPlayingItem" in session %}
|
||||
{% with np=session.NowPlayingItem, ps=session.PlayState%}
|
||||
<tr>
|
||||
<td>
|
||||
{% if session.SupportsMediaControl %}
|
||||
<a href="{{url_for('stop_stream',session=session.Id)}}">
|
||||
{{render_icon("stop-circle")}}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/details?id={{np.Id}}">
|
||||
{{np.Name}}
|
||||
</a>
|
||||
({{(ps.PositionTicks/10_000_000)|timedelta(digits=0)}}/{{(np.RunTimeTicks/10_000_000)|timedelta(digits=0)}})
|
||||
{% if ps.IsPaused %}
|
||||
(Paused)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/details?id={{np.SeriesId}}">
|
||||
{{np.SeriesName}}
|
||||
</a>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/details?id={{np.SeasonId}}">
|
||||
({{np.SeasonName}})
|
||||
</a>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{% if ("AudioStreamIndex" in ps) and ("SubtitleStreamIndex" in ps) %}
|
||||
{{np.MediaStreams[ps.AudioStreamIndex].Language or "None"}}/{{np.MediaStreams[ps.SubtitleStreamIndex].Language or "None"}}
|
||||
{% else %}
|
||||
Unk/Unk
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/useredit.html?userId={{session.UserId}}">
|
||||
{{session.UserName}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{session.DeviceName}}
|
||||
</td>
|
||||
<td>
|
||||
{% if ps.PlayMethod =="Transcode" %}
|
||||
<p title="{{session.TranscodingInfo.Bitrate|filesizeformat(binary=False)}}/s | {{session.TranscodingInfo.CompletionPercentage|round(2)}}%">
|
||||
{{ps.PlayMethod}}
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
{{ps.PlayMethod}}
|
||||
</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
<table class="table table-sm table-bordered">
|
||||
{% for name, value in counts.items() %}
|
||||
{% if value != 0 %}
|
||||
<tr>
|
||||
<td>{{name}}</td>
|
||||
<td>{{value}}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</table>
|
||||
{% if library %}
|
||||
<h3>{{library|count}} Items</h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<h4>Users</h4>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Last Login</th>
|
||||
<th>Last Active</th>
|
||||
<th>Bandwidth Limit</th>
|
||||
</tr>
|
||||
{% for user in jellyfin.users|sort(attribute="LastLoginDate",reverse=True) %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/useredit.html?userId={{user.Id}}">
|
||||
{{user.Name}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if "LastLoginDate" in user %}
|
||||
{{user.LastLoginDate|fromiso|ago_dt_utc(2)}} ago
|
||||
{% else %}
|
||||
Never
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if "LastActivityDate" in user %}
|
||||
{{user.LastActivityDate|fromiso|ago_dt_utc(2)}} ago
|
||||
{% else %}
|
||||
Never
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{user.Policy.RemoteClientBitrateLimit|filesizeformat(binary=False)}}/s</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% set tabs = [] %}
|
||||
{% for title,group in library.values()|groupby("Type") %}
|
||||
{% do tabs.append((title,[make_table(group)])) %}
|
||||
{% endfor %}
|
||||
{{make_tabs(tabs)}}
|
||||
{% endblock %}
|
||||
|
|
56
templates/jellyfin/movie.html
Normal file
56
templates/jellyfin/movie.html
Normal file
|
@ -0,0 +1,56 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'utils.html' import custom_render_form_row,make_tabs %}
|
||||
{% from 'bootstrap/utils.html' import render_icon %}
|
||||
{% from 'bootstrap/form.html' import render_form, render_field, render_form_row %}
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
<h1><a href={{info.LocalAddress}}>Jellyfin</a> v{{info.Version}}</h1>
|
||||
{% if status.HasUpdateAvailable %}
|
||||
<h3>Update available</h3>
|
||||
{% endif %}
|
||||
{% if status.HasPendingRestart %}
|
||||
<h3>Restart pending</h3>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{% for ext in item.ExternalUrls %}
|
||||
<a href={{ext.Url}}><span class="badge badge-secondary">{{ext.Name}}</span></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 title="{{item.Id}}">
|
||||
{{item.Name}}
|
||||
{% if item.IsHD %}
|
||||
<span class="badge badge-primary">HD</span>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<p>{{item.Overview}}</p>
|
||||
|
||||
<hr>
|
||||
|
||||
{# <img src={{info.LocalAddress}}/Items/{{item.Id}}/Images/Primary> #}
|
||||
|
||||
{% set data = [
|
||||
("Path", item.Path),
|
||||
("Genres", item.Genres|join(", ")),
|
||||
] %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-sm table-bordered">
|
||||
{% for k,v in data %}
|
||||
<tr>
|
||||
<td>{{k}}</td>
|
||||
<td>{{v}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pre>{{item|pformat}}</pre>
|
||||
|
||||
{% endblock %}
|
70
templates/jellyfin/series.html
Normal file
70
templates/jellyfin/series.html
Normal file
|
@ -0,0 +1,70 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'utils.html' import custom_render_form_row,make_tabs %}
|
||||
{% from 'bootstrap/utils.html' import render_icon %}
|
||||
{% from 'bootstrap/form.html' import render_form, render_field, render_form_row %}
|
||||
|
||||
{% block app_content %}
|
||||
|
||||
<h1><a href={{info.LocalAddress}}>Jellyfin</a> v{{info.Version}}</h1>
|
||||
{% if status.HasUpdateAvailable %}
|
||||
<h3>Update available</h3>
|
||||
{% endif %}
|
||||
{% if status.HasPendingRestart %}
|
||||
<h3>Restart pending</h3>
|
||||
{% endif %}
|
||||
|
||||
<h2 title="{{item.Id}}"><a href="{{info.LocalAddress}}/web/index.html#!/details?id={{item.Id}}">{{item.Name}}</a></h2>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{% for ext in item.ExternalUrls %}
|
||||
<a href={{ext.Url}}><span class="badge badge-secondary">{{ext.Name}}</span></a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<img class="rounded" src={{info.LocalAddress}}/Items/{{item.Id}}/Images/Primary>
|
||||
</div>
|
||||
|
||||
<p>{{item.Overview}}</p>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<td>Path</td>
|
||||
<td>{{item.Path}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{% for season in item.Seasons %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h3>{{season.Name}}</h3>
|
||||
<table class="table table-sm table-bordered">
|
||||
{% for episode in season.Episodes %}
|
||||
<tr>
|
||||
<td>{{episode.Name}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<table class="table table-sm table-bordered">
|
||||
{% for k,v in item|flatten %}
|
||||
<tr>
|
||||
<td>{{k}}</td>
|
||||
<td>{{v}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
16
templates/login.html
Normal file
16
templates/login.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'utils.html' import custom_render_form_row,make_tabs %}
|
||||
|
||||
{% block app_content %}
|
||||
<img src="{{url_for('static',filename='icon.svg')}}" class="mx-auto d-block" width=150 height=150/>
|
||||
<div class="container" style="max-width: 30% !important;">
|
||||
<h2>Login</h2>
|
||||
<form method="post" class="form">
|
||||
{{form.csrf_token()}}
|
||||
{{custom_render_form_row([form.username])}}
|
||||
{{custom_render_form_row([form.password])}}
|
||||
{{custom_render_form_row([form.remember])}}
|
||||
{{custom_render_form_row([form.login])}}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -208,9 +208,11 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<h2>Trackers</h2>
|
||||
<a href="{{url_for('qbittorent_add_trackers',infohash=qbt.info.hash)}}">
|
||||
<span class="badge badge-primary">Add default trackers</span>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{url_for('qbittorrent.add_trackers',infohash=qbt.info.hash)}}">
|
||||
<span class="badge badge-primary">Add default trackers</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{% set state_label,badge_type = status_map[torrent.state] or (torrent.state,'light') %}
|
||||
|
||||
<li class="list-group-item">
|
||||
<a href="{{url_for('qbittorrent_details',infohash=torrent.hash)}}">{{torrent.name|truncate(75)}}</a>
|
||||
<a href="{{url_for('qbittorrent.details',infohash=torrent.hash)}}">{{torrent.name|truncate(75)}}</a>
|
||||
(DL: {{torrent.dlspeed|filesizeformat(binary=true)}}/s, UL: {{torrent.upspeed|filesizeformat(binary=true)}}/s)
|
||||
<span class="badge badge-{{badge_type}}">{{state_label}}</span>
|
||||
{% if torrent.category %}
|
||||
|
@ -27,12 +27,12 @@
|
|||
|
||||
{% block app_content %}
|
||||
|
||||
<h2>
|
||||
<h1>
|
||||
<a href="{{config.APP_CONFIG.qbt_url}}">QBittorrent</a>
|
||||
{{qbt.version}}
|
||||
(DL: {{qbt.server_state.dl_info_speed|filesizeformat(binary=True)}}/s,
|
||||
UL: {{qbt.server_state.up_info_speed|filesizeformat(binary=True)}}/s)
|
||||
</h2>
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
@ -99,7 +99,7 @@
|
|||
{% set state_label,badge_type = status_map[state] or (state,'light') %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href={{url_for("qbittorrent",state=state)}} >{{state_label}}</a>
|
||||
<a href={{url_for("qbittorrent.index",state=state)}} >{{state_label}}</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
{{torrents|length}}
|
||||
|
@ -110,7 +110,7 @@
|
|||
{% if state_filter %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<a href={{url_for("qbittorrent")}}>[Clear filter]</a>
|
||||
<a href={{url_for("qbittorrent.index")}}>[Clear filter]</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
</div>
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
{% endmacro %}
|
||||
|
||||
{% block app_content %}
|
||||
<h2>
|
||||
<h1>
|
||||
<a href="{{config.APP_CONFIG.radarr_url}}">Radarr</a>
|
||||
v{{status.version}} ({{movies|count}} Movies)
|
||||
</h2>
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
{% block app_content %}
|
||||
|
||||
<h1>
|
||||
Remote access <a href={{url_for("remote_add")}}>{{render_icon("person-plus-fill")}}</a>
|
||||
Remote access <a href={{url_for("remote.add")}}>{{render_icon("person-plus-fill")}}</a>
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<h4>SSH</h4>
|
||||
<table class="table table-sm">
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Type</th>
|
||||
|
@ -23,9 +23,9 @@ Remote access <a href={{url_for("remote_add")}}>{{render_icon("person-plus-fill"
|
|||
<tr {{ {"class":"text-muted" if key.disabled else none}|xmlattr }}>
|
||||
<td>
|
||||
{% if key.disabled %}
|
||||
<a href="{{url_for("remote",enabled=True,key=key.key)}}">{{render_icon("person-x-fill",color='danger')}}</a>
|
||||
<a href="{{url_for("remote.index",enabled=True,key=key.key)}}">{{render_icon("person-x-fill",color='danger')}}</a>
|
||||
{% else %}
|
||||
<a href="{{url_for("remote",enabled=False,key=key.key)}}">{{render_icon("person-check-fill",color='success')}}</a>
|
||||
<a href="{{url_for("remote.index",enabled=False,key=key.key)}}">{{render_icon("person-check-fill",color='success')}}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{key.type}}</td>
|
||||
|
@ -37,17 +37,99 @@ Remote access <a href={{url_for("remote_add")}}>{{render_icon("person-plus-fill"
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<h4>Active Streams</h4>
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<th>Episode</th>
|
||||
<th>Show</th>
|
||||
<th>Language</th>
|
||||
<th>User</th>
|
||||
<th>Device</th>
|
||||
<th>Mode</th>
|
||||
</tr>
|
||||
{% for session in jellyfin.sessions %}
|
||||
{% if "NowPlayingItem" in session %}
|
||||
{% with np=session.NowPlayingItem, ps=session.PlayState%}
|
||||
<tr>
|
||||
<td>
|
||||
{% if session.SupportsMediaControl %}
|
||||
<a href="{{url_for('remote.stop',session=session.Id)}}">
|
||||
{{render_icon("stop-circle")}}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a title="{{ps.MediaSourceId}}" href="{{cfg().jellyfin_url}}web/index.html#!/details?id={{np.Id}}">
|
||||
{{np.Name}}
|
||||
</a>
|
||||
({{(ps.PositionTicks/10_000_000)|timedelta(digits=0)}}/{{(np.RunTimeTicks/10_000_000)|timedelta(digits=0)}})
|
||||
{% if ps.IsPaused %}
|
||||
(Paused)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/details?id={{np.SeriesId}}">
|
||||
{{np.SeriesName}}
|
||||
</a>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/details?id={{np.SeasonId}}">
|
||||
({{np.SeasonName}})
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if ("AudioStreamIndex" in ps) and ("SubtitleStreamIndex" in ps) %}
|
||||
{% if ps.AudioStreamIndex == -1 %}
|
||||
{% set audio_lang = "-" %}
|
||||
{% else %}
|
||||
{% set audio_lang = np.MediaStreams[ps.AudioStreamIndex].Language or "?" %}
|
||||
{% endif %}
|
||||
{% if ps.SubtitleStreamIndex == -1 %}
|
||||
{% set subtitle_lang = "-" %}
|
||||
{% else %}
|
||||
{% set subtitle_lang = np.MediaStreams[ps.AudioStreamIndex].Language or "?" %}
|
||||
{% endif %}
|
||||
{{audio_lang}}/{{subtitle_lang}}
|
||||
{% else %}
|
||||
?/?
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/useredit.html?userId={{session.UserId}}">
|
||||
{{session.UserName}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{session.DeviceName}}
|
||||
</td>
|
||||
<td>
|
||||
{% if ps.PlayMethod =="Transcode" %}
|
||||
<p title="{{session.TranscodingInfo.Bitrate|filesizeformat(binary=False)}}/s | {{session.TranscodingInfo.CompletionPercentage|round(2)}}%">
|
||||
{{ps.PlayMethod}}
|
||||
</p>
|
||||
{% else %}
|
||||
<p>
|
||||
{{ps.PlayMethod}}
|
||||
</p>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg">
|
||||
<h4><a href="{{cfg().jellyfin_url}}web/index.html#!/userprofiles.html">Jellyfin</a></h4>
|
||||
<table class="table table-sm">
|
||||
<table class="table table-sm table-bordered">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Last Login</th>
|
||||
<th>Last Active</th>
|
||||
<th>Bandwidth Limit</th>
|
||||
</tr>
|
||||
{% for user in jf|sort(attribute="LastLoginDate",reverse=True) %}
|
||||
{% for user in jellyfin.users|defaultattr("LastLoginDate","")|sort(attribute="LastLoginDate",reverse=True) %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{cfg().jellyfin_url}}web/index.html#!/useredit.html?userId={{user.Id}}">
|
||||
|
@ -55,20 +137,26 @@ Remote access <a href={{url_for("remote_add")}}>{{render_icon("person-plus-fill"
|
|||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if "LastLoginDate" in user %}
|
||||
{% if user.LastLoginDate %}
|
||||
{{user.LastLoginDate|fromiso|ago_dt_utc(2)}} ago
|
||||
{% else %}
|
||||
Never
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if "LastActivityDate" in user %}
|
||||
{% if user.LastActivityDate %}
|
||||
{{user.LastActivityDate|fromiso|ago_dt_utc(2)}} ago
|
||||
{% else %}
|
||||
Never
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{user.Policy.RemoteClientBitrateLimit|filesizeformat(binary=False)}}/s</td>
|
||||
<td>
|
||||
{% if user.Policy.RemoteClientBitrateLimit!=0 %}
|
||||
{{user.Policy.RemoteClientBitrateLimit|filesizeformat(binary=False)}}/s
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
54
templates/requests/details.html
Normal file
54
templates/requests/details.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'bootstrap/utils.html' import render_icon %}
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Request details</h1>
|
||||
{% set data = request.data|fromjson%}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{% set label = {True:"Approved",False:"Declined",None:"Pending"}[request.approved] %}
|
||||
{% set class = {True:"bg-success",False:"bg-danger", None: ""}[request.approved] %}
|
||||
{% if request.approved and jellyfin_id %}
|
||||
{% set label = "Approved and Downloaded" %}
|
||||
{% endif %}
|
||||
{% if data.tvdbId %}
|
||||
{% set link="https://www.thetvdb.com/?tab=series&id=%s"|format(data.tvdbId) %}
|
||||
{% elif data.imdbId %}
|
||||
{% set link="https://www.imdb.com/title/%s"|format(data.imdbId) %}
|
||||
{% endif %}
|
||||
<p><b>Title</b>: <a href="{{link}}">{{data.title}}</a> ({{data.year}})</p>
|
||||
<p><b>Type</b>: {{{"sonarr":"TV Show","radarr":"Movie"}.get(request.request_type,"Unknown")}}</p>
|
||||
<p><b>Added</b>: {{request.added_date|ago_dt(0)}} ago</p>
|
||||
<p><b>Status</b>: <span class="{{class}}">{{label}}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>{{data.overview}}</p>
|
||||
{% if jellyfin_id %}
|
||||
<a class="btn btn-success" href="{{cfg().jellyfin_url}}web/index.html#!/details?id={{jellyfin_id}}">Open in Jellyfin</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% set downloads = (request.downloads|list) %}
|
||||
{% if downloads %}
|
||||
<h3>Downloads</h3>
|
||||
<table class="table table-sm">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Quality</th>
|
||||
<th>Progress</th>
|
||||
</tr>
|
||||
{% for download in downloads %}
|
||||
{% set torrent = download.download.info %}
|
||||
{% set dl_rate = torrent.downloaded / torrent.time_active %}
|
||||
{% set eta_act = [0, (torrent.size - torrent.downloaded) / dl_rate]|max %}
|
||||
<tr>
|
||||
<td><a href="{{url_for('requests.download_details',request_id=request.id,download_id=torrent.hash)}}">{{download.title}}</a></td>
|
||||
<td>{{download.quality.quality.name}}</td>
|
||||
<td>
|
||||
{{(torrent.progress*100)|round(2)}} % (ETA: {{[torrent.eta,eta_act]|min|round(0)|timedelta(clamp=true)}})
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
116
templates/requests/index.html
Normal file
116
templates/requests/index.html
Normal file
|
@ -0,0 +1,116 @@
|
|||
{% extends "base.html" %}
|
||||
{% from 'utils.html' import custom_render_form_row,make_tabs %}
|
||||
{% from 'bootstrap/utils.html' import render_icon %}
|
||||
{% from 'bootstrap/form.html' import render_form, render_field, render_form_row %}
|
||||
|
||||
{% macro search_tab() %}
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{{ form.csrf_token() }}
|
||||
{{ custom_render_form_row([form.query],render_args={'form_type':'horizontal','horizontal_columns':('lg',1,6)}) }}
|
||||
{{ custom_render_form_row([form.search_type],render_args={'form_type':'horizontal','horizontal_columns':('lg',1,6)}) }}
|
||||
{{ custom_render_form_row([form.search]) }}
|
||||
</form>
|
||||
{% if results %}
|
||||
<form method="post">
|
||||
{{ form.csrf_token() }}
|
||||
{% if search_type %}
|
||||
<input type="hidden" name="search_type" value="{{search_type}}">
|
||||
{% endif %}
|
||||
<table class="table table-sm table-bordered mt-3">
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>In Cinemas</th>
|
||||
<th>Digital Release</th>
|
||||
</tr>
|
||||
{% for result in results %}
|
||||
{% if result.tvdbId %}
|
||||
{% set link="https://www.thetvdb.com/?tab=series&id=%s"|format(result.tvdbId) %}
|
||||
{% elif result.imdbId %}
|
||||
{% set link="https://www.imdb.com/title/%s"|format(result.imdbId) %}
|
||||
{% endif %}
|
||||
{% if result.path %}
|
||||
{% set style="background-color: darkgreen !important;" %}
|
||||
{% else %}
|
||||
{% set style="" %}
|
||||
{% endif %}
|
||||
<tr style="{{style}}">
|
||||
<td>
|
||||
{% if result.path %}
|
||||
<input type="checkbox" disabled>
|
||||
{% else %}
|
||||
<input type="checkbox" name="selected[]" value="{{result|tojson|base64}}">
|
||||
{% endif %}
|
||||
<a href="{{link}}">{{result.title}}</a> ({{result.year}})
|
||||
</td>
|
||||
<td>
|
||||
{% if result.inCinemas %}
|
||||
{{result.inCinemas|fromiso}}
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if result.digitalRelease %}
|
||||
{{result.digitalRelease|fromiso}}
|
||||
{% else %}
|
||||
None
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<button class="btn btn-success" type="submit">Submit Request</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro request_queue() %}
|
||||
<form method="post">
|
||||
{{ form.csrf_token() }}
|
||||
<table class="table table-sm table-bordered mt-3">
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Requested at</th>
|
||||
{% if current_user.is_admin %}
|
||||
<th>Requested by</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% for request in requests %}
|
||||
{% set data = (request.data|fromjson) %}
|
||||
{% if request.approved==True %}
|
||||
<tr class="bg-success">
|
||||
{% elif request.approved==False %}
|
||||
<tr class="bg-danger">
|
||||
{% else %}
|
||||
<tr class="">
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if current_user.is_admin and request.approved!=True %}
|
||||
<input type="checkbox" name="selected[]" value="{{request.item_id}}">
|
||||
{% endif %}
|
||||
<a href="{{url_for('requests.details',request_id=request.id)}}" style="color: #eee; text-decoration: underline;">{{data.title}}</a> ({{data.year}})
|
||||
</td>
|
||||
<td>{{request.added_date}}</td>
|
||||
{% if current_user.is_admin %}
|
||||
<td>{{request.users|join(", ",attribute="user_name")}}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if current_user.is_admin %}
|
||||
<button name="approve" value="approve" class="btn btn-success" type="submit">Approve</button>
|
||||
<button name="decline" value="decline" class="btn btn-danger" type="submit">Decline</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
|
||||
{% block app_content %}
|
||||
<h1>Requests</h1>
|
||||
{% set requests_tabs = [
|
||||
('Queue',request_queue()),
|
||||
('Search',search_tab()),
|
||||
] %}
|
||||
{{ make_tabs(requests_tabs) }}
|
||||
{% endblock %}
|
|
@ -2,7 +2,7 @@
|
|||
{% macro tv_show_results(results) -%}
|
||||
<div class="d-flex flex-wrap">
|
||||
{% for result in results %}
|
||||
<form action="search/details" method="POST">
|
||||
<form action="details" method="POST">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="type" value="show"/>
|
||||
<input type="hidden" name="data" value="{{result|tojson|urlencode}}" />
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
{% for torrent in session.pop('new_torrents',{}).values() %}
|
||||
<p>
|
||||
Added <a class="alert-link" href="{{url_for('qbittorrent',infohash=torrent.hash)}}">{{torrent.name}}</a>
|
||||
Added <a class="alert-link" href="{{url_for('qbittorrent.details',infohash=torrent.hash)}}">{{torrent.name}}</a>
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -15,10 +15,10 @@
|
|||
{% endmacro %}
|
||||
|
||||
{% block app_content %}
|
||||
<h2>
|
||||
<h1>
|
||||
<a href="{{config.APP_CONFIG.sonarr_url}}">Sonarr</a>
|
||||
v{{status.version}} ({{series|count}} Shows)
|
||||
</h2>
|
||||
</h1>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
|
|
|
@ -22,13 +22,15 @@
|
|||
<div class="col-lg">
|
||||
<ul class="nav nav-pills mb-3" id="pills-tab" role="tablist">
|
||||
{% for label,tab in tabs if tab %}
|
||||
{% set id_name = [loop.index,tabs_id ]|join("-") %}
|
||||
{% if not (loop.first and loop.last) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{'active' if loop.first}}" id="nav-{{id_name}}-tab" data-toggle="pill" href="#pills-{{id_name}}" role="tab" aria-controls="pills-{{id_name}}" aria-selected="{{loop.first}}">
|
||||
{{label}}
|
||||
</a>
|
||||
</li>
|
||||
{% if tab %}
|
||||
{% set id_name = [loop.index,tabs_id ]|join("-") %}
|
||||
{% if not (loop.first and loop.last) %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{'active' if loop.first}}" id="nav-{{id_name}}-tab" data-toggle="pill" href="#pills-{{id_name}}" role="tab" aria-controls="pills-{{id_name}}" aria-selected="{{loop.first}}">
|
||||
{{label}}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@ -38,10 +40,12 @@
|
|||
<div class="col-lg">
|
||||
<div class="tab-content" id="searchResults">
|
||||
{% for label,tab in tabs if tab %}
|
||||
{% set id_name = [loop.index,tabs_id ]|join("-") %}
|
||||
<div class="tab-pane fade {{'show active' if loop.first}}" id="pills-{{id_name}}" role="tabpanel" aria-labelledby="nav-{{id_name}}-tab">
|
||||
{{ tab|safe }}
|
||||
</div>
|
||||
{% if tab %}
|
||||
{% set id_name = [loop.index,tabs_id ]|join("-") %}
|
||||
<div class="tab-pane fade {{'show active' if loop.first}}" id="pills-{{id_name}}" role="tabpanel" aria-labelledby="nav-{{id_name}}-tab">
|
||||
{{ tab|safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue