1
0
Fork 0
mirror of https://github.com/dani-garcia/vaultwarden.git synced 2025-05-17 00:53:56 +00:00

Redesign of the admin interface.

Main changes:
 - Splitted up settings and users into two separate pages.
 - Added verified shield when the e-mail address has been verified.
 - Added the amount of personal items in the database to the users overview.
 - Added Organizations and Diagnostics pages.
   - Shows if DNS resolving works.
   - Shows if there is a posible time drift.
   - Shows current versions of server and web-vault.
 - Optimized logo-gray.png using optipng

Items which can be added later:
 - Amount of cipher items accessible for a user, not only his personal items.
 - Amount of users per Org
 - Version update check in the diagnostics overview.
 - Copy/Pasteable runtime config which has sensitive data changed or removed for support questions either on the forum or github issues.
 - Option to delete Orgs and all its passwords (when there are no members anymore).
 - Etc....
This commit is contained in:
BlackDex 2020-05-28 10:42:36 +02:00
parent 4146612a32
commit 3c66deb5cc
12 changed files with 453 additions and 183 deletions

View file

@ -23,7 +23,7 @@ pub fn routes() -> Vec<Route> {
routes![
admin_login,
get_users,
get_users_json,
post_admin_login,
admin_page,
invite_user,
@ -36,6 +36,9 @@ pub fn routes() -> Vec<Route> {
delete_config,
backup_db,
test_smtp,
users_overview,
organizations_overview,
diagnostics,
]
}
@ -118,7 +121,9 @@ fn _validate_token(token: &str) -> bool {
struct AdminTemplateData {
page_content: String,
version: Option<&'static str>,
users: Vec<Value>,
users: Option<Vec<Value>>,
organizations: Option<Vec<Value>>,
diagnostics: Option<Value>,
config: Value,
can_backup: bool,
logged_in: bool,
@ -126,15 +131,59 @@ struct AdminTemplateData {
}
impl AdminTemplateData {
fn new(users: Vec<Value>) -> Self {
fn new() -> Self {
Self {
page_content: String::from("admin/page"),
page_content: String::from("admin/settings"),
version: VERSION,
users,
config: CONFIG.prepare_json(),
can_backup: *CAN_BACKUP,
logged_in: true,
urlpath: CONFIG.domain_path(),
users: None,
organizations: None,
diagnostics: None,
}
}
fn users(users: Vec<Value>) -> Self {
Self {
page_content: String::from("admin/users"),
version: VERSION,
users: Some(users),
config: CONFIG.prepare_json(),
can_backup: *CAN_BACKUP,
logged_in: true,
urlpath: CONFIG.domain_path(),
organizations: None,
diagnostics: None,
}
}
fn organizations(organizations: Vec<Value>) -> Self {
Self {
page_content: String::from("admin/organizations"),
version: VERSION,
organizations: Some(organizations),
config: CONFIG.prepare_json(),
can_backup: *CAN_BACKUP,
logged_in: true,
urlpath: CONFIG.domain_path(),
users: None,
diagnostics: None,
}
}
fn diagnostics(diagnostics: Value) -> Self {
Self {
page_content: String::from("admin/diagnostics"),
version: VERSION,
organizations: None,
config: CONFIG.prepare_json(),
can_backup: *CAN_BACKUP,
logged_in: true,
urlpath: CONFIG.domain_path(),
users: None,
diagnostics: Some(diagnostics),
}
}
@ -144,11 +193,8 @@ impl AdminTemplateData {
}
#[get("/", rank = 1)]
fn admin_page(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
let users = User::get_all(&conn);
let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect();
let text = AdminTemplateData::new(users_json).render()?;
fn admin_page(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
let text = AdminTemplateData::new().render()?;
Ok(Html(text))
}
@ -195,13 +241,29 @@ fn logout(mut cookies: Cookies) -> Result<Redirect, ()> {
}
#[get("/users")]
fn get_users(_token: AdminToken, conn: DbConn) -> JsonResult {
fn get_users_json(_token: AdminToken, conn: DbConn) -> JsonResult {
let users = User::get_all(&conn);
let users_json: Vec<Value> = users.iter().map(|u| u.to_json(&conn)).collect();
Ok(Json(Value::Array(users_json)))
}
#[get("/users/overview")]
fn users_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
let users = User::get_all(&conn);
let users_json: Vec<Value> = users.iter()
.map(|u| {
let mut usr = u.to_json(&conn);
if let Some(ciphers) = Cipher::count_owned_by_user(&u.uuid, &conn) {
usr["cipher_count"] = json!(ciphers);
};
usr
}).collect();
let text = AdminTemplateData::users(users_json).render()?;
Ok(Html(text))
}
#[post("/users/<uuid>/delete")]
fn delete_user(uuid: String, _token: AdminToken, conn: DbConn) -> EmptyResult {
let user = match User::find_by_uuid(&uuid, &conn) {
@ -242,6 +304,50 @@ fn update_revision_users(_token: AdminToken, conn: DbConn) -> EmptyResult {
User::update_all_revisions(&conn)
}
#[get("/organizations/overview")]
fn organizations_overview(_token: AdminToken, conn: DbConn) -> ApiResult<Html<String>> {
let organizations = Organization::get_all(&conn);
let organizations_json: Vec<Value> = organizations.iter().map(|o| o.to_json()).collect();
let text = AdminTemplateData::organizations(organizations_json).render()?;
Ok(Html(text))
}
#[derive(Deserialize, Serialize, Debug)]
#[allow(non_snake_case)]
pub struct WebVaultVersion {
version: String,
}
#[get("/diagnostics")]
fn diagnostics(_token: AdminToken, _conn: DbConn) -> ApiResult<Html<String>> {
use std::net::ToSocketAddrs;
use chrono::prelude::*;
use crate::util::read_file_string;
let vault_version_path = format!("{}/{}", CONFIG.web_vault_folder(), "version.json");
let vault_version_str = read_file_string(&vault_version_path)?;
let web_vault_version: WebVaultVersion = serde_json::from_str(&vault_version_str)?;
let github_ips = ("github.com", 0).to_socket_addrs().map(|mut i| i.next());
let dns_resolved = match github_ips {
Ok(Some(a)) => a.ip().to_string() ,
_ => "Could not resolve domain name.".to_string(),
};
let dt = Utc::now();
let server_time = dt.format("%Y-%m-%d %H:%M:%S").to_string();
let diagnostics_json = json!({
"dns_resolved": dns_resolved,
"server_time": server_time,
"web_vault_version": web_vault_version.version,
});
let text = AdminTemplateData::diagnostics(diagnostics_json).render()?;
Ok(Html(text))
}
#[post("/config", data = "<data>")]
fn post_config(data: Json<ConfigBuilder>, _token: AdminToken) -> EmptyResult {
let data: ConfigBuilder = data.into_inner();