Save worktree changes

This commit is contained in:
2026-07-25 02:29:19 +01:00
parent 8d3b7d557b
commit db9d718cd8
170 changed files with 11719 additions and 3414 deletions
+5 -5
View File
@@ -11,7 +11,7 @@
<div class="card-header">
<h3 class="card-title">Display name</h3>
</div>
<form id="account-name-form" method="post" action="/admin/account/name" data-async-save data-async-save-close-url="{{returnUrl}}">
<form id="account-name-form" method="post" action="/account/name" data-async-save data-async-save-close-url="{{returnUrl}}">
<input type="hidden" name="return_url" value="{{returnUrl}}" />
<div class="card-body">
<div class="mb-3">
@@ -21,7 +21,7 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Display name actions">
{{{saveActionButtons formId="account-name-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/dashboard" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
@@ -33,7 +33,7 @@
<div class="card-header">
<h3 class="card-title">Password</h3>
</div>
<form id="account-password-form" method="post" action="/admin/account/password" data-async-save data-async-save-close-url="{{returnUrl}}">
<form id="account-password-form" method="post" action="/account/password" data-async-save data-async-save-close-url="{{returnUrl}}">
<input type="hidden" name="return_url" value="{{returnUrl}}" />
<div class="card-body">
<div class="mb-3">
@@ -52,10 +52,10 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Password actions">
{{{saveActionButtons formId="account-password-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/dashboard" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
+19
View File
@@ -0,0 +1,19 @@
<div class="card mx-auto" style="max-width: 26rem;">
<div class="card-body p-4">
<h1 class="h4 mb-3">Sign in</h1>
{{#if message}}
<div class="alert alert-warning">{{message}}</div>
{{/if}}
<form method="post" action="/login">
<div class="mb-3">
<label for="username" class="form-label">Username</label>
<input id="username" name="username" type="text" class="form-control" autocomplete="username" required />
</div>
<div class="mb-3">
<label for="password" class="form-label">Password</label>
<input id="password" name="password" type="password" class="form-control" autocomplete="current-password" required />
</div>
<button type="submit" class="btn btn-primary w-100">Sign in</button>
</form>
</div>
</div>
@@ -0,0 +1,43 @@
<div class="page-header">
<div>
<h2>Add API source</h2>
<p>Capture an API URL, refresh interval, and store the latest JSON response.</p>
</div>
</div>
<div class="card card-outline card-primary admin-form-card">
<div class="card-header">
<h3 class="card-title">API source details</h3>
</div>
<form id="api-source-form" method="post" action="/data-sources/api-sources" data-async-save data-async-save-close-url="/data-sources/api-sources" data-async-save-new-url="/data-sources/api-sources/new">
<div class="card-body">
<div class="mb-3">
<label for="api-source-name" class="form-label">Name</label>
<input id="api-source-name" name="name" class="form-control" value="{{apiSource.name}}" required />
</div>
<div class="mb-3">
<label for="api-source-url" class="form-label">API URL</label>
<input id="api-source-url" name="api_url" type="url" class="form-control" value="{{apiSource.apiUrl}}" placeholder="https://example.com/api.json" required />
</div>
<div class="row g-3">
<div class="col-12 col-md-4">
<label for="api-source-interval" class="form-label">Update interval</label>
<input id="api-source-interval" name="update_interval_value" type="number" min="1" class="form-control" value="{{apiSource.updateIntervalValue}}" required />
</div>
<div class="col-12 col-md-4">
<label for="api-source-interval-unit" class="form-label">Unit</label>
<select id="api-source-interval-unit" name="update_interval_unit" class="form-select">
<option value="seconds" {{#if (eq apiSource.updateIntervalUnit 'seconds')}}selected{{/if}}>Seconds</option>
<option value="minutes" {{#if (eq apiSource.updateIntervalUnit 'minutes')}}selected{{/if}}>Minutes</option>
</select>
</div>
</div>
</div>
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="API source actions">
{{{saveActionButtons formId="api-source-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/data-sources/api-sources" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
</div>
@@ -0,0 +1,94 @@
<div class="page-header">
<div>
<h2>Edit API source - {{apiSource.name}}</h2>
<p>Adjust the endpoint or refresh cadence and review the latest JSON response.</p>
</div>
</div>
<div class="card card-outline card-primary admin-form-card">
<div class="card-header">
<h3 class="card-title">API source details</h3>
</div>
<form id="api-source-form" method="post" action="/data-sources/api-sources/{{apiSource.id}}" data-async-save data-async-save-refresh-target="#api-source-response-panel" data-async-save-refresh-state-url="/data-sources/api-sources/{{apiSource.id}}/state" data-async-save-close-url="/data-sources/api-sources" data-async-save-new-url="/data-sources/api-sources/new">
<div class="card-body">
<div class="mb-3">
<label for="api-source-name" class="form-label">Name</label>
<input id="api-source-name" name="name" class="form-control" value="{{apiSource.name}}" required />
</div>
<div class="mb-3">
<label for="api-source-url" class="form-label">API URL</label>
<input id="api-source-url" name="api_url" type="url" class="form-control" value="{{apiSource.apiUrl}}" placeholder="https://example.com/api.json" required />
</div>
<div class="row g-3">
<div class="col-12 col-md-4">
<label for="api-source-interval" class="form-label">Update interval</label>
<input id="api-source-interval" name="update_interval_value" type="number" min="1" class="form-control" value="{{apiSource.updateIntervalValue}}" required />
</div>
<div class="col-12 col-md-4">
<label for="api-source-interval-unit" class="form-label">Unit</label>
<select id="api-source-interval-unit" name="update_interval_unit" class="form-select">
<option value="seconds" {{#if (eq apiSource.updateIntervalUnit 'seconds')}}selected{{/if}}>Seconds</option>
<option value="minutes" {{#if (eq apiSource.updateIntervalUnit 'minutes')}}selected{{/if}}>Minutes</option>
</select>
</div>
</div>
</div>
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="API source actions">
{{{saveActionButtons formId="api-source-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/data-sources/api-sources" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="delete-api-source-form">Delete</button>
</div>
</div>
</form>
</div>
<div class="card card-outline card-secondary mt-4" id="api-source-response-panel">
<div class="card-header d-flex align-items-center gap-2">
<h3 class="card-title mb-0">Latest response</h3>
{{#if apiSource.lastResponseJson}}
<button
type="button"
class="btn btn-outline-secondary btn-sm ms-auto"
data-json-toggle
data-json-toggle-label-formatted="Format JSON"
data-json-toggle-label-compact="Unformat JSON"
aria-pressed="false"
>
<i class="bi bi-filetype-json me-1"></i>
<span data-json-toggle-label>Format JSON</span>
</button>
{{/if}}
</div>
<div class="card-body">
{{#if apiSource.lastPullError}}
<div class="alert alert-warning">{{apiSource.lastPullError}}</div>
{{/if}}
<dl class="row">
<dt class="col-12 col-md-3">Last pulled</dt>
<dd class="col-12 col-md-9">
{{#if apiSource.lastPulledAtValue}}
<time data-local-datetime datetime="{{apiSource.lastPulledAtValue}}">{{#if apiSource.lastPulledAtLabel}}{{apiSource.lastPulledAtLabel}}{{else}}{{apiSource.lastPulledAtValue}}{{/if}}</time>
{{else}}
-
{{/if}}
</dd>
<dt class="col-12 col-md-3">Status</dt>
<dd class="col-12 col-md-9">{{#if apiSource.lastResponseStatus}}{{apiSource.lastResponseStatus}}{{else}}-{{/if}}</dd>
<dt class="col-12 col-md-3">Content type</dt>
<dd class="col-12 col-md-9">{{#if apiSource.lastResponseContentType}}{{apiSource.lastResponseContentType}}{{else}}-{{/if}}</dd>
</dl>
{{#if apiSource.lastResponseJson}}
<div data-json-toggle-panel>
<script type="application/json" data-json-toggle-source>{{json apiSource.lastResponseJson}}</script>
<pre class="mb-0 small bg-body-tertiary border rounded p-3" style="white-space: pre-wrap; word-break: break-word;" data-json-toggle-output>{{apiSource.lastResponseJson}}</pre>
</div>
{{else}}
<div class="alert alert-secondary mb-0">No JSON response is stored yet.</div>
{{/if}}
</div>
</div>
<form id="delete-api-source-form" method="post" action="/data-sources/api-sources/{{apiSource.id}}/delete" data-confirm-message="Delete this API source?"></form>
@@ -0,0 +1,77 @@
<div class="page-header">
<div>
<h2>API sources</h2>
<p>Store API endpoints, refresh cadence, and the latest JSON response pulled from each endpoint.</p>
</div>
</div>
<div class="card card-outline card-primary">
<div class="card-header">
<h3 class="card-title">Saved sources</h3>
{{#if (hasPermission currentUser 'api-sources.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/data-sources/api-sources/new">Add API source</a>
</div>
{{/if}}
</div>
<div class="card-body table-responsive p-0">
<table class="table table-striped w-100 mb-0">
<thead>
<tr>
<th>Name</th>
<th>API URL</th>
<th>Refresh interval</th>
<th>Last pulled</th>
<th>Last response</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{#if apiSources.length}}
{{#each apiSources}}
<tr>
<td data-label="Name">{{name}}</td>
<td data-label="API URL" class="text-break">{{api_url}}</td>
<td data-label="Refresh interval">{{intervalLabel}}</td>
<td data-label="Last pulled">
{{#if lastPulledAtValue}}
<time data-local-datetime datetime="{{lastPulledAtValue}}">{{lastPullLabel}}</time>
{{else}}
{{lastPullLabel}}
{{/if}}
</td>
<td data-label="Last response">
{{#if last_pull_error}}
<span class="text-danger">Failed</span>
{{else if last_response_status}}
<span>{{last_response_status}}</span>
{{#if last_response_content_type}}<span class="text-muted small ms-2">{{last_response_content_type}}</span>{{/if}}
{{else}}
<span class="empty">-</span>
{{/if}}
</td>
<td data-label="Actions">
{{#if (anyPermission ../currentUser 'api-sources.update' 'api-sources.delete')}}
<div class="actions">
{{#if (hasPermission ../currentUser 'api-sources.update')}}
<a class="btn btn-sm btn-primary" href="/data-sources/api-sources/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'api-sources.delete')}}
<form class="inline-form" method="post" action="/data-sources/api-sources/{{id}}/delete" data-confirm-message="Delete this API source?">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
{{/if}}
</div>
{{else}}
<span class="empty">-</span>
{{/if}}
</td>
</tr>
{{/each}}
{{else}}
<tr><td colspan="6" class="empty">No API sources yet.</td></tr>
{{/if}}
</tbody>
</table>
</div>
</div>
@@ -0,0 +1,47 @@
<div class="page-header">
<div>
<h2>Add RSS feed</h2>
<p>Capture a feed URL, refresh interval, and the number of items to store.</p>
</div>
</div>
<div class="card card-outline card-primary admin-form-card">
<div class="card-header">
<h3 class="card-title">RSS feed details</h3>
</div>
<form id="rss-feed-form" method="post" action="/data-sources/rss-feeds" data-async-save data-async-save-close-url="/data-sources/rss-feeds" data-async-save-new-url="/data-sources/rss-feeds/new">
<div class="card-body">
<div class="mb-3">
<label for="rss-feed-name" class="form-label">Name</label>
<input id="rss-feed-name" name="name" class="form-control" value="{{rssFeed.name}}" required />
</div>
<div class="mb-3">
<label for="rss-feed-url" class="form-label">Feed URL</label>
<input id="rss-feed-url" name="feed_url" type="url" class="form-control" value="{{rssFeed.feedUrl}}" placeholder="https://example.com/feed.xml" required />
</div>
<div class="row g-3">
<div class="col-12 col-md-4">
<label for="rss-feed-interval" class="form-label">Update interval</label>
<input id="rss-feed-interval" name="update_interval_value" type="number" min="1" class="form-control" value="{{rssFeed.updateIntervalValue}}" required />
</div>
<div class="col-12 col-md-4">
<label for="rss-feed-interval-unit" class="form-label">Unit</label>
<select id="rss-feed-interval-unit" name="update_interval_unit" class="form-select">
<option value="seconds" {{#if (eq rssFeed.updateIntervalUnit 'seconds')}}selected{{/if}}>Seconds</option>
<option value="minutes" {{#if (eq rssFeed.updateIntervalUnit 'minutes')}}selected{{/if}}>Minutes</option>
</select>
</div>
<div class="col-12 col-md-4">
<label for="rss-feed-item-limit" class="form-label">Items pulled</label>
<input id="rss-feed-item-limit" name="item_limit" type="number" min="1" class="form-control" value="{{rssFeed.itemLimit}}" required />
</div>
</div>
</div>
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="RSS feed actions">
{{{saveActionButtons formId="rss-feed-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/data-sources/rss-feeds" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
</div>
@@ -0,0 +1,185 @@
<div class="page-header">
<div>
<h2>Edit RSS feed - {{rssFeed.name}}</h2>
<p>Adjust the source URL, refresh interval, or item count.</p>
</div>
</div>
<div class="card card-outline card-primary admin-form-card">
<div class="card-header">
<h3 class="card-title">RSS feed details</h3>
</div>
<form id="rss-feed-form" method="post" action="/data-sources/rss-feeds/{{rssFeed.id}}" data-async-save data-async-save-refresh-target="#rss-feed-items-panel" data-async-save-refresh-state-url="/data-sources/rss-feeds/{{rssFeed.id}}/state" data-async-save-close-url="/data-sources/rss-feeds" data-async-save-new-url="/data-sources/rss-feeds/new">
<div class="card-body">
<div class="mb-3">
<label for="rss-feed-name" class="form-label">Name</label>
<input id="rss-feed-name" name="name" class="form-control" value="{{rssFeed.name}}" required />
</div>
<div class="mb-3">
<label for="rss-feed-url" class="form-label">Feed URL</label>
<input id="rss-feed-url" name="feed_url" type="url" class="form-control" value="{{rssFeed.feedUrl}}" placeholder="https://example.com/feed.xml" required />
</div>
<div class="row g-3">
<div class="col-12 col-md-4">
<label for="rss-feed-interval" class="form-label">Update interval</label>
<input id="rss-feed-interval" name="update_interval_value" type="number" min="1" class="form-control" value="{{rssFeed.updateIntervalValue}}" required />
</div>
<div class="col-12 col-md-4">
<label for="rss-feed-interval-unit" class="form-label">Unit</label>
<select id="rss-feed-interval-unit" name="update_interval_unit" class="form-select">
<option value="seconds" {{#if (eq rssFeed.updateIntervalUnit 'seconds')}}selected{{/if}}>Seconds</option>
<option value="minutes" {{#if (eq rssFeed.updateIntervalUnit 'minutes')}}selected{{/if}}>Minutes</option>
</select>
</div>
<div class="col-12 col-md-4">
<label for="rss-feed-item-limit" class="form-label">Items pulled</label>
<input id="rss-feed-item-limit" name="item_limit" type="number" min="1" class="form-control" value="{{rssFeed.itemLimit}}" required />
</div>
</div>
</div>
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="RSS feed actions">
{{{saveActionButtons formId="rss-feed-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/data-sources/rss-feeds" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="delete-rss-feed-form">Delete</button>
</div>
</div>
</form>
</div>
<div class="card card-outline card-secondary mt-4" id="rss-feed-items-panel">
<div class="card-header">
<h3 class="card-title">Pulled items</h3>
</div>
<div class="card-body">
{{#if pullError}}
<div class="alert alert-warning">{{pullError}}</div>
{{/if}}
{{#if pulledItems.length}}
<div class="accordion rss-feed-items-accordion" id="rss-feed-items-accordion">
{{#each pulledItems}}
<div class="accordion-item">
<h2 class="accordion-header" id="rss-feed-item-heading-{{id}}-{{position}}">
<button class="accordion-button {{#unless @first}}collapsed{{/unless}}" type="button" data-bs-toggle="collapse" data-bs-target="#rss-feed-item-collapse-{{id}}-{{position}}" aria-expanded="{{#if @first}}true{{else}}false{{/if}}" aria-controls="rss-feed-item-collapse-{{id}}-{{position}}">
<span class="me-2">{{title}}</span>
<span class="text-muted small">{{#if pubDate}}{{pubDate}}{{/if}}</span>
</button>
</h2>
<div id="rss-feed-item-collapse-{{id}}-{{position}}" class="accordion-collapse collapse {{#if @first}}show{{/if}}" aria-labelledby="rss-feed-item-heading-{{id}}-{{position}}" data-bs-parent="#rss-feed-items-accordion">
<div class="accordion-body">
<dl class="row mb-0">
<dt class="col-12 col-md-3">Title</dt>
<dd class="col-12 col-md-9">{{title}}</dd>
<dt class="col-12 col-md-3">Link</dt>
<dd class="col-12 col-md-9">
{{#if link}}
<a href="{{link}}" target="_blank" rel="noopener noreferrer">{{link}}</a>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
<dt class="col-12 col-md-3">Published</dt>
<dd class="col-12 col-md-9">{{#if pubDate}}{{pubDate}}{{else}}-{{/if}}</dd>
<dt class="col-12 col-md-3">Author</dt>
<dd class="col-12 col-md-9">{{#if itemJson.author}}{{itemJson.author}}{{else}}-{{/if}}</dd>
<dt class="col-12 col-md-3">GUID</dt>
<dd class="col-12 col-md-9">{{#if itemJson.guid}}{{itemJson.guid}}{{else}}-{{/if}}</dd>
<dt class="col-12 col-md-3">Comments</dt>
<dd class="col-12 col-md-9">
{{#if itemJson.comments}}
<a href="{{itemJson.comments}}" target="_blank" rel="noopener noreferrer">{{itemJson.comments}}</a>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
<dt class="col-12 col-md-3">Description</dt>
<dd class="col-12 col-md-9">{{#if description}}{{description}}{{else}}<span class="empty">-</span>{{/if}}</dd>
<dt class="col-12 col-md-3">Categories</dt>
<dd class="col-12 col-md-9">
{{#if itemJson.categories.length}}
<ul class="list-group list-group-flush border rounded">
{{#each itemJson.categories}}
<li class="list-group-item d-flex justify-content-between align-items-start">
<span>{{value}}</span>
{{#if domain}}<span class="text-muted small ms-3">{{domain}}</span>{{/if}}
</li>
{{/each}}
</ul>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
<dt class="col-12 col-md-3">Enclosure</dt>
<dd class="col-12 col-md-9">
{{#if itemJson.enclosure}}
<dl class="row mb-0">
<dt class="col-12 col-lg-4">URL</dt>
<dd class="col-12 col-lg-8">
{{#if itemJson.enclosure.url}}
<a href="{{itemJson.enclosure.url}}" target="_blank" rel="noopener noreferrer">{{itemJson.enclosure.url}}</a>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
<dt class="col-12 col-lg-4">Length</dt>
<dd class="col-12 col-lg-8">{{#if itemJson.enclosure.length}}{{itemJson.enclosure.length}}{{else}}-{{/if}}</dd>
<dt class="col-12 col-lg-4">Type</dt>
<dd class="col-12 col-lg-8">{{#if itemJson.enclosure.type}}{{itemJson.enclosure.type}}{{else}}-{{/if}}</dd>
</dl>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
<dt class="col-12 col-md-3">Source</dt>
<dd class="col-12 col-md-9 mb-0">
{{#if itemJson.source}}
<dl class="row mb-0">
<dt class="col-12 col-lg-4">Title</dt>
<dd class="col-12 col-lg-8">{{#if itemJson.source.title}}{{itemJson.source.title}}{{else}}-{{/if}}</dd>
<dt class="col-12 col-lg-4">URL</dt>
<dd class="col-12 col-lg-8">
{{#if itemJson.source.url}}
<a href="{{itemJson.source.url}}" target="_blank" rel="noopener noreferrer">{{itemJson.source.url}}</a>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
</dl>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
<dt class="col-12 col-md-3">Raw item XML</dt>
<dd class="col-12 col-md-9 mb-0">
{{#if itemJson.rawXml}}
<details>
<summary class="small text-muted">Show raw XML</summary>
<pre class="mb-0 mt-2 small bg-body-tertiary border rounded p-3 text-wrap">{{itemJson.rawXml}}</pre>
</details>
{{else}}
<span class="empty">-</span>
{{/if}}
</dd>
</dl>
</div>
</div>
</div>
{{/each}}
</div>
{{else}}
<div class="alert alert-secondary mb-0">No feed items could be loaded.</div>
{{/if}}
</div>
</div>
<form id="delete-rss-feed-form" method="post" action="/data-sources/rss-feeds/{{rssFeed.id}}/delete" data-confirm-message="Delete this RSS feed?"></form>
@@ -0,0 +1,60 @@
<div class="page-header">
<div>
<h2>RSS feeds</h2>
<p>Store feed sources, refresh cadence, and how many entries to pull each time.</p>
</div>
</div>
<div class="card card-outline card-primary">
<div class="card-header">
<h3 class="card-title">Saved feeds</h3>
{{#if (hasPermission currentUser 'rss-feeds.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/data-sources/rss-feeds/new">Add RSS feed</a>
</div>
{{/if}}
</div>
<div class="card-body table-responsive p-0">
<table class="table table-striped w-100 mb-0">
<thead>
<tr>
<th>Name</th>
<th>Feed URL</th>
<th>Refresh interval</th>
<th>Items pulled</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{#if rssFeeds.length}}
{{#each rssFeeds}}
<tr>
<td data-label="Name">{{name}}</td>
<td data-label="Feed URL" class="text-break">{{feed_url}}</td>
<td data-label="Refresh interval">{{intervalLabel}}</td>
<td data-label="Items pulled">{{itemLabel}}</td>
<td data-label="Actions">
{{#if (anyPermission ../currentUser 'rss-feeds.update' 'rss-feeds.delete')}}
<div class="actions">
{{#if (hasPermission ../currentUser 'rss-feeds.update')}}
<a class="btn btn-sm btn-primary" href="/data-sources/rss-feeds/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'rss-feeds.delete')}}
<form class="inline-form" method="post" action="/data-sources/rss-feeds/{{id}}/delete" data-confirm-message="Delete this RSS feed?">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
{{/if}}
</div>
{{else}}
<span class="empty">-</span>
{{/if}}
</td>
</tr>
{{/each}}
{{else}}
<tr><td colspan="5" class="empty">No RSS feeds yet.</td></tr>
{{/if}}
</tbody>
</table>
</div>
</div>
-15
View File
@@ -1,15 +0,0 @@
<div class="container">
<div class="row justify-content-center">
<div class="text-center">
<div class="display-1 fw-bold text-primary lh-1 mb-3">{{statusCode}}</div>
<h1 class="h3 mb-3">{{errorTitle}}</h1>
<p class="text-secondary mb-4">
{{errorMessage}}
</p>
<a href="{{backUrl}}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-left me-1" aria-hidden="true"></i>
{{backLabel}}
</a>
</div>
</div>
</div>
+23
View File
@@ -0,0 +1,23 @@
<div class="card border-0 shadow-sm mx-auto" style="max-width: 48rem;">
<div class="card-body p-4 p-md-5">
<div class="d-flex align-items-start gap-3">
<div class="fs-1 text-danger">
<i class="bi bi-exclamation-triangle-fill"></i>
</div>
<div>
<p class="text-uppercase text-muted fw-semibold mb-1">{{statusCode}}</p>
<h1 class="h3 mb-3">{{errorTitle}}</h1>
<p class="mb-4">{{errorMessage}}</p>
{{#if detail}}
<pre class="small bg-body-tertiary border rounded p-3 mb-4 text-wrap">{{detail}}</pre>
{{/if}}
<div class="d-flex gap-2 flex-wrap">
<a class="btn btn-primary" href="{{backUrl}}">{{backLabel}}</a>
{{#if searchUrl}}
<a class="btn btn-outline-secondary" href="{{searchUrl}}">Search</a>
{{/if}}
</div>
</div>
</div>
</div>
</div>
-28
View File
@@ -1,28 +0,0 @@
<div class="login-logo mb-3">
<a href="/login" class="text-decoration-none fw-bold text-body">Pulse Signage</a>
</div>
<div class="card card-outline card-primary">
<div class="card-header text-center">
<h1 class="h4 mb-0">Sign in</h1>
</div>
<div class="card-body login-card-body">
<p class="login-box-msg">Use your admin credentials to manage screens, playlists, and slides.</p>
{{#if message}}
<div class="alert alert-danger py-2">{{message}}</div>
{{/if}}
<form method="post" action="/login" class="d-grid gap-3">
<div>
<label for="login-username" class="form-label">Username</label>
<input id="login-username" class="form-control form-control-lg" type="text" name="username" autocomplete="username" required autofocus />
</div>
<div>
<label for="login-password" class="form-label">Password</label>
<input id="login-password" class="form-control form-control-lg" type="password" name="password" autocomplete="current-password" required />
</div>
<button type="submit" class="btn btn-primary btn-lg">Sign in</button>
</form>
</div>
</div>
@@ -0,0 +1,264 @@
<div id="background-tasks-state" data-state-version="{{stateVersion}}" hidden></div>
<div class="page-header">
<div>
<h2>Background tasks</h2>
<p>Queued data refreshes run in the web process. Finished items stay here until you clear them.</p>
</div>
</div>
<div class="row g-3 mb-4">
<div class="col-6 col-xl-3">
<div class="card card-outline card-primary h-100">
<div class="card-body">
<div class="text-muted small text-uppercase">Queued</div>
<div class="fs-3 fw-semibold">{{summary.counts.queued}}</div>
</div>
</div>
</div>
<div class="col-6 col-xl-3">
<div class="card card-outline card-info h-100">
<div class="card-body">
<div class="text-muted small text-uppercase">Running</div>
<div class="fs-3 fw-semibold">{{summary.counts.running}}</div>
</div>
</div>
</div>
<div class="col-6 col-xl-3">
<div class="card card-outline card-danger h-100">
<div class="card-body">
<div class="text-muted small text-uppercase">Failed</div>
<div class="fs-3 fw-semibold">{{summary.counts.failed}}</div>
</div>
</div>
</div>
<div class="col-6 col-xl-3">
<div class="card card-outline card-success h-100">
<div class="card-body">
<div class="text-muted small text-uppercase">Finished</div>
<div class="fs-3 fw-semibold">{{summary.counts.completed}}</div>
</div>
</div>
</div>
</div>
<div class="card card-outline card-primary mb-4">
<div class="card-header d-flex align-items-center gap-2">
<h3 class="card-title mb-0">Task queue</h3>
<div class="ms-auto d-flex align-items-center gap-2">
{{#if canManage}}
{{#if canClearFinished}}
<form method="post" action="/settings/background-tasks/clear-finished">
<button type="submit" class="btn btn-outline-secondary btn-sm" data-confirm-message="Clear finished background tasks?">Clear finished</button>
</form>
{{/if}}
{{/if}}
<span class="text-muted small">Active workers: {{summary.activeCount}}</span>
</div>
</div>
<div class="card-body table-responsive p-0">
<table class="table table-striped w-100 mb-0 align-middle">
<thead>
<tr>
<th>Task</th>
<th>Category</th>
<th>Status</th>
<th>Queued</th>
<th>Started</th>
<th>Finished</th>
<th>Source</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{{#if tasks.length}}
{{#each tasks}}
<tr>
<td data-label="Task">
<div class="fw-semibold">{{title}}</div>
{{#if key}}
<div class="text-muted small text-break">{{key}}</div>
{{/if}}
{{#if errorMessage}}
<div class="text-danger small mt-1 text-break">{{errorMessage}}</div>
{{/if}}
</td>
<td data-label="Category">{{category}}</td>
<td data-label="Status">
{{#if (eq status 'queued')}}
<span class="badge text-bg-secondary">Queued</span>
{{else if (eq status 'running')}}
<span class="badge text-bg-primary">Running</span>
{{else if (eq status 'completed')}}
<span class="badge text-bg-success">Completed</span>
{{else if (eq status 'failed')}}
<span class="badge text-bg-danger">Failed</span>
{{else if (eq status 'canceled')}}
<span class="badge text-bg-warning">Canceled</span>
{{else}}
<span class="badge text-bg-light text-dark">{{status}}</span>
{{/if}}
</td>
<td data-label="Queued">
{{#if createdAt}}
<time data-local-datetime datetime="{{createdAt}}">{{createdAt}}</time>
{{else}}
-
{{/if}}
</td>
<td data-label="Started">
{{#if startedAt}}
<time data-local-datetime datetime="{{startedAt}}">{{startedAt}}</time>
{{else}}
-
{{/if}}
</td>
<td data-label="Finished">
{{#if finishedAt}}
<time data-local-datetime datetime="{{finishedAt}}">{{finishedAt}}</time>
{{else}}
-
{{/if}}
</td>
<td data-label="Source">
{{#if metadata.sourceName}}
<div>{{metadata.sourceName}}</div>
<div class="text-muted small">{{metadata.sourceType}} #{{metadata.sourceId}}</div>
{{else}}
<span class="empty">-</span>
{{/if}}
</td>
<td data-label="Actions">
<div class="d-flex flex-wrap gap-2">
{{#if canManage}}
{{#if (eq status 'queued')}}
<form method="post" action="/settings/background-tasks/{{id}}/cancel">
<button type="submit" class="btn btn-outline-danger btn-sm" data-confirm-message="Cancel this queued task?">Cancel</button>
</form>
{{/if}}
{{#if (eq status 'failed')}}
<form method="post" action="/settings/background-tasks/{{id}}/retry">
<button type="submit" class="btn btn-outline-primary btn-sm">Retry</button>
</form>
{{/if}}
{{/if}}
</div>
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="8" class="empty">No background tasks are queued right now.</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
{{#if pagination.hasMultiplePages}}
<div class="card-footer d-flex flex-wrap align-items-center justify-content-between gap-3">
<div class="text-muted small">
Showing {{pagination.startItem}}-{{pagination.endItem}} of {{pagination.totalItems}} tasks
</div>
<nav aria-label="Background task pages">
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{#unless pagination.hasPrevious}}disabled{{/unless}}">
<a class="page-link" href="{{#if pagination.hasPrevious}}/settings/background-tasks{{pagination.previousUrl}}{{else}}#{{/if}}" aria-label="Previous page">Previous</a>
</li>
{{#each pagination.pages}}
<li class="page-item {{#if active}}active{{/if}}">
<a class="page-link" href="/settings/background-tasks{{url}}">{{number}}</a>
</li>
{{/each}}
<li class="page-item {{#unless pagination.hasNext}}disabled{{/unless}}">
<a class="page-link" href="{{#if pagination.hasNext}}/settings/background-tasks{{pagination.nextUrl}}{{else}}#{{/if}}" aria-label="Next page">Next</a>
</li>
</ul>
</nav>
</div>
{{/if}}
</div>
<div class="card card-outline card-secondary">
<div class="card-header">
<h3 class="card-title mb-0">Scheduled refreshes</h3>
</div>
<div class="card-body table-responsive p-0">
<table class="table table-striped w-100 mb-0 align-middle">
<thead>
<tr>
<th>Task</th>
<th>Interval</th>
<th>Next run</th>
<th>Last run</th>
<th>Last result</th>
<th>Source</th>
</tr>
</thead>
<tbody>
{{#if recurringTasks.length}}
{{#each recurringTasks}}
<tr>
<td data-label="Task">
<div class="fw-semibold">{{title}}</div>
<div class="text-muted small text-break">{{key}}</div>
</td>
<td data-label="Interval">{{intervalLabel}}</td>
<td data-label="Next run">
{{#if nextRunAt}}
<time data-local-datetime datetime="{{nextRunAt}}">{{nextRunAt}}</time>
{{else}}-{{/if}}
</td>
<td data-label="Last run">
{{#if lastRunAt}}
<time data-local-datetime datetime="{{lastRunAt}}">{{lastRunAt}}</time>
{{else}}-{{/if}}
</td>
<td data-label="Last result">
{{#if lastStatus}}
<span class="badge {{#if (eq lastStatus 'failed')}}text-bg-danger{{else if (eq lastStatus 'completed')}}text-bg-success{{else}}text-bg-secondary{{/if}}">{{lastStatus}}</span>
{{#if lastError}}<div class="text-danger small mt-1 text-break">{{lastError}}</div>{{/if}}
{{else}}
<span class="empty">-</span>
{{/if}}
</td>
<td data-label="Source">
{{#if metadata.sourceName}}
<div>{{metadata.sourceName}}</div>
<div class="text-muted small">{{metadata.sourceType}} #{{metadata.sourceId}}</div>
{{else}}
<span class="empty">-</span>
{{/if}}
</td>
</tr>
{{/each}}
{{else}}
<tr>
<td colspan="6" class="empty">No scheduled refreshes are registered yet.</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
{{#if recurringPagination.hasMultiplePages}}
<div class="card-footer d-flex flex-wrap align-items-center justify-content-between gap-3">
<div class="text-muted small">
Showing {{recurringPagination.startItem}}-{{recurringPagination.endItem}} of {{recurringPagination.totalItems}} scheduled refreshes
</div>
<nav aria-label="Scheduled refresh pages">
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{#unless recurringPagination.hasPrevious}}disabled{{/unless}}">
<a class="page-link" href="{{#if recurringPagination.hasPrevious}}/settings/background-tasks{{recurringPagination.previousUrl}}{{else}}#{{/if}}" aria-label="Previous page">Previous</a>
</li>
{{#each recurringPagination.pages}}
<li class="page-item {{#if active}}active{{/if}}">
<a class="page-link" href="/settings/background-tasks{{url}}">{{number}}</a>
</li>
{{/each}}
<li class="page-item {{#unless recurringPagination.hasNext}}disabled{{/unless}}">
<a class="page-link" href="{{#if recurringPagination.hasNext}}/settings/background-tasks{{recurringPagination.nextUrl}}{{else}}#{{/if}}" aria-label="Next page">Next</a>
</li>
</ul>
</nav>
</div>
{{/if}}
</div>
@@ -5,7 +5,7 @@
</div>
</div>
<form method="post" action="/admin/rbac" id="role-create-form" data-async-save data-async-save-close-url="/admin/rbac">
<form method="post" action="/rbac" id="role-create-form" data-async-save data-async-save-close-url="/rbac">
<div class="row g-4">
<div class="col-12 col-xl-4 d-flex flex-column gap-4">
<div class="card card-outline card-primary admin-form-card">
@@ -75,7 +75,7 @@
<div class="d-flex justify-content-end mt-4">
<div class="btn-group" role="group" aria-label="Role actions">
{{{saveActionButtons formId="role-create-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/rbac" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/rbac" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
@@ -5,7 +5,7 @@
</div>
</div>
<form method="post" action="/admin/rbac/{{role.id}}" id="role-edit-form" data-async-save data-async-save-close-url="/admin/rbac">
<form method="post" action="/rbac/{{role.id}}" id="role-edit-form" data-async-save data-async-save-close-url="/rbac">
<input type="hidden" name="permissions_present" value="1" />
<input type="hidden" name="users_present" value="1" />
@@ -130,4 +130,4 @@
</div>
</form>
<form method="post" action="/admin/rbac/{{role.id}}/delete" id="role-delete-form" class="d-none" data-confirm-message="Delete {{role.name}}?"></form>
<form method="post" action="/rbac/{{role.id}}/delete" id="role-delete-form" class="d-none" data-confirm-message="Delete {{role.name}}?"></form>
@@ -12,7 +12,7 @@
<h3 class="card-title">Existing roles</h3>
{{#if (hasPermission currentUser 'rbac.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/rbac/new">Add role</a>
<a class="btn btn-primary btn-sm" href="/rbac/new">Add role</a>
</div>
{{/if}}
</div>
@@ -51,11 +51,11 @@
{{#if (anyPermission ../currentUser 'rbac.update' 'rbac.delete')}}
<div class="actions users-row-actions">
{{#if (hasPermission ../currentUser 'rbac.update')}}
<a class="btn btn-sm btn-primary" href="/admin/rbac/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/rbac/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'rbac.delete')}}
{{#unless user_count}}
<form method="post" action="/admin/rbac/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{name}}?">
<form method="post" action="/rbac/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{name}}?">
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
{{else}}
@@ -75,4 +75,4 @@
</div>
</div>
</div>
</div>
</div>
@@ -11,7 +11,7 @@
<div class="card-header">
<h3 class="card-title">User details</h3>
</div>
<form id="user-form" method="post" action="/admin/users">
<form id="user-form" method="post" action="/users">
<div class="card-body">
<div class="row g-3">
<div class="col-12 col-md-6">
@@ -35,7 +35,7 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="User actions">
{{{saveActionButtons formId="user-form" saveLabel="Save" showSaveAndClose=false showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/users" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/users" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
@@ -71,4 +71,4 @@
</div>
</div>
</div>
</div>
</div>
@@ -14,7 +14,7 @@
<div class="card-header">
<h3 class="card-title">Profile</h3>
</div>
<form id="user-profile-form" method="post" action="/admin/users/{{user.id}}/username" data-async-save data-async-save-close-url="/admin/users">
<form id="user-profile-form" method="post" action="/users/{{user.id}}/username" data-async-save data-async-save-close-url="/users">
<div class="card-body">
<div class="mb-3">
<label for="edit-user-username" class="form-label">Username</label>
@@ -28,7 +28,7 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Profile actions">
{{{saveActionButtons formId="user-profile-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndCloseValue="close" showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/users" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/users" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="delete-user-form">Delete</button>
</div>
</div>
@@ -41,7 +41,7 @@
<div class="card-header">
<h3 class="card-title">Reset password</h3>
</div>
<form id="user-password-form" method="post" action="/admin/users/{{user.id}}/password" data-async-save data-async-save-close-url="/admin/users">
<form id="user-password-form" method="post" action="/users/{{user.id}}/password" data-async-save data-async-save-close-url="/users">
<div class="card-body">
<div class="mb-3">
<label for="edit-user-password" class="form-label">New password</label>
@@ -66,7 +66,7 @@
<div class="card-header">
<h3 class="card-title">Roles</h3>
</div>
<form id="user-roles-form" method="post" action="/admin/users/{{user.id}}/roles" data-async-save data-async-save-close-url="/admin/users">
<form id="user-roles-form" method="post" action="/users/{{user.id}}/roles" data-async-save data-async-save-close-url="/users">
<div class="card-body">
<div class="row g-3">
{{#if roles.length}}
@@ -83,7 +83,7 @@
{{/each}}
{{else}}
<div class="col-12">
<div class="alert alert-warning mb-0">Create at least one role before assigning users.</div>
<div class="alert alert-warning mb-0">Create at least one user before assigning this role.</div>
</div>
{{/if}}
</div>
@@ -100,4 +100,4 @@
</div>
</div>
<form id="delete-user-form" method="post" action="/admin/users/{{user.id}}/delete" data-confirm-message="Delete {{user.username}}?"></form>
<form id="delete-user-form" method="post" action="/users/{{user.id}}/delete" class="d-none" data-confirm-message="Delete {{user.username}}?"></form>
@@ -10,7 +10,7 @@
<h3 class="card-title">Existing users</h3>
{{#if (hasPermission currentUser 'users.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/users/new">Add user</a>
<a class="btn btn-primary btn-sm" href="/users/new">Add user</a>
</div>
{{/if}}
</div>
@@ -67,10 +67,10 @@
<div class="actions users-row-actions">
{{#unless isCurrentUser}}
{{#if (hasPermission ../currentUser 'users.update')}}
<a class="btn btn-sm btn-primary" href="/admin/users/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/users/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'users.delete')}}
<form method="post" action="/admin/users/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{username}}?">
<form method="post" action="/users/{{id}}/delete" class="inline-form" data-confirm-message="Delete {{username}}?">
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
</form>
{{/if}}
@@ -90,4 +90,4 @@
</tbody>
</table>
</div>
</div>
</div>
@@ -82,13 +82,13 @@
{{/if}}
<script src="/assets/adminlte/js/bootstrap.bundle.min.js"></script>
<script src="/assets/adminlte/js/adminlte.min.js"></script>
<script src="/assets/js/toast.js"></script>
<script src="/assets/js/admin-page.js"></script>
<script src="/assets/js/system-status.js"></script>
<script src="/assets/js/admin/toast.js"></script>
<script src="/assets/js/admin/admin-page.js"></script>
<script src="/assets/js/admin/system-status.js"></script>
{{#if scripts.length}}
{{#each scripts}}
<script src="/assets/{{this}}"></script>
{{/each}}
{{/if}}
</body>
</html>
</html>
@@ -22,20 +22,20 @@
<main class="d-flex align-items-center justify-content-center min-vh-100 p-3 {{bodyClass}}">
{{{body}}}
</main>
<script src="/assets/js/admin-page.js"></script>
<script src="/assets/js/system-status.js"></script>
<script src="/assets/js/admin/admin-page.js"></script>
<script src="/assets/js/admin/system-status.js"></script>
{{else if authShell}}
<main class="login-page d-flex align-items-center justify-content-center min-vh-100 p-3">
<div class="login-box">
{{{body}}}
</div>
</main>
<script src="/assets/js/admin-page.js"></script>
<script src="/assets/js/system-status.js"></script>
<script src="/assets/js/admin/admin-page.js"></script>
<script src="/assets/js/admin/system-status.js"></script>
{{else}}
<div id="app-toast-container" class="toast-container position-fixed top-0 end-0 p-3">
{{#if message}}
<div class="toast align-items-center text-bg-{{#if messageVariant}}{{messageVariant}}{{else}}primary{{/if}} border-0" id="app-toast" role="status" aria-live="polite" aria-atomic="true" data-bs-autohide="true" data-bs-delay="4000" data-toast-variant="{{#if messageVariant}}{{messageVariant}}{{else}}primary{{/if}}">
<div class="toast align-items-center text-bg-{{#if messageVariant}}{{messageVariant}}{{else}}info{{/if}} border-0" id="app-toast" role="status" aria-live="polite" aria-atomic="true" data-bs-autohide="true" data-bs-delay="4000" data-toast-variant="{{#if messageVariant}}{{messageVariant}}{{else}}info{{/if}}">
<div class="d-flex">
<div class="toast-body">{{message}}</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Dismiss notification"></button>
@@ -111,18 +111,10 @@
<span class="user-menu-avatar user-menu-avatar--lg" aria-hidden="true">{{userInitial currentUser.name currentUser.username}}</span>
<p>
{{#if currentUser.name}}{{currentUser.name}}{{else}}{{currentUser.username}}{{/if}}
{{!-- <small>Member since today</small> --}}
</p>
</li>
{{!-- <li class="user-body">
<div class="row">
<div class="col-4 text-center"><a href="/admin/account">Account</a></div>
<div class="col-4 text-center"><a href="/admin/account">Settings</a></div>
<div class="col-4 text-center"><a href="/logout">Logout</a></div>
</div>
</li> --}}
<li class="user-footer">
<a class="btn btn-outline-secondary" href="/admin/account">Profile</a>
<a class="btn btn-outline-secondary" href="/account">Profile</a>
<a class="btn btn-outline-danger float-end" href="/logout">Sign out</a>
</li>
{{else}}
@@ -136,17 +128,19 @@
<aside class="app-sidebar bg-body-secondary shadow d-flex flex-column" data-bs-theme="dark">
<div class="sidebar-brand">
<a href="/admin" class="brand-link">
<a href="/dashboard" class="brand-link">
<span class="brand-text fw-light">Pulse Signage</span>
</a>
</div>
<div class="sidebar-wrapper d-flex flex-column flex-grow-1 min-h-0">
<nav class="mt-2 flex-grow-1" aria-label="Main navigation">
<ul class="nav sidebar-menu flex-column" data-lte-toggle="treeview" data-accordion="false" role="menu" id="navigation">
<li class="nav-header">MAIN NAVIGATION</li>
{{#if (anyPermission currentUser 'dashboard.read' 'clients.read' 'screens.read' 'playlists.read' 'slides.read' 'templates.read' 'canvas-sizes.read')}}
<li class="nav-header">DIGITAL SIGNAGE</li>
{{/if}}
{{#if (hasPermission currentUser 'dashboard.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'dashboard')}}active{{/if}}" href="/admin">
<a class="nav-link {{#if (eq active 'dashboard')}}active{{/if}}" href="/dashboard">
<i class="nav-icon bi bi-speedometer2"></i>
<p>Dashboard</p>
</a>
@@ -154,7 +148,7 @@
{{/if}}
{{#if (hasPermission currentUser 'clients.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'clients')}}active{{/if}}" href="/admin/clients">
<a class="nav-link {{#if (eq active 'clients')}}active{{/if}}" href="/clients">
<i class="nav-icon bi bi-broadcast"></i>
<p>Connected clients</p>
</a>
@@ -162,7 +156,7 @@
{{/if}}
{{#if (hasPermission currentUser 'screens.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'screens')}}active{{/if}}" href="/admin/screens">
<a class="nav-link {{#if (eq active 'screens')}}active{{/if}}" href="/screens">
<i class="nav-icon bi bi-display"></i>
<p>Screens</p>
</a>
@@ -170,7 +164,7 @@
{{/if}}
{{#if (hasPermission currentUser 'playlists.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'playlists')}}active{{/if}}" href="/admin/playlists">
<a class="nav-link {{#if (eq active 'playlists')}}active{{/if}}" href="/playlists">
<i class="nav-icon bi bi-collection-play"></i>
<p>Playlists</p>
</a>
@@ -178,7 +172,7 @@
{{/if}}
{{#if (hasPermission currentUser 'slides.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'slides')}}active{{/if}}" href="/admin/slides">
<a class="nav-link {{#if (eq active 'slides')}}active{{/if}}" href="/slides">
<i class="nav-icon bi bi-file-earmark-richtext"></i>
<p>Slides</p>
</a>
@@ -186,7 +180,7 @@
{{/if}}
{{#if (hasPermission currentUser 'templates.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'templates')}}active{{/if}}" href="/admin/templates">
<a class="nav-link {{#if (eq active 'templates')}}active{{/if}}" href="/templates">
<i class="nav-icon bi bi-layout-text-window-reverse"></i>
<p>Slide templates</p>
</a>
@@ -194,17 +188,36 @@
{{/if}}
{{#if (hasPermission currentUser 'canvas-sizes.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'canvas-sizes')}}active{{/if}}" href="/admin/canvas-sizes">
<a class="nav-link {{#if (eq active 'canvas-sizes')}}active{{/if}}" href="/canvas-sizes">
<i class="nav-icon bi bi-aspect-ratio"></i>
<p>Canvas sizes</p>
</a>
</li>
{{/if}}
{{#if (anyPermission currentUser 'users.read' 'rbac.read')}}
{{#if (anyPermission currentUser 'rss-feeds.read' 'api-sources.read')}}
<li class="nav-header">DATA SOURCES</li>
{{#if (hasPermission currentUser 'rss-feeds.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'rss-feeds')}}active{{/if}}" href="/data-sources/rss-feeds">
<i class="nav-icon bi bi-rss"></i>
<p>RSS feeds</p>
</a>
</li>
{{/if}}
{{#if (hasPermission currentUser 'api-sources.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'api-sources')}}active{{/if}}" href="/data-sources/api-sources">
<i class="nav-icon bi bi-braces"></i>
<p>API sources</p>
</a>
</li>
{{/if}}
{{/if}}
{{#if (anyPermission currentUser 'background-tasks.read' 'users.read' 'rbac.read')}}
<li class="nav-header">SETTINGS</li>
{{#if (hasPermission currentUser 'users.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'users')}}active{{/if}}" href="/admin/users">
<a class="nav-link {{#if (eq active 'users')}}active{{/if}}" href="/users">
<i class="nav-icon bi bi-people"></i>
<p>Users</p>
</a>
@@ -212,12 +225,20 @@
{{/if}}
{{#if (hasPermission currentUser 'rbac.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'rbac')}}active{{/if}}" href="/admin/rbac">
<a class="nav-link {{#if (eq active 'rbac')}}active{{/if}}" href="/rbac">
<i class="nav-icon bi bi-shield-lock"></i>
<p>Roles and permissions</p>
</a>
</li>
{{/if}}
{{#if (anyPermission currentUser 'background-tasks.read')}}
<li class="nav-item">
<a class="nav-link {{#if (eq active 'background-tasks')}}active{{/if}}" href="/settings/background-tasks">
<i class="nav-icon bi bi-list-task"></i>
<p>Background tasks</p>
</a>
</li>
{{/if}}
{{/if}}
</ul>
</nav>
@@ -242,9 +263,9 @@
</dialog>
<script src="/assets/adminlte/js/bootstrap.bundle.min.js"></script>
<script src="/assets/adminlte/js/adminlte.min.js"></script>
<script src="/assets/js/toast.js"></script>
<script src="/assets/js/admin/toast.js"></script>
<script src="/assets/js/table-sort.js"></script>
<script src="/assets/js/admin-page.js"></script>
<script src="/assets/js/admin/admin-page.js"></script>
{{#if scripts.length}}
{{#each scripts}}
{{#if (isExternalUrl this)}}
@@ -254,7 +275,7 @@
{{/if}}
{{/each}}
{{/if}}
<script src="/assets/js/system-status.js"></script>
<script src="/assets/js/admin/system-status.js"></script>
{{/if}}
</body>
</html>
</html>
@@ -9,7 +9,7 @@
<div class="card-header">
<h3 class="card-title">Canvas size details</h3>
</div>
<form id="canvas-size-form" method="post" action="/admin/canvas-sizes">
<form id="canvas-size-form" method="post" action="/canvas-sizes">
<div class="card-body">
<div class="mb-3">
<label for="canvas-size-name" class="form-label">Name</label>
@@ -29,8 +29,9 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Canvas size actions">
{{{saveActionButtons formId="canvas-size-form" saveLabel="Save" showSaveAndClose=false showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/canvas-sizes" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/canvas-sizes" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
</div>
@@ -9,7 +9,7 @@
<div class="card-header">
<h3 class="card-title">Canvas size details</h3>
</div>
<form id="canvas-size-form" method="post" action="/admin/canvas-sizes/{{canvasSize.id}}" data-async-save data-async-save-close-url="/admin/canvas-sizes" data-async-save-new-url="/admin/canvas-sizes/new">
<form id="canvas-size-form" method="post" action="/canvas-sizes/{{canvasSize.id}}" data-async-save data-async-save-close-url="/canvas-sizes" data-async-save-new-url="/canvas-sizes/new">
<div class="card-body">
<div class="mb-3">
<label for="canvas-size-name" class="form-label">Name</label>
@@ -29,11 +29,12 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Canvas size actions">
{{{saveActionButtons formId="canvas-size-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/admin/canvas-sizes" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/canvas-sizes" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="delete-canvas-size-form">Delete</button>
</div>
</div>
</form>
</div>
<form id="delete-canvas-size-form" method="post" action="/admin/canvas-sizes/{{canvasSize.id}}/delete" data-confirm-message="Delete this canvas size?"></form>
<form id="delete-canvas-size-form" method="post" action="/canvas-sizes/{{canvasSize.id}}/delete" data-confirm-message="Delete this canvas size?"></form>
@@ -10,7 +10,7 @@
<h3 class="card-title">Saved sizes</h3>
{{#if (hasPermission currentUser 'canvas-sizes.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/canvas-sizes/new">Add canvas size</a>
<a class="btn btn-primary btn-sm" href="/canvas-sizes/new">Add canvas size</a>
</div>
{{/if}}
</div>
@@ -35,10 +35,10 @@
{{#if (anyPermission ../currentUser 'canvas-sizes.update' 'canvas-sizes.delete')}}
<div class="actions">
{{#if (hasPermission ../currentUser 'canvas-sizes.update')}}
<a class="btn btn-sm btn-primary" href="/admin/canvas-sizes/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/canvas-sizes/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'canvas-sizes.delete')}}
<form class="inline-form" method="post" action="/admin/canvas-sizes/{{id}}/delete" data-confirm-message="Delete this canvas size?">
<form class="inline-form" method="post" action="/canvas-sizes/{{id}}/delete" data-confirm-message="Delete this canvas size?">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
{{/if}}
@@ -55,3 +55,4 @@
</tbody>
</table>
</div>
@@ -60,29 +60,29 @@
{{#if (hasPermission currentUser 'clients.allow')}}
<td data-label="Actions" class="text-end">
<div class="actions justify-content-end">
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-confirm-message="Reloading will restart the player page. Continue?" data-async-command>
<form method="post" action="/clients/{{screen_slug}}/commands" class="inline-form" data-confirm-message="Reloading will restart the player page. Continue?" data-async-command>
<input type="hidden" name="command" value="reload" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-danger">Reload</button>
</form>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="previous" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-warning" aria-label="Previous slide">◀</button>
</form>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="next" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-warning" aria-label="Next slide">▶</button>
</form>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="pause" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm btn-info">
<i class="bi bi-pause-fill me-1" aria-hidden="false"></i>Pause
</button>
</form>
<form method="post" action="/admin/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<form method="post" action="/clients/{{screen_slug}}/commands" class="inline-form" data-async-command>
<input type="hidden" name="command" value="blackout" />
<input type="hidden" name="connectionId" value="{{id}}" />
<button type="submit" class="btn btn-sm {{#if blackout}}btn-success{{else}}btn-secondary{{/if}}">
@@ -100,4 +100,4 @@
</tbody>
</table>
</div>
</div>
</div>
@@ -50,7 +50,7 @@
<div class="dashboard-action-grid">
<form
method="post"
action="/admin/commands"
action="/commands"
class="dashboard-action-form"
data-confirm-message="Reload all connected clients?"
data-async-command
@@ -65,7 +65,7 @@
</form>
<form
method="post"
action="/admin/commands"
action="/commands"
class="dashboard-action-form"
data-confirm-message="Blackout all connected clients?"
data-async-command
@@ -135,4 +135,4 @@
</div>
</div>
</div>
{{/if}}
{{/if}}
@@ -9,7 +9,7 @@
<div class="card-header">
<h3 class="card-title">Playlist details</h3>
</div>
<form id="playlist-edit-form" method="post" action="/admin/playlists">
<form id="playlist-edit-form" method="post" action="/playlists" data-async-save data-async-save-new-url="/playlists/new">
<div class="card-body">
<div class="mb-0">
<label for="playlist-name" class="form-label">Name</label>
@@ -19,8 +19,9 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Playlist actions">
{{{saveActionButtons formId="playlist-edit-form" saveLabel="Save" showSaveAndClose=false showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/playlists" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/playlists" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
</div>
@@ -12,7 +12,7 @@
<h3 class="card-title">Playlist details</h3>
</div>
<div class="card-body">
<form id="playlist-edit-form" method="post" action="/admin/playlists/{{playlist.id}}" data-async-save data-async-save-close-url="/admin/playlists" data-async-save-new-url="/admin/playlists/new">
<form id="playlist-edit-form" method="post" action="/playlists/{{playlist.id}}" data-async-save data-async-save-close-url="/playlists" data-async-save-new-url="/playlists/new">
<div class="row g-3 align-items-end">
<div class="col-12 col-lg-8">
<label for="playlist-name" class="form-label">Name</label>
@@ -61,7 +61,7 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group playlist-actions-group" role="group" aria-label="Playlist actions">
{{{saveActionButtons formId="playlist-edit-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/admin/playlists" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/playlists" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="delete-playlist-form">Delete</button>
</div>
</div>
@@ -127,7 +127,7 @@
</td>
<td data-label="Actions">
<div class="actions playlist-item-actions">
<button type="button" class="btn btn-sm btn-primary" data-schedule-config="/admin/playlists/{{../playlist.id}}/slides/{{id}}/config" data-schedule-config-row="existing-{{id}}">Schedule</button>
<button type="button" class="btn btn-sm btn-primary" data-schedule-config="/playlists/{{../playlist.id}}/slides/{{id}}/config" data-schedule-config-row="existing-{{id}}">Schedule</button>
<button type="button" class="btn btn-sm btn-danger" data-playlist-remove-row>Remove</button>
</div>
</td>
@@ -143,4 +143,5 @@
</div>
</div>
<form id="delete-playlist-form" method="post" action="/admin/playlists/{{playlist.id}}/delete" data-confirm-message="Delete this playlist?"></form>
<form id="delete-playlist-form" method="post" action="/playlists/{{playlist.id}}/delete" data-confirm-message="Delete this playlist?"></form>
@@ -10,7 +10,7 @@
<h3 class="card-title">Existing playlists</h3>
{{#if (hasPermission currentUser 'playlists.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/playlists/new">Create playlist</a>
<a class="btn btn-primary btn-sm" href="/playlists/new">Create playlist</a>
</div>
{{/if}}
</div>
@@ -33,10 +33,10 @@
{{#if (anyPermission ../currentUser 'playlists.update' 'playlists.delete')}}
<div class="actions">
{{#if (hasPermission ../currentUser 'playlists.update')}}
<a class="btn btn-sm btn-primary" href="/admin/playlists/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/playlists/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'playlists.delete')}}
<form class="inline-form" method="post" action="/admin/playlists/{{id}}/delete" data-confirm-message="Delete this playlist?">
<form class="inline-form" method="post" action="/playlists/{{id}}/delete" data-confirm-message="Delete this playlist?">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
{{/if}}
@@ -54,3 +54,4 @@
</table>
</div>
</div>
@@ -2,7 +2,7 @@
<div class="card-header">
<h3 class="card-title">Slide schedule</h3>
</div>
<form method="post" action="/admin/playlists/{{playlist.id}}/slides/{{playlistSlide.id}}/config">
<form method="post" action="/playlists/{{playlist.id}}/slides/{{playlistSlide.id}}/config">
<div class="card-body">
<input type="hidden" name="playlist_slide_id" value="{{playlistSlide.id}}" />
<input type="hidden" name="row_key" value="{{rowKey}}" />
@@ -66,3 +66,4 @@
</div>
</form>
</div>
@@ -9,7 +9,7 @@
<div class="card-header">
<h3 class="card-title">Screen details</h3>
</div>
<form id="screen-edit-form" method="post" action="/admin/screens">
<form id="screen-edit-form" method="post" action="/screens" data-async-save data-async-save-new-url="/screens/new">
<div class="card-body">
<div class="row g-3">
<div class="col-12 col-md-6">
@@ -34,8 +34,9 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group" role="group" aria-label="Screen actions">
{{{saveActionButtons formId="screen-edit-form" saveLabel="Save" showSaveAndClose=false showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/screens" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/screens" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</form>
</div>
@@ -1,6 +1,6 @@
<div class="page-header">
<div>
<h2>Edit screen</h2>
<h2>Edit screen - {{screen.name}}</h2>
<p>Update the display name and assigned playlist.</p>
</div>
</div>
@@ -11,7 +11,7 @@
<div class="card-header">
<h3 class="card-title">Screen details</h3>
</div>
<form id="screen-edit-form" method="post" action="/admin/screens/{{screen.id}}" data-async-save data-async-save-close-url="/admin/screens" data-async-save-new-url="/admin/screens/new">
<form id="screen-edit-form" method="post" action="/screens/{{screen.id}}" data-async-save data-async-save-close-url="/screens" data-async-save-new-url="/screens/new">
<div class="card-body">
<div class="row g-3">
<div class="col-12 col-md-6">
@@ -36,7 +36,7 @@
<div class="card-footer d-flex justify-content-end">
<div class="btn-group screen-actions-group" role="group" aria-label="Screen actions">
{{{saveActionButtons formId="screen-edit-form" saveLabel="Save" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/admin/screens" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/screens" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="screen-delete-form">Delete</button>
</div>
</div>
@@ -54,4 +54,4 @@
</div>
</div>
</div>
</div>
</div>
@@ -10,7 +10,7 @@
<h3 class="card-title">Existing screens</h3>
{{#if (hasPermission currentUser 'screens.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/screens/new">Add screen</a>
<a class="btn btn-primary btn-sm" href="/screens/new">Add screen</a>
</div>
{{/if}}
</div>
@@ -43,10 +43,10 @@
{{#if (anyPermission ../currentUser 'screens.update' 'screens.delete')}}
<div class="actions">
{{#if (hasPermission ../currentUser 'screens.update')}}
<a class="btn btn-sm btn-primary" href="/admin/screens/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/screens/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'screens.delete')}}
<form class="inline-form" method="post" action="/admin/screens/{{id}}/delete" data-confirm-message="Delete this screen?">
<form class="inline-form" method="post" action="/screens/{{id}}/delete" data-confirm-message="Delete this screen?">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
{{/if}}
@@ -64,3 +64,4 @@
</table>
</div>
</div>
@@ -6,7 +6,7 @@
</div>
<div class="slide-editor-shell">
<form method="post" action="{{action}}" enctype="multipart/form-data" id="slide-form" class="slide-form-stack" data-async-save-close-url="/admin/slides" data-async-save-new-url="/admin/slides/new">
<form method="post" action="{{action}}" enctype="multipart/form-data" id="slide-form" class="slide-form-stack" data-async-save-close-url="/slides" data-async-save-new-url="/slides/new">
<div class="card card-outline card-primary admin-form-card slide-template-card">
<div class="card-header">
<h3 class="card-title">Slide details</h3>
@@ -38,7 +38,7 @@
<div class="card-body d-grid gap-2">
<div class="btn-group template-actions-group slide-actions-group" role="group" aria-label="Slide actions">
{{{saveActionButtons formId="slide-form" saveLabel=saveLabel saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/admin/slides" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/slides" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
{{#if isEdit}}
<button type="submit" class="btn btn-danger" form="delete-slide-form">Delete</button>
{{/if}}
@@ -68,8 +68,9 @@
</div>
{{#if isEdit}}
<form id="delete-slide-form" method="post" action="/admin/slides/{{slide.id}}/delete" data-confirm-message="Delete this slide?"></form>
<form id="delete-slide-form" method="post" action="/slides/{{slide.id}}/delete" data-confirm-message="Delete this slide?"></form>
{{/if}}
<textarea id="slide-editor-data" hidden>{{json slideEditorData}}</textarea>
<script type="module" src="/assets/js/slides/slide-form.js"></script>
<script type="module" src="/assets/js/slides/slide-form.js?v=20260724"></script>
@@ -10,7 +10,7 @@
<h3 class="card-title">Existing slides</h3>
{{#if (hasPermission currentUser 'slides.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/slides/new">Create slide</a>
<a class="btn btn-primary btn-sm" href="/slides/new">Create slide</a>
</div>
{{/if}}
</div>
@@ -33,10 +33,10 @@
{{#if (anyPermission ../currentUser 'slides.update' 'slides.delete')}}
<div class="actions">
{{#if (hasPermission ../currentUser 'slides.update')}}
<a class="btn btn-sm btn-primary" href="/admin/slides/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/slides/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'slides.delete')}}
<form class="inline-form" method="post" action="/admin/slides/{{id}}/delete" data-confirm-message="Delete this slide?">
<form class="inline-form" method="post" action="/slides/{{id}}/delete" data-confirm-message="Delete this slide?">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
{{/if}}
@@ -54,3 +54,4 @@
</table>
</div>
</div>
@@ -5,7 +5,7 @@
</div>
</div>
<form method="post" action="/admin/templates" enctype="multipart/form-data" id="template-form">
<form method="post" action="/templates" enctype="multipart/form-data" id="template-form">
<input type="hidden" name="canvas_size_id" id="canvas-size-id" value="{{template.canvas_size_id}}" />
<input type="hidden" name="canvas_width" id="canvas-width" value="{{template.canvas_size_width}}" />
<input type="hidden" name="canvas_height" id="canvas-height" value="{{template.canvas_size_height}}" />
@@ -33,7 +33,7 @@
<div class="card-body d-grid gap-2">
<div class="btn-group template-actions-group" role="group" aria-label="Template actions">
{{{saveActionButtons formId="template-form" saveLabel="Save" saveButtonClass="btn btn-success template-save-button" showSaveAndClose=false showSaveAndNew=false}}}
<a class="btn btn-warning" href="/admin/templates" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/templates" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
</div>
</div>
</div>
@@ -116,10 +116,13 @@
<div class="modal-body">
<p class="text-body-secondary mb-3">Choose the type of region you want to add to this template.</p>
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="text">Text region</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="html">HTML region</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="image">Image region</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="webpage">Webpage region</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="html">HTML</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="image">Image</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="api">API</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="rtmp">RTMP</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="rss">RSS</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="text">Text</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="webpage">Webpage</button>
</div>
</div>
</div>
@@ -145,6 +148,7 @@
<div class="region-field-grid region-field-grid--size">
<label>Width<input class="form-control" type="number" name="region_width[]" value="300" required /></label>
<label>Height<input class="form-control" type="number" name="region_height[]" value="120" required /></label>
<label>Lock ratio<input class="form-control" type="text" name="region_lock_ratio[]" value="" placeholder="16:9 or 5:4" /></label>
</div>
<div class="d-flex justify-content-end"><button type="button" class="btn btn-sm btn-outline-danger remove-region">Remove</button></div>
</div>
@@ -152,3 +156,4 @@
</template>
<textarea id="template-editor-data" hidden>{{json template.regions}}</textarea>
@@ -5,7 +5,7 @@
</div>
</div>
<form method="post" action="/admin/templates/{{template.id}}" enctype="multipart/form-data" id="template-form" data-async-save data-async-save-close-url="/admin/templates" data-async-save-new-url="/admin/templates/new">
<form method="post" action="/templates/{{template.id}}" enctype="multipart/form-data" id="template-form" data-async-save data-async-save-close-url="/templates" data-async-save-new-url="/templates/new">
<input type="hidden" name="canvas_size_id" id="canvas-size-id" value="{{template.canvas_size_id}}" />
<input type="hidden" name="canvas_width" id="canvas-width" value="{{template.canvas_size_width}}" />
<input type="hidden" name="canvas_height" id="canvas-height" value="{{template.canvas_size_height}}" />
@@ -33,7 +33,7 @@
<div class="card-body d-grid gap-2">
<div class="btn-group template-actions-group" role="group" aria-label="Template actions">
{{{saveActionButtons formId="template-form" saveLabel="Save" saveButtonClass="btn btn-success template-save-button" saveAndCloseLabel="Save and Close" saveAndNewLabel="Save and New" saveAndCloseValue="close" saveAndNewValue="new"}}}
<a class="btn btn-warning" href="/admin/templates" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<a class="btn btn-warning" href="/templates" data-confirm-unsaved="You have unsaved changes. Leave this page?">Cancel</a>
<button type="submit" class="btn btn-danger" form="delete-template-form">Delete</button>
</div>
</div>
@@ -111,7 +111,7 @@
</div>
</form>
<form id="delete-template-form" method="post" action="/admin/templates/{{template.id}}/delete" data-confirm-message="Delete this template?"></form>
<form id="delete-template-form" method="post" action="/templates/{{template.id}}/delete" data-confirm-message="Delete this template?"></form>
<div class="modal fade" id="region-add-modal" tabindex="-1" aria-labelledby="region-add-modal-label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
@@ -123,9 +123,12 @@
<div class="modal-body">
<p class="text-body-secondary mb-3">Choose the type of region you want to add to this template.</p>
<div class="d-grid gap-2">
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="text">Text</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="html">HTML</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="image">Image</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="api">API</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="rtmp">RTMP</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="rss">RSS</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="text">Text</button>
<button type="button" class="btn btn-outline-secondary text-start" data-add-region-type="webpage">Webpage</button>
</div>
</div>
@@ -152,6 +155,7 @@
<div class="region-field-grid region-field-grid--size">
<label>Width<input class="form-control" type="number" name="region_width[]" value="300" required /></label>
<label>Height<input class="form-control" type="number" name="region_height[]" value="120" required /></label>
<label>Lock ratio<input class="form-control" type="text" name="region_lock_ratio[]" value="" placeholder="16:9 or 5:4" /></label>
</div>
<div class="d-flex justify-content-end"><button type="button" class="btn btn-sm btn-outline-danger remove-region">Remove</button></div>
</div>
@@ -159,3 +163,4 @@
</template>
<textarea id="template-editor-data" hidden>{{json template.regions}}</textarea>
@@ -10,7 +10,7 @@
<h3 class="card-title">Existing templates</h3>
{{#if (hasPermission currentUser 'templates.create')}}
<div class="card-tools">
<a class="btn btn-primary btn-sm" href="/admin/templates/new">Create template</a>
<a class="btn btn-primary btn-sm" href="/templates/new">Create template</a>
</div>
{{/if}}
</div>
@@ -35,10 +35,10 @@
{{#if (anyPermission ../currentUser 'templates.update' 'templates.delete')}}
<div class="actions">
{{#if (hasPermission ../currentUser 'templates.update')}}
<a class="btn btn-sm btn-primary" href="/admin/templates/{{id}}/edit">Edit</a>
<a class="btn btn-sm btn-primary" href="/templates/{{id}}/edit">Edit</a>
{{/if}}
{{#if (hasPermission ../currentUser 'templates.delete')}}
<form class="inline-form" method="post" action="/admin/templates/{{id}}/delete" data-confirm-message="Delete this template?">
<form class="inline-form" method="post" action="/templates/{{id}}/delete" data-confirm-message="Delete this template?">
<button class="btn btn-sm btn-danger" type="submit">Delete</button>
</form>
{{/if}}
@@ -56,3 +56,4 @@
</table>
</div>
</div>