geforkt von mirrored/vaultwarden
Merge pull request #1353 from BlackDex/admin-interface
Extra features for admin interface.
Dieser Commit ist enthalten in:
Commit
d41350050b
5 geänderte Dateien mit 181 neuen und 13 gelöschten Zeilen
|
@ -13,7 +13,7 @@ use rocket::{
|
||||||
use rocket_contrib::json::Json;
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{ApiResult, EmptyResult, JsonResult},
|
api::{ApiResult, EmptyResult, JsonResult, NumberOrString},
|
||||||
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
auth::{decode_admin, encode_jwt, generate_admin_claims, ClientIp},
|
||||||
config::ConfigBuilder,
|
config::ConfigBuilder,
|
||||||
db::{backup_database, models::*, DbConn, DbConnType},
|
db::{backup_database, models::*, DbConn, DbConnType},
|
||||||
|
@ -40,6 +40,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
disable_user,
|
disable_user,
|
||||||
enable_user,
|
enable_user,
|
||||||
remove_2fa,
|
remove_2fa,
|
||||||
|
update_user_org_type,
|
||||||
update_revision_users,
|
update_revision_users,
|
||||||
post_config,
|
post_config,
|
||||||
delete_config,
|
delete_config,
|
||||||
|
@ -47,6 +48,7 @@ pub fn routes() -> Vec<Route> {
|
||||||
test_smtp,
|
test_smtp,
|
||||||
users_overview,
|
users_overview,
|
||||||
organizations_overview,
|
organizations_overview,
|
||||||
|
delete_organization,
|
||||||
diagnostics,
|
diagnostics,
|
||||||
get_diagnostics_config
|
get_diagnostics_config
|
||||||
]
|
]
|
||||||
|
@ -367,6 +369,41 @@ fn remove_2fa(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
|
||||||
user.save(&conn)
|
user.save(&conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct UserOrgTypeData {
|
||||||
|
user_type: NumberOrString,
|
||||||
|
user_uuid: String,
|
||||||
|
org_uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/users/org_type", data = "<data>")]
|
||||||
|
fn update_user_org_type(data: Json<UserOrgTypeData>, _token: AdminToken, conn: DbConn) -> EmptyResult {
|
||||||
|
let data: UserOrgTypeData = data.into_inner();
|
||||||
|
|
||||||
|
let mut user_to_edit = match UserOrganization::find_by_user_and_org(&data.user_uuid, &data.org_uuid, &conn) {
|
||||||
|
Some(user) => user,
|
||||||
|
None => err!("The specified user isn't member of the organization"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_type = match UserOrgType::from_str(&data.user_type.into_string()) {
|
||||||
|
Some(new_type) => new_type as i32,
|
||||||
|
None => err!("Invalid type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if user_to_edit.atype == UserOrgType::Owner && new_type != UserOrgType::Owner {
|
||||||
|
// Removing owner permmission, check that there are at least another owner
|
||||||
|
let num_owners = UserOrganization::find_by_org_and_type(&data.org_uuid, UserOrgType::Owner as i32, &conn).len();
|
||||||
|
|
||||||
|
if num_owners <= 1 {
|
||||||
|
err!("Can't change the type of the last owner")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user_to_edit.atype = new_type as i32;
|
||||||
|
user_to_edit.save(&conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[post("/users/update_revision")]
|
#[post("/users/update_revision")]
|
||||||
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
|
fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
|
||||||
User::update_all_revisions(&conn)
|
User::update_all_revisions(&conn)
|
||||||
|
@ -390,6 +427,12 @@ fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<St
|
||||||
Ok(Html(text))
|
Ok(Html(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/organizations/<uuid>/delete")]
|
||||||
|
fn delete_organization(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
|
||||||
|
let org = Organization::find_by_uuid(&uuid, &conn).map_res("Organization doesn't exist")?;
|
||||||
|
org.delete(&conn)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct WebVaultVersion {
|
struct WebVaultVersion {
|
||||||
version: String,
|
version: String,
|
||||||
|
@ -443,7 +486,7 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
|
||||||
let web_vault_version: WebVaultVersion = serde_json::from_str(&vault_version_str)?;
|
let web_vault_version: WebVaultVersion = serde_json::from_str(&vault_version_str)?;
|
||||||
|
|
||||||
// Execute some environment checks
|
// Execute some environment checks
|
||||||
let running_within_docker = std::path::Path::new("/.dockerenv").exists();
|
let running_within_docker = std::path::Path::new("/.dockerenv").exists() || std::path::Path::new("/run/.containerenv").exists();
|
||||||
let has_http_access = has_http_access();
|
let has_http_access = has_http_access();
|
||||||
let uses_proxy = env::var_os("HTTP_PROXY").is_some()
|
let uses_proxy = env::var_os("HTTP_PROXY").is_some()
|
||||||
|| env::var_os("http_proxy").is_some()
|
|| env::var_os("http_proxy").is_some()
|
||||||
|
@ -471,9 +514,15 @@ fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
|
||||||
}
|
}
|
||||||
_ => "-".to_string(),
|
_ => "-".to_string(),
|
||||||
},
|
},
|
||||||
|
// Do not fetch the web-vault version when running within Docker.
|
||||||
|
// The web-vault version is embedded within the container it self, and should not be updated manually
|
||||||
|
if running_within_docker {
|
||||||
|
"-".to_string()
|
||||||
|
} else {
|
||||||
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest") {
|
match get_github_api::<GitRelease>("https://api.github.com/repos/dani-garcia/bw_web_builds/releases/latest") {
|
||||||
Ok(r) => r.tag_name.trim_start_matches('v').to_string(),
|
Ok(r) => r.tag_name.trim_start_matches('v').to_string(),
|
||||||
_ => "-".to_string(),
|
_ => "-".to_string(),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -876,14 +876,20 @@ fn js_escape_helper<'reg, 'rc>(
|
||||||
.param(0)
|
.param(0)
|
||||||
.ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
|
.ok_or_else(|| RenderError::new("Param not found for helper \"js_escape\""))?;
|
||||||
|
|
||||||
|
let no_quote = h
|
||||||
|
.param(1)
|
||||||
|
.is_some();
|
||||||
|
|
||||||
let value = param
|
let value = param
|
||||||
.value()
|
.value()
|
||||||
.as_str()
|
.as_str()
|
||||||
.ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
|
.ok_or_else(|| RenderError::new("Param for helper \"js_escape\" is not a String"))?;
|
||||||
|
|
||||||
let escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
|
let mut escaped_value = value.replace('\\', "").replace('\'', "\\x22").replace('\"', "\\x27");
|
||||||
let quoted_value = format!(""{}"", escaped_value);
|
if ! no_quote {
|
||||||
|
escaped_value = format!(""{}"", escaped_value);
|
||||||
|
}
|
||||||
|
|
||||||
out.write("ed_value)?;
|
out.write(&escaped_value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,14 @@
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
<span id="web-installed">{{diagnostics.web_vault_version}}</span>
|
<span id="web-installed">{{diagnostics.web_vault_version}}</span>
|
||||||
</dd>
|
</dd>
|
||||||
|
{{#unless diagnostics.running_within_docker}}
|
||||||
<dt class="col-sm-5">Web Latest
|
<dt class="col-sm-5">Web Latest
|
||||||
<span class="badge badge-secondary d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
|
<span class="badge badge-secondary d-none" id="web-failed" title="Unable to determine latest version.">Unknown</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
<span id="web-latest">{{diagnostics.latest_web_build}}</span>
|
<span id="web-latest">{{diagnostics.latest_web_build}}</span>
|
||||||
</dd>
|
</dd>
|
||||||
|
{{/unless}}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -93,8 +95,10 @@
|
||||||
</dd>
|
</dd>
|
||||||
|
|
||||||
<dt class="col-sm-5">Domain configuration
|
<dt class="col-sm-5">Domain configuration
|
||||||
<span class="badge badge-success d-none" id="domain-success" title="Domain variable seems to be correct.">Ok</span>
|
<span class="badge badge-success d-none" id="domain-success" title="The domain variable matches the browser location and seems to be configured correctly.">Match</span>
|
||||||
<span class="badge badge-danger d-none" id="domain-warning" title="Domain variable is not configured correctly.
Some features may not work as expected!">Error</span>
|
<span class="badge badge-danger d-none" id="domain-warning" title="The domain variable does not matches the browsers location.
The domain variable does not seem to be configured correctly.
Some features may not work as expected!">No Match</span>
|
||||||
|
<span class="badge badge-success d-none" id="https-success" title="Configurued to use HTTPS">HTTPS</span>
|
||||||
|
<span class="badge badge-danger d-none" id="https-warning" title="Not configured to use HTTPS.
Some features may not work as expected!">No HTTPS</span>
|
||||||
</dt>
|
</dt>
|
||||||
<dd class="col-sm-7">
|
<dd class="col-sm-7">
|
||||||
<span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{diagnostics.admin_url}}</span></span>
|
<span id="domain-server" class="d-block"><b>Server:</b> <span id="domain-server-string">{{diagnostics.admin_url}}</span></span>
|
||||||
|
@ -139,6 +143,7 @@
|
||||||
dnsCheck = false;
|
dnsCheck = false;
|
||||||
timeCheck = false;
|
timeCheck = false;
|
||||||
domainCheck = false;
|
domainCheck = false;
|
||||||
|
httpsCheck = false;
|
||||||
(() => {
|
(() => {
|
||||||
// ================================
|
// ================================
|
||||||
// Date & Time Check
|
// Date & Time Check
|
||||||
|
@ -181,10 +186,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const webInstalled = document.getElementById('web-installed').innerText;
|
const webInstalled = document.getElementById('web-installed').innerText;
|
||||||
const webLatest = document.getElementById('web-latest').innerText;
|
|
||||||
|
|
||||||
checkVersions('server', serverInstalled, serverLatest, serverLatestCommit);
|
checkVersions('server', serverInstalled, serverLatest, serverLatestCommit);
|
||||||
|
|
||||||
|
{{#unless diagnostics.running_within_docker}}
|
||||||
|
const webLatest = document.getElementById('web-latest').innerText;
|
||||||
checkVersions('web', webInstalled, webLatest);
|
checkVersions('web', webInstalled, webLatest);
|
||||||
|
{{/unless}}
|
||||||
|
|
||||||
function checkVersions(platform, installed, latest, commit=null) {
|
function checkVersions(platform, installed, latest, commit=null) {
|
||||||
if (installed === '-' || latest === '-') {
|
if (installed === '-' || latest === '-') {
|
||||||
|
@ -238,6 +245,14 @@
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('domain-warning').classList.remove('d-none');
|
document.getElementById('domain-warning').classList.remove('d-none');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for HTTPS at domain-server-string
|
||||||
|
if (document.getElementById('domain-server-string').innerText.toLowerCase().startsWith('https://') ) {
|
||||||
|
document.getElementById('https-success').classList.remove('d-none');
|
||||||
|
httpsCheck = true;
|
||||||
|
} else {
|
||||||
|
document.getElementById('https-warning').classList.remove('d-none');
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// ================================
|
// ================================
|
||||||
|
@ -253,10 +268,14 @@
|
||||||
supportString += "* DNS Check: " + dnsCheck + "\n";
|
supportString += "* DNS Check: " + dnsCheck + "\n";
|
||||||
supportString += "* Time Check: " + timeCheck + "\n";
|
supportString += "* Time Check: " + timeCheck + "\n";
|
||||||
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
|
supportString += "* Domain Configuration Check: " + domainCheck + "\n";
|
||||||
|
supportString += "* HTTPS Check: " + httpsCheck + "\n";
|
||||||
supportString += "* Database type: {{ diagnostics.db_type }}\n";
|
supportString += "* Database type: {{ diagnostics.db_type }}\n";
|
||||||
{{#case diagnostics.db_type "MySQL" "PostgreSQL"}}
|
{{#case diagnostics.db_type "MySQL" "PostgreSQL"}}
|
||||||
supportString += "* Database version: [PLEASE PROVIDE DATABASE VERSION]\n";
|
supportString += "* Database version: [PLEASE PROVIDE DATABASE VERSION]\n";
|
||||||
{{/case}}
|
{{/case}}
|
||||||
|
supportString += "* Clients used: \n";
|
||||||
|
supportString += "* Reverse proxy and version: \n";
|
||||||
|
supportString += "* Other relevant information: \n";
|
||||||
|
|
||||||
jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
|
jsonResponse = await fetch('{{urlpath}}/admin/diagnostics/config');
|
||||||
configJson = await jsonResponse.json();
|
configJson = await jsonResponse.json();
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
<th>Users</th>
|
<th>Users</th>
|
||||||
<th>Items</th>
|
<th>Items</th>
|
||||||
<th>Attachments</th>
|
<th>Attachments</th>
|
||||||
|
<th style="width: 120px; min-width: 120px;">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -37,6 +38,9 @@
|
||||||
<span class="d-block"><strong>Size:</strong> {{attachment_size}}</span>
|
<span class="d-block"><strong>Size:</strong> {{attachment_size}}</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
|
<td style="font-size: 90%; text-align: right; padding-right: 15px">
|
||||||
|
<a class="d-block" href="#" onclick='deleteOrganization({{jsesc Id}}, {{jsesc Name}}, {{jsesc BillingEmail}})'>Delete Organization</a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -50,6 +54,25 @@
|
||||||
<script src="{{urlpath}}/bwrs_static/jquery-3.5.1.slim.js"></script>
|
<script src="{{urlpath}}/bwrs_static/jquery-3.5.1.slim.js"></script>
|
||||||
<script src="{{urlpath}}/bwrs_static/datatables.js"></script>
|
<script src="{{urlpath}}/bwrs_static/datatables.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
function deleteOrganization(id, name, billing_email) {
|
||||||
|
// First make sure the user wants to delete this organization
|
||||||
|
var continueDelete = confirm("WARNING: All data of this organization ("+ name +") will be lost!\nMake sure you have a backup, this cannot be undone!");
|
||||||
|
if (continueDelete == true) {
|
||||||
|
var input_org_uuid = prompt("To delete the organization '" + name + " (" + billing_email +")', please type the organization uuid below.")
|
||||||
|
if (input_org_uuid != null) {
|
||||||
|
if (input_org_uuid == id) {
|
||||||
|
_post("{{urlpath}}/admin/organizations/" + id + "/delete",
|
||||||
|
"Organization deleted correctly",
|
||||||
|
"Error deleting organization");
|
||||||
|
} else {
|
||||||
|
alert("Wrong organization uuid, please try again")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
document.querySelectorAll("img.identicon").forEach(function (e, i) {
|
document.querySelectorAll("img.identicon").forEach(function (e, i) {
|
||||||
e.src = identicon(e.dataset.src);
|
e.src = identicon(e.dataset.src);
|
||||||
});
|
});
|
||||||
|
@ -59,6 +82,9 @@
|
||||||
"responsive": true,
|
"responsive": true,
|
||||||
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
|
"lengthMenu": [ [-1, 5, 10, 25, 50], ["All", 5, 10, 25, 50] ],
|
||||||
"pageLength": -1, // Default show all
|
"pageLength": -1, // Default show all
|
||||||
|
"columnDefs": [
|
||||||
|
{ "targets": 4, "searchable": false, "orderable": false }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
|
@ -57,7 +57,7 @@
|
||||||
<td>
|
<td>
|
||||||
<div class="overflow-auto" style="max-height: 120px;">
|
<div class="overflow-auto" style="max-height: 120px;">
|
||||||
{{#each Organizations}}
|
{{#each Organizations}}
|
||||||
<span class="badge badge-primary" data-orgtype="{{Type}}">{{Name}}</span>
|
<button class="badge badge-primary" data-toggle="modal" data-target="#userOrgTypeDialog" data-orgtype="{{Type}}" data-orguuid="{{jsesc Id no_quote}}" data-orgname="{{jsesc Name no_quote}}" data-useremail="{{jsesc ../Email no_quote}}" data-useruuid="{{jsesc ../Id no_quote}}">{{Name}}</button>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -100,6 +100,41 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="userOrgTypeDialog" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h6 class="modal-title" id="userOrgTypeDialogTitle"></h6>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form class="form" id="userOrgTypeForm" onsubmit="updateUserOrgType(); return false;">
|
||||||
|
<input type="hidden" name="user_uuid" id="userOrgTypeUserUuid" value="">
|
||||||
|
<input type="hidden" name="org_uuid" id="userOrgTypeOrgUuid" value="">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="radio">
|
||||||
|
<label><input type="radio" value="2" class="form-radio-input" name="user_type" id="userOrgTypeUser"> User</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio">
|
||||||
|
<label><input type="radio" value="3" class="form-radio-input" name="user_type" id="userOrgTypeManager"> Manager</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio">
|
||||||
|
<label><input type="radio" value="1" class="form-radio-input" name="user_type" id="userOrgTypeAdmin"> Admin</label>
|
||||||
|
</div>
|
||||||
|
<div class="radio">
|
||||||
|
<label><input type="radio" value="0" class="form-radio-input" name="user_type" id="userOrgTypeOwner"> Owner</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-sm btn-secondary" data-dismiss="modal">Cancel</button>
|
||||||
|
<button type="submit" class="btn btn-sm btn-primary">Change Role</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/datatables.css" />
|
<link rel="stylesheet" href="{{urlpath}}/bwrs_static/datatables.css" />
|
||||||
|
@ -220,4 +255,37 @@
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var userOrgTypeDialog = document.getElementById('userOrgTypeDialog');
|
||||||
|
// Fill the form and title
|
||||||
|
userOrgTypeDialog.addEventListener('show.bs.modal', function(event){
|
||||||
|
let userOrgType = event.relatedTarget.getAttribute("data-orgtype");
|
||||||
|
let userOrgTypeName = OrgTypes[userOrgType]["name"];
|
||||||
|
let orgName = event.relatedTarget.getAttribute("data-orgname");
|
||||||
|
let userEmail = event.relatedTarget.getAttribute("data-useremail");
|
||||||
|
let orgUuid = event.relatedTarget.getAttribute("data-orguuid");
|
||||||
|
let userUuid = event.relatedTarget.getAttribute("data-useruuid");
|
||||||
|
|
||||||
|
document.getElementById("userOrgTypeDialogTitle").innerHTML = "<b>Update User Type:</b><br><b>Organization:</b> " + orgName + "<br><b>User:</b> " + userEmail;
|
||||||
|
document.getElementById("userOrgTypeUserUuid").value = userUuid;
|
||||||
|
document.getElementById("userOrgTypeOrgUuid").value = orgUuid;
|
||||||
|
document.getElementById("userOrgType"+userOrgTypeName).checked = true;
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
// Prevent accidental submission of the form with valid elements after the modal has been hidden.
|
||||||
|
userOrgTypeDialog.addEventListener('hide.bs.modal', function(event){
|
||||||
|
document.getElementById("userOrgTypeDialogTitle").innerHTML = '';
|
||||||
|
document.getElementById("userOrgTypeUserUuid").value = '';
|
||||||
|
document.getElementById("userOrgTypeOrgUuid").value = '';
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
function updateUserOrgType() {
|
||||||
|
let orgForm = document.getElementById("userOrgTypeForm");
|
||||||
|
const data = JSON.stringify(Object.fromEntries(new FormData(orgForm).entries()));
|
||||||
|
|
||||||
|
_post("{{urlpath}}/admin/users/org_type",
|
||||||
|
"Updated organization type of the user successfully",
|
||||||
|
"Error updating organization type of the user", data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
Laden …
In neuem Issue referenzieren