mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
Start adding translation support for admin
This commit is contained in:
parent
a102199d5a
commit
4ac62a107c
@ -84,6 +84,7 @@ func (app *appContext) loadConfig() error {
|
|||||||
|
|
||||||
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
|
||||||
|
|
||||||
|
app.storage.lang.chosenFormLang = app.config.Section("ui").Key("language").MustString("en-us")
|
||||||
app.storage.lang.chosenFormLang = app.config.Section("ui").Key("language").MustString("en-us")
|
app.storage.lang.chosenFormLang = app.config.Section("ui").Key("language").MustString("en-us")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
166
html/admin.html
166
html/admin.html
@ -16,164 +16,152 @@
|
|||||||
<body class="max-w-full overflow-x-hidden section">
|
<body class="max-w-full overflow-x-hidden section">
|
||||||
<div id="modal-login" class="modal">
|
<div id="modal-login" class="modal">
|
||||||
<form class="modal-content card" id="form-login" href="">
|
<form class="modal-content card" id="form-login" href="">
|
||||||
<span class="heading">Login</span>
|
<span class="heading">{{ .strings.login }}</span>
|
||||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="username" id="login-user">
|
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="login-user">
|
||||||
<input type="password" class="field input ~neutral !high mb-1" placeholder="password" id="login-password">
|
<input type="password" class="field input ~neutral !high mb-1" placeholder="{{ .strings.password }}" id="login-password">
|
||||||
<label>
|
<label>
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge !normal full-width center supra submit">Login</span>
|
<span class="button ~urge !normal full-width center supra submit">{{ .strings.login }}</span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-add-user" class="modal">
|
<div id="modal-add-user" class="modal">
|
||||||
<form class="modal-content card" id="form-add-user" href="">
|
<form class="modal-content card" id="form-add-user" href="">
|
||||||
<span class="heading">New User <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.newUser }} <span class="modal-close">×</span></span>
|
||||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="username" id="add-user-user">
|
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="add-user-user">
|
||||||
<input type="email" class="field input ~neutral !high mt-half mb-1" placeholder="email address">
|
<input type="email" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}">
|
||||||
<input type="password" class="field input ~neutral !high mb-1" placeholder="password" id="add-user-password">
|
<input type="password" class="field input ~neutral !high mb-1" placeholder="{{ .strings.password }}" id="add-user-password">
|
||||||
<label>
|
<label>
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge !normal full-width center supra submit">Create</span>
|
<span class="button ~urge !normal full-width center supra submit">{{ .strings.create }}</span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-about" class="modal">
|
<div id="modal-about" class="modal">
|
||||||
<div class="modal-content content card">
|
<div class="modal-content content card">
|
||||||
<span class="heading">About <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.aboutProgram }} <span class="modal-close">×</span></span>
|
||||||
<img src="/banner.svg" class="mt-1" alt="jfa-go banner">
|
<img src="/banner.svg" class="mt-1" alt="jfa-go banner">
|
||||||
<p><i class="icon ri-github-fill"></i><a href="https://github.com/hrfee/jfa-go">jfa-go</a></p>
|
<p><i class="icon ri-github-fill"></i><a href="https://github.com/hrfee/jfa-go">jfa-go</a></p>
|
||||||
<p>Version <span class="code monospace">{{ .version }}</span></p>
|
<p>{{ .strings.version }} <span class="code monospace">{{ .version }}</span></p>
|
||||||
<p>Commit <span class="code monospace">{{ .commit }}</span></p>
|
<p>{{ .strings.commitNoun }} <span class="code monospace">{{ .commit }}</span></p>
|
||||||
<p><a href="https://github.com/hrfee/jfa-go/blob/main/LICENSE">Available under the MIT License.</a></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 id="modal-modify-user" class="modal">
|
<div id="modal-modify-user" class="modal">
|
||||||
<form class="modal-content card" id="form-modify-user" href="">
|
<form class="modal-content card" id="form-modify-user" href="">
|
||||||
<span class="heading">Modify Settings for <span id="header-modify-user"></span> <span class="modal-close">×</span></span>
|
<span class="heading"><span id="header-modify-user"></span> <span class="modal-close">×</span></span>
|
||||||
<p class="content">Apply settings from an existing profile, or source them directly from a user.</p>
|
<p class="content">{{ .strings.modifySettingsDescription }}</p>
|
||||||
<div class="flex-row mb-1">
|
<div class="flex-row mb-1">
|
||||||
<label class="flex-row-group mr-1">
|
<label class="flex-row-group mr-1">
|
||||||
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-profile" checked>
|
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-profile" checked>
|
||||||
<span class="button ~neutral !high supra full-width center">Profile</span>
|
<span class="button ~neutral !high supra full-width center">{{ .strings.profile }}</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="flex-row-group ml-1">
|
<label class="flex-row-group ml-1">
|
||||||
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-user">
|
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-user">
|
||||||
<span class="button ~neutral !normal supra full-width center">User</span>
|
<span class="button ~neutral !normal supra full-width center">{{ .strings.user }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="select ~neutral !normal mb-1">
|
<div class="select ~neutral !normal mb-1">
|
||||||
<select id="modify-user-profiles">
|
<select id="modify-user-profiles"></select>
|
||||||
<option>Friends</option>
|
|
||||||
<option>Family</option>
|
|
||||||
<option>Default</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="select ~neutral !normal mb-1 unfocused">
|
<div class="select ~neutral !normal mb-1 unfocused">
|
||||||
<select id="modify-user-users">
|
<select id="modify-user-users"></select>
|
||||||
<option>Person</option>
|
|
||||||
<option>Other person</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<label class="switch mb-1">
|
<label class="switch mb-1">
|
||||||
<input type="checkbox" id="modify-user-homescreen" checked>
|
<input type="checkbox" id="modify-user-homescreen" checked>
|
||||||
<span>Apply homescreen layout</span>
|
<span>{{ .strings.applyHomescreenLayout }}</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge !normal full-width center supra submit">Apply</span>
|
<span class="button ~urge !normal full-width center supra submit">{{ .strings.apply }}</span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-delete-user" class="modal">
|
<div id="modal-delete-user" class="modal">
|
||||||
<form class="modal-content card" id="form-delete-user" href="">
|
<form class="modal-content card" id="form-delete-user" href="">
|
||||||
<span class="heading">Delete <span id="header-delete-user"></span> <span class="modal-close">×</span></span>
|
<span class="heading"><span id="header-delete-user"></span> <span class="modal-close">×</span></span>
|
||||||
<div class="content mt-half">
|
<div class="content mt-half">
|
||||||
<label class="switch mb-1">
|
<label class="switch mb-1">
|
||||||
<input type="checkbox" id="delete-user-notify" checked>
|
<input type="checkbox" id="delete-user-notify" checked>
|
||||||
<span>Send notification email</span>
|
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea id="textarea-delete-user" class="textarea full-width ~neutral !normal mb-1" placeholder="Your account has been deleted."></textarea>
|
<textarea id="textarea-delete-user" class="textarea full-width ~neutral !normal mb-1" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
|
||||||
<label>
|
<label>
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~critical !normal full-width center supra submit">Delete</span>
|
<span class="button ~critical !normal full-width center supra submit">{{ .strings.delete }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-restart" class="modal">
|
<div id="modal-restart" class="modal">
|
||||||
<div class="modal-content card ~critical !low">
|
<div class="modal-content card ~critical !low">
|
||||||
<span class="heading">Restart needed <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.settingsRestartRequired }} <span class="modal-close">×</span></span>
|
||||||
<p class="content pb-1">A restart is needed to apply some settings you changed. Do it now or later?</p>
|
<p class="content pb-1">{{ .strings.settingsRestartRequiredDescription }}</p>
|
||||||
<div class="fr">
|
<div class="fr">
|
||||||
<span class="button ~info !normal" id="settings-apply-no-restart">Apply, restart later</span>
|
<span class="button ~info !normal" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
|
||||||
<span class="button ~critical !normal" id="settings-apply-restart">Apply & restart</span>
|
<span class="button ~critical !normal" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-refresh" class="modal">
|
<div id="modal-refresh" class="modal">
|
||||||
<div class="modal-content card ~neutral !normal">
|
<div class="modal-content card ~neutral !normal">
|
||||||
<span class="heading">Settings applied.</span>
|
<span class="heading">{{ .strings.settingsApplied }}</span>
|
||||||
<p class="content">Refresh the page in a few seconds.</p>
|
<p class="content">{{ .strings.settingsRefreshPage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-ombi-defaults" class="modal">
|
<div id="modal-ombi-defaults" class="modal">
|
||||||
<form class="modal-content card" id="form-ombi-defaults" href="">
|
<form class="modal-content card" id="form-ombi-defaults" href="">
|
||||||
<span class="heading">Ombi user defaults <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.ombiUserDefaults }} <span class="modal-close">×</span></span>
|
||||||
<p class="content">Create an Ombi user and configure it, then select it here. It's settings/permissions will be stored and applied to new ombi users created by jfa-go.</p>
|
<p class="content">{{ .strings.ombiUserDefaultsDescription }}</p>
|
||||||
<div class="select ~neutral !normal mb-1">
|
<div class="select ~neutral !normal mb-1">
|
||||||
<select>
|
<select></select>
|
||||||
<option>Person</option>
|
|
||||||
<option>Other person</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<label>
|
<label>
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge !normal full-width center supra submit">Submit</span>
|
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-user-profiles" class="modal">
|
<div id="modal-user-profiles" class="modal">
|
||||||
<div class="modal-content wide card">
|
<div class="modal-content wide card">
|
||||||
<span class="heading">User profiles <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.userProfiles }} <span class="modal-close">×</span></span>
|
||||||
<p class="support lg">Profiles are applied to users when they create an account. A profile includes library access rights and homescreen layout.</p>
|
<p class="support lg">{{ .strings.userProfilesDescription }}</p>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>{{ .strings.name }}</th>
|
||||||
<th>Default</th>
|
<th>{{ .strings.userProfilesIsDefault }}</th>
|
||||||
<th>From</th>
|
<th>{{ .strings.from }}</th>
|
||||||
<th>Libraries</th>
|
<th>{{ .strings.userProfilesLibraries }}</th>
|
||||||
<th><span class="button ~neutral !high" id="button-profile-create">Create</span></th>
|
<th><span class="button ~neutral !high" id="button-profile-create">{{ .strings.create }}</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="table-profiles">
|
<tbody id="table-profiles"></tbody>
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-add-profile" class="modal">
|
<div id="modal-add-profile" class="modal">
|
||||||
<form class="modal-content card" id="form-add-profile" href="">
|
<form class="modal-content card" id="form-add-profile" href="">
|
||||||
<span class="heading">Add profile <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.addProfile }} <span class="modal-close">×</span></span>
|
||||||
<p class="content">Create a Jellyfin user and configure it. Select it here, and when this profile is applied to an invite, new users will be created with its settings.</p>
|
<p class="content">{{ .strings.addProfileDescription }}</p>
|
||||||
<label>
|
<label>
|
||||||
<span class="supra">Profile Name </span>
|
<span class="supra">{{ .strings.addProfileNameOf }} </span>
|
||||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="Name" id="add-profile-name">
|
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.name }}" id="add-profile-name">
|
||||||
<label>
|
<label>
|
||||||
<span class="supra">User</span>
|
<span class="supra">{{ .strings.user }}</span>
|
||||||
<div class="select ~neutral !normal mt-half mb-1">
|
<div class="select ~neutral !normal mt-half mb-1">
|
||||||
<select id="add-profile-user">
|
<select id="add-profile-user"></select>
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label class="switch mb-1">
|
<label class="switch mb-1">
|
||||||
<input type="checkbox" id="add-profile-homescreen" checked>
|
<input type="checkbox" id="add-profile-homescreen" checked>
|
||||||
<span>Store homescreen layout</span>
|
<span>{{ .strings.addProfileStoreHomescreenLayout }}</span>
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<input type="submit" class="unfocused">
|
<input type="submit" class="unfocused">
|
||||||
<span class="button ~urge !normal full-width center supra submit">Create</span>
|
<span class="button ~urge !normal full-width center supra submit">{{ .strings.create }}</span>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -182,40 +170,40 @@
|
|||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<header class="flex flex-wrap items-center justify-between">
|
<header class="flex flex-wrap items-center justify-between">
|
||||||
<div class="text-neutral-700">
|
<div class="text-neutral-700">
|
||||||
<span id="button-tab-invites" class="tab-button portal">Invites</span>
|
<span id="button-tab-invites" class="tab-button portal">{{ .strings.invites }}</span>
|
||||||
<span id="button-tab-accounts" class="tab-button portal">Accounts</span>
|
<span id="button-tab-accounts" class="tab-button portal">{{ .strings.accounts }}</span>
|
||||||
<span id="button-tab-settings" class="tab-button portal">Settings</span>
|
<span id="button-tab-settings" class="tab-button portal">{{ .strings.settings }}</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<div class="text-neutral-700">
|
<div class="text-neutral-700">
|
||||||
<span class="button ~critical !normal mb-1 unfocused" id="logout-button">Logout</span>
|
<span class="button ~critical !normal mb-1 unfocused" id="logout-button">{{ .strings.logout }}</span>
|
||||||
<span id="button-theme" class="button ~neutral !normal mb-1">Theme</span>
|
<span id="button-theme" class="button ~neutral !normal mb-1">{{ .strings.theme }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-invites">
|
<div id="tab-invites">
|
||||||
<div class="card ~neutral !low invites mb-1">
|
<div class="card ~neutral !low invites mb-1">
|
||||||
<span class="heading">Invites</span>
|
<span class="heading">{{ .strings.invites }}</span>
|
||||||
<div id="invites"></div>
|
<div id="invites"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !low">
|
<div class="card ~neutral !low">
|
||||||
<span class="heading">Create</span>
|
<span class="heading">{{ .strings.create }}</span>
|
||||||
<div class="row" id="create-inv">
|
<div class="row" id="create-inv">
|
||||||
<div class="card ~neutral !normal col">
|
<div class="card ~neutral !normal col">
|
||||||
<label class="label supra" for="create-days">Days</label>
|
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
<div class="select ~neutral !normal mb-1 mt-half">
|
||||||
<select id="create-days">
|
<select id="create-days">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<label class="label supra" for="create-hours">Hours</label>
|
<label class="label supra" for="create-hours">{{ .strings.inviteHours }}</label>
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
<div class="select ~neutral !normal mb-1 mt-half">
|
||||||
<select id="create-hours">
|
<select id="create-hours">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<label class="label supra" for="create-minutes">Minutes</label>
|
<label class="label supra" for="create-minutes">{{ .strings.inviteMinutes }}</label>
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
<div class="select ~neutral !normal mb-1 mt-half">
|
||||||
<select id="create-minutes">
|
<select id="create-minutes">
|
||||||
<option>0</option>
|
<option>0</option>
|
||||||
@ -223,7 +211,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !normal col">
|
<div class="card ~neutral !normal col">
|
||||||
<label class="label supra" for="create-uses">Number of uses</label>
|
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>
|
||||||
<div class="flex-expand mb-1 mt-half">
|
<div class="flex-expand mb-1 mt-half">
|
||||||
<input type="number" min="0" id="create-uses" class="input ~neutral !normal mr-1" value=1>
|
<input type="number" min="0" id="create-uses" class="input ~neutral !normal mr-1" value=1>
|
||||||
<label for="create-inf-uses" class="button ~neutral !normal">
|
<label for="create-inf-uses" class="button ~neutral !normal">
|
||||||
@ -231,14 +219,14 @@
|
|||||||
<input type="checkbox" class="unfocused" id="create-inf-uses" aria-label="Set uses to infinite">
|
<input type="checkbox" class="unfocused" id="create-inf-uses" aria-label="Set uses to infinite">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p class="support unfocused" id="create-inf-uses-warning"><span class="badge ~critical">Warning</span> invites with infinite uses can be used abusively.</p>
|
<p class="support unfocused" id="create-inf-uses-warning"><span class="badge ~critical">{{ .strings.warning }}</span> {{ .strings.inviteInfiniteUsesWarning }}</p>
|
||||||
<label class="label supra">Profile</label>
|
<label class="label supra">{{ .strings.profile }}</label>
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
<div class="select ~neutral !normal mb-1 mt-half">
|
||||||
<select id="create-profile">
|
<select id="create-profile">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div id="create-send-to-container">
|
<div id="create-send-to-container">
|
||||||
<label class="label supra">Send to</label>
|
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
|
||||||
<div class="flex-expand mb-1 mt-half">
|
<div class="flex-expand mb-1 mt-half">
|
||||||
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
|
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
|
||||||
<label for="create-send-to-enabled" class="button ~neutral !normal">
|
<label for="create-send-to-enabled" class="button ~neutral !normal">
|
||||||
@ -246,27 +234,27 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="button ~urge !normal supra full-width center lg" id="create-submit">Create</span>
|
<span class="button ~urge !normal supra full-width center lg" id="create-submit">{{ .strings.create }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="tab-accounts" class="unfocused">
|
<div id="tab-accounts" class="unfocused">
|
||||||
<div class="card ~neutral !low accounts mb-1">
|
<div class="card ~neutral !low accounts mb-1">
|
||||||
<span class="heading">Accounts</span>
|
<span class="heading">{{ .strings.accounts }}</span>
|
||||||
<div class="fr">
|
<div class="fr">
|
||||||
<span class="button ~neutral !normal" id="accounts-add-user">Add User</span>
|
<span class="button ~neutral !normal" id="accounts-add-user">{{ .quantityStrings.addUser.singular }}</span>
|
||||||
<span class="button ~urge !normal" id="accounts-modify-user">Modify Settings</span>
|
<span class="button ~urge !normal" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||||
<span class="button ~critical !normal" id="accounts-delete-user">Delete User</span>
|
<span class="button ~critical !normal" id="accounts-delete-user">{{ .quantityStrings.deleteUser.singular }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !normal accounts-header table-responsive mt-half">
|
<div class="card ~neutral !normal accounts-header table-responsive mt-half">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input type="checkbox" value="" id="accounts-select-all"></th>
|
<th><input type="checkbox" value="" id="accounts-select-all"></th>
|
||||||
<th>Username</th>
|
<th>{{ .strings.username }}</th>
|
||||||
<th>Email Address</th>
|
<th>{{ .strings.emailAddress }}</th>
|
||||||
<th>Last Active</th>
|
<th>{{ .strings.lastActiveTime }}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="accounts-list"></tbody>
|
<tbody id="accounts-list"></tbody>
|
||||||
@ -276,15 +264,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="tab-settings" class="unfocused">
|
<div id="tab-settings" class="unfocused">
|
||||||
<div class="card ~neutral !low settings overflow">
|
<div class="card ~neutral !low settings overflow">
|
||||||
<span class="heading">Settings</span>
|
<span class="heading">{{ .string.settings }}</span>
|
||||||
<div class="fr">
|
<div class="fr">
|
||||||
<span class="button ~neutral !normal unfocused" id="settings-save">Save</span>
|
<span class="button ~neutral !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="card ~neutral !normal col" id="settings-sidebar">
|
<div class="card ~neutral !normal col" id="settings-sidebar">
|
||||||
<aside class="aside sm ~info mb-half">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info">R</span> indicates changes require a restart.</aside>
|
<aside class="aside sm ~info mb-half" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info">R</span> indicates changes require a restart.</aside>
|
||||||
<span class="button ~neutral !low settings-section-button mb-half" id="setting-about"><span class="flex">About <i class="ri-information-line ml-half"></i></span></span>
|
<span class="button ~neutral !low settings-section-button mb-half" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-half"></i></span></span>
|
||||||
<span class="button ~neutral !low settings-section-button mb-half" id="setting-profiles"><span class="flex">User profiles <i class="ri-user-line ml-half"></i></span></span>
|
<span class="button ~neutral !low settings-section-button mb-half" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-half"></i></span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !normal col overflow" id="settings-panel"></div>
|
<div class="card ~neutral !normal col overflow" id="settings-panel"></div>
|
||||||
</div>
|
</div>
|
||||||
|
5
lang/admin/README.md
Normal file
5
lang/admin/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
##### admin page translation
|
||||||
|
|
||||||
|
* [x] static page content
|
||||||
|
* [ ] Typescript:
|
||||||
|
* [x] accounts.ts
|
80
lang/admin/en-us.json
Normal file
80
lang/admin/en-us.json
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"name": "English (US)"
|
||||||
|
},
|
||||||
|
"strings": {
|
||||||
|
"invites": "Invites",
|
||||||
|
"accounts": "Accounts",
|
||||||
|
"settings": "Settings",
|
||||||
|
"theme": "Theme",
|
||||||
|
"inviteDays": "Days",
|
||||||
|
"inviteHours": "Hours",
|
||||||
|
"inviteMinutes": "Minutes",
|
||||||
|
"inviteNumberOfUses": "Number of uses",
|
||||||
|
"warning": "Warning",
|
||||||
|
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
|
||||||
|
"inviteSendToEmail": "Send to",
|
||||||
|
"login": "Login",
|
||||||
|
"logout": "Logout",
|
||||||
|
"create": "Create",
|
||||||
|
"apply": "Apply",
|
||||||
|
"delete": "Delete",
|
||||||
|
"submit": "Submit",
|
||||||
|
"name": "Name",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password",
|
||||||
|
"emailAddress": "Email Address",
|
||||||
|
"lastActiveTime": "Last Active",
|
||||||
|
"from": "From",
|
||||||
|
"user": "User",
|
||||||
|
"aboutProgram": "About",
|
||||||
|
"version": "Version",
|
||||||
|
"commitNoun": "Commit",
|
||||||
|
"newUser": "New User",
|
||||||
|
"profile": "Profile",
|
||||||
|
"modifySettings": "Modify Settings",
|
||||||
|
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
|
||||||
|
"applyHomescreenLayout": "Apply homescreen layout",
|
||||||
|
"sendDeleteNotificationEmail": "Send notification email",
|
||||||
|
"sendDeleteNotifiationExample": "Your account has been deleted.",
|
||||||
|
"settingsRestartRequired": "Restart needed",
|
||||||
|
"settingsRestartRequiredDescription": "A restart is necessary to apply some settings you changed. Restart now or later?",
|
||||||
|
"settingsApplyRestartLater": "Apply, restart later",
|
||||||
|
"settingsApplyRestartNow": "Apply & restart",
|
||||||
|
"settingsApplied": "Settings applied.",
|
||||||
|
"settingsRefreshPage": "Refresh the page in a few seconds",
|
||||||
|
"settingsRequiredOrRestartMessage": "Note: {*} indicates a required field, {R} indicates changes require a restart.",
|
||||||
|
"settingsSave": "Save",
|
||||||
|
"ombiUserDefaults": "Ombi user defaults",
|
||||||
|
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go",
|
||||||
|
"userProfiles": "User Profiles",
|
||||||
|
"userProfilesDescription": "Profiles are applied to users when they create an account. A profile include library access rights and homescreen layout.",
|
||||||
|
"userProfilesIsDefault": "Default",
|
||||||
|
"userProfilesLibraries": "Libraries",
|
||||||
|
"addProfile": "Add Profile",
|
||||||
|
"addProfileDescription": "Create a Jellyfin user and configure it, then select it below. When this profile is applied to an invite, new users will be created with the settings.",
|
||||||
|
"addProfileNameOf": "Profile Name",
|
||||||
|
"addProfileStoreHomescreenLayout": "Store homescreen layout"
|
||||||
|
},
|
||||||
|
"variableStrings": {
|
||||||
|
"settingsRequiredOrRestartMessage": "Note: {*} indicates a required field, {R} indicates changes require a restart."
|
||||||
|
},
|
||||||
|
"quantityStrings": {
|
||||||
|
"modifySettingsFor": {
|
||||||
|
"singular": "Modify Settings for {n} user",
|
||||||
|
"plural": "Modify Settings for {n} users"
|
||||||
|
},
|
||||||
|
"deleteNUsers": {
|
||||||
|
"singular": "Delete {n} user",
|
||||||
|
"plural": "Delete {n} users"
|
||||||
|
},
|
||||||
|
"addUser": {
|
||||||
|
"singular": "Add user",
|
||||||
|
"plural": "Add users"
|
||||||
|
},
|
||||||
|
"deleteUser": {
|
||||||
|
"singular": "Delete User",
|
||||||
|
"plural": "Delete Users"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
main.go
1
main.go
@ -514,6 +514,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form")
|
app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form")
|
||||||
|
app.storage.lang.AdminPath = filepath.Join(app.localPath, "lang", "admin")
|
||||||
err = app.storage.loadLang()
|
err = app.storage.loadLang()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
||||||
|
81
storage.go
81
storage.go
@ -21,9 +21,12 @@ type Storage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Lang struct {
|
type Lang struct {
|
||||||
chosenFormLang string
|
chosenFormLang string
|
||||||
FormPath string
|
chosenAdminLang string
|
||||||
Form map[string]map[string]interface{}
|
AdminPath string
|
||||||
|
Admin map[string]map[string]interface{}
|
||||||
|
FormPath string
|
||||||
|
Form map[string]map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
// timePattern: %Y-%m-%dT%H:%M:%S.%f
|
||||||
@ -60,46 +63,58 @@ func (st *Storage) storeInvites() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (st *Storage) loadLang() error {
|
func (st *Storage) loadLang() error {
|
||||||
formFiles, err := ioutil.ReadDir(st.lang.FormPath)
|
loadData := func(path string) (map[string]map[string]interface{}, error) {
|
||||||
st.lang.Form = map[string]map[string]interface{}{}
|
files, err := ioutil.ReadDir(path)
|
||||||
|
out := map[string]map[string]interface{}{}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range files {
|
||||||
|
index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
||||||
|
var data map[string]interface{}
|
||||||
|
if substituteStrings != "" {
|
||||||
|
var file []byte
|
||||||
|
var err error
|
||||||
|
file, err = ioutil.ReadFile(filepath.Join(path, f.Name()))
|
||||||
|
if err != nil {
|
||||||
|
file = []byte("{}")
|
||||||
|
}
|
||||||
|
// Replace Jellyfin with emby on form
|
||||||
|
file = []byte(strings.ReplaceAll(string(file), "Jellyfin", substituteStrings))
|
||||||
|
err = json.Unmarshal(file, &data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR: Failed to read \"%s\": %s", path, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := loadJSON(filepath.Join(path, f.Name()), &data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out[index] = data
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
form, err := loadData(st.lang.FormPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, f := range formFiles {
|
for index, lang := range form {
|
||||||
index := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name()))
|
strings := lang["strings"].(map[string]interface{})
|
||||||
var data map[string]interface{}
|
|
||||||
if substituteStrings != "" {
|
|
||||||
var file []byte
|
|
||||||
var err error
|
|
||||||
file, err = ioutil.ReadFile(filepath.Join(st.lang.FormPath, f.Name()))
|
|
||||||
if err != nil {
|
|
||||||
file = []byte("{}")
|
|
||||||
}
|
|
||||||
// Replace Jellyfin with emby on form
|
|
||||||
file = []byte(strings.ReplaceAll(string(file), "Jellyfin", substituteStrings))
|
|
||||||
err = json.Unmarshal(file, &data)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("ERROR: Failed to read \"%s\": %s", st.lang.FormPath, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := loadJSON(filepath.Join(st.lang.FormPath, f.Name()), &data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strings := data["strings"].(map[string]interface{})
|
|
||||||
validationStrings := strings["validationStrings"].(map[string]interface{})
|
validationStrings := strings["validationStrings"].(map[string]interface{})
|
||||||
vS, err := json.Marshal(validationStrings)
|
vS, err := json.Marshal(validationStrings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
strings["validationStrings"] = string(vS)
|
strings["validationStrings"] = string(vS)
|
||||||
data["strings"] = strings
|
lang["strings"] = strings
|
||||||
st.lang.Form[index] = data
|
form[index] = lang
|
||||||
}
|
}
|
||||||
return nil
|
st.lang.Form = form
|
||||||
|
admin, err := loadData(st.lang.AdminPath)
|
||||||
|
st.lang.Admin = admin
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *Storage) loadEmails() error {
|
func (st *Storage) loadEmails() error {
|
||||||
|
26
views.go
26
views.go
@ -13,19 +13,27 @@ func gcHTML(gc *gin.Context, code int, file string, templ gin.H) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) AdminPage(gc *gin.Context) {
|
func (app *appContext) AdminPage(gc *gin.Context) {
|
||||||
|
lang := gc.Query("lang")
|
||||||
|
if lang == "" {
|
||||||
|
lang = app.storage.lang.chosenFormLang
|
||||||
|
} else if _, ok := app.storage.lang.Form[lang]; !ok {
|
||||||
|
lang = app.storage.lang.chosenFormLang
|
||||||
|
}
|
||||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
||||||
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
||||||
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
||||||
gcHTML(gc, http.StatusOK, "admin.html", gin.H{
|
gcHTML(gc, http.StatusOK, "admin.html", gin.H{
|
||||||
"urlBase": app.URLBase,
|
"urlBase": app.URLBase,
|
||||||
"cssClass": app.cssClass,
|
"cssClass": app.cssClass,
|
||||||
"contactMessage": "",
|
"contactMessage": "",
|
||||||
"email_enabled": emailEnabled,
|
"email_enabled": emailEnabled,
|
||||||
"notifications": notificationsEnabled,
|
"notifications": notificationsEnabled,
|
||||||
"version": VERSION,
|
"version": VERSION,
|
||||||
"commit": COMMIT,
|
"commit": COMMIT,
|
||||||
"ombiEnabled": ombiEnabled,
|
"ombiEnabled": ombiEnabled,
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
|
"strings": app.storage.lang.Admin[lang]["strings"],
|
||||||
|
"quantityStrings": app.storage.lang.Admin[lang]["quantityStrings"],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user