diff --git a/.env.template b/.env.template index 013b3ff57c..61cd046b48 100644 --- a/.env.template +++ b/.env.template @@ -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"} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 619295d15a..8063827d7e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 0d062e358f..b83eb071db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.7" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", @@ -104,7 +104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", - "event-listener 5.0.0", + "event-listener 5.2.0", "event-listener-strategy 0.5.0", "futures-core", "pin-project-lite", @@ -146,7 +146,7 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.2.0", "async-executor", - "async-io 2.3.1", + "async-io 2.3.2", "async-lock 3.3.0", "blocking", "futures-lite 2.2.0", @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65" +checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" dependencies = [ "async-lock 3.3.0", "cfg-if", @@ -185,7 +185,7 @@ dependencies = [ "futures-io", "futures-lite 2.2.0", "parking", - "polling 3.4.0", + "polling 3.5.0", "rustix 0.38.31", "slab", "tracing", @@ -235,7 +235,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e47d90f65a225c4527103a8d747001fc56e375203592b25ad103e1ca13124c5" dependencies = [ - "async-io 2.3.1", + "async-io 2.3.2", "async-lock 2.8.0", "atomic-waker", "cfg-if", @@ -293,7 +293,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -304,13 +304,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -375,9 +375,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bigdecimal" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06619be423ea5bb86c95f087d5707942791a08a85530df0db2209a3ecfb8bc9" +checksum = "9324c8014cd04590682b34f1e9448d38f0674d0f7b2dc553331016ef0e4e9ebc" dependencies = [ "autocfg", "libm", @@ -440,9 +440,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -461,15 +461,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" -version = "1.14.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea31d69bda4949c1c1562c1e6f042a1caefac98cdc8a298260a2ff41c1e2d42b" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" [[package]] name = "byteorder" @@ -485,9 +485,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cached" -version = "0.48.1" +version = "0.49.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355face540df58778b96814c48abb3c2ed67c4878a8087ab1819c1fedeec505f" +checksum = "f251fd1e72720ca07bf5d8e310f54a193fd053479a1f6342c6663ee4fa01cf96" dependencies = [ "ahash", "async-trait", @@ -503,9 +503,9 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d52f526f7cbc875b296856ca8c964a9f6290556922c303a8a3883e3c676e6a1" +checksum = "ad9f16c0d84de31a2ab7fdf5f7783c14631f7075cf464eb3bb43119f61c9cb2a" dependencies = [ "darling", "proc-macro2", @@ -521,12 +521,9 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -536,22 +533,22 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] name = "chrono-tz" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d7b79e99bfaa0d47da0687c43aa3b7381938a62ad3a6498599039321f660b7" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" dependencies = [ "chrono", "chrono-tz-build", @@ -627,6 +624,23 @@ dependencies = [ "url", ] +[[package]] +name = "cookie_store" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4934e6b7e8419148b6ef56950d277af8561060b56afd59e2aadf98b59fce6baa" +dependencies = [ + "cookie 0.18.0", + "idna 0.5.0", + "log", + "publicsuffix", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -654,18 +668,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "cron" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff76b51e4c068c52bfd2866e1567bee7c567ae8f24ada09fd4307019e25eab7" +checksum = "6f8c3e73077b4b4a6ab1ea5047c37c57aee77657bc8ecd6f29b0af082d0b0c07" dependencies = [ "chrono", "nom", @@ -787,14 +801,14 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] name = "diesel" -version = "2.1.4" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8" +checksum = "03fc05c17098f21b89bc7d98fe1dd3cce2c11c2ad8e145f2a44fe08ed28eb559" dependencies = [ "bigdecimal", "bitflags 2.4.2", @@ -816,14 +830,14 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44" +checksum = "5d02eecb814ae714ffe61ddc2db2dd03e6c49a42e269b5001355500d431cce0c" dependencies = [ "diesel_table_macro_syntax", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -853,7 +867,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" dependencies = [ - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -875,9 +889,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "email-encoding" @@ -916,7 +930,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -974,9 +988,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.0.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1" +checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" dependencies = [ "concurrent-queue", "parking", @@ -999,7 +1013,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" dependencies = [ - "event-listener 5.0.0", + "event-listener 5.2.0", "pin-project-lite", ] @@ -1032,14 +1046,14 @@ dependencies = [ [[package]] name = "figment" -version = "0.10.14" +version = "0.10.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6e5bc7bd59d60d0d45a6ccab6cf0f4ce28698fb4e81e750ddf229c9b824026" +checksum = "7270677e7067213e04f323b55084586195f18308cd7546cfac9f873344ccceb6" dependencies = [ "atomic 0.6.0", "pear", "serde", - "toml 0.8.10", + "toml 0.8.11", "uncased", "version_check", ] @@ -1168,7 +1182,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -1185,9 +1199,9 @@ checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" @@ -1269,9 +1283,9 @@ dependencies = [ [[package]] name = "governor" -version = "0.6.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" dependencies = [ "cfg-if", "dashmap", @@ -1280,16 +1294,18 @@ dependencies = [ "no-std-compat", "nonzero_ext", "parking_lot", + "portable-atomic", "quanta", "rand", "smallvec", + "spinning_top", ] [[package]] name = "h2" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" dependencies = [ "bytes", "fnv", @@ -1306,9 +1322,9 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" [[package]] name = "handlebars" @@ -1343,9 +1359,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hmac" @@ -1387,9 +1403,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1436,7 +1452,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.5", + "socket2 0.5.6", "tokio", "tower-service", "tracing", @@ -1517,9 +1533,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.2" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" dependencies = [ "equivalent", "hashbrown", @@ -1558,7 +1574,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ - "socket2 0.5.5", + "socket2 0.5.6", "widestring", "windows-sys 0.48.0", "winreg", @@ -1572,12 +1588,12 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", - "rustix 0.38.31", + "libc", "windows-sys 0.52.0", ] @@ -1606,9 +1622,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -1667,7 +1683,7 @@ dependencies = [ "percent-encoding", "quoted_printable", "serde", - "socket2 0.5.5", + "socket2 0.5.6", "tokio", "tokio-native-tls", "tracing", @@ -1737,9 +1753,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "value-bag", ] @@ -1768,15 +1784,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "match_cfg" version = "0.1.0" @@ -1851,9 +1858,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", @@ -1965,7 +1972,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -1998,9 +2005,9 @@ dependencies = [ [[package]] name = "num_threads" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] @@ -2022,9 +2029,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.63" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ "bitflags 2.4.2", "cfg-if", @@ -2043,7 +2050,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -2054,18 +2061,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.2.2+3.2.1" +version = "300.2.3+3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bbfad0063610ac26ee79f7484739e2b07555a75c42453b89263830b5c8103bc" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.99" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -2155,7 +2162,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -2176,9 +2183,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" +checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" dependencies = [ "memchr", "thiserror", @@ -2187,9 +2194,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" +checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" dependencies = [ "pest", "pest_generator", @@ -2197,22 +2204,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" +checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] name = "pest_meta" -version = "2.7.7" +version = "2.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" +checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" dependencies = [ "once_cell", "pest", @@ -2288,9 +2295,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "polling" @@ -2310,9 +2317,9 @@ dependencies = [ [[package]] name = "polling" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14" +checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" dependencies = [ "cfg-if", "concurrent-queue", @@ -2322,6 +2329,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -2345,9 +2358,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -2360,7 +2373,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", "version_check", "yansi", ] @@ -2392,13 +2405,12 @@ dependencies = [ [[package]] name = "quanta" -version = "0.11.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" +checksum = "9ca0b7bac0b97248c40bb77288fc52029cf1459c0461ea1b05ee32ccf011de2c" dependencies = [ "crossbeam-utils", "libc", - "mach2", "once_cell", "raw-cpuid", "wasi", @@ -2470,11 +2482,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "10.7.0" +version = "11.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +checksum = "9d86a7c4638d42c44551f4791a20e687dbb4c3de1f33c43dd71e355cd429def1" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.2", ] [[package]] @@ -2503,7 +2515,7 @@ checksum = "5fddb4f8d99b0a2ebafc65a87a69a7b9875e4b1ae1f00db265d300ef7f28bccc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -2514,7 +2526,7 @@ checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", + "regex-automata 0.4.6", "regex-syntax 0.8.2", ] @@ -2529,9 +2541,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -2563,15 +2575,15 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "78bf93c4af7a8bb7d879d51cebe797356ff10ae8516ace542b5182d9dcac10b2" dependencies = [ "async-compression", "base64 0.21.7", "bytes", "cookie 0.17.0", - "cookie_store", + "cookie_store 0.20.0", "encoding_rs", "futures-core", "futures-util", @@ -2620,16 +2632,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -2703,7 +2716,7 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn 2.0.48", + "syn 2.0.53", "unicode-xid", "version_check", ] @@ -2841,9 +2854,9 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -2919,15 +2932,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] @@ -2944,20 +2957,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -3080,12 +3093,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -3094,6 +3107,15 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "spinning_top" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + [[package]] name = "stable-pattern" version = "0.1.0" @@ -3150,9 +3172,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", @@ -3201,9 +3223,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand 2.0.1", @@ -3213,29 +3235,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -3312,7 +3334,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2 0.5.6", "tokio-macros", "windows-sys 0.48.0", ] @@ -3325,7 +3347,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -3362,9 +3384,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -3411,14 +3433,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "af06656561d28735e9c1cd63dfd57132c8155426aa6af24f36a00a351f88c48e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.4", + "toml_edit 0.22.7", ] [[package]] @@ -3440,20 +3462,20 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.4" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" +checksum = "18769cd1cec395d70860ceb4d932812a0b4d06b1a4bb336745a4d21b9496e992" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.5", ] [[package]] @@ -3494,7 +3516,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] [[package]] @@ -3652,9 +3674,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -3706,9 +3728,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b" +checksum = "8fec26a25bd6fca441cdd0f769fd7f891bae119f996de31f86a5eddccef54c1d" [[package]] name = "vaultwarden" @@ -3720,8 +3742,8 @@ dependencies = [ "cached", "chrono", "chrono-tz", - "cookie 0.17.0", - "cookie_store", + "cookie 0.18.0", + "cookie_store 0.21.0", "dashmap", "data-encoding", "data-url", @@ -3762,7 +3784,6 @@ dependencies = [ "syslog", "time", "tokio", - "tokio-tungstenite", "totp-lite", "tracing", "url", @@ -3792,9 +3813,9 @@ checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -3817,9 +3838,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -3827,24 +3848,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -3854,9 +3875,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3864,22 +3885,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-streams" @@ -3896,9 +3917,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -3988,7 +4009,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -4006,7 +4027,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.4", ] [[package]] @@ -4026,17 +4047,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -4047,9 +4068,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -4059,9 +4080,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -4071,9 +4092,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -4083,9 +4104,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -4095,9 +4116,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -4107,9 +4128,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -4119,15 +4140,24 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] [[package]] name = "winnow" -version = "0.5.39" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" +checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" dependencies = [ "memchr", ] @@ -4144,9 +4174,9 @@ dependencies = [ [[package]] name = "yansi" -version = "1.0.0-rc.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" dependencies = [ "is-terminal", ] @@ -4184,5 +4214,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.53", ] diff --git a/Cargo.toml b/Cargo.toml index 93a6ef87d6..26916626f0 100644 --- a/Cargo.toml +++ b/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 diff --git a/src/api/core/accounts.rs b/src/api/core/accounts.rs index 91555a552b..b8a837a0d9 100644 --- a/src/api/core/accounts.rs +++ b/src/api/core/accounts.rs @@ -559,6 +559,8 @@ async fn post_email_token(data: JsonUpcase, 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); diff --git a/src/api/core/events.rs b/src/api/core/events.rs index d7aaeb4a59..dd6b92e0a5 100644 --- a/src/api/core/events.rs +++ b/src/api/core/events.rs @@ -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)); } diff --git a/src/api/core/two_factor/authenticator.rs b/src/api/core/two_factor/authenticator.rs index f46184831b..c959e0d44c 100644 --- a/src/api/core/two_factor/authenticator.rs +++ b/src/api/core/two_factor/authenticator.rs @@ -156,7 +156,7 @@ pub async fn validate_totp_code( let time = (current_timestamp + step * 30i64) as u64; let generated = totp_custom::(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 { diff --git a/src/api/mod.rs b/src/api/mod.rs index 99915bdffa..c6838aaa34 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -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, diff --git a/src/api/notifications.rs b/src/api/notifications.rs index da2664cfd7..1f64b86edb 100644 --- a/src/api/notifications.rs +++ b/src/api/notifications.rs @@ -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> = Lazy::new(|| { +pub static WS_USERS: Lazy> = 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 = Lazy::new(|| !CONFIG.enable_websocket() && !CONFIG.push_enabled()); + pub fn routes() -> Vec { - 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?")] 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?")] -fn anonymous_websockets_hub<'r>( - ws: rocket_ws::WebSocket, - token: String, - ip: ClientIp, -) -> Result { +fn anonymous_websockets_hub<'r>(ws: WebSocket, token: String, ip: ClientIp) -> Result { 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) { + // 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>, 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>; pub type AnonymousNotify<'a> = &'a rocket::State>; - -pub fn start_notification_server() -> Arc { - let users = Arc::clone(&WS_USERS); - if CONFIG.websocket_enabled() { - let users2 = Arc::::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::::clone(&users2), addr)); - } - - _ = &mut shutdown_rx => { - break; - } - } - } - - info!("Shutting down WebSockets server!") - }); - } - - users -} - -async fn handle_connection(stream: TcpStream, users: Arc, addr: SocketAddr) -> Result<(), Error> { - let mut user_uuid: Option = 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::(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 { - 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 -} diff --git a/src/api/web.rs b/src/api/web.rs index dad36a7f1b..67248c835e 100644 --- a/src/api/web.rs +++ b/src/api/web.rs @@ -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}")), } diff --git a/src/auth.rs b/src/auth.rs index 85b6359e2e..7eabbc1eb6 100644 --- a/src/auth.rs +++ b/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 = Lazy::new(|| format!("{}|send", CONFIG.do static JWT_ORG_API_KEY_ISSUER: Lazy = Lazy::new(|| format!("{}|api.organization", CONFIG.domain_origin())); static JWT_FILE_DOWNLOAD_ISSUER: Lazy = Lazy::new(|| format!("{}|file_download", CONFIG.domain_origin())); -static PRIVATE_RSA_KEY: Lazy = 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 = 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 = OnceCell::new(); +static PUBLIC_RSA_KEY: OnceCell = 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(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(token: &str, issuer: String) -> Result 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, diff --git a/src/config.rs b/src/config.rs index 2f0e9264bb..01f387ec8c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -39,7 +39,6 @@ macro_rules! make_config { struct Inner { rocket_shutdown_handle: Option, - ws_shutdown_handle: Option>, 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(); } diff --git a/src/db/models/attachment.rs b/src/db/models/attachment.rs index 8f05e6b427..f8eca72f68 100644 --- a/src/db/models/attachment.rs +++ b/src/db/models/attachment.rs @@ -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. diff --git a/src/error.rs b/src/error.rs index f0969bffda..784aad6ade 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 { diff --git a/src/main.rs b/src/main.rs index 05f43c5a0c..e3b293832e 100644 --- a/src/main.rs +++ b/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::>().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()) diff --git a/src/static/scripts/bootstrap.bundle.js b/src/static/scripts/bootstrap.bundle.js index 3a02ceb396..491038c28c 100644 --- a/src/static/scripts/bootstrap.bundle.js +++ b/src/static/scripts/bootstrap.bundle.js @@ -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}`; diff --git a/src/static/scripts/bootstrap.css b/src/static/scripts/bootstrap.css index 11e83fa593..32ea6e9c2f 100644 --- a/src/static/scripts/bootstrap.css +++ b/src/static/scripts/bootstrap.css @@ -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 { diff --git a/src/static/scripts/datatables.css b/src/static/scripts/datatables.css index e320547961..4c93a7d576 100644 --- a/src/static/scripts/datatables.css +++ b/src/static/scripts/datatables.css @@ -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; -} -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, +div.dt-scroll-body tfoot tr { + height: 0; +} +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; +} + +: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.dataTables_processing { +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; } diff --git a/src/static/scripts/datatables.js b/src/static/scripts/datatables.js index 735ba65eb6..f67d3f28c1 100644 --- a/src/static/scripts/datatables.js +++ b/src/static/scripts/datatables.js @@ -4,37 +4,34 @@ * * 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 */ -/*! DataTables 1.13.6 - * ©2008-2023 SpryMedia Ltd - datatables.net/license +/*! DataTables 2.0.0 + * © SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables - * @version 1.13.6 + * @version 2.0.0 * @author SpryMedia Ltd * @contact www.datatables.net * @copyright SpryMedia Ltd. * * This source file is free software, available under the following license: - * MIT license - http://datatables.net/license + * MIT license - https://datatables.net/license * * This source file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. * - * For details please refer to: http://www.datatables.net + * For details please refer to: https://www.datatables.net */ -/*jslint evil: true, undef: true, browser: true */ -/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ - (function( factory ) { "use strict"; @@ -66,15 +63,14 @@ }; } else { - return factory( jq, window, window.document ); + module.exports = factory( jq, window, window.document ); } } else { // Browser window.DataTable = factory( jQuery, window, document ); } -} -(function( $, window, document, undefined ) { +}(function( $, window, document ) { "use strict"; @@ -95,968 +91,219 @@ options = selector; } - /** - * Perform a jQuery selector action on the table's TR elements (from the tbody) and - * return the resulting jQuery object. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter - * criterion ("applied") or all TR elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {object} jQuery object, filtered by the given selector. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Highlight every second row - * oTable.$('tr:odd').css('backgroundColor', 'blue'); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to rows with 'Webkit' in them, add a background colour and then - * // remove the filter, thus highlighting the 'Webkit' rows only. - * oTable.fnFilter('Webkit'); - * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); - * oTable.fnFilter(''); - * } ); - */ - this.$ = function ( sSelector, oOpts ) - { - return this.api(true).$( sSelector, oOpts ); - }; - - - /** - * Almost identical to $ in operation, but in this case returns the data for the matched - * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes - * rather than any descendants, so the data can be obtained for the row/cell. If matching - * rows are found, the data returned is the original data array/object that was used to - * create the row (or a generated array if from a DOM source). - * - * This method is often useful in-combination with $ where both functions are given the - * same parameters and the array indexes will match identically. - * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on - * @param {object} [oOpts] Optional parameters for modifying the rows to be included - * @param {string} [oOpts.filter=none] Select elements that meet the current filter - * criterion ("applied") or all elements (i.e. no filter). - * @param {string} [oOpts.order=current] Order of the data in the processed array. - * Can be either 'current', whereby the current sorting of the table is used, or - * 'original' whereby the original order the data was read into the table is used. - * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page - * ("current") or not ("all"). If 'current' is given, then order is assumed to be - * 'current' and filter is 'applied', regardless of what they might be given as. - * @returns {array} Data for the matched elements. If any elements, as a result of the - * selector, were not TR, TD or TH elements in the DataTable, they will have a null - * entry in the array. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the data from the first row in the table - * var data = oTable._('tr:first'); - * - * // Do something useful with the data - * alert( "First cell is: "+data[0] ); - * } ); - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Filter to 'Webkit' and get all data for - * oTable.fnFilter('Webkit'); - * var data = oTable._('tr', {"search": "applied"}); - * - * // Do something with the data - * alert( data.length+" rows matched the search" ); - * } ); - */ - this._ = function ( sSelector, oOpts ) - { - return this.api(true).rows( sSelector, oOpts ).data(); - }; - - - /** - * Create a DataTables Api instance, with the currently selected tables for - * the Api's context. - * @param {boolean} [traditional=false] Set the API instance's context to be - * only the table referred to by the `DataTable.ext.iApiIndex` option, as was - * used in the API presented by DataTables 1.9- (i.e. the traditional mode), - * or if all tables captured in the jQuery object should be used. - * @return {DataTables.Api} - */ - this.api = function ( traditional ) - { - return traditional ? - new _Api( - _fnSettingsFromNode( this[ _ext.iApiIndex ] ) - ) : - new _Api( this ); - }; - - - /** - * Add a single new row or multiple rows of data to the table. Please note - * that this is suitable for client-side processing only - if you are using - * server-side processing (i.e. "bServerSide": true), then to add data, you - * must add it to the data source, i.e. the server-side, through an Ajax call. - * @param {array|object} data The data to be added to the table. This can be: - *
    - *
  • 1D array of data - add a single row with the data provided
  • - *
  • 2D array of arrays - add multiple rows in a single call
  • - *
  • object - data object when using mData
  • - *
  • array of objects - multiple data objects when using mData
  • - *
- * @param {bool} [redraw=true] redraw the table or not - * @returns {array} An array of integers, representing the list of indexes in - * aoData ({@link DataTable.models.oSettings}) that have been added to - * the table. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Global var for counter - * var giCount = 2; - * - * $(document).ready(function() { - * $('#example').dataTable(); - * } ); - * - * function fnClickAddRow() { - * $('#example').dataTable().fnAddData( [ - * giCount+".1", - * giCount+".2", - * giCount+".3", - * giCount+".4" ] - * ); - * - * giCount++; - * } - */ - this.fnAddData = function( data, redraw ) - { - var api = this.api( true ); - - /* Check if we want to add multiple rows or not */ - var rows = Array.isArray(data) && ( Array.isArray(data[0]) || $.isPlainObject(data[0]) ) ? - api.rows.add( data ) : - api.row.add( data ); - - if ( redraw === undefined || redraw ) { - api.draw(); - } - - return rows.flatten().toArray(); - }; - - - /** - * This function will make DataTables recalculate the column sizes, based on the data - * contained in the table and the sizes applied to the columns (in the DOM, CSS or - * through the sWidth parameter). This can be useful when the width of the table's - * parent element changes (for example a window resize). - * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable( { - * "sScrollY": "200px", - * "bPaginate": false - * } ); - * - * $(window).on('resize', function () { - * oTable.fnAdjustColumnSizing(); - * } ); - * } ); - */ - this.fnAdjustColumnSizing = function ( bRedraw ) - { - var api = this.api( true ).columns.adjust(); - var settings = api.settings()[0]; - var scroll = settings.oScroll; - - if ( bRedraw === undefined || bRedraw ) { - api.draw( false ); - } - else if ( scroll.sX !== "" || scroll.sY !== "" ) { - /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ - _fnScrollDraw( settings ); - } - }; - - - /** - * Quickly and simply clear a table - * @param {bool} [bRedraw=true] redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) - * oTable.fnClearTable(); - * } ); - */ - this.fnClearTable = function( bRedraw ) - { - var api = this.api( true ).clear(); - - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - }; - - - /** - * The exact opposite of 'opening' a row, this function will close any rows which - * are currently 'open'. - * @param {node} nTr the table row to 'close' - * @returns {int} 0 on success, or 1 if failed (can't find the row) - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnClose = function( nTr ) + var _that = this; + var emptyInit = options === undefined; + var len = this.length; + + if ( emptyInit ) { + options = {}; + } + + // Method to get DT API instance from jQuery object + this.api = function () { - this.api( true ).row( nTr ).child.hide(); + return new _Api( this ); }; - - - /** - * Remove a row for the table - * @param {mixed} target The index of the row from aoData to be deleted, or - * the TR element you want to delete - * @param {function|null} [callBack] Callback function - * @param {bool} [redraw=true] Redraw the table or not - * @returns {array} The row that was deleted - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Immediately remove the first row - * oTable.fnDeleteRow( 0 ); - * } ); - */ - this.fnDeleteRow = function( target, callback, redraw ) - { - var api = this.api( true ); - var rows = api.rows( target ); - var settings = rows.settings()[0]; - var data = settings.aoData[ rows[0][0] ]; - - rows.remove(); - - if ( callback ) { - callback.call( this, settings, data ); + + this.each(function() { + // For each initialisation we want to give it a clean initialisation + // object that can be bashed around + var o = {}; + var oInit = len > 1 ? // optimisation for single table case + _fnExtend( o, options, true ) : + options; + + + var i=0, iLen; + var sId = this.getAttribute( 'id' ); + var bInitHandedOff = false; + var defaults = DataTable.defaults; + var $this = $(this); + + + /* Sanity check */ + if ( this.nodeName.toLowerCase() != 'table' ) + { + _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); + return; } - - if ( redraw === undefined || redraw ) { - api.draw(); + + $(this).trigger( 'options.dt', oInit ); + + /* Backwards compatibility for the defaults */ + _fnCompatOpts( defaults ); + _fnCompatCols( defaults.column ); + + /* Convert the camel-case defaults to Hungarian */ + _fnCamelToHungarian( defaults, defaults, true ); + _fnCamelToHungarian( defaults.column, defaults.column, true ); + + /* Setting up the initialisation object */ + _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); + + + + /* Check to see if we are re-initialising a table */ + var allSettings = DataTable.settings; + for ( i=0, iLen=allSettings.length ; i').prependTo(this), + fastData: function (row, column, type) { + return _fnGetCellData(oSettings, row, column, type); + } + } ); + oSettings.nTable = this; + oSettings.oInit = oInit; + + allSettings.push( oSettings ); + + // Make a single API instance available for internal handling + oSettings.api = new _Api( oSettings ); + + // Need to add the instance after the instance after the settings object has been added + // to the settings array, so we can self reference the table instance if more than one + oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); + + // Backwards compatibility, before we apply all the defaults + _fnCompatOpts( oInit ); + + // If the length menu is given, but the init display length is not, use the length menu + if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) + { + oInit.iDisplayLength = Array.isArray(oInit.aLengthMenu[0]) + ? oInit.aLengthMenu[0][0] + : $.isPlainObject( oInit.aLengthMenu[0] ) + ? oInit.aLengthMenu[0].value + : oInit.aLengthMenu[0]; } - - api.draw(); - }; - - - /** - * Get the data for the whole table, an individual row or an individual cell based on the - * provided parameters. - * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as - * a TR node then the data source for the whole row will be returned. If given as a - * TD/TH cell node then iCol will be automatically calculated and the data for the - * cell returned. If given as an integer, then this is treated as the aoData internal - * data index for the row (see fnGetPosition) and the data for that row used. - * @param {int} [col] Optional column index that you want the data of. - * @returns {array|object|string} If mRow is undefined, then the data for all rows is - * returned. If mRow is defined, just data for that row, and is iCol is - * defined, only data for the designated cell is returned. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * // Row data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('tr').click( function () { - * var data = oTable.fnGetData( this ); - * // ... do something with the array / object of data for the row - * } ); - * } ); - * - * @example - * // Individual cell data - * $(document).ready(function() { - * oTable = $('#example').dataTable(); - * - * oTable.$('td').click( function () { - * var sData = oTable.fnGetData( this ); - * alert( 'The cell clicked on had the value of '+sData ); - * } ); - * } ); - */ - this.fnGetData = function( src, col ) - { - var api = this.api( true ); - - if ( src !== undefined ) { - var type = src.nodeName ? src.nodeName.toLowerCase() : ''; - - return col !== undefined || type == 'td' || type == 'th' ? - api.cell( src, col ).data() : - api.row( src ).data() || null; - } - - return api.data().toArray(); - }; - - - /** - * Get an array of the TR nodes that are used in the table's body. Note that you will - * typically want to use the '$' API method in preference to this as it is more - * flexible. - * @param {int} [iRow] Optional row index for the TR element you want - * @returns {array|node} If iRow is undefined, returns an array of all TR elements - * in the table's body, or iRow is defined, just the TR element requested. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Get the nodes from the table - * var nNodes = oTable.fnGetNodes( ); - * } ); - */ - this.fnGetNodes = function( iRow ) - { - var api = this.api( true ); - - return iRow !== undefined ? - api.row( iRow ).node() : - api.rows().nodes().flatten().toArray(); - }; - - - /** - * Get the array indexes of a particular cell from it's DOM element - * and column index including hidden columns - * @param {node} node this can either be a TR, TD or TH in the table's body - * @returns {int} If nNode is given as a TR, then a single index is returned, or - * if given as a cell, an array of [row index, column index (visible), - * column index (all)] is given. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * $('#example tbody td').click( function () { - * // Get the position of the current data from the node - * var aPos = oTable.fnGetPosition( this ); - * - * // Get the data array for this row - * var aData = oTable.fnGetData( aPos[0] ); - * - * // Update the data array and return the value - * aData[ aPos[1] ] = 'clicked'; - * this.innerHTML = 'clicked'; - * } ); - * - * // Init DataTables - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnGetPosition = function( node ) - { - var api = this.api( true ); - var nodeName = node.nodeName.toUpperCase(); - - if ( nodeName == 'TR' ) { - return api.row( node ).index(); - } - else if ( nodeName == 'TD' || nodeName == 'TH' ) { - var cell = api.cell( node ).index(); - - return [ - cell.row, - cell.columnVisible, - cell.column - ]; - } - return null; - }; - - - /** - * Check to see if a row is 'open' or not. - * @param {node} nTr the table row to check - * @returns {boolean} true if the row is currently open, false otherwise - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnIsOpen = function( nTr ) - { - return this.api( true ).row( nTr ).child.isShown(); - }; - - - /** - * This function will place a new row directly after a row which is currently - * on display on the page, with the HTML contents that is passed into the - * function. This can be used, for example, to ask for confirmation that a - * particular record should be deleted. - * @param {node} nTr The table row to 'open' - * @param {string|node|jQuery} mHtml The HTML to put into the row - * @param {string} sClass Class to give the new TD cell - * @returns {node} The row opened. Note that if the table row passed in as the - * first parameter, is not found in the table, this method will silently - * return. - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable; - * - * // 'open' an information row when a row is clicked on - * $('#example tbody tr').click( function () { - * if ( oTable.fnIsOpen(this) ) { - * oTable.fnClose( this ); - * } else { - * oTable.fnOpen( this, "Temporary row opened", "info_row" ); - * } - * } ); - * - * oTable = $('#example').dataTable(); - * } ); - */ - this.fnOpen = function( nTr, mHtml, sClass ) - { - return this.api( true ) - .row( nTr ) - .child( mHtml, sClass ) - .show() - .child()[0]; - }; - - - /** - * Change the pagination - provides the internal logic for pagination in a simple API - * function. With this function you can have a DataTables table go to the next, - * previous, first or last pages. - * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" - * or page number to jump to (integer), note that page 0 is the first page. - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnPageChange( 'next' ); - * } ); - */ - this.fnPageChange = function ( mAction, bRedraw ) - { - var api = this.api( true ).page( mAction ); - - if ( bRedraw === undefined || bRedraw ) { - api.draw(false); - } - }; - - - /** - * Show a particular column - * @param {int} iCol The column whose display should be changed - * @param {bool} bShow Show (true) or hide (false) the column - * @param {bool} [bRedraw=true] Redraw the table or not - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Hide the second column after initialisation - * oTable.fnSetColumnVis( 1, false ); - * } ); - */ - this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) - { - var api = this.api( true ).column( iCol ).visible( bShow ); - - if ( bRedraw === undefined || bRedraw ) { - api.columns.adjust().draw(); - } - }; - - - /** - * Get the settings for a particular table for external manipulation - * @returns {object} DataTables settings object. See - * {@link DataTable.models.oSettings} - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * var oSettings = oTable.fnSettings(); - * - * // Show an example parameter from the settings - * alert( oSettings._iDisplayStart ); - * } ); - */ - this.fnSettings = function() - { - return _fnSettingsFromNode( this[_ext.iApiIndex] ); - }; - - - /** - * Sort the table by a particular column - * @param {int} iCol the data index to sort on. Note that this will not match the - * 'display index' if you have hidden data entries - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort immediately with columns 0 and 1 - * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); - * } ); - */ - this.fnSort = function( aaSort ) - { - this.api( true ).order( aaSort ).draw(); - }; - - - /** - * Attach a sort listener to an element for a given column - * @param {node} nNode the element to attach the sort listener to - * @param {int} iColumn the column that a click on this node will sort on - * @param {function} [fnCallback] callback function when sort is run - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * - * // Sort on column 1, when 'sorter' is clicked on - * oTable.fnSortListener( document.getElementById('sorter'), 1 ); - * } ); - */ - this.fnSortListener = function( nNode, iColumn, fnCallback ) - { - this.api( true ).order.listener( nNode, iColumn, fnCallback ); - }; - - - /** - * Update a table cell or row - this method will accept either a single value to - * update the cell with, an array of values with one element for each column or - * an object in the same format as the original data source. The function is - * self-referencing in order to make the multi column updates easier. - * @param {object|array|string} mData Data to update the cell/row with - * @param {node|int} mRow TR element you want to update or the aoData index - * @param {int} [iColumn] The column to update, give as null or undefined to - * update a whole row. - * @param {bool} [bRedraw=true] Redraw the table or not - * @param {bool} [bAction=true] Perform pre-draw actions or not - * @returns {int} 0 on success, 1 on error - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell - * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row - * } ); - */ - this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) - { - var api = this.api( true ); - - if ( iColumn === undefined || iColumn === null ) { - api.row( mRow ).data( mData ); - } - else { - api.cell( mRow, iColumn ).data( mData ); - } - - if ( bAction === undefined || bAction ) { - api.columns.adjust(); - } - - if ( bRedraw === undefined || bRedraw ) { - api.draw(); - } - return 0; - }; - - - /** - * Provide a common method for plug-ins to check the version of DataTables being used, in order - * to ensure compatibility. - * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the - * formats "X" and "X.Y" are also acceptable. - * @returns {boolean} true if this version of DataTables is greater or equal to the required - * version, or false if this version of DataTales is not suitable - * @method - * @dtopt API - * @deprecated Since v1.10 - * - * @example - * $(document).ready(function() { - * var oTable = $('#example').dataTable(); - * alert( oTable.fnVersionCheck( '1.9.0' ) ); - * } ); - */ - this.fnVersionCheck = _ext.fnVersionCheck; - - - var _that = this; - var emptyInit = options === undefined; - var len = this.length; - - if ( emptyInit ) { - options = {}; - } - - this.oApi = this.internal = _ext.internal; - - // Extend with old style plug-in API methods - for ( var fn in DataTable.ext.internal ) { - if ( fn ) { - this[fn] = _fnExternApiFunc(fn); - } - } - - this.each(function() { - // For each initialisation we want to give it a clean initialisation - // object that can be bashed around - var o = {}; - var oInit = len > 1 ? // optimisation for single table case - _fnExtend( o, options, true ) : - options; - - /*global oInit,_that,emptyInit*/ - var i=0, iLen, j, jLen, k, kLen; - var sId = this.getAttribute( 'id' ); - var bInitHandedOff = false; - var defaults = DataTable.defaults; - var $this = $(this); + // Apply the defaults and init options to make a single init object will all + // options defined from defaults and instance options. + oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); - /* Sanity check */ - if ( this.nodeName.toLowerCase() != 'table' ) - { - _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); - return; - } - /* Backwards compatibility for the defaults */ - _fnCompatOpts( defaults ); - _fnCompatCols( defaults.column ); + // Map the initialisation options onto the settings object + _fnMap( oSettings.oFeatures, oInit, [ + "bPaginate", + "bLengthChange", + "bFilter", + "bSort", + "bSortMulti", + "bInfo", + "bProcessing", + "bAutoWidth", + "bSortClasses", + "bServerSide", + "bDeferRender" + ] ); + _fnMap( oSettings, oInit, [ + "ajax", + "fnFormatNumber", + "sServerMethod", + "aaSorting", + "aaSortingFixed", + "aLengthMenu", + "sPaginationType", + "iStateDuration", + "bSortCellsTop", + "iTabIndex", + "sDom", + "fnStateLoadCallback", + "fnStateSaveCallback", + "renderer", + "searchDelay", + "rowId", + "caption", + "layout", + [ "iCookieDuration", "iStateDuration" ], // backwards compat + [ "oSearch", "oPreviousSearch" ], + [ "aoSearchCols", "aoPreSearchCols" ], + [ "iDisplayLength", "_iDisplayLength" ] + ] ); + _fnMap( oSettings.oScroll, oInit, [ + [ "sScrollX", "sX" ], + [ "sScrollXInner", "sXInner" ], + [ "sScrollY", "sY" ], + [ "bScrollCollapse", "bCollapse" ] + ] ); + _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); - /* Convert the camel-case defaults to Hungarian */ - _fnCamelToHungarian( defaults, defaults, true ); - _fnCamelToHungarian( defaults.column, defaults.column, true ); + /* Callback functions which are array driven */ + _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback ); + _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams ); + _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams ); + _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded ); + _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback ); + _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow ); + _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback ); + _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback ); + _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete ); + _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback ); - /* Setting up the initialisation object */ - _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); + oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); + /* Browser support detection */ + _fnBrowserDetect( oSettings ); + var oClasses = oSettings.oClasses; - /* Check to see if we are re-initialising a table */ - var allSettings = DataTable.settings; - for ( i=0, iLen=allSettings.length ; i').appendTo($this); - } - oSettings.nTHead = thead[0]; - - var tbody = $this.children('tbody'); - if ( tbody.length === 0 ) { - tbody = $('').insertAfter(thead); - } - oSettings.nTBody = tbody[0]; - - var tfoot = $this.children('tfoot'); - if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { - // If we are a scrolling table, and no footer has been given, then we need to create - // a tfoot element for the caption element to be appended to - tfoot = $('').appendTo($this); - } - - if ( tfoot.length === 0 || tfoot.children().length === 0 ) { - $this.addClass( oClasses.sNoFooter ); - } - else if ( tfoot.length > 0 ) { - oSettings.nTFoot = tfoot[0]; - _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); - } - - /* Check if there is data passing into the constructor */ - if ( oInit.aaData ) { - for ( i=0 ; i').appendTo( $this ); + } + + caption.html( oSettings.caption ); + } + + // Store the caption side, so we can remove the element from the document + // when creating the element + if (caption.length) { + caption[0]._captionSide = caption.css('caption-side'); + oSettings.captionNode = caption[0]; + } + + if ( thead.length === 0 ) { + thead = $('').appendTo($this); + } + oSettings.nTHead = thead[0]; + $('tr', thead).addClass(oClasses.thead.row); + + var tbody = $this.children('tbody'); + if ( tbody.length === 0 ) { + tbody = $('').insertAfter(thead); + } + oSettings.nTBody = tbody[0]; + + var tfoot = $this.children('tfoot'); + if ( tfoot.length === 0 ) { + // If we are a scrolling table, and no footer has been given, then we need to create + // a tfoot element for the caption element to be appended to + tfoot = $('').appendTo($this); + } + oSettings.nTFoot = tfoot[0]; + $('tr', tfoot).addClass(oClasses.tfoot.row); + + // Check if there is data passing into the constructor + if ( oInit.aaData ) { + for ( i=0 ; iafnSortData + * for searching data. + * + * The functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for searching + * + * Each function is expected to return: + * + * * `{string|null}` Formatted string that will be used for the searching. + * + * @type object + * @default {} + * + * @example + * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { + * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); + * } + */ + search: {}, + + + /** + * Type based ordering. + * + * The column type tells DataTables what ordering to apply to the table + * when a column is sorted upon. The order for each type that is defined, + * is defined by the functions available in this object. + * + * Each ordering option can be described by three properties added to + * this object: + * + * * `{type}-pre` - Pre-formatting function + * * `{type}-asc` - Ascending order function + * * `{type}-desc` - Descending order function + * + * All three can be used together, only `{type}-pre` or only + * `{type}-asc` and `{type}-desc` together. It is generally recommended + * that only `{type}-pre` is used, as this provides the optimal + * implementation in terms of speed, although the others are provided + * for compatibility with existing Javascript sort functions. + * + * `{type}-pre`: Functions defined take a single parameter: + * + * 1. `{*}` Data from the column cell to be prepared for ordering + * + * And return: + * + * * `{*}` Data to be sorted upon + * + * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort + * functions, taking two parameters: + * + * 1. `{*}` Data to compare to the second parameter + * 2. `{*}` Data to compare to the first parameter + * + * And returning: + * + * * `{*}` Ordering match: <0 if first parameter should be sorted lower + * than the second parameter, ===0 if the two parameters are equal and + * >0 if the first parameter should be sorted height than the second + * parameter. + * + * @type object + * @default {} + * + * @example + * // Numeric ordering of formatted numbers with a pre-formatter + * $.extend( $.fn.dataTable.ext.type.order, { + * "string-pre": function(x) { + * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); + * return parseFloat( a ); + * } + * } ); + * + * @example + * // Case-sensitive string ordering, with no pre-formatting method + * $.extend( $.fn.dataTable.ext.order, { + * "string-case-asc": function(x,y) { + * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + * }, + * "string-case-desc": function(x,y) { + * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); + * } + * } ); + */ + order: {} + }, + + /** + * Unique DataTables instance counter + * + * @type int + * @private + */ + _unique: 0, + + + // + // Depreciated + // The following properties are retained for backwards compatibility only. + // The should not be used in new projects and will be removed in a future + // version + // + + /** + * Version check function. + * @type function + * @depreciated Since 1.10 + */ + fnVersionCheck: DataTable.fnVersionCheck, + + + /** + * Index for what 'this' index API functions should use + * @type int + * @deprecated Since v1.10 + */ + iApiIndex: 0, + + + /** + * Software version + * @type string + * @deprecated Since v1.10 + */ + sVersion: DataTable.version }; + // + // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts + // + $.extend( _ext, { + afnFiltering: _ext.search, + aTypes: _ext.type.detect, + ofnSearch: _ext.type.search, + oSort: _ext.type.order, + afnSortData: _ext.order, + aoFeatures: _ext.feature, + oStdClasses: _ext.classes, + oPagination: _ext.pager + } ); + + + $.extend( DataTable.ext.classes, { + container: 'dt-container', + empty: { + row: 'dt-empty' + }, + info: { + container: 'dt-info' + }, + length: { + container: 'dt-length', + select: 'dt-input' + }, + order: { + canAsc: 'dt-orderable-asc', + canDesc: 'dt-orderable-desc', + isAsc: 'dt-ordering-asc', + isDesc: 'dt-ordering-desc', + none: 'dt-orderable-none', + position: 'sorting_' + }, + processing: { + container: 'dt-processing' + }, + scrolling: { + body: 'dt-scroll-body', + container: 'dt-scroll', + footer: { + self: 'dt-scroll-foot', + inner: 'dt-scroll-footInner' + }, + header: { + self: 'dt-scroll-head', + inner: 'dt-scroll-headInner' + } + }, + search: { + container: 'dt-search', + input: 'dt-input' + }, + table: 'dataTable', + tbody: { + cell: '', + row: '' + }, + thead: { + cell: '', + row: '' + }, + tfoot: { + cell: '', + row: '' + }, + paging: { + active: 'current', + button: 'dt-paging-button', + container: 'dt-paging', + disabled: 'disabled' + } + } ); + + /* * It is useful to have variables which are scoped locally so only the * DataTables functions can access them and they don't leak into global space. @@ -1338,7 +1099,6 @@ // Defined else where // _selector_run // _selector_opts - // _selector_first // _selector_row_indexes var _ext; // DataTable.ext @@ -1352,12 +1112,12 @@ // This is not strict ISO8601 - Date.parse() is quite lax, although // implementations differ between browsers. - var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; + var _re_date = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/; // Escape regular expression special characters var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); - // http://en.wikipedia.org/wiki/Foreign_exchange_market + // https://en.wikipedia.org/wiki/Foreign_exchange_market // - \u20BD - Russian ruble. // - \u20a9 - South Korean Won // - \u20BA - Turkish Lira @@ -1427,12 +1187,17 @@ return _empty( d ) || typeof d === 'string'; }; - + // Is a string a number surrounded by HTML? var _htmlNumeric = function ( d, decimalPoint, formatted ) { if ( _empty( d ) ) { return true; } + // input and select strings mean that this isn't just a number + if (typeof d === 'string' && d.match(/<(input|select)/i)) { + return null; + } + var html = _isHtml( d ); return ! html ? null : @@ -1485,7 +1250,9 @@ } else { for ( ; i/g, '>') + .replace(/"/g, '"') : + d; + }; + + // Remove diacritics from a string by decomposing it and then removing + // non-ascii characters + var _normalize = function (str, both) { + if (typeof str !== 'string') { + return str; + } + + // It is faster to just run `normalize` than it is to check if + // we need to with a regex! + var res = str.normalize("NFD"); + + // Equally, here we check if a regex is needed or not + return res.length !== str.length + ? (both === true ? str + ' ' : '' ) + res.replace(/[\u0300-\u036f]/g, "") + : res; + } /** * Determine if all values in the array are unique. This means we can short @@ -1574,13 +1372,17 @@ */ var _unique = function ( src ) { + if (Array.from && Set) { + return Array.from(new Set(src)); + } + if ( _areAllUnique( src ) ) { return src.slice(); } // A faster unique method is to use object keys to identify used values, // but this doesn't work with arrays or objects, which we must also - // consider. See jsperf.com/compare-array-unique-versions/4 for more + // consider. See jsperf.app/compare-array-unique-versions/4 for more // information. var out = [], @@ -1615,40 +1417,20 @@ else { out.push(val); } - + return out; } - var _includes = function (search, start) { - if (start === undefined) { - start = 0; + // Similar to jQuery's addClass, but use classList.add + function _addClass(el, name) { + if (name) { + name.split(' ').forEach(function (n) { + if (n) { + // `add` does deduplication, so no need to check `contains` + el.classList.add(n); + } + }); } - - return this.indexOf(search, start) !== -1; - }; - - // Array.isArray polyfill. - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray - if (! Array.isArray) { - Array.isArray = function(arg) { - return Object.prototype.toString.call(arg) === '[object Array]'; - }; - } - - if (! Array.prototype.includes) { - Array.prototype.includes = _includes; - } - - // .trim() polyfill - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim - if (!String.prototype.trim) { - String.prototype.trim = function () { - return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); - }; - } - - if (! String.prototype.includes) { - String.prototype.includes = _includes; } /** @@ -1662,6 +1444,43 @@ * @namespace */ DataTable.util = { + /** + * Return a string with diacritic characters decomposed + * @param {*} mixed Function or string to normalize + * @param {*} both Return original string and the normalized string + * @returns String or undefined + */ + diacritics: function (mixed, both) { + var type = typeof mixed; + + if (type !== 'function') { + return _normalize(mixed, both); + } + _normalize = mixed; + }, + + /** + * Debounce a function + * + * @param {function} fn Function to be called + * @param {integer} freq Call frequency in mS + * @return {function} Wrapped function + */ + debounce: function ( fn, timeout ) { + var timer; + + return function () { + var that = this; + var args = arguments; + + clearTimeout(timer); + + timer = setTimeout( function () { + fn.apply(that, args); + }, timeout || 250 ); + }; + }, + /** * Throttle the calls to a function. Arguments and context are maintained * for the throttled function. @@ -1697,7 +1516,6 @@ }; }, - /** * Escape a string such that it can be used in a regular expression * @@ -1731,9 +1549,10 @@ source( data, 'set', val, meta ); }; } - else if ( typeof source === 'string' && (source.indexOf('.') !== -1 || - source.indexOf('[') !== -1 || source.indexOf('(') !== -1) ) - { + else if ( + typeof source === 'string' && (source.indexOf('.') !== -1 || + source.indexOf('[') !== -1 || source.indexOf('(') !== -1) + ) { // Like the get, we need to get data from a nested object var setData = function (data, val, src) { var a = _fnSplitObjNotation( src ), b; @@ -1849,9 +1668,10 @@ return source( data, type, row, meta ); }; } - else if ( typeof source === 'string' && (source.indexOf('.') !== -1 || - source.indexOf('[') !== -1 || source.indexOf('(') !== -1) ) - { + else if ( + typeof source === 'string' && (source.indexOf('.') !== -1 || + source.indexOf('[') !== -1 || source.indexOf('(') !== -1) + ) { /* If there is a . in the source string then the data source is in a * nested object so we loop over the data for each level to get the next * level down. On each loop we test for undefined, and if found immediately @@ -1926,11 +1746,39 @@ } else { // Array or flat object mapping - return function (data, type) { // row and meta also passed, but not used + return function (data) { // row and meta also passed, but not used return data[source]; }; } - } + }, + + stripHtml: function (mixed) { + var type = typeof mixed; + + if (type === 'function') { + _stripHtml = mixed; + return; + } + else if (type === 'string') { + return _stripHtml(mixed); + } + return mixed; + }, + + escapeHtml: function (mixed) { + var type = typeof mixed; + + if (type === 'function') { + _escapeHtml = mixed; + return; + } + else if (type === 'string' || Array.isArray(mixed)) { + return _escapeHtml(mixed); + } + return mixed; + }, + + unique: _unique }; @@ -1950,7 +1798,7 @@ newKey, map = {}; - $.each( o, function (key, val) { + $.each( o, function (key) { match = key.match(/^([^A-Z]+?)([A-Z])/); if ( match && hungarian.indexOf(match[1]+' ') !== -1 ) @@ -1988,7 +1836,7 @@ var hungarianKey; - $.each( user, function (key, val) { + $.each( user, function (key) { hungarianKey = src._hungarianMap[ key ]; if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) ) @@ -2011,57 +1859,6 @@ } ); } - - /** - * Language compatibility - when certain options are given, and others aren't, we - * need to duplicate the values over, in order to provide backwards compatibility - * with older language files. - * @param {object} oSettings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnLanguageCompat( lang ) - { - // Note the use of the Hungarian notation for the parameters in this method as - // this is called after the mapping of camelCase to Hungarian - var defaults = DataTable.defaults.oLanguage; - - // Default mapping - var defaultDecimal = defaults.sDecimal; - if ( defaultDecimal ) { - _addNumericSort( defaultDecimal ); - } - - if ( lang ) { - var zeroRecords = lang.sZeroRecords; - - // Backwards compatibility - if there is no sEmptyTable given, then use the same as - // sZeroRecords - assuming that is given. - if ( ! lang.sEmptyTable && zeroRecords && - defaults.sEmptyTable === "No data available in table" ) - { - _fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' ); - } - - // Likewise with loading records - if ( ! lang.sLoadingRecords && zeroRecords && - defaults.sLoadingRecords === "Loading..." ) - { - _fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' ); - } - - // Old parameter name of the thousands separator mapped onto the new - if ( lang.sInfoThousands ) { - lang.sThousands = lang.sInfoThousands; - } - - var decimal = lang.sDecimal; - if ( decimal && defaultDecimal !== decimal ) { - _addNumericSort( decimal ); - } - } - } - - /** * Map one parameter onto another * @param {object} o Object to map @@ -2113,6 +1910,11 @@ } } } + + // Enable search delay if server-side processing is enabled + if (init.serverSide && ! init.searchDelay) { + init.searchDelay = 400; + } } @@ -2156,7 +1958,7 @@ .css( { position: 'fixed', top: 0, - left: $(window).scrollLeft()*-1, // allow for scrolling + left: -1 * window.pageXOffset, // allow for scrolling height: 1, width: 1, overflow: 'hidden' @@ -2183,31 +1985,13 @@ var outer = n.children(); var inner = outer.children(); - // Numbers below, in order, are: - // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth - // - // IE6 XP: 100 100 100 83 - // IE7 Vista: 100 100 100 83 - // IE 8+ Windows: 83 83 100 83 - // Evergreen Windows: 83 83 100 83 - // Evergreen Mac with scrollbars: 85 85 100 85 - // Evergreen Mac without scrollbars: 100 100 100 100 - // Get scrollbar width browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; - // IE6/7 will oversize a width 100% element inside a scrolling element, to - // include the width of the scrollbar, while other browsers ensure the inner - // element is contained without forcing scrolling - browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; - // In rtl text layout, some browsers (most, but not all) will place the // scrollbar on the left, rather than the right. browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; - // IE8- don't provide height and width for getBoundingClientRect - browser.bBounding = n[0].getBoundingClientRect().width ? true : false; - n.remove(); } @@ -2215,58 +1999,22 @@ settings.oScroll.iBarWidth = DataTable.__browser.barWidth; } - - /** - * Array.prototype reduce[Right] method, used for browsers which don't support - * JS 1.6. Done this way to reduce code size, since we iterate either way - * @param {object} settings dataTables settings object - * @memberof DataTable#oApi - */ - function _fnReduce ( that, fn, init, start, end, inc ) - { - var - i = start, - value, - isSet = false; - - if ( init !== undefined ) { - value = init; - isSet = true; - } - - while ( i !== end ) { - if ( ! that.hasOwnProperty(i) ) { - continue; - } - - value = isSet ? - fn( value, that[i], i, that ) : - that[i]; - - isSet = true; - i += inc; - } - - return value; - } - /** * Add a column to the list used for the table with default values * @param {object} oSettings dataTables settings object - * @param {node} nTh The th element for this column * @memberof DataTable#oApi */ - function _fnAddColumn( oSettings, nTh ) + function _fnAddColumn( oSettings ) { // Add column to aoColumns array var oDefaults = DataTable.defaults.column; var iCol = oSettings.aoColumns.length; var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { - "nTh": nTh ? nTh : document.createElement('th'), - "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], "mData": oDefaults.mData ? oDefaults.mData : iCol, - idx: iCol + idx: iCol, + searchFixed: {}, + colEl: $('') } ); oSettings.aoColumns.push( oCol ); @@ -2275,9 +2023,6 @@ // with only some of the parameters defined, and also not give a default var searchCols = oSettings.aoPreSearchCols; searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); - - // Use the default column options function to initialise classes etc - _fnColumnOptions( oSettings, iCol, $(nTh).data() ); } @@ -2291,21 +2036,6 @@ function _fnColumnOptions( oSettings, iCol, oOptions ) { var oCol = oSettings.aoColumns[ iCol ]; - var oClasses = oSettings.oClasses; - var th = $(oCol.nTh); - - // Try to get width information from the DOM. We can't get it from CSS - // as we'd need to parse the CSS stylesheet. `width` option can override - if ( ! oCol.sWidthOrig ) { - // Width attribute - oCol.sWidthOrig = th.attr('width') || null; - - // Style attribute - var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); - if ( t ) { - oCol.sWidthOrig = t[1]; - } - } /* User specified column options */ if ( oOptions !== undefined && oOptions !== null ) @@ -2326,16 +2056,13 @@ { oCol._sManualType = oOptions.sType; } - + // `class` is a reserved word in Javascript, so we need to provide // the ability to use a valid name for the camel case input if ( oOptions.className && ! oOptions.sClass ) { oOptions.sClass = oOptions.className; } - if ( oOptions.sClass ) { - th.addClass( oOptions.sClass ); - } var origClass = oCol.sClass; @@ -2356,18 +2083,22 @@ oCol.aDataSort = [ oOptions.iDataSort ]; } _fnMap( oCol, oOptions, "aDataSort" ); - - // Fall back to the aria-label attribute on the table header if no ariaTitle is - // provided. - if (! oCol.ariaTitle) { - oCol.ariaTitle = th.attr("aria-label"); - } } /* Cache the data get and set functions for speed */ var mDataSrc = oCol.mData; var mData = _fnGetObjectDataFn( mDataSrc ); - var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; + + // The `render` option can be given as an array to access the helper rendering methods. + // The first element is the rendering method to use, the rest are the parameters to pass + if ( oCol.mRender && Array.isArray( oCol.mRender ) ) { + var copy = oCol.mRender.slice(); + var name = copy.shift(); + + oCol.mRender = DataTable.render[name].apply(window, copy); + } + + oCol._render = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; var attrTest = function( src ) { return typeof src === 'string' && src.indexOf('@') !== -1; @@ -2380,8 +2111,8 @@ oCol.fnGetData = function (rowData, type, meta) { var innerData = mData( rowData, type, undefined, meta ); - return mRender && type ? - mRender( innerData, type, rowData, meta ) : + return oCol._render && type ? + oCol._render( innerData, type, rowData, meta ) : innerData; }; oCol.fnSetData = function ( rowData, val, meta ) { @@ -2398,31 +2129,6 @@ if ( !oSettings.oFeatures.bSort ) { oCol.bSortable = false; - th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called - } - - /* Check that the class assignment is correct for sorting */ - var bAsc = $.inArray('asc', oCol.asSorting) !== -1; - var bDesc = $.inArray('desc', oCol.asSorting) !== -1; - if ( !oCol.bSortable || (!bAsc && !bDesc) ) - { - oCol.sSortingClass = oClasses.sSortableNone; - oCol.sSortingClassJUI = ""; - } - else if ( bAsc && !bDesc ) - { - oCol.sSortingClass = oClasses.sSortableAsc; - oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; - } - else if ( !bAsc && bDesc ) - { - oCol.sSortingClass = oClasses.sSortableDesc; - oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; - } - else - { - oCol.sSortingClass = oClasses.sSortable; - oCol.sSortingClassJUI = oClasses.sSortJUI; } } @@ -2435,27 +2141,33 @@ */ function _fnAdjustColumnSizing ( settings ) { - /* Not interested in doing column width calculation if auto-width is disabled */ - if ( settings.oFeatures.bAutoWidth !== false ) - { - var columns = settings.aoColumns; - - _fnCalculateColumnWidths( settings ); - for ( var i=0 , iLen=columns.length ; i= 0 ) + var target = aTargets[j]; + + if ( typeof target === 'number' && target >= 0 ) { /* Add columns that we don't yet know about */ - while( columns.length <= aTargets[j] ) + while( columns.length <= target ) { _fnAddColumn( oSettings ); } /* Integer, basic index */ - fn( aTargets[j], def ); + fn( target, def ); } - else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 ) + else if ( typeof target === 'number' && target < 0 ) { /* Negative integer, right to left column counting */ - fn( columns.length+aTargets[j], def ); + fn( columns.length+target, def ); } - else if ( typeof aTargets[j] === 'string' ) + else if ( typeof target === 'string' ) { - /* Class name matching on TH element */ - for ( k=0, kLen=columns.length ; k=0 if successful (index of new aoData entry), -1 if failed * @memberof DataTable#oApi */ - function _fnAddData ( oSettings, aDataIn, nTr, anTds ) + function _fnAddData ( settings, dataIn, tr, tds ) { /* Create the object for storing information about this new row */ - var iRow = oSettings.aoData.length; - var oData = $.extend( true, {}, DataTable.models.oRow, { - src: nTr ? 'dom' : 'data', - idx: iRow + var rowIdx = settings.aoData.length; + var rowModel = $.extend( true, {}, DataTable.models.oRow, { + src: tr ? 'dom' : 'data', + idx: rowIdx } ); - oData._aData = aDataIn; - oSettings.aoData.push( oData ); + rowModel._aData = dataIn; + settings.aoData.push( rowModel ); - /* Create the cells */ - var nTd, sThisType; - var columns = oSettings.aoColumns; + var columns = settings.aoColumns; - // Invalidate the column types as the new data needs to be revalidated for ( var i=0, iLen=columns.length ; i iTarget ) - { - a[i]--; - } - } - - if ( iTargetIndex != -1 && splice === undefined ) - { - a.splice( iTargetIndex, 1 ); - } - } - - /** * Mark cached data as invalid such that a re-read of the data will occur when * the cached data is next requested. Also update from the data source object. @@ -2986,16 +2809,11 @@ { var row = settings.aoData[ rowIdx ]; var i, ien; - var cellWrite = function ( cell, col ) { - // This is very frustrating, but in IE if you just write directly - // to innerHTML, and elements that are overwritten are GC'ed, - // even if there is a reference to them elsewhere - while ( cell.childNodes.length ) { - cell.removeChild( cell.firstChild ); - } - cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' ); - }; + // Remove the cached data for the row + row._aSortData = null; + row._aFilterData = null; + row.displayData = null; // Are we reading last data from DOM or the data object? if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) { @@ -3008,33 +2826,34 @@ else { // Reading from data object, update the DOM var cells = row.anCells; + var display = _fnGetRowDisplay(settings, rowIdx); if ( cells ) { if ( colIdx !== undefined ) { - cellWrite( cells[colIdx], colIdx ); + _fnWriteCell(cells[colIdx], display[colIdx]); } else { for ( i=0, ien=cells.length ; i').appendTo( thead ); + var classes = settings.oClasses; + var columns = settings.aoColumns; + var i, ien, row; + var target = side === 'header' + ? settings.nTHead + : settings.nTFoot; + var titleProp = side === 'header' ? 'sTitle' : side; + + // Footer might be defined + if (! target) { + return; } - for ( i=0, ien=columns.length ; i') + .appendTo( target ); - if ( createHeader ) { - cell.appendTo( row ); + for ( i=0, ien=columns.length ; i') + .html( columns[i][titleProp] || '' ) + .appendTo( row ); } + } - // 1.11 move into sorting - if ( oSettings.oFeatures.bSort ) { - cell.addClass( column.sSortingClass ); + var detected = _fnDetectHeader( settings, target, true ); - if ( column.bSortable !== false ) { - cell - .attr( 'tabindex', oSettings.iTabIndex ) - .attr( 'aria-controls', oSettings.sTableId ); + if (side === 'header') { + settings.aoHeader = detected; + } + else { + settings.aoFooter = detected; + } - _fnSortAttachListener( oSettings, column.nTh, i ); - } - } + // ARIA role for the rows + $(target).children('tr').attr('role', 'row'); - if ( column.sTitle != cell[0].innerHTML ) { - cell.html( column.sTitle ); - } + // Every cell needs to be passed through the renderer + $(target).children('tr').children('th, td') + .each( function () { + _fnRenderer( settings, side )( + settings, $(this), classes + ); + } ); + } - _fnRenderer( oSettings, 'header' )( - oSettings, cell, column, classes - ); + /** + * Build a layout structure for a header or footer + * + * @param {*} settings DataTables settings + * @param {*} source Source layout array + * @param {*} incColumns What columns should be included + * @returns Layout array + */ + function _fnHeaderLayout( settings, source, incColumns ) + { + var row, column, cell; + var local = []; + var structure = []; + var columns = settings.aoColumns; + var columnCount = columns.length; + var rowspan, colspan; + + if ( ! source ) { + return; } - if ( createHeader ) { - _fnDetectHeader( oSettings.aoHeader, thead ); + // Default is to work on only visible columns + if ( ! incColumns ) { + incColumns = _range(columnCount) + .filter(function (idx) { + return columns[idx].bVisible; + }); } - /* Deal with the footer - add classes if required */ - $(thead).children('tr').children('th, td').addClass( classes.sHeaderTH ); - $(tfoot).children('tr').children('th, td').addClass( classes.sFooterTH ); + // Make a copy of the master layout array, but with only the columns we want + for ( row=0 ; row