mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-14 22:30:10 +00:00
Harvey Tindall
301f502052
web UI now uses modules, and relies less on bodge to make things work. Also fixes an issue where invites where "failed to send to xx" appeared in invite form.
484 lines
29 KiB
HTML
484 lines
29 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<!-- Required meta tags -->
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
|
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
|
<link rel="manifest" href="/site.webmanifest">
|
|
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
|
<meta name="msapplication-TileColor" content="#603cba">
|
|
<meta name="theme-color" content="#ffffff">
|
|
<!-- Bootstrap CSS -->
|
|
|
|
<script>
|
|
// To grab theme preference
|
|
function getCookie(cname) {
|
|
let name = cname + "=";
|
|
let decodedCookie = decodeURIComponent(document.cookie);
|
|
let ca = decodedCookie.split(';');
|
|
for (let c of ca) {
|
|
while(c.charAt(0) == ' ') {
|
|
c = c.substring(1);
|
|
}
|
|
if (c.indexOf(name) == 0) {
|
|
return c.substring(name.length, c.length);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
{{ if .bs5 }}
|
|
window.bsVersion = 5;
|
|
{{ else }}
|
|
window.bsVersion = 4;
|
|
{{ end }}
|
|
window.cssFile = "{{ .cssFile }}";
|
|
var css = document.createElement('link');
|
|
css.setAttribute('rel', 'stylesheet');
|
|
css.setAttribute('type', 'text/css');
|
|
var cssCookie = getCookie("css");
|
|
if (cssCookie.includes('bs' + bsVersion)) {
|
|
cssFile = cssCookie;
|
|
} else if (cssCookie.includes('bs')) {
|
|
if (cssCookie.includes('jf')) {
|
|
cssFile = 'bs' + bsVersion + '-jf.css';
|
|
} else {
|
|
cssFile = 'bs' + bsVersion + '.css';
|
|
}
|
|
document.cookie = 'css=' + cssFile;
|
|
}
|
|
css.setAttribute('href', cssFile);
|
|
document.head.appendChild(css);
|
|
</script>
|
|
{{ if not .bs5 }}
|
|
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
|
{{ end }}
|
|
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
|
|
{{ if .bs5 }}
|
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous"></script>
|
|
{{ else }}
|
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
|
|
{{ end }}
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
<title>Admin</title>
|
|
</head>
|
|
<body class="smooth-transition">
|
|
<div class="modal fade" id="login" role="dialog" aria-labelledby="login" aria-hidden="true" data-backdrop="static">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="loginTitle">Login</h5>
|
|
</div>
|
|
<div class="modal-body" id="formBody">
|
|
<form action="#" method="POST" id="loginForm">
|
|
<div class="form-group">
|
|
<label for="username">Username</label>
|
|
<input type="text" class="form-control" id="username" name="username" placeholder="Username" required>
|
|
<label for="password">Password</label>
|
|
<input type="password" class="form-control" id="password" name="password" placeholder="Password" required>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="submit" id="loginSubmit" class="btn btn-primary" form="loginForm">Login</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="users" role="dialog" aria-labelledby="users" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="usersTitle">Users</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<ul class="list-group list-group-flush" id="userList">
|
|
</ul>
|
|
</div>
|
|
<div class="modal-footer" id="userFooter">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="userDefaults" role="dialog" aria-labelledby="users" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="defaultsTitle"></h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p id="userDefaultsDescription"></p>
|
|
<div class="mb-3" id="defaultsSourceSection">
|
|
<label for="defaultsSource" class="form-label">Use settings from:</label>
|
|
<select class="form-select" id="defaultsSource" aria-label="User settings source">
|
|
<option value="profile" selected>Profile</option>
|
|
<option value="fromUser">Source from existing user</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-3 unfocused" id="profileSelectBox">
|
|
<label for="profileSelect" class="form-label">Profile</label>
|
|
<select class="form-select" id="profileSelect" aria-label="Profile to apply">
|
|
</select>
|
|
</div>
|
|
<div class="mb-3 unfocused" id="newProfileBox">
|
|
<label for="newProfileName" class="form-label">Name</label>
|
|
<input type="text" class="form-control" id="newProfileName" aria-describedby="Profile Name">
|
|
</div>
|
|
<div id="defaultUserRadiosBox">
|
|
<label for="defaultUserRadios" class="form-label">Get settings from</label>
|
|
<div id="defaultUserRadios"></div>
|
|
</div>
|
|
<div class="form-check" style="margin-top: 1rem;">
|
|
<input class="form-check-input" type="checkbox" value="" id="storeDefaultHomescreen" checked>
|
|
<label class="form-check-label" for="storeDefaultHomescreen" id="storeHomescreenLabel"></label>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer" id="defaultsFooter">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-primary" id="storeDefaults">Submit</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{ if .ombiEnabled }}
|
|
<div class="modal fade" id="ombiDefaults" role="dialog" aria-labelledby="Ombi Users" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="ombiTitle">Ombi user defaults</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Create an Ombi user and configure it to your liking, then choose it from below to store the settings and permissions as a template for all new users.</p>
|
|
<div id="ombiUserRadios"></div>
|
|
</div>
|
|
<div class="modal-footer" id="ombiFooter">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-primary" id="storeOmbiDefaults">Submit</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
<div class="modal fade" id="restartModal" role="dialog" aria-labelledby="Restart Warning" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Warning</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>A restart is needed to apply some settings. Restart now, later, or cancel?</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light" data-dismiss="modal" id="restartModalCancel">Cancel</button>
|
|
<button type="button" class="btn btn-secondary" id="applyRestarts" data-dismiss="modal">Apply, Restart later</button>
|
|
<button type="button" class="btn btn-primary" id="applyAndRestart" data-dismiss="modal">Apply & Restart</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="refreshModal" role="dialog" aria-labelledby="Refresh page notice" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Settings applied.</h5>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Refresh the page in a few seconds.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="aboutModal" role="dialog" aria-labelledby="About jfa-go" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">About</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<img src="banner.svg" alt="jfa-go banner">
|
|
<p><a href="https://github.com/hrfee/jfa-go"><i class="fa fa-github"></i> jfa-go</a></p>
|
|
<p>Version <i>{{ .version }}</i></p>
|
|
<p>Commit <i>{{ .commit }}</i></p>
|
|
<p><a href="https://github.com/hrfee/jfa-go/blob/main/LICENSE">Available under the MIT License.</a></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="deleteModal" role="dialog" aria-labelledby="Account deletion" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="deleteModalTitle"></h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" value="" id="deleteModalNotify">
|
|
<label class="form-check-label" for="deleteModalNotify" id="deleteModalNotifyLabel">Notify users of account deletion</label>
|
|
</div>
|
|
<div class="mb-3 unfocused" id="deleteModalReasonBox">
|
|
<label for="deleteModalReason" class="form-label">Reason for deletion</label>
|
|
<textarea class="form-control" id="deleteModalReason" rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light" data-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-danger" id="deleteModalSend">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal fade" id="newUserModal" role="dialog" aria-labelledby="Create new user" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Create a user</h5>
|
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
<span aria-hidden="true">×</span>
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div class="mb-3">
|
|
<label for="newUserEmail" class="form-label">Email address</label>
|
|
<input type="email" class="form-control" id="newUserEmail" aria-describedby="Email address">
|
|
</div>
|
|
{{ if .username }}
|
|
<div class="mb-3">
|
|
<label for="newUserName" class="form-label">Username</label>
|
|
<input type="text" class="form-control" id="newUserName" aria-describedby="Username">
|
|
</div>
|
|
{{ end }}
|
|
<div class="mb-3">
|
|
<label for="newUserPassword" class="form-label">Password</label>
|
|
<input type="password" class="form-control" id="newUserPassword" aria-describedby="Password">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-light" data-dismiss="modal">Cancel</button>
|
|
<button type="button" class="btn btn-primary" id="newUserCreate">Create</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="pageContainer">
|
|
<ul class="nav nav-pills" style="margin-bottom: 2rem;">
|
|
<li class="nav-item">
|
|
<h2><a id="invitesTabButton" class="nl nav-link active">Invites</a></h2>
|
|
</li>
|
|
<li class="nav-item">
|
|
<h2><a id="accountsTabButton" class="nl nav-link">Accounts</a></h2>
|
|
</li>
|
|
<li class="nav-item">
|
|
<h2><a id="settingsTabButton" class="nl nav-link">Settings</a></h2>
|
|
</li>
|
|
</ul>
|
|
<div class="btn-group" role="group" id="headerButtons">
|
|
<button type="button" class="btn btn-danger unfocused" id="logoutButton">
|
|
Logout <i class="fa fa-sign-out"></i>
|
|
</button>
|
|
</div>
|
|
<div id="invitesTab">
|
|
<div class="card mb-3 tabGroup">
|
|
<div class="card-header">Current Invites</div>
|
|
<ul class="list-group list-group-flush" id="invites">
|
|
</ul>
|
|
</div>
|
|
<div class="linkForm">
|
|
<div class="card mb-3">
|
|
<div class="card-header">Generate Invite</div>
|
|
<div class="card-body">
|
|
<form action="#" method="POST" id="inviteForm" class="container">
|
|
<div class="row align-items-start">
|
|
<div class="col-sm">
|
|
<div class="form-group">
|
|
<label for="days">Days</label>
|
|
<select class="form-control form-select" id="days" name="days">
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="hours">Hours</label>
|
|
<select class="form-control form-select" id="hours" name="hours">
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="minutes">Minutes</label>
|
|
<select class="form-control form-select" id="minutes" name="minutes">
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="col">
|
|
<div class="form-group">
|
|
<label for="multiUseCount">
|
|
Multiple uses
|
|
</label>
|
|
<div class="input-group">
|
|
<div class="input-group-text">
|
|
<input class="form-check-input" type="checkbox" onchange="document.getElementById('multiUseCount').disabled = !this.checked; document.getElementById('noUseLimit').disabled = !this.checked" aria-label="Checkbox to allow choice of invite usage limit" name="multiple-uses" id="multiUseEnabled">
|
|
</div>
|
|
<input type="number" class="form-control" name="remaining-uses" id="multiUseCount">
|
|
</div>
|
|
</div>
|
|
<div class="form-group form-check" style="margin-top: 1rem; margin-bottom: 1rem;">
|
|
<input class="form-check-input" type="checkbox" value="" name="no-limit" id="noUseLimit" onchange="document.getElementById('multiUseCount').disabled = this.checked; if (this.checked) { document.getElementById('noLimitWarning').style = 'display: block;' } else { document.getElementById('noLimitWarning').style = 'display: none;'; }">
|
|
<label class="form-check-label" for="noUseLimit">
|
|
No use limit
|
|
</label>
|
|
<div id="noLimitWarning" class="form-text" style="display: none;">Warning: Unlimited usage invites pose a risk if published online.</div>
|
|
</div>
|
|
<div class="form-group" style="margin-bottom: 1rem;">
|
|
<label for="inviteProfile">Account creation profile</label>
|
|
<select class="form-control form-select" id="inviteProfile" name="profile">
|
|
</select>
|
|
</div>
|
|
{{ if .email_enabled }}
|
|
<div class="form-group">
|
|
<label for="send_to_address">Send invite to address</label>
|
|
<div class="input-group">
|
|
<div class="input-group-text">
|
|
<input class="form-check-input" type="checkbox" onchange="document.getElementById('send_to_address').disabled = !this.checked;" aria-label="Checkbox to allow input of email address" id="send_to_address_enabled">
|
|
</div>
|
|
<input type="email" class="form-control" placeholder="example@example.com" id="send_to_address" disabled>
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="form-group d-flex float-right">
|
|
<button type="submit" id="generateSubmit" class="btn btn-primary" style="margin-top: 1rem;">
|
|
Generate
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="accountsTab" class="unfocused">
|
|
<div class="card mb-3 tabGroup">
|
|
<div class="card-header d-flex" style="align-items: center;">
|
|
<div>Accounts</div>
|
|
<div class="ml-auto">
|
|
<button type="button" class="btn btn-secondary" id="accountsTabAddUser">Add User</button>
|
|
<button type="button" class="btn btn-primary unfocused" id="accountsTabSetDefaults">Modify Settings</button>
|
|
<button type="button" class="btn btn-danger unfocused" id="accountsTabDelete"></button>
|
|
</div>
|
|
</div>
|
|
<div class="card-body table-responsive">
|
|
<table class="table table-hover table-striped table-borderless">
|
|
<thead>
|
|
<tr>
|
|
{{ if .bs5 }}
|
|
<th scope="col"><input class="form-check-input" type="checkbox" value="" id="selectAll"></th>
|
|
{{ else }}
|
|
<th scope="col"><input type="checkbox" value="" id="selectAll"></th>
|
|
{{ end }}
|
|
<th scope="col">Username</th>
|
|
<th scope="col">Email Address</th>
|
|
<th scope="col">Last Active</th>
|
|
<th scope="col">Admin?</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="accountsList">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="settingsTab" class="unfocused mb-3 tabGroup card">
|
|
<div class="card-header d-flex" style="align-items: center;">
|
|
<div>Settings</div>
|
|
<div class="ml-auto">
|
|
<button type="button" class="btn btn-primary" id="settingsSave">Save</button>
|
|
</div>
|
|
</div>
|
|
<div class="container card-body">
|
|
<div class="row">
|
|
<div class="col-sm">
|
|
<div class="" id="settingsLeft">
|
|
<ul class="list-group list-group-flush" style="margin-bottom: 1rem;">
|
|
<p>Note: <sup class="text-danger">*</sup> Indicates required field, <sup class="text-danger">R</sup> Indicates changes require a restart.</p>
|
|
<button type="button" class="list-group-item list-group-item-action static" id="openAbout">
|
|
About <i class="fa fa-info-circle settingIcon"></i>
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" id="profiles_button">
|
|
User Profiles <i class="fa fa-user settingIcon"></i>
|
|
</button>
|
|
{{ if .ombiEnabled }}
|
|
<button type="button" class="list-group-item list-group-item-action static" id="openOmbiDefaults">
|
|
Ombi User Defaults <i class="fa fa-chain-broken settingIcon"></i>
|
|
</button>
|
|
{{ end }}
|
|
</ul>
|
|
<div class="list-group list-group-flush" id="settingsSections">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col">
|
|
<div class="" id="settingsContent">
|
|
<div id="profiles" class="unfocused">
|
|
<div class="card card-body">
|
|
<p>Profiles are applied to users when they create an account. They include things like access rights and homescreen layout. You can create them here.</p>
|
|
<table class="table table-sm table-striped table-borderless">
|
|
<thead>
|
|
<tr>
|
|
<th scope="col">Name</th>
|
|
<th scope="col">Default</th>
|
|
<th scope="col">From</th>
|
|
<th scope="col">Admin?</th>
|
|
<th scope="col">Libraries</th>
|
|
<th scope="col"><button class="btn btn-outline-primary" onclick="createProfile()">Create</button></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="profileList">
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="contactBox">
|
|
<p>{{ .contactMessage }}</p>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
window.bs5 = {{ .bs5 }};
|
|
window.availableProfiles = [];
|
|
{{ if .notifications }}
|
|
window.notifications_enabled = true;
|
|
{{ else }}
|
|
window.notifications_enabled = false;
|
|
{{ end }}
|
|
</script>
|
|
<script src="admin.js" type="module"></script>
|
|
<script src="invites.js" type="module"></script>
|
|
{{ if .ombiEnabled }}
|
|
<script src="ombi.js" type="module"></script>
|
|
{{ end }}
|
|
</body>
|
|
</html>
|