mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-23 09:30:12 +00:00
Harvey Tindall
32b8ed4aa2
i wanted to split up the web ui components into multiple files, and figured it'd be a good chance to try out typescript. run make typescript to compile everything in ts/ and put it in data/static/. This is less of a rewrite and more of a refactoring, most of it still works the same but bits have been cleaned up too. Remaining javascript found in setup.js and form.html
453 lines
26 KiB
HTML
453 lines
26 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 }}
|
|
var bsVersion = 5;
|
|
{{ else }}
|
|
var bsVersion = 4;
|
|
{{ end }}
|
|
var 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="settingsMenu" role="dialog" aria-labelledby="settings menu" 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="settingsTitle">Settings</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" 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" id="openAbout">
|
|
About <i class="fa fa-info-circle settingIcon"></i>
|
|
</button>
|
|
<button type="button" class="list-group-item list-group-item-action" id="openDefaultsWizard">
|
|
New User Defaults <i class="fa fa-user settingIcon"></i>
|
|
</button>
|
|
{{ if .ombiEnabled }}
|
|
<button type="button" class="list-group-item list-group-item-action" id="openOmbiDefaults">
|
|
Ombi User Defaults <i class="fa fa-chain-broken settingIcon"></i>
|
|
</button>
|
|
{{ end }}
|
|
</ul>
|
|
<div class="list-group list-group-flush" id="settingsList">
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer" id="settingsFooter">
|
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
|
<button type="button" class="btn btn-primary" id="settingsSave">Save</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">Use settings from:</label>
|
|
<select class="form-select" id="defaultsSource" aria-label="User settings source">
|
|
<option value="userTemplate" selected>Use existing user template</option>
|
|
<option value="fromUser">Source from existing user</option>
|
|
</select>
|
|
</div>
|
|
<div id="defaultUserRadios"></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">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>
|
|
</ul>
|
|
<div class="btn-group" role="group" id="headerButtons">
|
|
<button type="button" class="btn btn-primary" id="openSettings">
|
|
Settings <i class="fa fa-cog"></i>
|
|
</button>
|
|
<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>
|
|
{{ 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 class="contactBox">
|
|
<p>{{ .contactMessage }}</p>
|
|
</div>
|
|
</div>
|
|
<script src="serialize.js"></script>
|
|
<script>
|
|
{{ if .notifications }}
|
|
var notifications_enabled = true;
|
|
{{ else }}
|
|
var notifications_enabled = false;
|
|
{{ end }}
|
|
</script>
|
|
{{ if .bs5 }}
|
|
<script src="bs5.js"></script>
|
|
{{ else }}
|
|
<script src="bs4.js"></script>
|
|
{{ end }}
|
|
<script src="animation.js"></script>
|
|
<script src="accounts.js"></script>
|
|
<script src="invites.js"></script>
|
|
<script src="admin.js"></script>
|
|
<script src="settings.js"></script>
|
|
{{ if .ombiEnabled }}
|
|
<script src="ombi.js"></script>
|
|
{{ end }}
|
|
</body>
|
|
</html>
|