mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-09 20:00:12 +00:00
Compare commits
5 Commits
baf5e6a593
...
3fa4b01115
Author | SHA1 | Date | |
---|---|---|---|
3fa4b01115 | |||
65f402fd35 | |||
46f1bc20c8 | |||
a13a72c626 | |||
5a80145607 |
@ -22,7 +22,7 @@
|
||||
font-family: 'Hanken Grotesk';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ *
|
||||
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
|
||||
/* hanken-grotesk-700 - cyrillic-ext_latin_vietnamese */
|
||||
|
@ -417,9 +417,11 @@
|
||||
</span>
|
||||
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
|
||||
</div>
|
||||
{{ if .userPageEnabled }}
|
||||
<div class="top-4 right-4 absolute">
|
||||
<a class="button ~info" href="/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="page-container">
|
||||
<div class="mb-4">
|
||||
<header class="flex flex-wrap items-center justify-between">
|
||||
|
@ -146,6 +146,7 @@
|
||||
<label class="row switch pb-4">
|
||||
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
|
||||
</label>
|
||||
<p class="support pb-4 pl-4 mt-1">{{ .lang.Login.authorizeManualUserPageNotice }}</p>
|
||||
</div>
|
||||
<div id="login-manual">
|
||||
<label class="label">
|
||||
@ -238,6 +239,21 @@
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.UserPage.title }}</span>
|
||||
<p class="content my-2">{{ .lang.UserPage.description }}</p>
|
||||
<p class="content my-2">{{ .lang.UserPage.customizeMessages }}</p>
|
||||
<label class="row switch pb-4">
|
||||
<input type="checkbox" class="mr-2" id="userpage-enabled"><span>{{ .lang.Strings.enabled }}</span>
|
||||
</label>
|
||||
<p class="support mb-1 mt-1">{{ .lang.UserPage.requiredSettings }}</p>
|
||||
<section class="section ~neutral banner footer flex-expand middle">
|
||||
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
|
||||
<div>
|
||||
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="card ~neutral @low mb-2 unfocused">
|
||||
<span class="heading">{{ .lang.Messages.title }}</span>
|
||||
<p class="content my-2" id="messages-description"></p>
|
||||
@ -391,7 +407,7 @@
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="mr-2" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
|
||||
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
|
||||
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.resetLinksNotice }} {{ .lang.PasswordResets.resetLinksRequiredForUserPage }}</p>
|
||||
</label>
|
||||
<label class="switch">
|
||||
<input type="checkbox" class="mr-2" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
|
||||
|
1
lang.go
1
lang.go
@ -123,6 +123,7 @@ type setupLang struct {
|
||||
Email langSection `json:"email"`
|
||||
Messages langSection `json:"messages"`
|
||||
Notifications langSection `json:"notifications"`
|
||||
UserPage langSection `json:"userPage"`
|
||||
WelcomeEmails langSection `json:"welcomeEmails"`
|
||||
PasswordResets langSection `json:"passwordResets"`
|
||||
InviteEmails langSection `json:"inviteEmails"`
|
||||
|
@ -70,6 +70,7 @@
|
||||
"adminOnly": "Admin users only (recommended)",
|
||||
"allowAll": "Allow all Jellyfin users to login",
|
||||
"allowAllDescription": "Not recommended, you should allow individual users to login once setup.",
|
||||
"authorizeManualUserPageNotice": "Using this will disable the \"User Page\" feature.",
|
||||
"emailNotice": "Your email address can be used to receive notifications."
|
||||
},
|
||||
"jellyfinEmby": {
|
||||
@ -109,6 +110,12 @@
|
||||
"title": "Admin Notifications",
|
||||
"description": "If enabled, you can choose (per invite) to receive an message when an invite expires, or a user is created. If you didn't choose the Jellyfin login method, make sure you provided your email address, or add another contact method later."
|
||||
},
|
||||
"userPage": {
|
||||
"title": "User Page",
|
||||
"description": "The user page (shown as \"My Account\") allows users to access information about their account, such as their contact methods and account expiry. They can also change their password, start a password reset, and link/change contact methods, without having to ask you. Additionally, customized Markdown messages can be shown to the users before and after logging in.",
|
||||
"customizeMessages": "Click the edit button next to \"User Page\" in settings to set them later.",
|
||||
"requiredSettings": "Log-in to jfa-go via Jellyfin must be set. Ensure \"reset password via link\" is selected later for self-service password resets."
|
||||
},
|
||||
"welcomeEmails": {
|
||||
"title": "Welcome messages",
|
||||
"description": "If enabled, an message will be sent to new users with the Jellyfin/Emby URL and their username."
|
||||
@ -119,10 +126,11 @@
|
||||
},
|
||||
"passwordResets": {
|
||||
"title": "Password Resets",
|
||||
"description": "When a user tries to reset their password, Jellyfin creates a file named 'passwordreset-*.json' which contains a PIN. jfa-go reads the file and sends the PIN to the user.",
|
||||
"description": "When a user tries to reset their password, Jellyfin creates a file named 'passwordreset-*.json' which contains a PIN. jfa-go reads the file and sends the PIN to the user. If you enabled the \"User Page\" feature, a reset can also be performed there, given a username, email, or contact method.",
|
||||
"pathToJellyfin": "Path to Jellyfin configuration directory",
|
||||
"pathToJellyfinNotice": "If you don't know where this is, try resetting your password in Jellyfin. A popup with '<path to jellyfin>/passwordreset-*.json' will appear.",
|
||||
"pathToJellyfinNotice": "If you don't know where this is, try resetting your password in Jellyfin. A popup with '<path to jellyfin>/passwordreset-*.json' will appear. This is not necessary if you only want to use self-service password resets through the \"User Page\".",
|
||||
"resetLinks": "Send a link instead of a PIN",
|
||||
"resetLinksRequiredForUserPage": "Required for self-service password reset on the User Page.",
|
||||
"resetLinksNotice": "If Ombi integration is enabled, use this to sync Jellyfin password resets with Ombi.",
|
||||
"resetLinksLanguage": "Default reset link language",
|
||||
"setPassword": "Set password through link",
|
||||
|
2
setup.go
2
setup.go
@ -133,6 +133,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
|
||||
patchLang(&lang.Email, &fallback.Email, &english.Email)
|
||||
patchLang(&lang.Messages, &fallback.Messages, &english.Messages)
|
||||
patchLang(&lang.Notifications, &fallback.Notifications, &english.Notifications)
|
||||
patchLang(&lang.UserPage, &fallback.UserPage, &english.UserPage)
|
||||
patchLang(&lang.PasswordResets, &fallback.PasswordResets, &english.PasswordResets)
|
||||
patchLang(&lang.InviteEmails, &fallback.InviteEmails, &english.InviteEmails)
|
||||
patchLang(&lang.PasswordValidation, &fallback.PasswordValidation, &english.PasswordValidation)
|
||||
@ -150,6 +151,7 @@ func (st *Storage) loadLangSetup(filesystems ...fs.FS) error {
|
||||
patchLang(&lang.Email, &english.Email)
|
||||
patchLang(&lang.Messages, &english.Messages)
|
||||
patchLang(&lang.Notifications, &english.Notifications)
|
||||
patchLang(&lang.UserPage, &english.UserPage)
|
||||
patchLang(&lang.PasswordResets, &english.PasswordResets)
|
||||
patchLang(&lang.InviteEmails, &english.InviteEmails)
|
||||
patchLang(&lang.PasswordValidation, &english.PasswordValidation)
|
||||
|
@ -23,10 +23,44 @@ module.exports = {
|
||||
opacity: '0'
|
||||
}
|
||||
},
|
||||
'slide-in': {
|
||||
'0%': {
|
||||
opacity: '0',
|
||||
transform: 'translateY(-100%)'
|
||||
},
|
||||
'100%': {
|
||||
opacity: '1',
|
||||
transform: 'translateY(0%)'
|
||||
},
|
||||
},
|
||||
'slide-out': {
|
||||
'0%': {
|
||||
opacity: '1',
|
||||
transform: 'translateY(0%)'
|
||||
},
|
||||
'100%': {
|
||||
opacity: '0',
|
||||
transform: 'translateY(-100%)'
|
||||
},
|
||||
},
|
||||
'pulse': {
|
||||
'0%': {
|
||||
transform: 'scale(1)'
|
||||
},
|
||||
'50%': {
|
||||
transform: 'scale(1.05)'
|
||||
},
|
||||
'100%': {
|
||||
transform: 'scale(1)'
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fade-in 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
'fade-out': 'fade-out 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)'
|
||||
'fade-out': 'fade-out 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
'slide-in': 'slide-in 0.2s cubic-bezier(.08,.52,.01,.98)',
|
||||
'slide-out': 'slide-out 0.2s cubic-bezier(.08,.52,.01,.98)',
|
||||
'pulse': 'pulse 0.2s cubic-bezier(0.25, 0.45, 0.45, 0.94)'
|
||||
},
|
||||
colors: {
|
||||
neutral: colors.slate,
|
||||
|
@ -113,7 +113,8 @@ export class notificationBox implements NotificationBox {
|
||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||
closeButton.classList.add("button", "~critical", "@low", "ml-4");
|
||||
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
||||
closeButton.onclick = () => { this._box.removeChild(noti); };
|
||||
closeButton.onclick = () => this._close(noti);
|
||||
noti.classList.add("animate-slide-in");
|
||||
noti.appendChild(closeButton);
|
||||
return noti;
|
||||
}
|
||||
@ -125,11 +126,21 @@ export class notificationBox implements NotificationBox {
|
||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
||||
closeButton.classList.add("button", "~positive", "@low", "ml-4");
|
||||
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
||||
closeButton.onclick = () => { this._box.removeChild(noti); };
|
||||
closeButton.onclick = () => this._close(noti);
|
||||
noti.classList.add("animate-slide-in");
|
||||
noti.appendChild(closeButton);
|
||||
return noti;
|
||||
}
|
||||
|
||||
private _close = (noti: HTMLElement) => {
|
||||
noti.classList.remove("animate-slide-in");
|
||||
noti.classList.add("animate-slide-out");
|
||||
noti.addEventListener(window.animationEvent, () => {
|
||||
this._box.removeChild(noti);
|
||||
}, false);
|
||||
}
|
||||
|
||||
|
||||
connectionError = () => { this.customError("connectionError", window.lang.notif("errorConnection")); }
|
||||
|
||||
customError = (type: string, message: string) => {
|
||||
@ -138,11 +149,13 @@ export class notificationBox implements NotificationBox {
|
||||
noti.classList.add("error-" + type);
|
||||
const previousNoti: HTMLElement | undefined = this._box.querySelector("aside.error-" + type);
|
||||
if (this._errorTypes[type] && previousNoti !== undefined && previousNoti != null) {
|
||||
previousNoti.remove();
|
||||
this._box.removeChild(previousNoti);
|
||||
noti.classList.add("animate-pulse");
|
||||
noti.classList.remove("animate-slide-in");
|
||||
}
|
||||
this._box.appendChild(noti);
|
||||
this._errorTypes[type] = true;
|
||||
setTimeout(() => { if (this._box.contains(noti)) { this._box.removeChild(noti); this._errorTypes[type] = false; } }, this.timeout*1000);
|
||||
setTimeout(() => { if (this._box.contains(noti)) { this._close(noti); this._errorTypes[type] = false; } }, this.timeout*1000);
|
||||
}
|
||||
|
||||
customPositive = (type: string, bold: string, message: string) => {
|
||||
@ -151,11 +164,13 @@ export class notificationBox implements NotificationBox {
|
||||
noti.classList.add("positive-" + type);
|
||||
const previousNoti: HTMLElement | undefined = this._box.querySelector("aside.positive-" + type);
|
||||
if (this._positiveTypes[type] && previousNoti !== undefined && previousNoti != null) {
|
||||
previousNoti.remove();
|
||||
this._box.removeChild(previousNoti);
|
||||
noti.classList.add("animate-pulse");
|
||||
noti.classList.remove("animate-slide-in");
|
||||
}
|
||||
this._box.appendChild(noti);
|
||||
this._positiveTypes[type] = true;
|
||||
setTimeout(() => { if (this._box.contains(noti)) { this._box.removeChild(noti); this._positiveTypes[type] = false; } }, this.timeout*1000);
|
||||
setTimeout(() => { if (this._box.contains(noti)) { this._close(noti); this._positiveTypes[type] = false; } }, this.timeout*1000);
|
||||
}
|
||||
|
||||
customSuccess = (type: string, message: string) => this.customPositive(type, window.lang.strings("success") + ":", message)
|
||||
|
@ -5,11 +5,12 @@ export class Login {
|
||||
private _modal: Modal;
|
||||
private _form: HTMLFormElement;
|
||||
private _url: string;
|
||||
private _endpoint: string;
|
||||
private _onLogin: (username: string, password: string) => void;
|
||||
private _logoutButton: HTMLElement = null;
|
||||
|
||||
constructor(modal: Modal, endpoint: string) {
|
||||
|
||||
this._endpoint = endpoint;
|
||||
this._url = window.URLBase + endpoint;
|
||||
if (this._url[this._url.length-1] != '/') this._url += "/";
|
||||
|
||||
@ -32,14 +33,22 @@ export class Login {
|
||||
bindLogout = (button: HTMLElement) => {
|
||||
this._logoutButton = button;
|
||||
this._logoutButton.classList.add("unfocused");
|
||||
this._logoutButton.onclick = () => _post(this._url + "logout", null, (req: XMLHttpRequest): boolean => {
|
||||
const logoutFunc = (url: string, tryAgain: boolean) => {
|
||||
_post(url + "logout", null, (req: XMLHttpRequest): boolean => {
|
||||
if (req.readyState == 4 && req.status == 200) {
|
||||
window.token = "";
|
||||
location.reload();
|
||||
return false;
|
||||
}
|
||||
}, false, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4 && req.status == 404 && tryAgain) {
|
||||
console.log("trying without URL Base...");
|
||||
logoutFunc(this._endpoint, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
this._logoutButton.onclick = () => logoutFunc(this._url, true);
|
||||
};
|
||||
|
||||
get onLogin() { return this._onLogin; }
|
||||
set onLogin(f: (username: string, password: string) => void) { this._onLogin = f; }
|
||||
|
@ -283,6 +283,9 @@ const settings = {
|
||||
"notifications": {
|
||||
"enabled": new Checkbox(get("notifications-enabled"))
|
||||
},
|
||||
"user_page": {
|
||||
"enabled": new Checkbox(get("userpage-enabled"))
|
||||
},
|
||||
"welcome_email": {
|
||||
"enabled": new Checkbox(get("welcome_email-enabled"), "", false, "welcome_email", "enabled"),
|
||||
"subject": new Input(get("welcome_email-subject"), "", "", "enabled", true, "welcome_email")
|
||||
|
Loading…
Reference in New Issue
Block a user