diff --git a/api.go b/api.go
index bd471e2..380bf8d 100644
--- a/api.go
+++ b/api.go
@@ -180,6 +180,58 @@ type newUserReq struct {
Code string `json:"code"`
}
+func (app *appContext) NewUserAdmin(gc *gin.Context) {
+ var req newUserReq
+ gc.BindJSON(&req)
+ existingUser, _, _ := app.jf.userByName(req.Username, false)
+ if existingUser != nil {
+ msg := fmt.Sprintf("User already exists named %s", req.Username)
+ app.info.Printf("%s New user failed: %s", req.Username, msg)
+ respond(401, msg, gc)
+ return
+ }
+ user, status, err := app.jf.newUser(req.Username, req.Password)
+ if !(status == 200 || status == 204) || err != nil {
+ app.err.Printf("%s New user failed: Jellyfin responded with %d", req.Username, status)
+ respond(401, "Unknown error", gc)
+ return
+ }
+ var id string
+ if user["Id"] != nil {
+ id = user["Id"].(string)
+ }
+ if len(app.storage.policy) != 0 {
+ status, err = app.jf.setPolicy(id, app.storage.policy)
+ if !(status == 200 || status == 204) {
+ app.err.Printf("%s: Failed to set user policy: Code %d", req.Username, status)
+ }
+ }
+ if len(app.storage.configuration) != 0 && len(app.storage.displayprefs) != 0 {
+ status, err = app.jf.setConfiguration(id, app.storage.configuration)
+ if (status == 200 || status == 204) && err == nil {
+ status, err = app.jf.setDisplayPreferences(id, app.storage.displayprefs)
+ } else {
+ app.err.Printf("%s: Failed to set configuration template: Code %d", req.Username, status)
+ }
+ }
+ if app.config.Section("password_resets").Key("enabled").MustBool(false) {
+ app.storage.emails[id] = req.Email
+ app.storage.storeEmails()
+ }
+ if app.config.Section("ombi").Key("enabled").MustBool(false) {
+ app.storage.loadOmbiTemplate()
+ if len(app.storage.ombi_template) != 0 {
+ errors, code, err := app.ombi.newUser(req.Username, req.Password, req.Email, app.storage.ombi_template)
+ if err != nil || code != 200 {
+ app.info.Printf("Failed to create Ombi user (%d): %s", code, err)
+ app.debug.Printf("Errors reported by Ombi: %s", strings.Join(errors, ", "))
+ } else {
+ app.info.Println("Created Ombi user")
+ }
+ }
+ }
+}
+
func (app *appContext) NewUser(gc *gin.Context) {
var req newUserReq
gc.BindJSON(&req)
diff --git a/data/static/accounts.js b/data/static/accounts.js
index 396a944..74d00a1 100644
--- a/data/static/accounts.js
+++ b/data/static/accounts.js
@@ -171,8 +171,6 @@ function changeEmail(icon, id) {
icon.parentNode.appendChild(cross);
}
-
-
function populateUsers() {
const acList = document.getElementById('accountsList');
acList.innerHTML = `
@@ -292,6 +290,74 @@ document.getElementById('defaultsSource').addEventListener('change', function()
}
})
+document.getElementById('newUserCreate').onclick = function() {
+ const ogText = this.textContent;
+ this.innerHTML = `
+ Creating...`;
+ const email = document.getElementById('newUserEmail').value;
+ var username = email;
+ if (document.getElementById('newUserName') != null) {
+ username = document.getElementById('newUserName').value;
+ }
+ const password = document.getElementById('newUserPassword').value;
+ if (!validEmail(email) && email != "") {
+ return;
+ }
+ const send = {
+ 'username': username,
+ 'password': password,
+ 'email': email
+ }
+ let req = new XMLHttpRequest()
+ req.open("POST", "/newUserAdmin", true);
+ req.responseType = 'json';
+ req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":"));
+ const button = this;
+ req.onreadystatechange = function() {
+ if (this.readyState == 4) {
+ button.textContent = ogText;
+ if (this.status == 200) {
+ if (button.classList.contains('btn-primary')) {
+ button.classList.remove('btn-primary');
+ }
+ button.classList.add('btn-success');
+ button.textContent = 'Success';
+ setTimeout(function() {
+ if (button.classList.contains('btn-success')) {
+ button.classList.remove('btn-success');
+ }
+ button.classList.add('btn-primary');
+ button.textContent = ogText;
+ newUserModal.hide();
+ }, 1000);
+ } else {
+ if (button.classList.contains('btn-primary')) {
+ button.classList.remove('btn-primary');
+ }
+ button.classList.add('btn-danger');
+ if ("error" in req.response) {
+ button.textContent = req.response["error"];
+ } else {
+ button.textContent = 'Failed';
+ }
+ setTimeout(function() {
+ if (button.classList.contains('btn-danger')) {
+ button.classList.remove('btn-danger');
+ }
+ button.classList.add('btn-primary');
+ button.textContent = ogText;
+ }, 2000);
+ }
+ }
+ };
+ req.send(JSON.stringify(send));
+}
-
-
+document.getElementById('accountsTabAddUser').onclick = function() {
+ document.getElementById('newUserEmail').value = '';
+ document.getElementById('newUserPassword').value = '';
+ if (document.getElementById('newUserName') != null) {
+ document.getElementById('newUserName').value = '';
+ }
+ newUserModal.show();
+};
diff --git a/data/static/admin.js b/data/static/admin.js
index 398b6d6..1152d54 100644
--- a/data/static/admin.js
+++ b/data/static/admin.js
@@ -135,6 +135,7 @@ var restartModal = createModal('restartModal');
var refreshModal = createModal('refreshModal');
var aboutModal = createModal('aboutModal');
var deleteModal = createModal('deleteModal');
+var newUserModal = createModal('newUserModal');
// Parsed invite: [, , <1: Empty invite (no delete/link), 0: Actual invite>, , , [], , , ]
function parseInvite(invite, empty = false) {
diff --git a/data/templates/admin.html b/data/templates/admin.html
index 4ba954d..4716a92 100644
--- a/data/templates/admin.html
+++ b/data/templates/admin.html
@@ -207,8 +207,8 @@
@@ -270,6 +270,38 @@
+