mirror of
https://github.com/dani-garcia/vaultwarden.git
synced 2025-08-17 08:22:30 +00:00
Merge branch 'main' into Use-64bit-timestamps-in-TOTP
This commit is contained in:
commit
ad12b4054d
26 changed files with 5297 additions and 7547 deletions
|
@ -84,12 +84,8 @@
|
|||
### WebSocket ###
|
||||
#################
|
||||
|
||||
## Enables websocket notifications
|
||||
# WEBSOCKET_ENABLED=false
|
||||
|
||||
## Controls the WebSocket server address and port
|
||||
# WEBSOCKET_ADDRESS=0.0.0.0
|
||||
# WEBSOCKET_PORT=3012
|
||||
## Enable websocket notifications
|
||||
# ENABLE_WEBSOCKET=true
|
||||
|
||||
##########################
|
||||
### Push notifications ###
|
||||
|
@ -477,12 +473,19 @@
|
|||
# SMTP_HOST=smtp.domain.tld
|
||||
# SMTP_FROM=vaultwarden@domain.tld
|
||||
# SMTP_FROM_NAME=Vaultwarden
|
||||
# SMTP_SECURITY=starttls # ("starttls", "force_tls", "off") Enable a secure connection. Default is "starttls" (Explicit - ports 587 or 25), "force_tls" (Implicit - port 465) or "off", no encryption (port 25)
|
||||
# SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS).
|
||||
# SMTP_USERNAME=username
|
||||
# SMTP_PASSWORD=password
|
||||
# SMTP_TIMEOUT=15
|
||||
|
||||
## Choose the type of secure connection for SMTP. The default is "starttls".
|
||||
## The available options are:
|
||||
## - "starttls": The default port is 587.
|
||||
## - "force_tls": The default port is 465.
|
||||
## - "off": The default port is 25.
|
||||
## Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 (submissions) is used for encrypted submission (Implicit TLS).
|
||||
# SMTP_SECURITY=starttls
|
||||
# SMTP_PORT=587
|
||||
|
||||
# Whether to send mail via the `sendmail` command
|
||||
# USE_SENDMAIL=false
|
||||
# Which sendmail command to use. The one found in the $PATH is used if not specified.
|
||||
|
@ -524,7 +527,8 @@
|
|||
## Rocket specific settings
|
||||
## See https://rocket.rs/v0.5/guide/configuration/ for more details.
|
||||
# ROCKET_ADDRESS=0.0.0.0
|
||||
# ROCKET_PORT=80 # Defaults to 80 in the Docker images, or 8000 otherwise.
|
||||
## The default port is 8000, unless running in a Docker container, in which case it is 80.
|
||||
# ROCKET_PORT=8000
|
||||
# ROCKET_TLS={certs="/path/to/certs.pem",key="/path/to/key.pem"}
|
||||
|
||||
|
||||
|
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -229,28 +229,28 @@ jobs:
|
|||
|
||||
# Upload artifacts to Github Actions
|
||||
- name: "Upload amd64 artifact"
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
if: ${{ matrix.base_image == 'alpine' }}
|
||||
with:
|
||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-amd64
|
||||
path: vaultwarden-amd64
|
||||
|
||||
- name: "Upload arm64 artifact"
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
if: ${{ matrix.base_image == 'alpine' }}
|
||||
with:
|
||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-arm64
|
||||
path: vaultwarden-arm64
|
||||
|
||||
- name: "Upload armv7 artifact"
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
if: ${{ matrix.base_image == 'alpine' }}
|
||||
with:
|
||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv7
|
||||
path: vaultwarden-armv7
|
||||
|
||||
- name: "Upload armv6 artifact"
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
|
||||
if: ${{ matrix.base_image == 'alpine' }}
|
||||
with:
|
||||
name: vaultwarden-${{ env.SOURCE_VERSION }}-linux-armv6
|
||||
|
|
482
Cargo.lock
generated
482
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
31
Cargo.toml
31
Cargo.toml
|
@ -40,7 +40,7 @@ syslog = "6.1.0"
|
|||
|
||||
[dependencies]
|
||||
# Logging
|
||||
log = "0.4.20"
|
||||
log = "0.4.21"
|
||||
fern = { version = "0.6.2", features = ["syslog-6", "reopen-1"] }
|
||||
tracing = { version = "0.1.40", features = ["log"] } # Needed to have lettre and webauthn-rs trace logging to work
|
||||
|
||||
|
@ -53,14 +53,13 @@ once_cell = "1.19.0"
|
|||
# Numerical libraries
|
||||
num-traits = "0.2.18"
|
||||
num-derive = "0.4.2"
|
||||
bigdecimal = "0.4.2"
|
||||
bigdecimal = "0.4.3"
|
||||
|
||||
# Web framework
|
||||
rocket = { version = "0.5.0", features = ["tls", "json"], default-features = false }
|
||||
rocket_ws = { version ="0.1.0" }
|
||||
|
||||
# WebSockets libraries
|
||||
tokio-tungstenite = "0.20.1"
|
||||
rmpv = "1.0.1" # MessagePack library
|
||||
|
||||
# Concurrent HashMap used for WebSocket messaging and favicons
|
||||
|
@ -71,11 +70,11 @@ futures = "0.3.30"
|
|||
tokio = { version = "1.36.0", features = ["rt-multi-thread", "fs", "io-util", "parking_lot", "time", "signal"] }
|
||||
|
||||
# A generic serialization/deserialization framework
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
|
||||
# A safe, extensible ORM and Query builder
|
||||
diesel = { version = "2.1.4", features = ["chrono", "r2d2", "numeric"] }
|
||||
diesel = { version = "2.1.5", features = ["chrono", "r2d2", "numeric"] }
|
||||
diesel_migrations = "2.1.0"
|
||||
diesel_logger = { version = "0.3.0", optional = true }
|
||||
|
||||
|
@ -84,14 +83,14 @@ libsqlite3-sys = { version = "0.27.0", features = ["bundled"], optional = true }
|
|||
|
||||
# Crypto-related libraries
|
||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
ring = "0.17.7"
|
||||
ring = "0.17.8"
|
||||
|
||||
# UUID generation
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
|
||||
# Date and time libraries
|
||||
chrono = { version = "0.4.33", features = ["clock", "serde"], default-features = false }
|
||||
chrono-tz = "0.8.5"
|
||||
chrono = { version = "0.4.34", features = ["clock", "serde"], default-features = false }
|
||||
chrono-tz = "0.8.6"
|
||||
time = "0.3.34"
|
||||
|
||||
# Job scheduler
|
||||
|
@ -124,7 +123,7 @@ email_address = "0.2.4"
|
|||
handlebars = { version = "5.1.0", features = ["dir_source"] }
|
||||
|
||||
# HTTP client (Used for favicons, version check, DUO and HIBP API)
|
||||
reqwest = { version = "0.11.24", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns", "native-tls-alpn"] }
|
||||
reqwest = { version = "0.11.26", features = ["stream", "json", "gzip", "brotli", "socks", "cookies", "trust-dns", "native-tls-alpn"] }
|
||||
|
||||
# Favicon extraction libraries
|
||||
html5gum = "0.5.7"
|
||||
|
@ -133,24 +132,24 @@ data-url = "0.3.1"
|
|||
bytes = "1.5.0"
|
||||
|
||||
# Cache function results (Used for version check and favicon fetching)
|
||||
cached = { version = "0.48.1", features = ["async"] }
|
||||
cached = { version = "0.49.2", features = ["async"] }
|
||||
|
||||
# Used for custom short lived cookie jar during favicon extraction
|
||||
cookie = "0.17.0"
|
||||
cookie_store = "0.20.0"
|
||||
cookie = "0.18.0"
|
||||
cookie_store = "0.21.0"
|
||||
|
||||
# Used by U2F, JWT and PostgreSQL
|
||||
openssl = "0.10.63"
|
||||
openssl = "0.10.64"
|
||||
|
||||
# CLI argument parsing
|
||||
pico-args = "0.5.0"
|
||||
|
||||
# Macro ident concatenation
|
||||
paste = "1.0.14"
|
||||
governor = "0.6.0"
|
||||
governor = "0.6.3"
|
||||
|
||||
# Check client versions for specific features.
|
||||
semver = "1.0.21"
|
||||
semver = "1.0.22"
|
||||
|
||||
# Allow overriding the default memory allocator
|
||||
# Mainly used for the musl builds, since the default musl malloc is very slow
|
||||
|
|
|
@ -559,6 +559,8 @@ async fn post_email_token(data: JsonUpcase<EmailTokenData>, headers: Headers, mu
|
|||
if let Err(e) = mail::send_change_email(&data.NewEmail, &token).await {
|
||||
error!("Error sending change-email email: {:#?}", e);
|
||||
}
|
||||
} else {
|
||||
debug!("Email change request for user ({}) to email ({}) with token ({})", user.uuid, data.NewEmail, token);
|
||||
}
|
||||
|
||||
user.email_new = Some(data.NewEmail);
|
||||
|
|
|
@ -289,7 +289,7 @@ async fn _log_event(
|
|||
let mut event = Event::new(event_type, event_date);
|
||||
match event_type {
|
||||
// 1000..=1099 Are user events, they need to be logged via log_user_event()
|
||||
// Collection Events
|
||||
// Cipher Events
|
||||
1100..=1199 => {
|
||||
event.cipher_uuid = Some(String::from(source_uuid));
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ pub async fn validate_totp_code(
|
|||
let time = (current_timestamp + step * 30i64) as u64;
|
||||
let generated = totp_custom::<Sha1>(30, 6, &decoded_secret, time);
|
||||
|
||||
// Check the the given code equals the generated and if the time_step is larger then the one last used.
|
||||
// Check the given code equals the generated and if the time_step is larger then the one last used.
|
||||
if generated == totp_code && time_step > twofactor.last_used {
|
||||
// If the step does not equals 0 the time is drifted either server or client side.
|
||||
if step != 0 {
|
||||
|
|
|
@ -23,7 +23,7 @@ pub use crate::api::{
|
|||
icons::routes as icons_routes,
|
||||
identity::routes as identity_routes,
|
||||
notifications::routes as notifications_routes,
|
||||
notifications::{start_notification_server, AnonymousNotify, Notify, UpdateType, WS_ANONYMOUS_SUBSCRIPTIONS},
|
||||
notifications::{AnonymousNotify, Notify, UpdateType, WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS},
|
||||
push::{
|
||||
push_cipher_update, push_folder_update, push_logout, push_send_update, push_user_update, register_push_device,
|
||||
unregister_push_device,
|
||||
|
|
|
@ -1,23 +1,11 @@
|
|||
use std::{
|
||||
net::{IpAddr, SocketAddr},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use std::{net::IpAddr, sync::Arc, time::Duration};
|
||||
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use rmpv::Value;
|
||||
use rocket::{
|
||||
futures::{SinkExt, StreamExt},
|
||||
Route,
|
||||
};
|
||||
use tokio::{
|
||||
net::{TcpListener, TcpStream},
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
use tokio_tungstenite::{
|
||||
accept_hdr_async,
|
||||
tungstenite::{handshake, Message},
|
||||
};
|
||||
use rocket::{futures::StreamExt, Route};
|
||||
use tokio::sync::mpsc::Sender;
|
||||
|
||||
use rocket_ws::{Message, WebSocket};
|
||||
|
||||
use crate::{
|
||||
auth::{ClientIp, WsAccessTokenHeader},
|
||||
|
@ -30,7 +18,7 @@ use crate::{
|
|||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static WS_USERS: Lazy<Arc<WebSocketUsers>> = Lazy::new(|| {
|
||||
pub static WS_USERS: Lazy<Arc<WebSocketUsers>> = Lazy::new(|| {
|
||||
Arc::new(WebSocketUsers {
|
||||
map: Arc::new(dashmap::DashMap::new()),
|
||||
})
|
||||
|
@ -47,8 +35,15 @@ use super::{
|
|||
push_send_update, push_user_update,
|
||||
};
|
||||
|
||||
static NOTIFICATIONS_DISABLED: Lazy<bool> = Lazy::new(|| !CONFIG.enable_websocket() && !CONFIG.push_enabled());
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![websockets_hub, anonymous_websockets_hub]
|
||||
if CONFIG.enable_websocket() {
|
||||
routes![websockets_hub, anonymous_websockets_hub]
|
||||
} else {
|
||||
info!("WebSocket are disabled, realtime sync functionality will not work!");
|
||||
routes![]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromForm, Debug)]
|
||||
|
@ -108,7 +103,7 @@ impl Drop for WSAnonymousEntryMapGuard {
|
|||
|
||||
#[get("/hub?<data..>")]
|
||||
fn websockets_hub<'r>(
|
||||
ws: rocket_ws::WebSocket,
|
||||
ws: WebSocket,
|
||||
data: WsAccessToken,
|
||||
ip: ClientIp,
|
||||
header_token: WsAccessTokenHeader,
|
||||
|
@ -192,11 +187,7 @@ fn websockets_hub<'r>(
|
|||
}
|
||||
|
||||
#[get("/anonymous-hub?<token..>")]
|
||||
fn anonymous_websockets_hub<'r>(
|
||||
ws: rocket_ws::WebSocket,
|
||||
token: String,
|
||||
ip: ClientIp,
|
||||
) -> Result<rocket_ws::Stream!['r], Error> {
|
||||
fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> Result<rocket_ws::Stream!['r], Error> {
|
||||
let addr = ip.ip;
|
||||
info!("Accepting Anonymous Rocket WS connection from {addr}");
|
||||
|
||||
|
@ -349,13 +340,19 @@ impl WebSocketUsers {
|
|||
|
||||
// NOTE: The last modified date needs to be updated before calling these methods
|
||||
pub async fn send_user_update(&self, ut: UpdateType, user: &User) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
if *NOTIFICATIONS_DISABLED {
|
||||
return;
|
||||
}
|
||||
let data = create_update(
|
||||
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
|
||||
ut,
|
||||
None,
|
||||
);
|
||||
|
||||
self.send_update(&user.uuid, &data).await;
|
||||
if CONFIG.enable_websocket() {
|
||||
self.send_update(&user.uuid, &data).await;
|
||||
}
|
||||
|
||||
if CONFIG.push_enabled() {
|
||||
push_user_update(ut, user);
|
||||
|
@ -363,13 +360,19 @@ impl WebSocketUsers {
|
|||
}
|
||||
|
||||
pub async fn send_logout(&self, user: &User, acting_device_uuid: Option<String>) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
if *NOTIFICATIONS_DISABLED {
|
||||
return;
|
||||
}
|
||||
let data = create_update(
|
||||
vec![("UserId".into(), user.uuid.clone().into()), ("Date".into(), serialize_date(user.updated_at))],
|
||||
UpdateType::LogOut,
|
||||
acting_device_uuid.clone(),
|
||||
);
|
||||
|
||||
self.send_update(&user.uuid, &data).await;
|
||||
if CONFIG.enable_websocket() {
|
||||
self.send_update(&user.uuid, &data).await;
|
||||
}
|
||||
|
||||
if CONFIG.push_enabled() {
|
||||
push_logout(user, acting_device_uuid);
|
||||
|
@ -383,6 +386,10 @@ impl WebSocketUsers {
|
|||
acting_device_uuid: &String,
|
||||
conn: &mut DbConn,
|
||||
) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
if *NOTIFICATIONS_DISABLED {
|
||||
return;
|
||||
}
|
||||
let data = create_update(
|
||||
vec![
|
||||
("Id".into(), folder.uuid.clone().into()),
|
||||
|
@ -393,7 +400,9 @@ impl WebSocketUsers {
|
|||
Some(acting_device_uuid.into()),
|
||||
);
|
||||
|
||||
self.send_update(&folder.user_uuid, &data).await;
|
||||
if CONFIG.enable_websocket() {
|
||||
self.send_update(&folder.user_uuid, &data).await;
|
||||
}
|
||||
|
||||
if CONFIG.push_enabled() {
|
||||
push_folder_update(ut, folder, acting_device_uuid, conn).await;
|
||||
|
@ -409,6 +418,10 @@ impl WebSocketUsers {
|
|||
collection_uuids: Option<Vec<String>>,
|
||||
conn: &mut DbConn,
|
||||
) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
if *NOTIFICATIONS_DISABLED {
|
||||
return;
|
||||
}
|
||||
let org_uuid = convert_option(cipher.organization_uuid.clone());
|
||||
// Depending if there are collections provided or not, we need to have different values for the following variables.
|
||||
// The user_uuid should be `null`, and the revision date should be set to now, else the clients won't sync the collection change.
|
||||
|
@ -434,8 +447,10 @@ impl WebSocketUsers {
|
|||
Some(acting_device_uuid.into()),
|
||||
);
|
||||
|
||||
for uuid in user_uuids {
|
||||
self.send_update(uuid, &data).await;
|
||||
if CONFIG.enable_websocket() {
|
||||
for uuid in user_uuids {
|
||||
self.send_update(uuid, &data).await;
|
||||
}
|
||||
}
|
||||
|
||||
if CONFIG.push_enabled() && user_uuids.len() == 1 {
|
||||
|
@ -451,6 +466,10 @@ impl WebSocketUsers {
|
|||
acting_device_uuid: &String,
|
||||
conn: &mut DbConn,
|
||||
) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
if *NOTIFICATIONS_DISABLED {
|
||||
return;
|
||||
}
|
||||
let user_uuid = convert_option(send.user_uuid.clone());
|
||||
|
||||
let data = create_update(
|
||||
|
@ -463,8 +482,10 @@ impl WebSocketUsers {
|
|||
None,
|
||||
);
|
||||
|
||||
for uuid in user_uuids {
|
||||
self.send_update(uuid, &data).await;
|
||||
if CONFIG.enable_websocket() {
|
||||
for uuid in user_uuids {
|
||||
self.send_update(uuid, &data).await;
|
||||
}
|
||||
}
|
||||
if CONFIG.push_enabled() && user_uuids.len() == 1 {
|
||||
push_send_update(ut, send, acting_device_uuid, conn).await;
|
||||
|
@ -478,12 +499,18 @@ impl WebSocketUsers {
|
|||
acting_device_uuid: &String,
|
||||
conn: &mut DbConn,
|
||||
) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
if *NOTIFICATIONS_DISABLED {
|
||||
return;
|
||||
}
|
||||
let data = create_update(
|
||||
vec![("Id".into(), auth_request_uuid.clone().into()), ("UserId".into(), user_uuid.clone().into())],
|
||||
UpdateType::AuthRequest,
|
||||
Some(acting_device_uuid.to_string()),
|
||||
);
|
||||
self.send_update(user_uuid, &data).await;
|
||||
if CONFIG.enable_websocket() {
|
||||
self.send_update(user_uuid, &data).await;
|
||||
}
|
||||
|
||||
if CONFIG.push_enabled() {
|
||||
push_auth_request(user_uuid.to_string(), auth_request_uuid.to_string(), conn).await;
|
||||
|
@ -497,12 +524,18 @@ impl WebSocketUsers {
|
|||
approving_device_uuid: String,
|
||||
conn: &mut DbConn,
|
||||
) {
|
||||
// Skip any processing if both WebSockets and Push are not active
|
||||
if *NOTIFICATIONS_DISABLED {
|
||||
return;
|
||||
}
|
||||
let data = create_update(
|
||||
vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())],
|
||||
UpdateType::AuthRequestResponse,
|
||||
approving_device_uuid.clone().into(),
|
||||
);
|
||||
self.send_update(auth_response_uuid, &data).await;
|
||||
if CONFIG.enable_websocket() {
|
||||
self.send_update(auth_response_uuid, &data).await;
|
||||
}
|
||||
|
||||
if CONFIG.push_enabled() {
|
||||
push_auth_response(user_uuid.to_string(), auth_response_uuid.to_string(), approving_device_uuid, conn)
|
||||
|
@ -526,6 +559,9 @@ impl AnonymousWebSocketSubscriptions {
|
|||
}
|
||||
|
||||
pub async fn send_auth_response(&self, user_uuid: &String, auth_response_uuid: &str) {
|
||||
if !CONFIG.enable_websocket() {
|
||||
return;
|
||||
}
|
||||
let data = create_anonymous_update(
|
||||
vec![("Id".into(), auth_response_uuid.to_owned().into()), ("UserId".into(), user_uuid.clone().into())],
|
||||
UpdateType::AuthRequestResponse,
|
||||
|
@ -620,127 +656,3 @@ pub enum UpdateType {
|
|||
|
||||
pub type Notify<'a> = &'a rocket::State<Arc<WebSocketUsers>>;
|
||||
pub type AnonymousNotify<'a> = &'a rocket::State<Arc<AnonymousWebSocketSubscriptions>>;
|
||||
|
||||
pub fn start_notification_server() -> Arc<WebSocketUsers> {
|
||||
let users = Arc::clone(&WS_USERS);
|
||||
if CONFIG.websocket_enabled() {
|
||||
let users2 = Arc::<WebSocketUsers>::clone(&users);
|
||||
tokio::spawn(async move {
|
||||
let addr = (CONFIG.websocket_address(), CONFIG.websocket_port());
|
||||
info!("Starting WebSockets server on {}:{}", addr.0, addr.1);
|
||||
let listener = TcpListener::bind(addr).await.expect("Can't listen on websocket port");
|
||||
|
||||
let (shutdown_tx, mut shutdown_rx) = tokio::sync::oneshot::channel::<()>();
|
||||
CONFIG.set_ws_shutdown_handle(shutdown_tx);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
Ok((stream, addr)) = listener.accept() => {
|
||||
tokio::spawn(handle_connection(stream, Arc::<WebSocketUsers>::clone(&users2), addr));
|
||||
}
|
||||
|
||||
_ = &mut shutdown_rx => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Shutting down WebSockets server!")
|
||||
});
|
||||
}
|
||||
|
||||
users
|
||||
}
|
||||
|
||||
async fn handle_connection(stream: TcpStream, users: Arc<WebSocketUsers>, addr: SocketAddr) -> Result<(), Error> {
|
||||
let mut user_uuid: Option<String> = None;
|
||||
|
||||
info!("Accepting WS connection from {addr}");
|
||||
|
||||
// Accept connection, do initial handshake, validate auth token and get the user ID
|
||||
use handshake::server::{Request, Response};
|
||||
let mut stream = accept_hdr_async(stream, |req: &Request, res: Response| {
|
||||
if let Some(token) = get_request_token(req) {
|
||||
if let Ok(claims) = crate::auth::decode_login(&token) {
|
||||
user_uuid = Some(claims.sub);
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
Err(Response::builder().status(401).body(None).unwrap())
|
||||
})
|
||||
.await?;
|
||||
|
||||
let user_uuid = user_uuid.expect("User UUID should be set after the handshake");
|
||||
|
||||
let (mut rx, guard) = {
|
||||
// Add a channel to send messages to this client to the map
|
||||
let entry_uuid = uuid::Uuid::new_v4();
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<Message>(100);
|
||||
users.map.entry(user_uuid.clone()).or_default().push((entry_uuid, tx));
|
||||
|
||||
// Once the guard goes out of scope, the connection will have been closed and the entry will be deleted from the map
|
||||
(rx, WSEntryMapGuard::new(users, user_uuid, entry_uuid, addr.ip()))
|
||||
};
|
||||
|
||||
let _guard = guard;
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(15));
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = stream.next() => {
|
||||
match res {
|
||||
Some(Ok(message)) => {
|
||||
match message {
|
||||
// Respond to any pings
|
||||
Message::Ping(ping) => stream.send(Message::Pong(ping)).await?,
|
||||
Message::Pong(_) => {/* Ignored */},
|
||||
|
||||
// We should receive an initial message with the protocol and version, and we will reply to it
|
||||
Message::Text(ref message) => {
|
||||
let msg = message.strip_suffix(RECORD_SEPARATOR as char).unwrap_or(message);
|
||||
|
||||
if serde_json::from_str(msg).ok() == Some(INITIAL_MESSAGE) {
|
||||
stream.send(Message::binary(INITIAL_RESPONSE)).await?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Just echo anything else the client sends
|
||||
_ => stream.send(message).await?,
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
res = rx.recv() => {
|
||||
match res {
|
||||
Some(res) => stream.send(res).await?,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
_ = interval.tick() => stream.send(Message::Ping(create_ping())).await?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_request_token(req: &handshake::server::Request) -> Option<String> {
|
||||
const ACCESS_TOKEN_KEY: &str = "access_token=";
|
||||
|
||||
if let Some(Ok(auth)) = req.headers().get("Authorization").map(|a| a.to_str()) {
|
||||
if let Some(token_part) = auth.strip_prefix("Bearer ") {
|
||||
return Some(token_part.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(params) = req.uri().query() {
|
||||
let params_iter = params.split('&').take(1);
|
||||
for val in params_iter {
|
||||
if let Some(stripped) = val.strip_prefix(ACCESS_TOKEN_KEY) {
|
||||
return Some(stripped.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
|
@ -173,8 +173,8 @@ pub fn static_files(filename: &str) -> Result<(ContentType, &'static [u8]), Erro
|
|||
"jdenticon.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jdenticon.js"))),
|
||||
"datatables.js" => Ok((ContentType::JavaScript, include_bytes!("../static/scripts/datatables.js"))),
|
||||
"datatables.css" => Ok((ContentType::CSS, include_bytes!("../static/scripts/datatables.css"))),
|
||||
"jquery-3.7.0.slim.js" => {
|
||||
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.7.0.slim.js")))
|
||||
"jquery-3.7.1.slim.js" => {
|
||||
Ok((ContentType::JavaScript, include_bytes!("../static/scripts/jquery-3.7.1.slim.js")))
|
||||
}
|
||||
_ => err!(format!("Static file not found: {filename}")),
|
||||
}
|
||||
|
|
59
src/auth.rs
59
src/auth.rs
|
@ -2,9 +2,10 @@
|
|||
//
|
||||
use chrono::{Duration, Utc};
|
||||
use num_traits::FromPrimitive;
|
||||
use once_cell::sync::Lazy;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
|
||||
use jsonwebtoken::{self, errors::ErrorKind, Algorithm, DecodingKey, EncodingKey, Header};
|
||||
use openssl::rsa::Rsa;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::ser::Serialize;
|
||||
|
||||
|
@ -26,23 +27,45 @@ static JWT_SEND_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|send", CONFIG.do
|
|||
static JWT_ORG_API_KEY_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin()));
|
||||
static JWT_FILE_DOWNLOAD_ISSUER: Lazy<String> = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin()));
|
||||
|
||||
static PRIVATE_RSA_KEY: Lazy<EncodingKey> = Lazy::new(|| {
|
||||
let key =
|
||||
std::fs::read(CONFIG.private_rsa_key()).unwrap_or_else(|e| panic!("Error loading private RSA Key. \n{e}"));
|
||||
EncodingKey::from_rsa_pem(&key).unwrap_or_else(|e| panic!("Error decoding private RSA Key.\n{e}"))
|
||||
});
|
||||
static PUBLIC_RSA_KEY: Lazy<DecodingKey> = Lazy::new(|| {
|
||||
let key = std::fs::read(CONFIG.public_rsa_key()).unwrap_or_else(|e| panic!("Error loading public RSA Key. \n{e}"));
|
||||
DecodingKey::from_rsa_pem(&key).unwrap_or_else(|e| panic!("Error decoding public RSA Key.\n{e}"))
|
||||
});
|
||||
static PRIVATE_RSA_KEY: OnceCell<EncodingKey> = OnceCell::new();
|
||||
static PUBLIC_RSA_KEY: OnceCell<DecodingKey> = OnceCell::new();
|
||||
|
||||
pub fn load_keys() {
|
||||
Lazy::force(&PRIVATE_RSA_KEY);
|
||||
Lazy::force(&PUBLIC_RSA_KEY);
|
||||
pub fn initialize_keys() -> Result<(), crate::error::Error> {
|
||||
let mut priv_key_buffer = Vec::with_capacity(2048);
|
||||
|
||||
let priv_key = {
|
||||
let mut priv_key_file = File::options().create(true).read(true).write(true).open(CONFIG.private_rsa_key())?;
|
||||
|
||||
#[allow(clippy::verbose_file_reads)]
|
||||
let bytes_read = priv_key_file.read_to_end(&mut priv_key_buffer)?;
|
||||
|
||||
if bytes_read > 0 {
|
||||
Rsa::private_key_from_pem(&priv_key_buffer[..bytes_read])?
|
||||
} else {
|
||||
// Only create the key if the file doesn't exist or is empty
|
||||
let rsa_key = openssl::rsa::Rsa::generate(2048)?;
|
||||
priv_key_buffer = rsa_key.private_key_to_pem()?;
|
||||
priv_key_file.write_all(&priv_key_buffer)?;
|
||||
info!("Private key created correctly.");
|
||||
rsa_key
|
||||
}
|
||||
};
|
||||
|
||||
let pub_key_buffer = priv_key.public_key_to_pem()?;
|
||||
|
||||
let enc = EncodingKey::from_rsa_pem(&priv_key_buffer)?;
|
||||
let dec: DecodingKey = DecodingKey::from_rsa_pem(&pub_key_buffer)?;
|
||||
if PRIVATE_RSA_KEY.set(enc).is_err() {
|
||||
err!("PRIVATE_RSA_KEY must only be initialized once")
|
||||
}
|
||||
if PUBLIC_RSA_KEY.set(dec).is_err() {
|
||||
err!("PUBLIC_RSA_KEY must only be initialized once")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn encode_jwt<T: Serialize>(claims: &T) -> String {
|
||||
match jsonwebtoken::encode(&JWT_HEADER, claims, &PRIVATE_RSA_KEY) {
|
||||
match jsonwebtoken::encode(&JWT_HEADER, claims, PRIVATE_RSA_KEY.wait()) {
|
||||
Ok(token) => token,
|
||||
Err(e) => panic!("Error encoding jwt {e}"),
|
||||
}
|
||||
|
@ -56,7 +79,7 @@ fn decode_jwt<T: DeserializeOwned>(token: &str, issuer: String) -> Result<T, Err
|
|||
validation.set_issuer(&[issuer]);
|
||||
|
||||
let token = token.replace(char::is_whitespace, "");
|
||||
match jsonwebtoken::decode(&token, &PUBLIC_RSA_KEY, &validation) {
|
||||
match jsonwebtoken::decode(&token, PUBLIC_RSA_KEY.wait(), &validation) {
|
||||
Ok(d) => Ok(d.claims),
|
||||
Err(err) => match *err.kind() {
|
||||
ErrorKind::InvalidToken => err!("Token is invalid"),
|
||||
|
@ -799,7 +822,11 @@ impl<'r> FromRequest<'r> for OwnerHeaders {
|
|||
//
|
||||
// Client IP address detection
|
||||
//
|
||||
use std::net::IpAddr;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
net::IpAddr,
|
||||
};
|
||||
|
||||
pub struct ClientIp {
|
||||
pub ip: IpAddr,
|
||||
|
|
|
@ -39,7 +39,6 @@ macro_rules! make_config {
|
|||
|
||||
struct Inner {
|
||||
rocket_shutdown_handle: Option<rocket::Shutdown>,
|
||||
ws_shutdown_handle: Option<tokio::sync::oneshot::Sender<()>>,
|
||||
|
||||
templates: Handlebars<'static>,
|
||||
config: ConfigItems,
|
||||
|
@ -361,7 +360,7 @@ make_config! {
|
|||
/// Sends folder
|
||||
sends_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "sends");
|
||||
/// Temp folder |> Used for storing temporary file uploads
|
||||
tmp_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "tmp");
|
||||
tmp_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "tmp");
|
||||
/// Templates folder
|
||||
templates_folder: String, false, auto, |c| format!("{}/{}", c.data_folder, "templates");
|
||||
/// Session JWT key
|
||||
|
@ -371,11 +370,7 @@ make_config! {
|
|||
},
|
||||
ws {
|
||||
/// Enable websocket notifications
|
||||
websocket_enabled: bool, false, def, false;
|
||||
/// Websocket address
|
||||
websocket_address: String, false, def, "0.0.0.0".to_string();
|
||||
/// Websocket port
|
||||
websocket_port: u16, false, def, 3012;
|
||||
enable_websocket: bool, false, def, true;
|
||||
},
|
||||
push {
|
||||
/// Enable push notifications
|
||||
|
@ -1071,7 +1066,6 @@ impl Config {
|
|||
Ok(Config {
|
||||
inner: RwLock::new(Inner {
|
||||
rocket_shutdown_handle: None,
|
||||
ws_shutdown_handle: None,
|
||||
templates: load_templates(&config.templates_folder),
|
||||
config,
|
||||
_env,
|
||||
|
@ -1164,7 +1158,7 @@ impl Config {
|
|||
}
|
||||
|
||||
pub fn delete_user_config(&self) -> Result<(), Error> {
|
||||
crate::util::delete_file(&CONFIG_FILE)?;
|
||||
std::fs::remove_file(&*CONFIG_FILE)?;
|
||||
|
||||
// Empty user config
|
||||
let usr = ConfigBuilder::default();
|
||||
|
@ -1189,9 +1183,6 @@ impl Config {
|
|||
pub fn private_rsa_key(&self) -> String {
|
||||
format!("{}.pem", CONFIG.rsa_key_filename())
|
||||
}
|
||||
pub fn public_rsa_key(&self) -> String {
|
||||
format!("{}.pub.pem", CONFIG.rsa_key_filename())
|
||||
}
|
||||
pub fn mail_enabled(&self) -> bool {
|
||||
let inner = &self.inner.read().unwrap().config;
|
||||
inner._enable_smtp && (inner.smtp_host.is_some() || inner.use_sendmail)
|
||||
|
@ -1240,16 +1231,8 @@ impl Config {
|
|||
self.inner.write().unwrap().rocket_shutdown_handle = Some(handle);
|
||||
}
|
||||
|
||||
pub fn set_ws_shutdown_handle(&self, handle: tokio::sync::oneshot::Sender<()>) {
|
||||
self.inner.write().unwrap().ws_shutdown_handle = Some(handle);
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
if let Ok(mut c) = self.inner.write() {
|
||||
if let Some(handle) = c.ws_shutdown_handle.take() {
|
||||
handle.send(()).ok();
|
||||
}
|
||||
|
||||
if let Some(handle) = c.rocket_shutdown_handle.take() {
|
||||
handle.notify();
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ impl Attachment {
|
|||
|
||||
let file_path = &self.get_file_path();
|
||||
|
||||
match crate::util::delete_file(file_path) {
|
||||
match std::fs::remove_file(file_path) {
|
||||
// Ignore "file not found" errors. This can happen when the
|
||||
// upstream caller has already cleaned up the file as part of
|
||||
// its own error handling.
|
||||
|
|
|
@ -52,7 +52,6 @@ use rocket::error::Error as RocketErr;
|
|||
use serde_json::{Error as SerdeErr, Value};
|
||||
use std::io::Error as IoErr;
|
||||
use std::time::SystemTimeError as TimeErr;
|
||||
use tokio_tungstenite::tungstenite::Error as TungstError;
|
||||
use webauthn_rs::error::WebauthnError as WebauthnErr;
|
||||
use yubico::yubicoerror::YubicoError as YubiErr;
|
||||
|
||||
|
@ -91,7 +90,6 @@ make_error! {
|
|||
|
||||
DieselCon(DieselConErr): _has_source, _api_error,
|
||||
Webauthn(WebauthnErr): _has_source, _api_error,
|
||||
WebSocket(TungstError): _has_source, _api_error,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Error {
|
||||
|
|
37
src/main.rs
37
src/main.rs
|
@ -52,7 +52,7 @@ mod ratelimit;
|
|||
mod util;
|
||||
|
||||
use crate::api::purge_auth_requests;
|
||||
use crate::api::WS_ANONYMOUS_SUBSCRIPTIONS;
|
||||
use crate::api::{WS_ANONYMOUS_SUBSCRIPTIONS, WS_USERS};
|
||||
pub use config::CONFIG;
|
||||
pub use error::{Error, MapResult};
|
||||
use rocket::data::{Limits, ToByteUnit};
|
||||
|
@ -65,13 +65,17 @@ async fn main() -> Result<(), Error> {
|
|||
launch_info();
|
||||
|
||||
use log::LevelFilter as LF;
|
||||
let level = LF::from_str(&CONFIG.log_level()).expect("Valid log level");
|
||||
let level = LF::from_str(&CONFIG.log_level()).unwrap_or_else(|_| {
|
||||
let valid_log_levels = LF::iter().map(|lvl| lvl.as_str().to_lowercase()).collect::<Vec<String>>().join(", ");
|
||||
println!("Log level must be one of the following: {valid_log_levels}");
|
||||
exit(1);
|
||||
});
|
||||
init_logging(level).ok();
|
||||
|
||||
let extra_debug = matches!(level, LF::Trace | LF::Debug);
|
||||
|
||||
check_data_folder().await;
|
||||
check_rsa_keys().unwrap_or_else(|_| {
|
||||
auth::initialize_keys().unwrap_or_else(|_| {
|
||||
error!("Error creating keys, exiting...");
|
||||
exit(1);
|
||||
});
|
||||
|
@ -444,31 +448,6 @@ async fn container_data_folder_is_persistent(data_folder: &str) -> bool {
|
|||
true
|
||||
}
|
||||
|
||||
fn check_rsa_keys() -> Result<(), crate::error::Error> {
|
||||
// If the RSA keys don't exist, try to create them
|
||||
let priv_path = CONFIG.private_rsa_key();
|
||||
let pub_path = CONFIG.public_rsa_key();
|
||||
|
||||
if !util::file_exists(&priv_path) {
|
||||
let rsa_key = openssl::rsa::Rsa::generate(2048)?;
|
||||
|
||||
let priv_key = rsa_key.private_key_to_pem()?;
|
||||
crate::util::write_file(&priv_path, &priv_key)?;
|
||||
info!("Private key created correctly.");
|
||||
}
|
||||
|
||||
if !util::file_exists(&pub_path) {
|
||||
let rsa_key = openssl::rsa::Rsa::private_key_from_pem(&std::fs::read(&priv_path)?)?;
|
||||
|
||||
let pub_key = rsa_key.public_key_to_pem()?;
|
||||
crate::util::write_file(&pub_path, &pub_key)?;
|
||||
info!("Public key created correctly.");
|
||||
}
|
||||
|
||||
auth::load_keys();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_web_vault() {
|
||||
if !CONFIG.web_vault_enabled() {
|
||||
return;
|
||||
|
@ -522,7 +501,7 @@ async fn launch_rocket(pool: db::DbPool, extra_debug: bool) -> Result<(), Error>
|
|||
.register([basepath, "/api"].concat(), api::core_catchers())
|
||||
.register([basepath, "/admin"].concat(), api::admin_catchers())
|
||||
.manage(pool)
|
||||
.manage(api::start_notification_server())
|
||||
.manage(Arc::clone(&WS_USERS))
|
||||
.manage(Arc::clone(&WS_ANONYMOUS_SUBSCRIPTIONS))
|
||||
.attach(util::AppHeaders())
|
||||
.attach(util::Cors())
|
||||
|
|
10
src/static/scripts/bootstrap.bundle.js
vendored
10
src/static/scripts/bootstrap.bundle.js
vendored
|
@ -1,5 +1,5 @@
|
|||
/*!
|
||||
* Bootstrap v5.3.1 (https://getbootstrap.com/)
|
||||
* Bootstrap v5.3.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2023 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
|
@ -648,7 +648,7 @@
|
|||
* Constants
|
||||
*/
|
||||
|
||||
const VERSION = '5.3.1';
|
||||
const VERSION = '5.3.2';
|
||||
|
||||
/**
|
||||
* Class definition
|
||||
|
@ -729,9 +729,9 @@
|
|||
if (hrefAttribute.includes('#') && !hrefAttribute.startsWith('#')) {
|
||||
hrefAttribute = `#${hrefAttribute.split('#')[1]}`;
|
||||
}
|
||||
selector = hrefAttribute && hrefAttribute !== '#' ? hrefAttribute.trim() : null;
|
||||
selector = hrefAttribute && hrefAttribute !== '#' ? parseSelector(hrefAttribute.trim()) : null;
|
||||
}
|
||||
return parseSelector(selector);
|
||||
return selector;
|
||||
};
|
||||
const SelectorEngine = {
|
||||
find(selector, element = document.documentElement) {
|
||||
|
@ -5866,7 +5866,7 @@
|
|||
const CLASS_DROPDOWN = 'dropdown';
|
||||
const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle';
|
||||
const SELECTOR_DROPDOWN_MENU = '.dropdown-menu';
|
||||
const NOT_SELECTOR_DROPDOWN_TOGGLE = ':not(.dropdown-toggle)';
|
||||
const NOT_SELECTOR_DROPDOWN_TOGGLE = `:not(${SELECTOR_DROPDOWN_TOGGLE})`;
|
||||
const SELECTOR_TAB_PANEL = '.list-group, .nav, [role="tablist"]';
|
||||
const SELECTOR_OUTER = '.nav-item, .list-group-item';
|
||||
const SELECTOR_INNER = `.nav-link${NOT_SELECTOR_DROPDOWN_TOGGLE}, .list-group-item${NOT_SELECTOR_DROPDOWN_TOGGLE}, [role="tab"]${NOT_SELECTOR_DROPDOWN_TOGGLE}`;
|
||||
|
|
81
src/static/scripts/bootstrap.css
vendored
81
src/static/scripts/bootstrap.css
vendored
|
@ -1,6 +1,6 @@
|
|||
@charset "UTF-8";
|
||||
/*!
|
||||
* Bootstrap v5.3.1 (https://getbootstrap.com/)
|
||||
* Bootstrap v5.3.2 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2023 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
|
@ -99,6 +99,7 @@
|
|||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
|
@ -170,6 +171,8 @@
|
|||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
|
@ -325,6 +328,7 @@ small, .small {
|
|||
|
||||
mark, .mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
|
@ -819,7 +823,7 @@ progress {
|
|||
|
||||
.row-cols-3 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 33.3333333333%;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
|
||||
.row-cols-4 > * {
|
||||
|
@ -834,7 +838,7 @@ progress {
|
|||
|
||||
.row-cols-6 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 16.6666666667%;
|
||||
width: 16.66666667%;
|
||||
}
|
||||
|
||||
.col-auto {
|
||||
|
@ -1024,7 +1028,7 @@ progress {
|
|||
}
|
||||
.row-cols-sm-3 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 33.3333333333%;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
.row-cols-sm-4 > * {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1036,7 +1040,7 @@ progress {
|
|||
}
|
||||
.row-cols-sm-6 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 16.6666666667%;
|
||||
width: 16.66666667%;
|
||||
}
|
||||
.col-sm-auto {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1193,7 +1197,7 @@ progress {
|
|||
}
|
||||
.row-cols-md-3 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 33.3333333333%;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
.row-cols-md-4 > * {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1205,7 +1209,7 @@ progress {
|
|||
}
|
||||
.row-cols-md-6 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 16.6666666667%;
|
||||
width: 16.66666667%;
|
||||
}
|
||||
.col-md-auto {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1362,7 +1366,7 @@ progress {
|
|||
}
|
||||
.row-cols-lg-3 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 33.3333333333%;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
.row-cols-lg-4 > * {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1374,7 +1378,7 @@ progress {
|
|||
}
|
||||
.row-cols-lg-6 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 16.6666666667%;
|
||||
width: 16.66666667%;
|
||||
}
|
||||
.col-lg-auto {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1531,7 +1535,7 @@ progress {
|
|||
}
|
||||
.row-cols-xl-3 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 33.3333333333%;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
.row-cols-xl-4 > * {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1543,7 +1547,7 @@ progress {
|
|||
}
|
||||
.row-cols-xl-6 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 16.6666666667%;
|
||||
width: 16.66666667%;
|
||||
}
|
||||
.col-xl-auto {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1700,7 +1704,7 @@ progress {
|
|||
}
|
||||
.row-cols-xxl-3 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 33.3333333333%;
|
||||
width: 33.33333333%;
|
||||
}
|
||||
.row-cols-xxl-4 > * {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1712,7 +1716,7 @@ progress {
|
|||
}
|
||||
.row-cols-xxl-6 > * {
|
||||
flex: 0 0 auto;
|
||||
width: 16.6666666667%;
|
||||
width: 16.66666667%;
|
||||
}
|
||||
.col-xxl-auto {
|
||||
flex: 0 0 auto;
|
||||
|
@ -1856,16 +1860,16 @@ progress {
|
|||
--bs-table-bg-type: initial;
|
||||
--bs-table-color-state: initial;
|
||||
--bs-table-bg-state: initial;
|
||||
--bs-table-color: var(--bs-body-color);
|
||||
--bs-table-color: var(--bs-emphasis-color);
|
||||
--bs-table-bg: var(--bs-body-bg);
|
||||
--bs-table-border-color: var(--bs-border-color);
|
||||
--bs-table-accent-bg: transparent;
|
||||
--bs-table-striped-color: var(--bs-body-color);
|
||||
--bs-table-striped-bg: rgba(0, 0, 0, 0.05);
|
||||
--bs-table-active-color: var(--bs-body-color);
|
||||
--bs-table-active-bg: rgba(0, 0, 0, 0.1);
|
||||
--bs-table-hover-color: var(--bs-body-color);
|
||||
--bs-table-hover-bg: rgba(0, 0, 0, 0.075);
|
||||
--bs-table-striped-color: var(--bs-emphasis-color);
|
||||
--bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05);
|
||||
--bs-table-active-color: var(--bs-emphasis-color);
|
||||
--bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1);
|
||||
--bs-table-hover-color: var(--bs-emphasis-color);
|
||||
--bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075);
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
vertical-align: top;
|
||||
|
@ -1934,7 +1938,7 @@ progress {
|
|||
.table-primary {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #cfe2ff;
|
||||
--bs-table-border-color: #bacbe6;
|
||||
--bs-table-border-color: #a6b5cc;
|
||||
--bs-table-striped-bg: #c5d7f2;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #bacbe6;
|
||||
|
@ -1948,7 +1952,7 @@ progress {
|
|||
.table-secondary {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #e2e3e5;
|
||||
--bs-table-border-color: #cbccce;
|
||||
--bs-table-border-color: #b5b6b7;
|
||||
--bs-table-striped-bg: #d7d8da;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #cbccce;
|
||||
|
@ -1962,7 +1966,7 @@ progress {
|
|||
.table-success {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #d1e7dd;
|
||||
--bs-table-border-color: #bcd0c7;
|
||||
--bs-table-border-color: #a7b9b1;
|
||||
--bs-table-striped-bg: #c7dbd2;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #bcd0c7;
|
||||
|
@ -1976,7 +1980,7 @@ progress {
|
|||
.table-info {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #cff4fc;
|
||||
--bs-table-border-color: #badce3;
|
||||
--bs-table-border-color: #a6c3ca;
|
||||
--bs-table-striped-bg: #c5e8ef;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #badce3;
|
||||
|
@ -1990,7 +1994,7 @@ progress {
|
|||
.table-warning {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #fff3cd;
|
||||
--bs-table-border-color: #e6dbb9;
|
||||
--bs-table-border-color: #ccc2a4;
|
||||
--bs-table-striped-bg: #f2e7c3;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #e6dbb9;
|
||||
|
@ -2004,7 +2008,7 @@ progress {
|
|||
.table-danger {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #f8d7da;
|
||||
--bs-table-border-color: #dfc2c4;
|
||||
--bs-table-border-color: #c6acae;
|
||||
--bs-table-striped-bg: #eccccf;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #dfc2c4;
|
||||
|
@ -2018,7 +2022,7 @@ progress {
|
|||
.table-light {
|
||||
--bs-table-color: #000;
|
||||
--bs-table-bg: #f8f9fa;
|
||||
--bs-table-border-color: #dfe0e1;
|
||||
--bs-table-border-color: #c6c7c8;
|
||||
--bs-table-striped-bg: #ecedee;
|
||||
--bs-table-striped-color: #000;
|
||||
--bs-table-active-bg: #dfe0e1;
|
||||
|
@ -2032,7 +2036,7 @@ progress {
|
|||
.table-dark {
|
||||
--bs-table-color: #fff;
|
||||
--bs-table-bg: #212529;
|
||||
--bs-table-border-color: #373b3e;
|
||||
--bs-table-border-color: #4d5154;
|
||||
--bs-table-striped-bg: #2c3034;
|
||||
--bs-table-striped-color: #fff;
|
||||
--bs-table-active-bg: #373b3e;
|
||||
|
@ -2388,6 +2392,7 @@ textarea.form-control-lg {
|
|||
|
||||
.form-check-input {
|
||||
--bs-form-check-bg: var(--bs-body-bg);
|
||||
flex-shrink: 0;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
|
@ -2544,7 +2549,7 @@ textarea.form-control-lg {
|
|||
height: 0.5rem;
|
||||
color: transparent;
|
||||
cursor: pointer;
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
background-color: var(--bs-secondary-bg);
|
||||
border-color: transparent;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
@ -2573,7 +2578,7 @@ textarea.form-control-lg {
|
|||
height: 0.5rem;
|
||||
color: transparent;
|
||||
cursor: pointer;
|
||||
background-color: var(--bs-tertiary-bg);
|
||||
background-color: var(--bs-secondary-bg);
|
||||
border-color: transparent;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
@ -3431,7 +3436,7 @@ textarea.form-control-lg {
|
|||
--bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width));
|
||||
--bs-dropdown-divider-bg: var(--bs-border-color-translucent);
|
||||
--bs-dropdown-divider-margin-y: 0.5rem;
|
||||
--bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-dropdown-box-shadow: var(--bs-box-shadow);
|
||||
--bs-dropdown-link-color: var(--bs-body-color);
|
||||
--bs-dropdown-link-hover-color: var(--bs-body-color);
|
||||
--bs-dropdown-link-hover-bg: var(--bs-tertiary-bg);
|
||||
|
@ -5473,7 +5478,7 @@ textarea.form-control-lg {
|
|||
--bs-modal-border-color: var(--bs-border-color-translucent);
|
||||
--bs-modal-border-width: var(--bs-border-width);
|
||||
--bs-modal-border-radius: var(--bs-border-radius-lg);
|
||||
--bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-modal-box-shadow: var(--bs-box-shadow-sm);
|
||||
--bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));
|
||||
--bs-modal-header-padding-x: 1rem;
|
||||
--bs-modal-header-padding-y: 1rem;
|
||||
|
@ -5614,7 +5619,7 @@ textarea.form-control-lg {
|
|||
@media (min-width: 576px) {
|
||||
.modal {
|
||||
--bs-modal-margin: 1.75rem;
|
||||
--bs-modal-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-modal-box-shadow: var(--bs-box-shadow);
|
||||
}
|
||||
.modal-dialog {
|
||||
max-width: var(--bs-modal-width);
|
||||
|
@ -5866,7 +5871,7 @@ textarea.form-control-lg {
|
|||
--bs-popover-border-color: var(--bs-border-color-translucent);
|
||||
--bs-popover-border-radius: var(--bs-border-radius-lg);
|
||||
--bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width));
|
||||
--bs-popover-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-popover-box-shadow: var(--bs-box-shadow);
|
||||
--bs-popover-header-padding-x: 1rem;
|
||||
--bs-popover-header-padding-y: 0.5rem;
|
||||
--bs-popover-header-font-size: 1rem;
|
||||
|
@ -6301,7 +6306,7 @@ textarea.form-control-lg {
|
|||
--bs-offcanvas-bg: var(--bs-body-bg);
|
||||
--bs-offcanvas-border-width: var(--bs-border-width);
|
||||
--bs-offcanvas-border-color: var(--bs-border-color-translucent);
|
||||
--bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-offcanvas-box-shadow: var(--bs-box-shadow-sm);
|
||||
--bs-offcanvas-transition: transform 0.3s ease-in-out;
|
||||
--bs-offcanvas-title-line-height: 1.5;
|
||||
}
|
||||
|
@ -7380,15 +7385,15 @@ textarea.form-control-lg {
|
|||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
|
||||
box-shadow: var(--bs-box-shadow) !important;
|
||||
}
|
||||
|
||||
.shadow-sm {
|
||||
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
|
||||
box-shadow: var(--bs-box-shadow-sm) !important;
|
||||
}
|
||||
|
||||
.shadow-lg {
|
||||
box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;
|
||||
box-shadow: var(--bs-box-shadow-lg) !important;
|
||||
}
|
||||
|
||||
.shadow-none {
|
||||
|
|
348
src/static/scripts/datatables.css
vendored
348
src/static/scripts/datatables.css
vendored
|
@ -4,10 +4,10 @@
|
|||
*
|
||||
* To rebuild or modify this file with the latest versions of the included
|
||||
* software please visit:
|
||||
* https://datatables.net/download/#bs5/dt-1.13.6
|
||||
* https://datatables.net/download/#bs5/dt-2.0.0
|
||||
*
|
||||
* Included libraries:
|
||||
* DataTables 1.13.6
|
||||
* DataTables 2.0.0
|
||||
*/
|
||||
|
||||
@charset "UTF-8";
|
||||
|
@ -30,76 +30,124 @@ table.dataTable td.dt-control {
|
|||
}
|
||||
table.dataTable td.dt-control:before {
|
||||
display: inline-block;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
content: "►";
|
||||
box-sizing: border-box;
|
||||
content: "";
|
||||
border-top: 5px solid transparent;
|
||||
border-left: 10px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 5px solid transparent;
|
||||
border-right: 0px solid transparent;
|
||||
}
|
||||
table.dataTable tr.dt-hasChild td.dt-control:before {
|
||||
content: "▼";
|
||||
border-top: 10px solid rgba(0, 0, 0, 0.5);
|
||||
border-left: 5px solid transparent;
|
||||
border-bottom: 0px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
html.dark table.dataTable td.dt-control:before {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
html.dark table.dataTable td.dt-control:before,
|
||||
:root[data-bs-theme=dark] table.dataTable td.dt-control:before {
|
||||
border-left-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
html.dark table.dataTable tr.dt-hasChild td.dt-control:before {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
|
||||
:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
|
||||
border-top-color: rgba(255, 255, 255, 0.5);
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
table.dataTable thead > tr > th.sorting, table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting_asc_disabled, table.dataTable thead > tr > th.sorting_desc_disabled,
|
||||
table.dataTable thead > tr > td.sorting,
|
||||
table.dataTable thead > tr > td.sorting_asc,
|
||||
table.dataTable thead > tr > td.sorting_desc,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding-right: 26px;
|
||||
div.dt-scroll-body thead tr,
|
||||
div.dt-scroll-body tfoot tr {
|
||||
height: 0;
|
||||
}
|
||||
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:after,
|
||||
table.dataTable thead > tr > td.sorting:before,
|
||||
table.dataTable thead > tr > td.sorting:after,
|
||||
table.dataTable thead > tr > td.sorting_asc:before,
|
||||
table.dataTable thead > tr > td.sorting_asc:after,
|
||||
table.dataTable thead > tr > td.sorting_desc:before,
|
||||
table.dataTable thead > tr > td.sorting_desc:after,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled:before,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled:after,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled:before,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled:after {
|
||||
div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td,
|
||||
div.dt-scroll-body tfoot tr th,
|
||||
div.dt-scroll-body tfoot tr td {
|
||||
height: 0 !important;
|
||||
padding-top: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
border-top-width: 0px !important;
|
||||
border-bottom-width: 0px !important;
|
||||
}
|
||||
div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing,
|
||||
div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,
|
||||
div.dt-scroll-body tfoot tr td div.dt-scroll-sizing {
|
||||
height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
table.dataTable thead > tr > th:active,
|
||||
table.dataTable thead > tr > td:active {
|
||||
outline: none;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
opacity: 0.125;
|
||||
right: 10px;
|
||||
line-height: 9px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
table.dataTable thead > tr > th.sorting:before, table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:before, table.dataTable thead > tr > th.sorting_asc_disabled:before, table.dataTable thead > tr > th.sorting_desc_disabled:before,
|
||||
table.dataTable thead > tr > td.sorting:before,
|
||||
table.dataTable thead > tr > td.sorting_asc:before,
|
||||
table.dataTable thead > tr > td.sorting_desc:before,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled:before,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled:before {
|
||||
bottom: 50%;
|
||||
content: "▲";
|
||||
content: "▲"/"";
|
||||
}
|
||||
table.dataTable thead > tr > th.sorting:after, table.dataTable thead > tr > th.sorting_asc:after, table.dataTable thead > tr > th.sorting_desc:after, table.dataTable thead > tr > th.sorting_asc_disabled:after, table.dataTable thead > tr > th.sorting_desc_disabled:after,
|
||||
table.dataTable thead > tr > td.sorting:after,
|
||||
table.dataTable thead > tr > td.sorting_asc:after,
|
||||
table.dataTable thead > tr > td.sorting_desc:after,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled:after,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled:after {
|
||||
table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
content: "▼";
|
||||
content: "▼"/"";
|
||||
}
|
||||
table.dataTable thead > tr > th.sorting_asc:before, table.dataTable thead > tr > th.sorting_desc:after,
|
||||
table.dataTable thead > tr > td.sorting_asc:before,
|
||||
table.dataTable thead > tr > td.sorting_desc:after {
|
||||
table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc {
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 12px;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
||||
left: 0;
|
||||
opacity: 0.125;
|
||||
line-height: 9px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc {
|
||||
cursor: pointer;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc:hover,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc:hover {
|
||||
outline: 2px solid rgba(0, 0, 0, 0.05);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
||||
opacity: 0.6;
|
||||
}
|
||||
table.dataTable thead > tr > th.sorting_desc_disabled:after, table.dataTable thead > tr > th.sorting_asc_disabled:before,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled:after,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled:before {
|
||||
table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
|
||||
display: none;
|
||||
}
|
||||
table.dataTable thead > tr > th:active,
|
||||
|
@ -107,29 +155,39 @@ table.dataTable thead > tr > td:active {
|
|||
outline: none;
|
||||
}
|
||||
|
||||
div.dataTables_scrollBody > table.dataTable > thead > tr > th:before, div.dataTables_scrollBody > table.dataTable > thead > tr > th:after,
|
||||
div.dataTables_scrollBody > table.dataTable > thead > tr > td:before,
|
||||
div.dataTables_scrollBody > table.dataTable > thead > tr > td:after {
|
||||
display: none;
|
||||
div.dt-scroll-body > table.dataTable > thead > tr > th,
|
||||
div.dt-scroll-body > table.dataTable > thead > tr > td {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.dataTables_processing {
|
||||
:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover,
|
||||
:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover,
|
||||
:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover {
|
||||
outline: 2px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
div.dt-processing {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
margin-left: -100px;
|
||||
margin-top: -26px;
|
||||
margin-top: -22px;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
z-index: 10;
|
||||
}
|
||||
div.dataTables_processing > div:last-child {
|
||||
div.dt-processing > div:last-child {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 15px;
|
||||
margin: 1em auto;
|
||||
}
|
||||
div.dataTables_processing > div:last-child > div {
|
||||
div.dt-processing > div:last-child > div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 13px;
|
||||
|
@ -139,19 +197,19 @@ div.dataTables_processing > div:last-child > div {
|
|||
background: rgb(var(--dt-row-selected));
|
||||
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||
}
|
||||
div.dataTables_processing > div:last-child > div:nth-child(1) {
|
||||
div.dt-processing > div:last-child > div:nth-child(1) {
|
||||
left: 8px;
|
||||
animation: datatables-loader-1 0.6s infinite;
|
||||
}
|
||||
div.dataTables_processing > div:last-child > div:nth-child(2) {
|
||||
div.dt-processing > div:last-child > div:nth-child(2) {
|
||||
left: 8px;
|
||||
animation: datatables-loader-2 0.6s infinite;
|
||||
}
|
||||
div.dataTables_processing > div:last-child > div:nth-child(3) {
|
||||
div.dt-processing > div:last-child > div:nth-child(3) {
|
||||
left: 32px;
|
||||
animation: datatables-loader-2 0.6s infinite;
|
||||
}
|
||||
div.dataTables_processing > div:last-child > div:nth-child(4) {
|
||||
div.dt-processing > div:last-child > div:nth-child(4) {
|
||||
left: 56px;
|
||||
animation: datatables-loader-3 0.6s infinite;
|
||||
}
|
||||
|
@ -183,13 +241,16 @@ div.dataTables_processing > div:last-child > div:nth-child(4) {
|
|||
table.dataTable.nowrap th, table.dataTable.nowrap td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable th,
|
||||
table.dataTable td {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
table.dataTable th.dt-left,
|
||||
table.dataTable td.dt-left {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable th.dt-center,
|
||||
table.dataTable td.dt-center,
|
||||
table.dataTable td.dataTables_empty {
|
||||
table.dataTable td.dt-center {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable th.dt-right,
|
||||
|
@ -204,6 +265,16 @@ table.dataTable th.dt-nowrap,
|
|||
table.dataTable td.dt-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable th.dt-empty,
|
||||
table.dataTable td.dt-empty {
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
|
||||
table.dataTable td.dt-type-numeric,
|
||||
table.dataTable td.dt-type-date {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable thead th,
|
||||
table.dataTable thead td,
|
||||
table.dataTable tfoot th,
|
||||
|
@ -266,179 +337,150 @@ table.dataTable tbody td.dt-body-nowrap {
|
|||
* ©2020 SpryMedia Ltd, all rights reserved.
|
||||
* License: MIT datatables.net/license/mit
|
||||
*/
|
||||
table.dataTable {
|
||||
table.table.dataTable {
|
||||
clear: both;
|
||||
margin-top: 6px !important;
|
||||
margin-bottom: 6px !important;
|
||||
max-width: none !important;
|
||||
border-collapse: separate !important;
|
||||
margin-bottom: 0;
|
||||
max-width: none;
|
||||
border-spacing: 0;
|
||||
}
|
||||
table.dataTable td,
|
||||
table.dataTable th {
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
table.dataTable td.dataTables_empty,
|
||||
table.dataTable th.dataTables_empty {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable.nowrap th,
|
||||
table.dataTable.nowrap td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
|
||||
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
|
||||
box-shadow: none;
|
||||
}
|
||||
table.dataTable > tbody > tr {
|
||||
table.table.dataTable > :not(caption) > * > * {
|
||||
background-color: transparent;
|
||||
}
|
||||
table.dataTable > tbody > tr.selected > * {
|
||||
table.table.dataTable > tbody > tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
table.table.dataTable > tbody > tr.selected > * {
|
||||
box-shadow: inset 0 0 0 9999px rgb(13, 110, 253);
|
||||
box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected));
|
||||
color: rgb(255, 255, 255);
|
||||
color: rgb(var(--dt-row-selected-text));
|
||||
}
|
||||
table.dataTable > tbody > tr.selected a {
|
||||
table.table.dataTable > tbody > tr.selected a {
|
||||
color: rgb(9, 10, 11);
|
||||
color: rgb(var(--dt-row-selected-link));
|
||||
}
|
||||
table.dataTable.table-striped > tbody > tr.odd > * {
|
||||
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
|
||||
}
|
||||
table.dataTable.table-striped > tbody > tr.odd.selected > * {
|
||||
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
|
||||
}
|
||||
table.dataTable.table-hover > tbody > tr:hover > * {
|
||||
table.table.dataTable.table-hover > tbody > tr:hover > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
|
||||
}
|
||||
table.dataTable.table-hover > tbody > tr.selected:hover > * {
|
||||
table.table.dataTable.table-hover > tbody > tr.selected:hover > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
|
||||
}
|
||||
|
||||
div.dataTables_wrapper div.dataTables_length label {
|
||||
div.dt-container div.dt-length label {
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_length select {
|
||||
div.dt-container div.dt-length select {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_filter {
|
||||
div.dt-container div.dt-search {
|
||||
text-align: right;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_filter label {
|
||||
div.dt-container div.dt-search label {
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_filter input {
|
||||
div.dt-container div.dt-search input {
|
||||
margin-left: 0.5em;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_info {
|
||||
div.dt-container div.dt-info {
|
||||
padding-top: 0.85em;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_paginate {
|
||||
div.dt-container div.dt-paging {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
|
||||
div.dt-container div.dt-paging ul.pagination {
|
||||
margin: 2px 0;
|
||||
white-space: nowrap;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
div.dataTables_wrapper div.dt-row {
|
||||
div.dt-container div.dt-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dataTables_scrollHead table.dataTable {
|
||||
div.dt-scroll-head table.dataTable {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
div.dataTables_scrollBody > table {
|
||||
div.dt-scroll-body {
|
||||
border-bottom-color: var(--bs-border-color);
|
||||
border-bottom-width: var(--bs-border-width);
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
div.dt-scroll-body > table {
|
||||
border-top: none;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
div.dataTables_scrollBody > table > thead .sorting:before,
|
||||
div.dataTables_scrollBody > table > thead .sorting_asc:before,
|
||||
div.dataTables_scrollBody > table > thead .sorting_desc:before,
|
||||
div.dataTables_scrollBody > table > thead .sorting:after,
|
||||
div.dataTables_scrollBody > table > thead .sorting_asc:after,
|
||||
div.dataTables_scrollBody > table > thead .sorting_desc:after {
|
||||
display: none;
|
||||
div.dt-scroll-body > table > tbody > tr:first-child {
|
||||
border-top-width: 0;
|
||||
}
|
||||
div.dataTables_scrollBody > table > tbody tr:first-child th,
|
||||
div.dataTables_scrollBody > table > tbody tr:first-child td {
|
||||
border-top: none;
|
||||
div.dt-scroll-body > table > thead > tr {
|
||||
border-width: 0 !important;
|
||||
}
|
||||
div.dt-scroll-body > table > tbody > tr:last-child > * {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
div.dataTables_scrollFoot > .dataTables_scrollFootInner {
|
||||
div.dt-scroll-foot > .dt-scroll-footInner {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
div.dataTables_scrollFoot > .dataTables_scrollFootInner > table {
|
||||
div.dt-scroll-foot > .dt-scroll-footInner > table {
|
||||
margin-top: 0 !important;
|
||||
border-top: none;
|
||||
}
|
||||
div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child {
|
||||
border-top-width: 0 !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
div.dataTables_wrapper div.dataTables_length,
|
||||
div.dataTables_wrapper div.dataTables_filter,
|
||||
div.dataTables_wrapper div.dataTables_info,
|
||||
div.dataTables_wrapper div.dataTables_paginate {
|
||||
div.dt-container div.dt-length,
|
||||
div.dt-container div.dt-search,
|
||||
div.dt-container div.dt-info,
|
||||
div.dt-container div.dt-paging {
|
||||
text-align: center;
|
||||
}
|
||||
div.dataTables_wrapper div.dataTables_paginate ul.pagination {
|
||||
div.dt-container .row {
|
||||
--bs-gutter-y: 0.5rem;
|
||||
}
|
||||
div.dt-container div.dt-paging ul.pagination {
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled) {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
table.table-bordered.dataTable {
|
||||
border-right-width: 0;
|
||||
}
|
||||
table.table-bordered.dataTable thead tr:first-child th,
|
||||
table.table-bordered.dataTable thead tr:first-child td {
|
||||
border-top-width: 1px;
|
||||
}
|
||||
table.table-bordered.dataTable th,
|
||||
table.table-bordered.dataTable td {
|
||||
border-left-width: 0;
|
||||
}
|
||||
table.table-bordered.dataTable th:first-child, table.table-bordered.dataTable th:first-child,
|
||||
table.table-bordered.dataTable td:first-child,
|
||||
table.table-bordered.dataTable td:first-child {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child,
|
||||
table.table-bordered.dataTable td:last-child,
|
||||
table.table-bordered.dataTable td:last-child {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
table.table-bordered.dataTable th,
|
||||
table.table-bordered.dataTable td {
|
||||
border-bottom-width: 1px;
|
||||
table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):before, table.dataTable.table-sm > thead > tr > th:not(.sorting_disabled):after {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
div.dataTables_scrollHead table.table-bordered {
|
||||
div.dt-scroll-head table.table-bordered {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
div.table-responsive > div.dataTables_wrapper > div.row {
|
||||
div.table-responsive > div.dt-container > div.row {
|
||||
margin: 0;
|
||||
}
|
||||
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:first-child {
|
||||
div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
div.table-responsive > div.dataTables_wrapper > div.row > div[class^=col-]:last-child {
|
||||
div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
|
|
11387
src/static/scripts/datatables.js
vendored
11387
src/static/scripts/datatables.js
vendored
File diff suppressed because it is too large
Load diff
|
@ -1,12 +1,12 @@
|
|||
/*!
|
||||
* jQuery JavaScript Library v3.7.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
|
||||
* jQuery JavaScript Library v3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween
|
||||
* https://jquery.com/
|
||||
*
|
||||
* Copyright OpenJS Foundation and other contributors
|
||||
* Released under the MIT license
|
||||
* https://jquery.org/license
|
||||
*
|
||||
* Date: 2023-05-11T18:29Z
|
||||
* Date: 2023-08-28T13:37Z
|
||||
*/
|
||||
( function( global, factory ) {
|
||||
|
||||
|
@ -147,7 +147,7 @@ function toType( obj ) {
|
|||
|
||||
|
||||
|
||||
var version = "3.7.0 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",
|
||||
var version = "3.7.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/animatedSelector,-effects/Tween",
|
||||
|
||||
rhtmlSuffix = /HTML$/i,
|
||||
|
||||
|
@ -411,9 +411,14 @@ jQuery.extend( {
|
|||
// Do not traverse comment nodes
|
||||
ret += jQuery.text( node );
|
||||
}
|
||||
} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
|
||||
}
|
||||
if ( nodeType === 1 || nodeType === 11 ) {
|
||||
return elem.textContent;
|
||||
} else if ( nodeType === 3 || nodeType === 4 ) {
|
||||
}
|
||||
if ( nodeType === 9 ) {
|
||||
return elem.documentElement.textContent;
|
||||
}
|
||||
if ( nodeType === 3 || nodeType === 4 ) {
|
||||
return elem.nodeValue;
|
||||
}
|
||||
|
||||
|
@ -1126,12 +1131,17 @@ function setDocument( node ) {
|
|||
documentElement.msMatchesSelector;
|
||||
|
||||
// Support: IE 9 - 11+, Edge 12 - 18+
|
||||
// Accessing iframe documents after unload throws "permission denied" errors (see trac-13936)
|
||||
// Support: IE 11+, Edge 17 - 18+
|
||||
// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
|
||||
// two documents; shallow comparisons work.
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if ( preferredDoc != document &&
|
||||
// Accessing iframe documents after unload throws "permission denied" errors
|
||||
// (see trac-13936).
|
||||
// Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`,
|
||||
// all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well.
|
||||
if ( documentElement.msMatchesSelector &&
|
||||
|
||||
// Support: IE 11+, Edge 17 - 18+
|
||||
// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
|
||||
// two documents; shallow comparisons work.
|
||||
// eslint-disable-next-line eqeqeq
|
||||
preferredDoc != document &&
|
||||
( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {
|
||||
|
||||
// Support: IE 9 - 11+, Edge 12 - 18+
|
||||
|
@ -2694,12 +2704,12 @@ jQuery.find = find;
|
|||
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
|
||||
jQuery.unique = jQuery.uniqueSort;
|
||||
|
||||
// These have always been private, but they used to be documented
|
||||
// as part of Sizzle so let's maintain them in the 3.x line
|
||||
// for backwards compatibility purposes.
|
||||
// These have always been private, but they used to be documented as part of
|
||||
// Sizzle so let's maintain them for now for backwards compatibility purposes.
|
||||
find.compile = compile;
|
||||
find.select = select;
|
||||
find.setDocument = setDocument;
|
||||
find.tokenize = tokenize;
|
||||
|
||||
find.escape = jQuery.escapeSelector;
|
||||
find.getText = jQuery.text;
|
||||
|
@ -5913,7 +5923,7 @@ function domManip( collection, args, callback, ignored ) {
|
|||
if ( hasScripts ) {
|
||||
doc = scripts[ scripts.length - 1 ].ownerDocument;
|
||||
|
||||
// Reenable scripts
|
||||
// Re-enable scripts
|
||||
jQuery.map( scripts, restoreScript );
|
||||
|
||||
// Evaluate executable scripts on first document insertion
|
||||
|
@ -6370,7 +6380,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|||
trChild = document.createElement( "div" );
|
||||
|
||||
table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
|
||||
tr.style.cssText = "border:1px solid";
|
||||
tr.style.cssText = "box-sizing:content-box;border:1px solid";
|
||||
|
||||
// Support: Chrome 86+
|
||||
// Height set through cssText does not get applied.
|
||||
|
@ -6382,7 +6392,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );
|
|||
// In our bodyBackground.html iframe,
|
||||
// display for all div elements is set to "inline",
|
||||
// which causes a problem only in Android 8 Chrome 86.
|
||||
// Ensuring the div is display: block
|
||||
// Ensuring the div is `display: block`
|
||||
// gets around this issue.
|
||||
trChild.style.display = "block";
|
||||
|
||||
|
@ -8451,7 +8461,9 @@ jQuery.fn.extend( {
|
|||
},
|
||||
|
||||
hover: function( fnOver, fnOut ) {
|
||||
return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
|
||||
return this
|
||||
.on( "mouseenter", fnOver )
|
||||
.on( "mouseleave", fnOut || fnOver );
|
||||
}
|
||||
} );
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
</main>
|
||||
|
||||
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
||||
<script src="{{urlpath}}/vw_static/jquery-3.7.0.slim.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/jquery-3.7.1.slim.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/admin_organizations.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
||||
|
|
|
@ -140,7 +140,7 @@
|
|||
</main>
|
||||
|
||||
<link rel="stylesheet" href="{{urlpath}}/vw_static/datatables.css" />
|
||||
<script src="{{urlpath}}/vw_static/jquery-3.7.0.slim.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/jquery-3.7.1.slim.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/datatables.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/admin_users.js"></script>
|
||||
<script src="{{urlpath}}/vw_static/jdenticon.js"></script>
|
||||
|
|
|
@ -2,5 +2,5 @@ Your Email Change
|
|||
<!---------------->
|
||||
To finalize changing your email address enter the following code in web vault: {{token}}
|
||||
|
||||
If you did not try to change an email address, you can safely ignore this email.
|
||||
If you did not try to change your email address, contact your administrator.
|
||||
{{> email/email_footer_text }}
|
||||
|
|
|
@ -9,7 +9,7 @@ Your Email Change
|
|||
</tr>
|
||||
<tr style="margin: 0; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: center;" valign="top" align="center">
|
||||
If you did not try to change an email address, you can safely ignore this email.
|
||||
If you did not try to change your email address, contact your administrator.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
42
src/util.rs
42
src/util.rs
|
@ -1,11 +1,7 @@
|
|||
//
|
||||
// Web Headers and caching
|
||||
//
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Cursor, ErrorKind},
|
||||
ops::Deref,
|
||||
};
|
||||
use std::{collections::HashMap, io::Cursor, ops::Deref, path::Path};
|
||||
|
||||
use num_traits::ToPrimitive;
|
||||
use rocket::{
|
||||
|
@ -334,40 +330,6 @@ impl Fairing for BetterLogging {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// File handling
|
||||
//
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Result as IOResult,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
pub fn file_exists(path: &str) -> bool {
|
||||
Path::new(path).exists()
|
||||
}
|
||||
|
||||
pub fn write_file(path: &str, content: &[u8]) -> Result<(), crate::error::Error> {
|
||||
use std::io::Write;
|
||||
let mut f = match File::create(path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
if e.kind() == ErrorKind::PermissionDenied {
|
||||
error!("Can't create '{}': Permission denied", path);
|
||||
}
|
||||
return Err(From::from(e));
|
||||
}
|
||||
};
|
||||
|
||||
f.write_all(content)?;
|
||||
f.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_file(path: &str) -> IOResult<()> {
|
||||
fs::remove_file(path)
|
||||
}
|
||||
|
||||
pub fn get_display_size(size: i64) -> String {
|
||||
const UNITS: [&str; 6] = ["bytes", "KB", "MB", "GB", "TB", "PB"];
|
||||
|
||||
|
@ -444,7 +406,7 @@ pub fn get_env_str_value(key: &str) -> Option<String> {
|
|||
match (value_from_env, value_file) {
|
||||
(Ok(_), Ok(_)) => panic!("You should not define both {key} and {key_file}!"),
|
||||
(Ok(v_env), Err(_)) => Some(v_env),
|
||||
(Err(_), Ok(v_file)) => match fs::read_to_string(v_file) {
|
||||
(Err(_), Ok(v_file)) => match std::fs::read_to_string(v_file) {
|
||||
Ok(content) => Some(content.trim().to_string()),
|
||||
Err(e) => panic!("Failed to load {key}: {e:?}"),
|
||||
},
|
||||
|
|
|
@ -10,19 +10,19 @@ import urllib.request
|
|||
|
||||
from collections import OrderedDict
|
||||
|
||||
if not (2 <= len(sys.argv) <= 3):
|
||||
print("usage: %s <OUTPUT-FILE> [GIT-REF]" % sys.argv[0])
|
||||
if not 2 <= len(sys.argv) <= 3:
|
||||
print(f"usage: {sys.argv[0]} <OUTPUT-FILE> [GIT-REF]")
|
||||
print()
|
||||
print("This script generates a global equivalent domains JSON file from")
|
||||
print("the upstream Bitwarden source repo.")
|
||||
sys.exit(1)
|
||||
|
||||
OUTPUT_FILE = sys.argv[1]
|
||||
GIT_REF = 'master' if len(sys.argv) == 2 else sys.argv[2]
|
||||
GIT_REF = 'main' if len(sys.argv) == 2 else sys.argv[2]
|
||||
|
||||
BASE_URL = 'https://github.com/bitwarden/server/raw/%s' % GIT_REF
|
||||
ENUMS_URL = '%s/src/Core/Enums/GlobalEquivalentDomainsType.cs' % BASE_URL
|
||||
DOMAIN_LISTS_URL = '%s/src/Core/Utilities/StaticStore.cs' % BASE_URL
|
||||
BASE_URL = f'https://github.com/bitwarden/server/raw/{GIT_REF}'
|
||||
ENUMS_URL = f'{BASE_URL}/src/Core/Enums/GlobalEquivalentDomainsType.cs'
|
||||
DOMAIN_LISTS_URL = f'{BASE_URL}/src/Core/Utilities/StaticStore.cs'
|
||||
|
||||
# Enum lines look like:
|
||||
#
|
||||
|
@ -77,5 +77,5 @@ for name, domain_list in domain_lists.items():
|
|||
global_domains.append(entry)
|
||||
|
||||
# Write out the global domains JSON file.
|
||||
with open(OUTPUT_FILE, 'w') as f:
|
||||
with open(file=OUTPUT_FILE, mode='w', encoding='utf-8') as f:
|
||||
json.dump(global_domains, f, indent=2)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue