diff --git a/dataprovider/admin.go b/dataprovider/admin.go
index b82aebb9..b5998982 100644
--- a/dataprovider/admin.go
+++ b/dataprovider/admin.go
@@ -356,6 +356,14 @@ func (a *Admin) GetPermissionsAsString() string {
return strings.Join(a.Permissions, ", ")
}
+// GetLastLoginAsString returns the last login as string
+func (a *Admin) GetLastLoginAsString() string {
+ if a.LastLogin > 0 {
+ return util.GetTimeFromMsecSinceEpoch(a.LastLogin).UTC().Format(iso8601UTCFormat)
+ }
+ return ""
+}
+
// GetAllowedIPAsString returns the allowed IP as comma separated string
func (a *Admin) GetAllowedIPAsString() string {
return strings.Join(a.Filters.AllowList, ",")
@@ -366,18 +374,6 @@ func (a *Admin) GetValidPerms() []string {
return validAdminPerms
}
-// GetInfoString returns admin's info as string.
-func (a *Admin) GetInfoString() string {
- var result strings.Builder
- if a.Email != "" {
- result.WriteString(fmt.Sprintf("Email: %v. ", a.Email))
- }
- if len(a.Filters.AllowList) > 0 {
- result.WriteString(fmt.Sprintf("Allowed IP/Mask: %v. ", len(a.Filters.AllowList)))
- }
- return result.String()
-}
-
// CanManageMFA returns true if the admin can add a multi-factor authentication configuration
func (a *Admin) CanManageMFA() bool {
return len(mfa.GetAvailableTOTPConfigs()) > 0
diff --git a/dataprovider/dataprovider.go b/dataprovider/dataprovider.go
index 99299a64..46f73dbc 100644
--- a/dataprovider/dataprovider.go
+++ b/dataprovider/dataprovider.go
@@ -90,6 +90,7 @@ const (
operationDelete = "delete"
sqlPrefixValidChars = "abcdefghijklmnopqrstuvwxyz_0123456789"
maxHookResponseSize = 1048576 // 1MB
+ iso8601UTCFormat = "2006-01-02 15:04:05Z"
)
// Supported algorithms for hashing passwords.
diff --git a/dataprovider/user.go b/dataprovider/user.go
index 88b55c4f..505a1392 100644
--- a/dataprovider/user.go
+++ b/dataprovider/user.go
@@ -1284,11 +1284,6 @@ func (u *User) GetQuotaSummary() string {
sb.WriteString(fmt.Sprintf("DL: %v/%v", util.ByteCountIEC(u.UsedDownloadDataTransfer),
util.ByteCountIEC(u.DownloadDataTransfer*1048576)))
}
- if u.LastQuotaUpdate > 0 {
- addSection()
- t := util.GetTimeFromMsecSinceEpoch(u.LastQuotaUpdate)
- sb.WriteString(fmt.Sprintf("Last update: %v", t.Format("2006-01-02 15:04"))) // YYYY-MM-DD HH:MM
- }
return sb.String()
}
@@ -1316,28 +1311,83 @@ func (u *User) GetPermissionsAsString() string {
// GetBandwidthAsString returns bandwidth limits if defines
func (u *User) GetBandwidthAsString() string {
- result := "DL: "
+ var sb strings.Builder
+ sb.WriteString("DL: ")
if u.DownloadBandwidth > 0 {
- result += util.ByteCountIEC(u.DownloadBandwidth*1000) + "/s."
+ sb.WriteString(util.ByteCountIEC(u.DownloadBandwidth*1000) + "/s.")
} else {
- result += "unlimited."
+ sb.WriteString("unlimited.")
}
- result += " UL: "
+ sb.WriteString(" UL: ")
if u.UploadBandwidth > 0 {
- result += util.ByteCountIEC(u.UploadBandwidth*1000) + "/s."
+ sb.WriteString(util.ByteCountIEC(u.UploadBandwidth*1000) + "/s.")
} else {
- result += "unlimited."
+ sb.WriteString("unlimited.")
}
- return result
+ return sb.String()
}
// GetMFAStatusAsString returns MFA status
func (u *User) GetMFAStatusAsString() string {
- result := "-"
if u.Filters.TOTPConfig.Enabled {
- result = strings.Join(u.Filters.TOTPConfig.Protocols, ", ")
+ return strings.Join(u.Filters.TOTPConfig.Protocols, ", ")
}
- return result
+ return "Disabled"
+}
+
+// GetLastLoginAsString returns the last login as string
+func (u *User) GetLastLoginAsString() string {
+ if u.LastLogin > 0 {
+ return util.GetTimeFromMsecSinceEpoch(u.LastLogin).UTC().Format(iso8601UTCFormat)
+ }
+ return ""
+}
+
+// GetLastQuotaUpdateAsString returns the last quota update as string
+func (u *User) GetLastQuotaUpdateAsString() string {
+ if u.LastQuotaUpdate > 0 {
+ return util.GetTimeFromMsecSinceEpoch(u.LastQuotaUpdate).UTC().Format(iso8601UTCFormat)
+ }
+ return ""
+}
+
+// GetStorageDescrition returns the storage description
+func (u *User) GetStorageDescrition() string {
+ switch u.FsConfig.Provider {
+ case sdk.LocalFilesystemProvider:
+ return fmt.Sprintf("Local: %v", u.GetHomeDir())
+ case sdk.S3FilesystemProvider:
+ return fmt.Sprintf("S3: %v", u.FsConfig.S3Config.Bucket)
+ case sdk.GCSFilesystemProvider:
+ return fmt.Sprintf("GCS: %v", u.FsConfig.GCSConfig.Bucket)
+ case sdk.AzureBlobFilesystemProvider:
+ return fmt.Sprintf("AzBlob: %v", u.FsConfig.AzBlobConfig.Container)
+ case sdk.CryptedFilesystemProvider:
+ return fmt.Sprintf("Encrypted: %v", u.GetHomeDir())
+ case sdk.SFTPFilesystemProvider:
+ return fmt.Sprintf("SFTP: %v", u.FsConfig.SFTPConfig.Endpoint)
+ default:
+ return ""
+ }
+}
+
+// GetGroupsAsString returns the user's groups as a string
+func (u *User) GetGroupsAsString() string {
+ if len(u.Groups) == 0 {
+ return ""
+ }
+ var groups []string
+ for _, g := range u.Groups {
+ if g.Type == sdk.GroupTypePrimary {
+ groups = append(groups, "")
+ copy(groups[1:], groups)
+ groups[0] = g.Name
+ } else {
+ groups = append(groups, g.Name)
+ }
+ }
+
+ return strings.Join(groups, ",")
}
// GetInfoString returns user's info as string.
@@ -1345,13 +1395,6 @@ func (u *User) GetMFAStatusAsString() string {
// gid, denied and allowed IP/Mask are returned
func (u *User) GetInfoString() string {
var result strings.Builder
- if u.LastLogin > 0 {
- t := util.GetTimeFromMsecSinceEpoch(u.LastLogin)
- result.WriteString(fmt.Sprintf("Last login: %v. ", t.Format("2006-01-02 15:04"))) // YYYY-MM-DD HH:MM
- }
- if u.FsConfig.Provider != sdk.LocalFilesystemProvider {
- result.WriteString(fmt.Sprintf("Storage: %s. ", u.FsConfig.Provider.ShortInfo()))
- }
if len(u.PublicKeys) > 0 {
result.WriteString(fmt.Sprintf("Public keys: %v. ", len(u.PublicKeys)))
}
@@ -1364,6 +1407,12 @@ func (u *User) GetInfoString() string {
if u.GID > 0 {
result.WriteString(fmt.Sprintf("GID: %v. ", u.GID))
}
+ if len(u.Filters.DeniedLoginMethods) > 0 {
+ result.WriteString(fmt.Sprintf("Denied login methods: %v. ", strings.Join(u.Filters.DeniedLoginMethods, ",")))
+ }
+ if len(u.Filters.DeniedProtocols) > 0 {
+ result.WriteString(fmt.Sprintf("Denied protocols: %v. ", strings.Join(u.Filters.DeniedProtocols, ",")))
+ }
if len(u.Filters.DeniedIP) > 0 {
result.WriteString(fmt.Sprintf("Denied IP/Mask: %v. ", len(u.Filters.DeniedIP)))
}
diff --git a/httpd/webadmin.go b/httpd/webadmin.go
index b10de826..aac69ffc 100644
--- a/httpd/webadmin.go
+++ b/httpd/webadmin.go
@@ -130,7 +130,6 @@ type basePage struct {
CSRFToken string
HasDefender bool
HasExternalLogin bool
- HasMFA bool
LoggedAdmin *dataprovider.Admin
Branding UIBranding
}
@@ -486,7 +485,6 @@ func (s *httpdServer) getBasePageData(title, currentURL string, r *http.Request)
Version: version.GetAsString(),
LoggedAdmin: getAdminFromToken(r),
HasDefender: common.Config.DefenderConfig.Enabled,
- HasMFA: len(mfa.GetAvailableTOTPConfigs()) > 0,
HasExternalLogin: isLoggedInWithOIDC(r),
CSRFToken: csrfToken,
Branding: s.binding.Branding.WebAdmin,
diff --git a/static/vendor/datatables/buttons.bootstrap4.min.css b/static/vendor/datatables/buttons.bootstrap4.min.css
index 90185c40..6de372c4 100644
--- a/static/vendor/datatables/buttons.bootstrap4.min.css
+++ b/static/vendor/datatables/buttons.bootstrap4.min.css
@@ -1 +1 @@
-@keyframes dtb-spinner{100%{transform:rotate(360deg)}}@-o-keyframes dtb-spinner{100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes dtb-spinner{100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dtb-spinner{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes dtb-spinner{100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}div.dt-button-info{position:fixed;top:50%;left:50%;width:400px;margin-top:-100px;margin-left:-200px;background-color:white;border:2px solid #111;box-shadow:3px 3px 8px rgba(0, 0, 0, 0.3);border-radius:3px;text-align:center;z-index:21}div.dt-button-info h2{padding:.5em;margin:0;font-weight:normal;border-bottom:1px solid #ddd;background-color:#f3f3f3}div.dt-button-info>div{padding:1em}button.dtb-hide-drop{display:none !important}div.dt-button-collection-title{text-align:center;padding:.3em 0 .5em;font-size:.9em}div.dt-button-collection-title:empty{display:none}div.dt-button-collection{position:absolute;z-index:2001;width:100%}div.dt-button-collection div.dropdown-menu{display:block;z-index:2002;min-width:100%}div.dt-button-collection div.dt-button-collection-title{background-color:white;border:1px solid rgba(0, 0, 0, 0.15)}div.dt-button-collection.fixed{position:fixed;top:50%;left:50%;margin-left:-75px;border-radius:0}div.dt-button-collection.fixed.two-column{margin-left:-200px}div.dt-button-collection.fixed.three-column{margin-left:-225px}div.dt-button-collection.fixed.four-column{margin-left:-300px}div.dt-button-collection>:last-child{display:block !important;-webkit-column-gap:8px;-moz-column-gap:8px;-ms-column-gap:8px;-o-column-gap:8px;column-gap:8px}div.dt-button-collection>:last-child>*{-webkit-column-break-inside:avoid;break-inside:avoid}div.dt-button-collection.two-column{width:400px}div.dt-button-collection.two-column>:last-child{padding-bottom:1px;-webkit-column-count:2;-moz-column-count:2;-ms-column-count:2;-o-column-count:2;column-count:2}div.dt-button-collection.three-column{width:450px}div.dt-button-collection.three-column>:last-child{padding-bottom:1px;-webkit-column-count:3;-moz-column-count:3;-ms-column-count:3;-o-column-count:3;column-count:3}div.dt-button-collection.four-column{width:600px}div.dt-button-collection.four-column>:last-child{padding-bottom:1px;-webkit-column-count:4;-moz-column-count:4;-ms-column-count:4;-o-column-count:4;column-count:4}div.dt-button-collection .dt-button{border-radius:0}div.dt-button-collection.fixed{max-width:none}div.dt-button-collection.fixed:before,div.dt-button-collection.fixed:after{display:none}div.dt-button-collection div.dt-btn-split-wrapper{width:100%;padding-left:5px;padding-right:5px}div.dt-button-collection button.dt-btn-split-drop-button{width:100%;color:#212529;border:none;background-color:white;border-radius:0px;margin-left:0px !important}div.dt-button-collection button.dt-btn-split-drop-button:focus{border:none;border-radius:0px;outline:none}div.dt-button-collection button.dt-btn-split-drop-button:hover{background-color:#e9ecef}div.dt-button-collection button.dt-btn-split-drop-button:active{background-color:#007bff !important}div.dt-button-background{position:fixed;top:0;left:0;width:100%;height:100%;z-index:999}@media screen and (max-width: 767px){div.dt-buttons{float:none;width:100%;text-align:center;margin-bottom:.5em}div.dt-buttons a.btn{float:none}}div.dt-buttons button.btn.processing,div.dt-buttons div.btn.processing,div.dt-buttons a.btn.processing{color:rgba(0, 0, 0, 0.2)}div.dt-buttons button.btn.processing:after,div.dt-buttons div.btn.processing:after,div.dt-buttons a.btn.processing:after{position:absolute;top:50%;left:50%;width:16px;height:16px;margin:-8px 0 0 -8px;box-sizing:border-box;display:block;content:" ";border:2px solid #282828;border-radius:50%;border-left-color:transparent;border-right-color:transparent;animation:dtb-spinner 1500ms infinite linear;-o-animation:dtb-spinner 1500ms infinite linear;-ms-animation:dtb-spinner 1500ms infinite linear;-webkit-animation:dtb-spinner 1500ms infinite linear;-moz-animation:dtb-spinner 1500ms infinite linear}div.dt-btn-split-wrapper button.dt-btn-split-drop{border-top-right-radius:.25rem !important;border-bottom-right-radius:.25rem !important}div.dataTables_wrapper div.dt-buttons.btn-group button.btn:last-child:first-of-type{border-radius:4px !important}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group button.btn:last-of-type{border-top-left-radius:0px !important;border-bottom-left-radius:0px !important}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group button.btn:first-of-type{border-top-right-radius:0px !important;border-bottom-right-radius:0px !important}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:last-child{border:1px solid #6c757d;border-top-right-radius:4px !important;border-bottom-right-radius:4px !important}
+@keyframes dtb-spinner{100%{transform:rotate(360deg)}}@-o-keyframes dtb-spinner{100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes dtb-spinner{100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes dtb-spinner{100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes dtb-spinner{100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}div.dataTables_wrapper{position:relative}div.dt-buttons{position:initial}div.dt-button-info{position:fixed;top:50%;left:50%;width:400px;margin-top:-100px;margin-left:-200px;background-color:white;border:2px solid #111;box-shadow:3px 4px 10px 1px rgba(0, 0, 0, 0.3);border-radius:3px;text-align:center;z-index:21}div.dt-button-info h2{padding:.5em;margin:0;font-weight:normal;border-bottom:1px solid #ddd;background-color:#f3f3f3}div.dt-button-info>div{padding:1em}div.dtb-popover-close{position:absolute;top:10px;right:10px;width:22px;height:22px;border:1px solid #eaeaea;background-color:#f9f9f9;text-align:center;border-radius:3px;cursor:pointer;z-index:2003}button.dtb-hide-drop{display:none !important}div.dt-button-collection-title{text-align:center;padding:.3em 0 .5em;margin-left:.5em;margin-right:.5em;font-size:.9em}div.dt-button-collection-title:empty{display:none}span.dt-button-spacer{display:inline-block;margin:.5em;white-space:nowrap}span.dt-button-spacer.bar{border-left:1px solid rgba(0, 0, 0, 0.3);vertical-align:middle;padding-left:.5em}span.dt-button-spacer.bar:empty{height:1em;width:1px;padding-left:0}div.dt-button-collection span.dt-button-spacer{width:100%;font-size:.9em;text-align:center;margin:.5em 0}div.dt-button-collection span.dt-button-spacer:empty{height:0;width:100%}div.dt-button-collection span.dt-button-spacer.bar{border-left:none;border-bottom:1px solid rgba(0, 0, 0, 0.3);padding-left:0}div.dt-button-collection{position:absolute;z-index:2001;background-color:white;border:1px solid rgba(0, 0, 0, 0.15);border-radius:4px;box-shadow:0 6px 12px rgba(0, 0, 0, 0.175);padding:.5rem;width:200px}div.dt-button-collection div.dropdown-menu{position:relative;display:block;z-index:2002;min-width:100%;background-color:transparent;border:none;box-shadow:none;padding:0;border-radius:0}div.dt-button-collection.fixed{position:fixed;display:block;top:50%;left:50%;margin-left:-75px;border-radius:5px;background-color:white}div.dt-button-collection.fixed.two-column{margin-left:-200px}div.dt-button-collection.fixed.three-column{margin-left:-225px}div.dt-button-collection.fixed.four-column{margin-left:-300px}div.dt-button-collection.fixed.columns{margin-left:-409px}@media screen and (max-width: 1024px){div.dt-button-collection.fixed.columns{margin-left:-308px}}@media screen and (max-width: 640px){div.dt-button-collection.fixed.columns{margin-left:-203px}}@media screen and (max-width: 460px){div.dt-button-collection.fixed.columns{margin-left:-100px}}div.dt-button-collection.fixed>:last-child{max-height:100vh;overflow:auto}div.dt-button-collection.two-column>:last-child,div.dt-button-collection.three-column>:last-child,div.dt-button-collection.four-column>:last-child{display:block !important;-webkit-column-gap:8px;-moz-column-gap:8px;-ms-column-gap:8px;-o-column-gap:8px;column-gap:8px}div.dt-button-collection.two-column>:last-child>*,div.dt-button-collection.three-column>:last-child>*,div.dt-button-collection.four-column>:last-child>*{-webkit-column-break-inside:avoid;break-inside:avoid}div.dt-button-collection.two-column{width:400px}div.dt-button-collection.two-column>:last-child{padding-bottom:1px;column-count:2}div.dt-button-collection.three-column{width:450px}div.dt-button-collection.three-column>:last-child{padding-bottom:1px;column-count:3}div.dt-button-collection.four-column{width:600px}div.dt-button-collection.four-column>:last-child{padding-bottom:1px;column-count:4}div.dt-button-collection .dt-button{border-radius:0}div.dt-button-collection.columns{width:auto}div.dt-button-collection.columns>:last-child{display:flex;flex-wrap:wrap;justify-content:flex-start;align-items:center;gap:6px;width:818px;padding-bottom:1px}div.dt-button-collection.columns>:last-child .dt-button{min-width:200px;flex:0 1;margin:0}div.dt-button-collection.columns.dtb-b3>:last-child,div.dt-button-collection.columns.dtb-b2>:last-child,div.dt-button-collection.columns.dtb-b1>:last-child{justify-content:space-between}div.dt-button-collection.columns.dtb-b3 .dt-button{flex:1 1 32%}div.dt-button-collection.columns.dtb-b2 .dt-button{flex:1 1 48%}div.dt-button-collection.columns.dtb-b1 .dt-button{flex:1 1 100%}@media screen and (max-width: 1024px){div.dt-button-collection.columns>:last-child{width:612px}}@media screen and (max-width: 640px){div.dt-button-collection.columns>:last-child{width:406px}div.dt-button-collection.columns.dtb-b3 .dt-button{flex:0 1 32%}}@media screen and (max-width: 460px){div.dt-button-collection.columns>:last-child{width:200px}}div.dt-button-collection.fixed:before,div.dt-button-collection.fixed:after{display:none}div.dt-button-collection .btn-group{flex:1 1 auto}div.dt-button-collection .dt-button{min-width:200px}div.dt-button-collection div.dt-btn-split-wrapper{width:100%;padding-left:5px;padding-right:5px}div.dt-button-collection button.dt-btn-split-drop-button{width:100%;color:#212529;border:none;background-color:white;border-radius:0px;margin-left:0px !important}div.dt-button-collection button.dt-btn-split-drop-button:focus{border:none;border-radius:0px;outline:none}div.dt-button-collection button.dt-btn-split-drop-button:hover{background-color:#e9ecef}div.dt-button-collection button.dt-btn-split-drop-button:active{background-color:#007bff !important}div.dt-button-background{position:fixed;top:0;left:0;width:100%;height:100%;z-index:999}@media screen and (max-width: 767px){div.dt-buttons{float:none;width:100%;text-align:center;margin-bottom:.5em}div.dt-buttons a.btn{float:none}}div.dt-buttons button.btn.processing,div.dt-buttons div.btn.processing,div.dt-buttons a.btn.processing{color:rgba(0, 0, 0, 0.2)}div.dt-buttons button.btn.processing:after,div.dt-buttons div.btn.processing:after,div.dt-buttons a.btn.processing:after{position:absolute;top:50%;left:50%;width:16px;height:16px;margin:-8px 0 0 -8px;box-sizing:border-box;display:block;content:" ";border:2px solid #282828;border-radius:50%;border-left-color:transparent;border-right-color:transparent;animation:dtb-spinner 1500ms infinite linear;-o-animation:dtb-spinner 1500ms infinite linear;-ms-animation:dtb-spinner 1500ms infinite linear;-webkit-animation:dtb-spinner 1500ms infinite linear;-moz-animation:dtb-spinner 1500ms infinite linear}div.dt-buttons div.btn-group{position:initial}div.dt-btn-split-wrapper:active:not(.disabled) button,div.dt-btn-split-wrapper.active:not(.disabled) button{background-color:#5a6268;border-color:#545b62}div.dt-btn-split-wrapper:active:not(.disabled) button.dt-btn-split-drop,div.dt-btn-split-wrapper.active:not(.disabled) button.dt-btn-split-drop{box-shadow:none;background-color:#6c757d;border-color:#6c757d}div.dt-btn-split-wrapper:active:not(.disabled) button:hover,div.dt-btn-split-wrapper.active:not(.disabled) button:hover{background-color:#5a6268;border-color:#545b62}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group{border-radius:4px !important}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:last-child{border-top-left-radius:0px !important;border-bottom-left-radius:0px !important}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:first-child{border-top-right-radius:0px !important;border-bottom-right-radius:0px !important}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group:last-child:first-child{border-top-left-radius:4px !important;border-bottom-left-radius:4px !important;border-top-right-radius:4px !important;border-bottom-right-radius:4px !important}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group button.dt-btn-split-drop:last-child{border:1px solid #6c757d}div.dataTables_wrapper div.dt-buttons.btn-group div.btn-group div.dt-btn-split-wrapper{border:none}div.dt-button-collection div.btn-group{border-radius:4px !important}div.dt-button-collection div.btn-group button{border-radius:4px}div.dt-button-collection div.btn-group button:last-child{border-top-left-radius:0px !important;border-bottom-left-radius:0px !important}div.dt-button-collection div.btn-group button:first-child{border-top-right-radius:0px !important;border-bottom-right-radius:0px !important}div.dt-button-collection div.btn-group button:last-child:first-child{border-top-left-radius:4px !important;border-bottom-left-radius:4px !important;border-top-right-radius:4px !important;border-bottom-right-radius:4px !important}div.dt-button-collection div.btn-group button.dt-btn-split-drop:last-child{border:1px solid #6c757d}div.dt-button-collection div.btn-group div.dt-btn-split-wrapper{border:none}span.dt-button-spacer.bar:empty{height:inherit}div.dt-button-collection span.dt-button-spacer{padding-left:1rem !important;text-align:left}
diff --git a/static/vendor/datatables/buttons.bootstrap4.min.js b/static/vendor/datatables/buttons.bootstrap4.min.js
index 426ea1d1..3d3d41c8 100644
--- a/static/vendor/datatables/buttons.bootstrap4.min.js
+++ b/static/vendor/datatables/buttons.bootstrap4.min.js
@@ -3,5 +3,5 @@
©2016 SpryMedia Ltd - datatables.net/license
*/
(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net-bs4","datatables.net-buttons"],function(a){return c(a,window,document)}):"object"===typeof exports?module.exports=function(a,b){a||(a=window);b&&b.fn.dataTable||(b=require("datatables.net-bs4")(a,b).$);b.fn.dataTable.Buttons||require("datatables.net-buttons")(a,b);return c(b,a,a.document)}:c(jQuery,window,document)})(function(c,a,b,f){a=c.fn.dataTable;c.extend(!0,a.Buttons.defaults,{dom:{container:{className:"dt-buttons btn-group flex-wrap"},
-button:{className:"btn btn-secondary"},collection:{tag:"div",className:"dropdown-menu",button:{tag:"a",className:"dt-button dropdown-item",active:"active",disabled:"disabled"}},splitWrapper:{tag:"div",className:"dt-btn-split-wrapper btn-group"},splitDropdown:{tag:"button",text:"",className:"btn btn-secondary dt-btn-split-drop dropdown-toggle dropdown-toggle-split",align:"split-left",splitAlignClass:"dt-button-split-left"},splitDropdownButton:{tag:"button",className:"dt-btn-split-drop-button btn btn-secondary"}},
-buttonCreated:function(e,d){return e.buttons?c('
').append(d):d}});a.ext.buttons.collection.className+=" dropdown-toggle";a.ext.buttons.collection.rightAlignClassName="dropdown-menu-right";return a.Buttons});
+button:{className:"btn btn-secondary"},collection:{tag:"div",className:"dropdown-menu",closeButton:!1,button:{tag:"a",className:"dt-button dropdown-item",active:"active",disabled:"disabled"}},splitWrapper:{tag:"div",className:"dt-btn-split-wrapper btn-group",closeButton:!1},splitDropdown:{tag:"button",text:"",className:"btn btn-secondary dt-btn-split-drop dropdown-toggle dropdown-toggle-split",closeButton:!1,align:"split-left",splitAlignClass:"dt-button-split-left"},splitDropdownButton:{tag:"button",
+className:"dt-btn-split-drop-button btn btn-secondary",closeButton:!1}},buttonCreated:function(e,d){return e.buttons?c('').append(d):d}});a.ext.buttons.collection.className+=" dropdown-toggle";a.ext.buttons.collection.rightAlignClassName="dropdown-menu-right";return a.Buttons});
diff --git a/static/vendor/datatables/buttons.colVis.min.js b/static/vendor/datatables/buttons.colVis.min.js
new file mode 100644
index 00000000..1d2b9a39
--- /dev/null
+++ b/static/vendor/datatables/buttons.colVis.min.js
@@ -0,0 +1,10 @@
+/*!
+ Column visibility buttons for Buttons and DataTables.
+ 2016 SpryMedia Ltd - datatables.net/license
+*/
+(function(h){"function"===typeof define&&define.amd?define(["jquery","datatables.net","datatables.net-buttons"],function(e){return h(e,window,document)}):"object"===typeof exports?module.exports=function(e,g){e||(e=window);g&&g.fn.dataTable||(g=require("datatables.net")(e,g).$);g.fn.dataTable.Buttons||require("datatables.net-buttons")(e,g);return h(g,e,e.document)}:h(jQuery,window,document)})(function(h,e,g,l){e=h.fn.dataTable;h.extend(e.ext.buttons,{colvis:function(b,a){var c=null,d={extend:"collection",
+init:function(f,k){c=k},text:function(f){return f.i18n("buttons.colvis","Column visibility")},className:"buttons-colvis",closeButton:!1,buttons:[{extend:"columnsToggle",columns:a.columns,columnText:a.columnText}]};b.on("column-reorder.dt"+a.namespace,function(f,k,m){b.button(null,b.button(null,c).node()).collectionRebuild([{extend:"columnsToggle",columns:a.columns,columnText:a.columnText}])});return d},columnsToggle:function(b,a){return b.columns(a.columns).indexes().map(function(c){return{extend:"columnToggle",
+columns:c,columnText:a.columnText}}).toArray()},columnToggle:function(b,a){return{extend:"columnVisibility",columns:a.columns,columnText:a.columnText}},columnsVisibility:function(b,a){return b.columns(a.columns).indexes().map(function(c){return{extend:"columnVisibility",columns:c,visibility:a.visibility,columnText:a.columnText}}).toArray()},columnVisibility:{columns:l,text:function(b,a,c){return c._columnText(b,c)},className:"buttons-columnVisibility",action:function(b,a,c,d){b=a.columns(d.columns);
+a=b.visible();b.visible(d.visibility!==l?d.visibility:!(a.length&&a[0]))},init:function(b,a,c){var d=this;a.attr("data-cv-idx",c.columns);b.on("column-visibility.dt"+c.namespace,function(f,k){k.bDestroying||k.nTable!=b.settings()[0].nTable||d.active(b.column(c.columns).visible())}).on("column-reorder.dt"+c.namespace,function(f,k,m){c.destroying||1!==b.columns(c.columns).count()||(d.text(c._columnText(b,c)),d.active(b.column(c.columns).visible()))});this.active(b.column(c.columns).visible())},destroy:function(b,
+a,c){b.off("column-visibility.dt"+c.namespace).off("column-reorder.dt"+c.namespace)},_columnText:function(b,a){var c=b.column(a.columns).index(),d=b.settings()[0].aoColumns[c].sTitle;d||(d=b.column(c).header().innerHTML);d=d.replace(/\n/g," ").replace(/
/gi," ").replace(/