diff --git a/CHANGELOG.md b/CHANGELOG.md index 37ace8a79ae..cb4b7543eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Enterprise Fixes: - [data-manager] Fixed editing an event whose key contains `&` creating undeletable duplicate rows in the events table Security Fixes: +- [core] Auth tokens are now granted explicit create/read/update/delete permissions per app and feature, enforced on every request as the intersection of the token's permissions and its owner's. A token can only be granted permissions that the credential creating it already holds. Permission to sign in to the dashboard is a separate property of the token, assigned by the server, instead of being inferred from the token's purpose. Token creation, listing and deletion require a full-permission credential. Tokens created before this change keep working under the previous app and endpoint restrictions - [hooks] Internal event hooks are now scoped to the apps the hook belongs to: app creation is a global-admin-only event, and remote-config, cohort, alert and hook-chaining events are only delivered when the event's app is one the hook is scoped to - [compliance-hub] The consents table now returns a fixed set of fields; a projection supplied on the request is no longer used to widen the response beyond the consent columns - [dashboards] Widgets are no longer copied when the copying user has no access to the apps they reference, and widget app ids are validated on widget create and update diff --git a/api/parts/mgmt/mail.js b/api/parts/mgmt/mail.js index 8f262213183..7a16cf64acf 100644 --- a/api/parts/mgmt/mail.js +++ b/api/parts/mgmt/mail.js @@ -269,6 +269,8 @@ mail.sendTimeBanWarning = function(member, db) { multi: false, owner: member._id, app: "", + //the mailed link logs the member back in, so this token carries login permission + can_login: true, callback: function(err, token) { mail.lookup(function(err2, host) { localize.getProperties(member.lang, function(err3, properties) { diff --git a/api/utils/authorizer.js b/api/utils/authorizer.js index d19ff519a0d..f526459d245 100644 --- a/api/utils/authorizer.js +++ b/api/utils/authorizer.js @@ -19,7 +19,9 @@ const log = require('./log.js')('core:authorizer'); * @param {string} options.token - token to store, if not provided, will be generated * @param {string} options.owner - id of the user who created this token * @param {string} options.app - list of the apps for which token was created -* @param {string} options.endpoint - regexp of endpoint(any string - is used as substring,to mach exact ^{yourpath}$) +* @param {string} options.endpoint - regexp of endpoint(any string - is used as substring,to mach exact ^{yourpath}$). Deprecated as an authorization mechanism, kept for tokens created before the permission model. +* @param {object} [options.token_permission] - CRUD permissions granted to this token, in the same shape as member.permission ({_:{a,u}, c/r/u/d:{appId:{all, allowed}}}). When set, the token authorizes only the intersection of this object and its owner's own permissions. When omitted, the token inherits the owner's full permissions. +* @param {boolean} [options.can_login=false] - if true, the token may be redeemed for a dashboard session at /login/token. SERVER-ASSIGNED ONLY - see the note below. * @param {string} options.tryReuse - if true - tries to find not expired token with same parameters. If not founds cretes new token. If found - updates token expiration time to new one and returns token. * @param {boolean} [options.temporary=false] - If logged in with temporary token. Doesn't kill other sessions on logout. * @param {function} options.callback - function called when saving was completed or errored, providing error object as first param and token string as second @@ -34,6 +36,13 @@ authorizer.save = function(options) { options.purpose = options.purpose || ""; options.temporary = options.temporary || false; //If logged in with temporary token. Doesn't kill other sessions on logout + // Login capability is a property of the token, never of its purpose string. It is set only + // where the server itself establishes or propagates a session (setLoggedInVariables, the + // renderer, the ban-warning mail, OIDC login), and by /i/token/create only when the creating + // credential already holds it. Defaulting to false here means any caller that does not ask + // for it explicitly produces a token that cannot be redeemed at /login/token. + options.can_login = options.can_login === true; + if (options.endpoint !== "" && !Array.isArray(options.endpoint)) { options.endpoint = [options.endpoint]; } @@ -53,8 +62,40 @@ authorizer.save = function(options) { } else if (member) { authorizer.clearExpiredTokens(options); + /** + * Build the token document to store. token_permission is only written when the + * token is actually scoped, so an unscoped token stays absent-means-unrestricted. + * @returns {object} document to insert into auth_tokens + */ + var buildTokenDoc = function() { + var doc = { + _id: options.token, + ttl: options.ttl, + ends: options.ttl + Math.round(Date.now() / 1000), + multi: options.multi, + owner: options.owner, + app: options.app, + endpoint: options.endpoint, + purpose: options.purpose, + temporary: options.temporary, + can_login: options.can_login + }; + if (options.token_permission) { + doc.token_permission = options.token_permission; + } + return doc; + }; if (options.tryReuse === true) { - var rules = {"multi": options.multi, "endpoint": options.endpoint, "app": options.app, "owner": options.owner, "purpose": options.purpose}; + var rules = {"multi": options.multi, "endpoint": options.endpoint, "app": options.app, "owner": options.owner, "purpose": options.purpose, "can_login": options.can_login}; + // Never reuse a token that grants something different from what is being asked + // for: an identical-looking token with a wider (or narrower) permission set is a + // different credential. + if (options.token_permission) { + rules.token_permission = options.token_permission; + } + else { + rules.token_permission = {$exists: false}; + } if (options.purpose === "LoggedInAuth") { //Login token, allow switching from expiring to not expiring(and other way around) //If there is changes to session expiration - this will allow to treat those tokens as same token. @@ -78,17 +119,7 @@ authorizer.save = function(options) { options.callback(err_token, token.value._id); } else { - options.db.collection("auth_tokens").insert({ - _id: options.token, - ttl: options.ttl, - ends: options.ttl + Math.round(Date.now() / 1000), - multi: options.multi, - owner: options.owner, - app: options.app, - endpoint: options.endpoint, - purpose: options.purpose, - temporary: options.temporary - }, function(err1) { + options.db.collection("auth_tokens").insert(buildTokenDoc(), function(err1) { if (typeof options.callback === "function") { options.callback(err1, options.token); } @@ -97,17 +128,7 @@ authorizer.save = function(options) { }); } else { - options.db.collection("auth_tokens").insert({ - _id: options.token, - ttl: options.ttl, - ends: options.ttl + Math.round(Date.now() / 1000), - multi: options.multi, - owner: options.owner, - app: options.app, - endpoint: options.endpoint, - purpose: options.purpose, - temporary: options.temporary - }, function(err1) { + options.db.collection("auth_tokens").insert(buildTokenDoc(), function(err1) { if (typeof options.callback === "function") { options.callback(err1, options.token); } @@ -249,7 +270,13 @@ var verify_token = function(options, return_owner, return_data) { if (Array.isArray(res.endpoint) && res.endpoint.length === 0) { res.endpoint = ""; } - if (res.endpoint && res.endpoint !== "") { + // The endpoint regex is the pre-permission-model scoping mechanism. It only ever + // constrained which paths a token could call, never what the resolved member was + // allowed to do, so it is not an authorization boundary. Tokens issued under the + // permission model carry token_permission instead, which rights.js intersects with + // the owner's own permissions on every request. Legacy tokens (no token_permission) + // keep being matched exactly as before, so existing integrations are unaffected. + if (!res.token_permission && res.endpoint && res.endpoint !== "") { //keep backwards compability if (!Array.isArray(res.endpoint)) { res.endpoint = [res.endpoint]; diff --git a/api/utils/requestProcessor.js b/api/utils/requestProcessor.js index 3287fe1c7dc..da80dcbf212 100644 --- a/api/utils/requestProcessor.js +++ b/api/utils/requestProcessor.js @@ -12,7 +12,7 @@ const Promise = require('bluebird'); const url = require('url'); const common = require('./common.js'); const countlyCommon = require('../lib/countly.common.js'); -const { validateAppAdmin, validateUser, validateRead, validateUserForRead, validateUserForWrite, validateGlobalAdmin, dbUserHasAccessToCollection, validateUpdate, validateDelete, validateCreate, getBaseAppFilter, getAdminApps, getUserAppsForFeaturePermission } = require('./rights.js'); +const { validateAppAdmin, validateUser, validateRead, validateUserForRead, validateUserForWrite, validateGlobalAdmin, dbUserHasAccessToCollection, validateUpdate, validateDelete, validateCreate, getBaseAppFilter, getAdminApps, getUserAppsForFeaturePermission, isPermissionSubset, getPermissionApps, isScopedCredential } = require('./rights.js'); const authorize = require('./authorizer.js'); const taskmanager = require('./taskmanager.js'); const plugins = require('../../plugins/pluginManager.js'); @@ -393,6 +393,8 @@ const processRequest = (params) => { owner: params.member._id, ttl: 300, purpose: "LoginAuthToken", + //the headless renderer authenticates by redeeming this at /login/token + can_login: true, callback: function(err2, token) { if (err2) { common.returnMessage(params, 400, 'Error creating token: ' + err2); @@ -2518,6 +2520,12 @@ const processRequest = (params) => { */ case 'delete': validateUser(() => { + //revoking the owner's other credentials is credential management, not + //something a token narrowed to a subset of the owner's access may do + if (isScopedCredential(params)) { + common.returnMessage(params, 403, "A restricted token cannot delete tokens"); + return; + } if (params.qstring.tokenid) { common.db.collection("auth_tokens").remove({ "_id": params.qstring.tokenid, @@ -2564,7 +2572,90 @@ const processRequest = (params) => { */ case 'create': validateUser(params, () => { - let ttl, multi, endpoint, purpose, apps; + let ttl, multi, endpoint, purpose, apps, tokenPermission; + + // The credential doing the creating, not its owner, is the ceiling for what + // may be granted. params.member is already that ceiling: rights.js bounds the + // member by the authenticating token's permissions before any handler runs, so + // for an api_key it is the full member, and for a scoped token it is exactly + // that token's authority. A token therefore cannot mint a child that reaches + // an app or feature it cannot reach itself, even though their owner can. + const creatorToken = params.token_data; + /** + * Whether a legacy app/endpoint scope value actually restricts anything. + * @param {string|Array} scope - the app or endpoint field of a token + * @returns {boolean} true if the token is restricted by it + */ + const isScopeRestricted = function(scope) { + return !(scope === undefined || scope === null || scope === "" || (Array.isArray(scope) && scope.length === 0)); + }; + // Tokens created before the permission model carry an app/endpoint regex scope + // instead of permissions. That scope is not carried into params.member, so + // there is nothing to bound such a token's grants by - refuse rather than + // issue a child that could be wider than its parent. + const creatorIsLegacyRestricted = !!creatorToken && !creatorToken.token_permission && (isScopeRestricted(creatorToken.app) || isScopeRestricted(creatorToken.endpoint)); + if (creatorIsLegacyRestricted) { + common.returnMessage(params, 403, "A restricted token cannot create tokens"); + return; + } + + if (params.qstring.permission && params.qstring.permission !== "") { + if (typeof params.qstring.permission === "string") { + try { + tokenPermission = JSON.parse(params.qstring.permission); + } + catch (ex) { + common.returnMessage(params, 400, "Invalid permission object"); + return; + } + } + else { + tokenPermission = params.qstring.permission; + } + if (typeof tokenPermission !== "object" || Array.isArray(tokenPermission)) { + common.returnMessage(params, 400, "Invalid permission object"); + return; + } + // The endpoint regex is not an authorization boundary and is ignored for + // permission-scoped tokens. Reject rather than store an inert restriction + // that would read as if it were enforced. + if (params.qstring.endpoint || params.qstring.endpointquery) { + common.returnMessage(params, 400, "Endpoint restrictions cannot be combined with permissions"); + return; + } + if (!isPermissionSubset(tokenPermission, params.member)) { + common.returnMessage(params, 403, "Token permissions must be a subset of the creating credential's permissions"); + return; + } + } + + // A scoped credential has to say what it is granting. A token with no + // token_permission is bounded only by its owner, so a child created without + // one would reach everything the owner can - wider than the parent that + // created it. The subset check above cannot catch this, because there is no + // permission object to compare; the omission itself is the escalation. + if (!tokenPermission && creatorToken && creatorToken.token_permission) { + common.returnMessage(params, 403, "A scoped token must state the permissions it grants"); + return; + } + + // Login is a capability of the token, never of its purpose string, and it is + // only ever passed on by a credential that holds it, to a child that is not + // narrowed. That makes a scoped token unable to produce a session, which is + // what the purpose allowlist used to (and could not) guarantee. + const requestedLogin = params.qstring.can_login === true || params.qstring.can_login === "true" || params.qstring.can_login === "1"; + let canLogin = false; + if (requestedLogin) { + //a token created before this model that carries no restriction at all is a + //full-permission credential, and keeps the login authority it has today + const creatorHasLogin = !creatorToken || creatorToken.can_login === true || (typeof creatorToken.can_login === "undefined" && !creatorToken.token_permission); + if (!creatorHasLogin || tokenPermission) { + common.returnMessage(params, 403, "The creating credential cannot grant login permission"); + return; + } + canLogin = true; + } + if (params.qstring.ttl) { ttl = parseInt(params.qstring.ttl); } @@ -2579,6 +2670,11 @@ const processRequest = (params) => { if (params.qstring.apps) { apps = params.qstring.apps.split(','); } + //keep the app list in step with the granted permissions, so the token list + //shows what the token reaches and the app check stays a useful early filter + if (tokenPermission && !params.qstring.apps) { + apps = getPermissionApps(tokenPermission); + } if (params.qstring.endpointquery && params.qstring.endpointquery !== "") { try { @@ -2608,6 +2704,8 @@ const processRequest = (params) => { app: apps, endpoint: endpoint, purpose: purpose, + token_permission: tokenPermission, + can_login: canLogin, callback: (err, token) => { if (err) { common.returnMessage(params, 404, err); @@ -2697,6 +2795,14 @@ const processRequest = (params) => { */ case 'list': validateUser(params, function() { + // Every document here includes its _id, which is the token itself. Listing + // them from a scoped token would hand it the owner's other credentials - + // including the unrestricted session token - and let it act as any of them, + // which is the escalation the permission model exists to prevent. + if (isScopedCredential(params)) { + common.returnMessage(params, 403, "A restricted token cannot list tokens"); + return; + } common.db.collection("auth_tokens").find({"owner": params.member._id + ""}).toArray(function(err, res) { if (err) { common.returnMessage(params, 404, err.message); diff --git a/api/utils/rights.js b/api/utils/rights.js index d1fe6e02896..d2251bf7b1a 100644 --- a/api/utils/rights.js +++ b/api/utils/rights.js @@ -31,10 +31,14 @@ function validate_token_if_exists(params) { qstring: params.qstring, token: token, req_path: params.fullPath, + //the whole token document is needed, not just the owner, so the token's own + //permissions can bound what the resolved member is allowed to do + return_data: true, callback: function(valid) { - //false or owner.id + //false or the token document if (valid) { - resolve(valid); + params.token_data = valid; + resolve(valid.owner); } else { resolve('token-invalid'); @@ -48,6 +52,28 @@ function validate_token_if_exists(params) { } }); } + +/** +* Bound a member by the permissions of the token used to authenticate as them. +* +* Authenticating with a token resolves to the token's owner, and every validator then decides +* what to allow from that member. Without this step the member is loaded at full strength, so a +* token deliberately scoped to one app authorizes everything its owner can do - the escalation +* this model exists to prevent. Applied as soon as the member is loaded, so that the validators' +* own permission checks already see the bounded member. +* +* A token with no token_permission (an api_key request, a dashboard session token, or a token +* created before this model) is returned unchanged, so existing integrations are unaffected. +* @param {params} params - {@link params} object, carrying token_data when a token was used +* @param {object} member - member document loaded for the token owner +* @returns {object} the member, bounded by the token's permissions when the token is scoped +*/ +function applyTokenScope(params, member) { + if (params.token_data && params.token_data.token_permission) { + return exports.intersectPermission(member, params.token_data.token_permission); + } + return member; +} /** * Validate user for read access by api_key for provided app_id (both required parameters for the request). * User must exist, must not be locked, must pass plugin validation (if any) and have at least user access to the provided app (which also must exist). @@ -87,6 +113,9 @@ exports.validateUserForRead = function(params, callback, callbackParam) { return false; } + //bound the member by the token used to authenticate, before anything is authorized + member = applyTokenScope(params, member); + if (typeof params.qstring.app_id === "undefined") { common.returnMessage(params, 401, 'No app_id provided'); reject('No app_id provided'); @@ -183,6 +212,9 @@ exports.validateUserForWrite = function(params, callback, callbackParam) { return false; } + //bound the member by the token used to authenticate, before anything is authorized + member = applyTokenScope(params, member); + if (!(module.exports.hasAdminAccess(member, params.qstring.app_id))) { common.returnMessage(params, 401, 'User does not have right'); reject('User does not have right'); @@ -272,6 +304,9 @@ exports.validateGlobalAdmin = function(params, callback, callbackParam) { return false; } + //bound the member by the token used to authenticate, before anything is authorized + member = applyTokenScope(params, member); + if (!member.global_admin) { common.returnMessage(params, 401, 'User does not have right'); reject('User does not have right'); @@ -343,6 +378,9 @@ exports.validateAppAdmin = function(params, callback, callbackParam) { return false; } + //bound the member by the token used to authenticate, before anything is authorized + member = applyTokenScope(params, member); + if (!params.qstring.app_id) { common.returnMessage(params, 400, 'No app id provided'); return false; @@ -428,6 +466,9 @@ exports.validateUser = function(params, callback, callbackParam) { return false; } + //bound the member by the token used to authenticate, before anything is authorized + member = applyTokenScope(params, member); + if (member && member.locked) { common.returnMessage(params, 401, 'User is locked'); reject('User is locked'); @@ -826,6 +867,9 @@ exports.validateRead = function(params, feature, callback, callbackParam) { return false; } + //bound the member by the token used to authenticate, before anything is authorized + member = applyTokenScope(params, member); + if (!member.global_admin && typeof params.qstring.app_id === "undefined") { common.returnMessage(params, 401, 'No app_id provided'); reject('No app_id provided'); @@ -980,6 +1024,9 @@ function validateWrite(params, feature, accessType, callback, callbackParam) { return false; } + //bound the member by the token used to authenticate, before anything is authorized + member = applyTokenScope(params, member); + if (!member.global_admin && /*appIdExceptions.indexOf(feature) === -1 && */ typeof params.qstring.app_id === "undefined") { common.returnMessage(params, 401, 'No app_id provided'); reject('No app_id provided'); @@ -1226,6 +1273,374 @@ exports.hasDeleteRight = function(feature, app_id, member) { return hasAppSpecificRight || hasGlobalAdminRight || hasAppAdminRight; }; +/** +* Access types in a permission object, in their canonical order. +*/ +var PERMISSION_TYPES = ["c", "r", "u", "d"]; + +/** +* Whether a principal grants a single feature on an app for one access type. +* +* A principal is anything holding authority: a member (with global_admin / permission, or the +* legacy admin_of / user_of arrays), or a bare token permission set wrapped as {permission: obj}. +* This is the same rule the hasCreateRight / hasReadRight / hasUpdateRight / hasDeleteRight +* helpers apply to a member, expressed once so it can also be applied to a token. +* @param {object} principal - object with permission (and optionally global_admin) +* @param {string} type - access type (c, r, u, d) +* @param {string} appId - id of the app +* @param {string} feature - feature name +* @returns {boolean} true if the principal allows that feature +*/ +function principalAllows(principal, type, appId, feature) { + if (!principal) { + return false; + } + if (principal.global_admin) { + return true; + } + var permission = principal.permission; + if (typeof permission === "undefined") { + //legacy member: admin_of grants everything on the app, user_of grants reads + if (Array.isArray(principal.admin_of) && principal.admin_of.indexOf(appId) !== -1) { + return true; + } + return type === "r" && Array.isArray(principal.user_of) && principal.user_of.indexOf(appId) !== -1; + } + if (permission._ && Array.isArray(permission._.a) && permission._.a.indexOf(appId) !== -1) { + return true; + } + var forType = permission[type]; + if (!forType || !forType[appId]) { + return false; + } + if (forType[appId].all === true) { + return true; + } + return !!(forType[appId].allowed && forType[appId].allowed[feature] === true); +} + +/** +* Whether a principal grants every feature on an app for one access type. +* +* Distinct from principalAllows because "all" also covers features that do not exist yet, so it +* may only be granted by a principal that itself holds "all". +* @param {object} principal - object with permission (and optionally global_admin) +* @param {string} type - access type (c, r, u, d) +* @param {string} appId - id of the app +* @returns {boolean} true if the principal allows everything for that app and type +*/ +function principalAllowsAll(principal, type, appId) { + if (!principal) { + return false; + } + if (principal.global_admin) { + return true; + } + var permission = principal.permission; + if (typeof permission === "undefined") { + if (Array.isArray(principal.admin_of) && principal.admin_of.indexOf(appId) !== -1) { + return true; + } + return type === "r" && Array.isArray(principal.user_of) && principal.user_of.indexOf(appId) !== -1; + } + if (permission._ && Array.isArray(permission._.a) && permission._.a.indexOf(appId) !== -1) { + return true; + } + var forType = permission[type]; + return !!(forType && forType[appId] && forType[appId].all === true); +} + +/** +* Every app id a principal refers to, whether through the _ grouping or a c/r/u/d entry. +* @param {object} principal - object with permission (and optionally the legacy arrays) +* @returns {string[]} list of app ids +*/ +function principalApps(principal) { + var apps = []; + /** + * Add an app id once. + * @param {string} appId - id of the app + * @returns {void} + */ + var push = function(appId) { + if (apps.indexOf(appId) === -1) { + apps.push(appId); + } + }; + if (!principal) { + return apps; + } + var permission = principal.permission; + if (typeof permission === "undefined") { + (principal.admin_of || []).forEach(push); + (principal.user_of || []).forEach(push); + return apps; + } + if (permission._) { + if (Array.isArray(permission._.a)) { + permission._.a.forEach(push); + } + if (Array.isArray(permission._.u)) { + for (var g = 0; g < permission._.u.length; g++) { + (permission._.u[g] || []).forEach(push); + } + } + } + for (var t = 0; t < PERMISSION_TYPES.length; t++) { + var forType = permission[PERMISSION_TYPES[t]]; + for (var appId in forType || {}) { + push(appId); + } + } + return apps; +} + +/** +* App ids a permission object actually grants something on. +* +* Distinct from principalApps: the permission editor emits an entry for every app the editing user +* can see, most of them granting nothing, and an empty entry is not a grant. Apps listed under _.u +* do count, because some validators authorize on app membership alone. +* @param {object} permission - permission object +* @returns {string[]} list of app ids the permission grants something on +*/ +function grantingApps(permission) { + var apps = []; + /** + * Add an app id once. + * @param {string} appId - id of the app + * @returns {void} + */ + var push = function(appId) { + if (apps.indexOf(appId) === -1) { + apps.push(appId); + } + }; + if (!permission) { + return apps; + } + if (permission._) { + if (Array.isArray(permission._.a)) { + permission._.a.forEach(push); + } + if (Array.isArray(permission._.u)) { + for (var g = 0; g < permission._.u.length; g++) { + (permission._.u[g] || []).forEach(push); + } + } + } + for (var t = 0; t < PERMISSION_TYPES.length; t++) { + var forType = permission[PERMISSION_TYPES[t]] || {}; + for (var appId in forType) { + var entry = forType[appId]; + if (!entry) { + continue; + } + if (entry.all === true) { + push(appId); + continue; + } + for (var feature in entry.allowed || {}) { + if (entry.allowed[feature] === true) { + push(appId); + break; + } + } + } + } + return apps; +} + +/** +* Feature names a principal explicitly allows for an app and access type. +* @param {object} principal - object with permission +* @param {string} type - access type (c, r, u, d) +* @param {string} appId - id of the app +* @returns {string[]} list of feature names +*/ +function principalFeatures(principal, type, appId) { + var features = []; + var permission = principal && principal.permission; + var entry = permission && permission[type] && permission[type][appId]; + if (entry && entry.allowed) { + for (var feature in entry.allowed) { + if (entry.allowed[feature] === true) { + features.push(feature); + } + } + } + return features; +} + +/** +* Whether one permission set grants nothing beyond what a ceiling principal already holds. +* +* This is what bounds a grant to the credential that creates it: a token may only pass on +* authority it holds itself. The ceiling is the member for an api_key or an unrestricted session, +* and the parent token's own permissions when a token creates a token - so a token scoped to app A +* cannot produce a child that reaches app B, even though their common owner can. +* @param {object} childPermission - permission object being granted +* @param {object} ceiling - principal that must already hold everything the child grants +* @returns {boolean} true if childPermission is a subset of the ceiling's authority +*/ +exports.isPermissionSubset = function(childPermission, ceiling) { + //a malformed permission grants nothing recognisable, and an empty-looking value must not be + //mistaken for "grants nothing, therefore a subset" + if (!childPermission || typeof childPermission !== "object" || Array.isArray(childPermission)) { + return false; + } + var t, appId, i; + //an app the child administers implies every feature of every type, present and future + var childAdminApps = (childPermission._ && Array.isArray(childPermission._.a)) ? childPermission._.a : []; + for (i = 0; i < childAdminApps.length; i++) { + for (t = 0; t < PERMISSION_TYPES.length; t++) { + if (!principalAllowsAll(ceiling, PERMISSION_TYPES[t], childAdminApps[i])) { + return false; + } + } + } + //an app the child can see at all must be an app the ceiling can see, since some validators + //authorize on app membership alone + var ceilingApps = principalApps(ceiling); + var childApps = grantingApps(childPermission); + for (i = 0; i < childApps.length; i++) { + if (!ceiling.global_admin && ceilingApps.indexOf(childApps[i]) === -1) { + return false; + } + } + for (t = 0; t < PERMISSION_TYPES.length; t++) { + var forType = childPermission[PERMISSION_TYPES[t]]; + for (appId in forType || {}) { + var entry = forType[appId]; + if (!entry) { + continue; + } + if (entry.all === true && !principalAllowsAll(ceiling, PERMISSION_TYPES[t], appId)) { + return false; + } + for (var feature in entry.allowed || {}) { + if (entry.allowed[feature] === true && !principalAllows(ceiling, PERMISSION_TYPES[t], appId, feature)) { + return false; + } + } + } + } + return true; +}; + +/** +* Whether the credential that authenticated this request is narrower than its owner. +* +* True for a token carrying token_permission, and for a token restricted by the legacy +* app/endpoint scope. False for an api_key (the member itself) and for an unrestricted token such +* as a dashboard session token. Used to keep credential management - which hands out and revokes +* the owner's other credentials - to credentials that are not themselves narrowed. +* @param {params} params - {@link params} object +* @returns {boolean} true if a scoped credential authenticated the request +*/ +exports.isScopedCredential = function(params) { + var token = params && params.token_data; + if (!token) { + return false; + } + if (token.token_permission) { + return true; + } + /** + * Whether a legacy scope value restricts anything. + * @param {string|Array} scope - the app or endpoint field of a token + * @returns {boolean} true if restricted + */ + var isScopeRestricted = function(scope) { + return !(scope === undefined || scope === null || scope === "" || (Array.isArray(scope) && scope.length === 0)); + }; + return isScopeRestricted(token.app) || isScopeRestricted(token.endpoint); +}; + +/** +* Every app id referenced by a permission object. +* @param {object} permission - permission object ({_:{a,u}, c/r/u/d:{appId:...}}) +* @returns {string[]} list of app ids +*/ +exports.getPermissionApps = function(permission) { + return principalApps({permission: permission}); +}; + +/** +* Bound a member by a token's permissions, returning a member that holds only what both allow. +* +* The subset check at creation time bounds a token to its creator, but the owner's own +* permissions can be reduced afterwards, so the intersection is recomputed on every request. +* The returned member never carries global_admin: that is precisely the authority a scoped token +* was narrowed away from, and leaving it set would let every global_admin check bypass the scope. +* @param {object} member - member document for the token owner +* @param {object} tokenPermission - permission object stored on the token +* @returns {object} a copy of the member holding only the intersection +*/ +exports.intersectPermission = function(member, tokenPermission) { + var scoped = Object.assign({}, member); + scoped.global_admin = false; + //the legacy arrays are an alternative expression of authority, so they cannot be carried over + delete scoped.admin_of; + delete scoped.user_of; + + var result = {_: {a: [], u: [[]]}, c: {}, r: {}, u: {}, d: {}}; + var tokenPrincipal = {permission: tokenPermission}; + var userApps = []; + var memberApps = principalApps(member); + var tokenAdminApps = (tokenPermission._ && Array.isArray(tokenPermission._.a)) ? tokenPermission._.a : []; + var tokenUserApps = []; + if (tokenPermission._ && Array.isArray(tokenPermission._.u)) { + for (var g = 0; g < tokenPermission._.u.length; g++) { + tokenUserApps = tokenUserApps.concat(tokenPermission._.u[g] || []); + } + } + + grantingApps(tokenPermission).forEach(function(appId) { + //an app the owner can no longer reach grants the token nothing, whatever the token says + if (!member.global_admin && memberApps.indexOf(appId) === -1) { + return; + } + var grantsAnything = false; + for (var t = 0; t < PERMISSION_TYPES.length; t++) { + var type = PERMISSION_TYPES[t]; + var tokenAll = principalAllowsAll(tokenPrincipal, type, appId); + if (tokenAll && principalAllowsAll(member, type, appId)) { + result[type][appId] = {all: true, allowed: {}}; + grantsAnything = true; + continue; + } + //whichever side is not "all" has a finite feature list, and that is what to walk + var candidates = tokenAll ? principalFeatures(member, type, appId) : principalFeatures(tokenPrincipal, type, appId); + var allowed = {}; + var any = false; + candidates.forEach(function(feature) { + if (principalAllows(tokenPrincipal, type, appId, feature) && principalAllows(member, type, appId, feature)) { + allowed[feature] = true; + any = true; + } + }); + if (any) { + result[type][appId] = {all: false, allowed: allowed}; + grantsAnything = true; + } + } + var isAdmin = tokenAdminApps.indexOf(appId) !== -1 && exports.hasAdminAccess(member, appId); + if (isAdmin) { + result._.a.push(appId); + } + else if (grantsAnything || tokenUserApps.indexOf(appId) !== -1) { + //app membership alone is what some validators check, so an app the token grants + //anything on - or names as a user app - stays visible + userApps.push(appId); + } + }); + + result._.u = [userApps]; + scoped.permission = result; + return scoped; +}; + exports.getUserApps = function(member) { let userApps = []; if (member.global_admin) { diff --git a/frontend/express/libs/members.js b/frontend/express/libs/members.js index 37cff1f3e4b..1cf92674191 100644 --- a/frontend/express/libs/members.js +++ b/frontend/express/libs/members.js @@ -225,6 +225,9 @@ function setLoggedInVariables(req, member, countlyDb, callback) { tryReuse: reuse, ttl: getSessionTimeoutInMs(req) / 1000, purpose: "LoggedInAuth", + //the server is establishing an interactive session here, which is the one place login + //permission originates; the token is unscoped, so it stays as capable as the session it is + can_login: true, callback: function(err2, token) { if (err2) { console.log(err2); @@ -487,17 +490,27 @@ membersUtility.loginWithToken = function(req, callback) { return callback(undefined); } - // Only allow tokens whose purpose is explicitly "log this member in". - // - "LoggedInAuth" — legitimate session tokens (set by setLoggedInVariables; - // mail.sendTimeBanWarning). - // - "LoginAuthToken" — short-lived (TTL=300, multi:false) tokens used by the - // server-side Puppeteer renderer to authenticate the - // headless Chrome session at /login/token/:token. - // Any other purpose — including arbitrary purposes settable via /i/token/create - // by a global admin — MUST NOT be redeemable here. - var allowedLoginPurposes = ["LoggedInAuth", "LoginAuthToken"]; - if (allowedLoginPurposes.indexOf(valid.purpose) === -1) { - plugins.callMethod("tokenLoginFailed", {req: req, data: {token: token, token_owner: valid.owner, reason: "wrong_purpose"}}); + // Redeeming a token here grants the owner's full dashboard session, so it is allowed + // only for a token that explicitly carries login permission. can_login is set by the + // server where a session is established or propagated (setLoggedInVariables, the + // renderer, the ban-warning mail, OIDC), and by /i/token/create only when the creating + // credential already holds it and the child is not narrowed. Purpose is a description, + // not a capability: it used to be the gate here while being freely settable at create + // time, which is what let a scoped token forge its way into a full session. + var canLogin = valid.can_login === true; + if (!canLogin && typeof valid.can_login === "undefined") { + // Tokens minted before this model have no can_login field. Honour the ones that + // were legitimately redeemable then - a login purpose and no restriction at all - + // so live sessions and existing login-token integrations keep working. Scoped + // legacy tokens stay locked out. + var isLoginScopeRestricted = function(scope) { + return !(scope === undefined || scope === null || scope === "" || (Array.isArray(scope) && scope.length === 0)); + }; + var legacyLoginPurposes = ["LoggedInAuth", "LoginAuthToken"]; + canLogin = legacyLoginPurposes.indexOf(valid.purpose) !== -1 && !isLoginScopeRestricted(valid.app) && !isLoginScopeRestricted(valid.endpoint) && !valid.token_permission; + } + if (!canLogin) { + plugins.callMethod("tokenLoginFailed", {req: req, data: {token: token, token_owner: valid.owner, reason: "no_login_permission"}}); return callback(undefined); } diff --git a/frontend/express/public/core/token-manager/javascripts/countly.views.js b/frontend/express/public/core/token-manager/javascripts/countly.views.js index 2b95a00fe13..be20d582dac 100644 --- a/frontend/express/public/core/token-manager/javascripts/countly.views.js +++ b/frontend/express/public/core/token-manager/javascripts/countly.views.js @@ -1,5 +1,7 @@ -/*global app, countlyVue, countlyCommon, CV, $, countlyGlobal, countlyTokenManager, CountlyHelpers */ +/*global app, countlyVue, countlyCommon, CV, $, countlyGlobal, countlyTokenManager, countlyUserManagement, countlyAuth, CountlyHelpers */ (function() { + var PERMISSION_TYPES = ["c", "r", "u", "d"]; + var TokenDrawer = countlyVue.views.create({ template: CV.T('/core/token-manager/templates/token-manager-drawer.html'), data: function() { @@ -7,69 +9,103 @@ tokenUsage: '0', tokenExpiration: '0', title: '', + features: [], + filteredFeatures: [], + searchQuery: '', + allByType: {c: false, r: false, u: false, d: false}, + permissionSet: countlyAuth.permissionSetGenerator(1)[0], constants: { "availableProps": [ { label: CV.i18n('token_manager.limit.h'), value: "hours" }, { label: CV.i18n('token_manager.limit.d'), value: "days" }, { label: CV.i18n('token_manager.limit.m'), value: "months" } - ], - "apps": this.appsData(), + ] } }; }, + mounted: function() { + var self = this; + $.when(countlyUserManagement.fetchFeatures()).then(function() { + self.features = countlyUserManagement.getFeatures() || []; + self.filteredFeatures = self.features; + }); + }, methods: { - appsData: function() { - var apps = []; - for (var appId in countlyGlobal.apps) { - apps.push({ value: appId, name: countlyGlobal.apps[appId].name }); - } - return apps; + featureBeautifier: function(feature) { + return countlyAuth.featureBeautifier(feature); }, - addEndpoint: function(endpoints) { - endpoints.push({parameters: [{}]}); + search: function() { + var self = this; + var query = (self.searchQuery || "").toLowerCase(); + if (query !== "") { + self.filteredFeatures = self.features.filter(function(feature) { + return self.featureBeautifier(feature).toLowerCase().indexOf(query) !== -1; + }); + } + else { + self.filteredFeatures = self.features; + } }, - addParameter: function(parameters) { - parameters.push({}); + clearSearch: function() { + this.searchQuery = ''; + this.filteredFeatures = this.features; }, - removeEndpoint: function(endpoints, endpointIndex) { - if (endpoints.length > 1) { - endpoints.splice(endpointIndex, 1); + //granting anything on a feature implies being able to read it, mirroring how a user's + //own permissions are edited in user management + setPermissionByFeature: function(type, feature) { + if (type !== 'r' && this.permissionSet[type].allowed[feature] && !this.permissionSet.r.allowed[feature]) { + this.$set(this.permissionSet.r.allowed, feature, true); } + if (type === 'r' && !this.permissionSet.r.allowed[feature]) { + for (var i = 0; i < PERMISSION_TYPES.length; i++) { + this.$set(this.permissionSet[PERMISSION_TYPES[i]].allowed, feature, false); + } + } + this.syncAllFlags(); }, - removeParameter: function(parameters, parameterIndex) { - if (parameters.length > 1) { - parameters.splice(parameterIndex, 1); + setPermissionByType: function(type) { + var on = this.allByType[type]; + for (var i = 0; i < this.filteredFeatures.length; i++) { + var feature = this.filteredFeatures[i]; + this.$set(this.permissionSet[type].allowed, feature, on); + if (on && type !== 'r') { + this.$set(this.permissionSet.r.allowed, feature, true); + } + if (!on && type === 'r') { + for (var j = 0; j < PERMISSION_TYPES.length; j++) { + this.$set(this.permissionSet[PERMISSION_TYPES[j]].allowed, feature, false); + } + } } + this.syncAllFlags(); + }, + //"all" must mean every feature, including ones added later, so it is only set when the + //whole list is selected - the server refuses an "all" grant the creator does not hold + syncAllFlags: function() { + var self = this; + PERMISSION_TYPES.forEach(function(type) { + var every = self.features.length > 0 && self.features.every(function(feature) { + return self.permissionSet[type].allowed[feature] === true; + }); + self.permissionSet[type].all = every; + self.allByType[type] = every; + }); + }, + buildPermission: function(apps) { + var permission = {_: {a: [], u: [apps]}, c: {}, r: {}, u: {}, d: {}}; + return countlyAuth.combinePermissionObject([apps], [this.permissionSet], permission); }, onClose: function() { this.tokenUsage = '0'; this.tokenExpiration = '0'; + this.searchQuery = ''; + this.filteredFeatures = this.features; + this.permissionSet = countlyAuth.permissionSetGenerator(1)[0]; + this.allByType = {c: false, r: false, u: false, d: false}; }, onSubmit: function(doc) { var self = this; var ttl = 0; - var selectApps = doc.selectApps; - var endpoints = doc.endpoints; - var newEndpoints = []; - endpoints.forEach(function(element) { - if (element.endpointName !== "" && element.endpointName) { - var obj = {params: {}} ; - obj.endpoint = element.endpointName; - element.parameters.forEach(function(item) { - var key = item.queryParameters1; - var value = item.queryParameters2; - obj.params[key] = value; - }); - newEndpoints.push(obj); - } - }); - endpoints = JSON.stringify(newEndpoints); - - if (self.tokenUsage === "1") { - if (doc.selectApps.length > 0) { - selectApps = doc.selectApps.join(","); - } - } if (self.tokenExpiration === "1") { if (doc.selectTime === "hours") { ttl = doc.timeInput * 3600; @@ -81,7 +117,26 @@ ttl = doc.timeInput * 3600 * 24 * 30; } } - countlyTokenManager.createTokenWithQuery(doc.description, endpoints, doc.checkboxMultipleTimes, selectApps, ttl, function() { + + var options = { + purpose: doc.description, + multi: doc.checkboxMultipleTimes, + ttl: ttl + }; + if (self.tokenUsage === "1") { + options.permission = self.buildPermission(doc.selectApps || []); + } + else { + //an unlimited token carries the creator's own permissions, and only such a + //token may be granted permission to sign in + options.canLogin = doc.checkboxCanLogin === true; + } + + countlyTokenManager.createTokenWithPermissions(options, function(err) { + if (err) { + CountlyHelpers.alert(CV.i18n('token_manager.create-error'), "red"); + return; + } self.$emit("create"); }); } @@ -156,6 +211,8 @@ row.purpose = row.purpose + ""; row.purpose = row.purpose[0].toUpperCase() + row.purpose.substring(1); } + row.canLogin = row.can_login === true; + row.permissionSummary = this.describePermission(row); if (Array.isArray(row.endpoint)) { var lines = []; for (var p = 0; p < row.endpoint.length; p++) { @@ -187,6 +244,40 @@ } this.tableData = tableData; }, + //describe what a token may do, so the list distinguishes a limited token from one that + //carries the owner's own permissions + describePermission: function(row) { + if (!row.token_permission) { + return CV.i18n('token_manager.permission.full'); + } + var counts = {c: 0, r: 0, u: 0, d: 0}; + var labels = []; + PERMISSION_TYPES.forEach(function(type) { + var forType = row.token_permission[type] || {}; + for (var appId in forType) { + var entry = forType[appId]; + if (!entry) { + continue; + } + var grants = entry.all === true; + for (var feature in entry.allowed || {}) { + if (entry.allowed[feature] === true) { + grants = true; + break; + } + } + if (grants) { + counts[type]++; + } + } + }); + PERMISSION_TYPES.forEach(function(type) { + if (counts[type] > 0) { + labels.push(CV.i18n('token_manager.permission.' + type)); + } + }); + return labels.length ? labels.join(", ") : CV.i18n('token_manager.permission.none'); + }, getColor: function(status) { if (status === "active") { return "green"; @@ -197,7 +288,7 @@ }, onCreateClick: function() { this.openDrawer("main", { - description: "", checkboxMultipleTimes: false, endpoints: [{parameters: [{}]}], selectApps: [] + description: "", checkboxMultipleTimes: false, checkboxCanLogin: false, selectApps: [] }); }, onDelete: function(row) { diff --git a/frontend/express/public/core/token-manager/stylesheets/_main.scss b/frontend/express/public/core/token-manager/stylesheets/_main.scss index e69de29bb2d..5ea14cd43af 100644 --- a/frontend/express/public/core/token-manager/stylesheets/_main.scss +++ b/frontend/express/public/core/token-manager/stylesheets/_main.scss @@ -0,0 +1,80 @@ +.token-manager-drawer { + // Two bordered radio cards sharing one line. `is-autosized` sets width:100%, and a flex + // item's default min-width:auto stops it shrinking below its own text, so the card with the + // longer description pushes the other one out of the drawer. Let them split the line evenly + // and allow both to shrink. + &__choice { + .el-radio.is-autosized { + flex: 1 1 0; + min-width: 0; + } + + .el-radio__label { + white-space: normal; + } + } + + // Feature permission grid. The column headers and the per-feature checkboxes are separate + // rows, so they line up only when both use the same column widths: a fixed-width name + // column, then four equal cells. + &__permission-header, + &__permission-row { + display: flex; + flex-direction: row; + align-items: center; + } + + &__permission-header { + border-bottom: 1px solid #ebebeb; + padding-bottom: 10px; + margin-bottom: 10px; + } + + &__permission-row { + height: 30px; + } + + &__feature-name { + width: 35%; + display: flex; + align-items: center; + justify-content: flex-start; + } + + &__feature-permissions { + width: 65%; + display: flex; + } + + // Equal cells, and the checkbox centred in each, so a row checkbox sits under the header + // checkbox above it. + &__permission-box { + flex: 1 1 0; + min-width: 0; + display: flex; + align-items: center; + justify-content: center; + + .el-checkbox { + margin-right: 0; + } + } + + // In the header the column name sits above its checkbox rather than beside it. Inline, the + // label widens the cell and pushes the checkbox off the centre the rows are aligned to, so + // the header boxes would sit ~20px left of the column they head. + &__permission-header &__permission-box { + .el-checkbox { + display: flex; + flex-direction: column-reverse; + align-items: center; + } + + .el-checkbox__label { + font-size: 11px; + padding-left: 0; + margin-bottom: 4px; + line-height: 1; + } + } +} diff --git a/frontend/express/public/core/token-manager/templates/token-manager-drawer.html b/frontend/express/public/core/token-manager/templates/token-manager-drawer.html index 0c1e1b3aba0..ac70f56e8d1 100644 --- a/frontend/express/public/core/token-manager/templates/token-manager-drawer.html +++ b/frontend/express/public/core/token-manager/templates/token-manager-drawer.html @@ -10,61 +10,84 @@ - -
-

{{i18n('token_manager.table.endpoints')}}

-

{{i18n('common.optional')}}

-
-

{{i18n('token_manager.table.endpoints-description')}}

-
- -
-

{{i18n('token_manager.table.endpoint-detail')}}

- - Remove - -
- - - - -
- - - - - -
- + {{i18n('token_manager.add-param')}} -
-
- - - + {{i18n('token_manager.add-new-endpoint')}} - -
-

{{i18n('token_manager.table.apps-title')}}

+

{{i18n('token_manager.permission.title')}}

-
- - {{i18n('token_manager.table.apps-allow')}} +

{{i18n('token_manager.permission.description')}}

+
+ + {{i18n('token_manager.permission.full-label')}} +

{{i18n('token_manager.permission.full-text')}}

- - {{i18n('token_manager.table.apps-limit')}} + + {{i18n('token_manager.permission.limited-label')}} +

{{i18n('token_manager.permission.limited-text')}}

+ + + {{i18n('token_manager.permission.can-login')}} + +

{{i18n('token_manager.permission.can-login-text')}}

+

{{i18n('token_manager.select-apps')}}

- - - - + + +
+ +
+

{{i18n('token_manager.permission.features')}}

+
+

{{i18n('token_manager.permission.features-description')}}

+ + + + +
+
+
+
+ {{i18n('management-users.create').toUpperCase()}} +
+
+ {{i18n('management-users.read').toUpperCase()}} +
+
+ {{i18n('management-users.update').toUpperCase()}} +
+
+ {{i18n('management-users.delete').toUpperCase()}} +
+
+
+
+
{{featureBeautifier(feature)}}
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
diff --git a/frontend/express/public/core/token-manager/templates/token-manager.html b/frontend/express/public/core/token-manager/templates/token-manager.html index d93a97adc2c..7c062f1c6b1 100644 --- a/frontend/express/public/core/token-manager/templates/token-manager.html +++ b/frontend/express/public/core/token-manager/templates/token-manager.html @@ -38,9 +38,11 @@ {{scope.row.app}} - + diff --git a/frontend/express/public/javascripts/countly/countly.token.manager.js b/frontend/express/public/javascripts/countly/countly.token.manager.js index ec2c44f79a6..bf5cb848ead 100644 --- a/frontend/express/public/javascripts/countly/countly.token.manager.js +++ b/frontend/express/public/javascripts/countly/countly.token.manager.js @@ -44,17 +44,35 @@ }); }; - countlyTokenManager.createTokenWithQuery = function(purpose, endpoint, multi, apps, ttl, callback) { + /** + * Create a token, optionally limited to a set of CRUD permissions. + * @param {object} options - token options + * @param {string} options.purpose - description of the token + * @param {boolean} options.multi - can the token be used more than once + * @param {number} options.ttl - seconds until the token expires, 0 never expires + * @param {object=} options.permission - permission object limiting the token, omitted for a + * token that carries the creator's own permissions + * @param {boolean=} options.canLogin - request permission to sign in to the dashboard. Granted + * only when the creating credential holds it and the token is not limited + * @param {function} callback - called with (error, response) + * @returns {object} jQuery ajax object + */ + countlyTokenManager.createTokenWithPermissions = function(options, callback) { + var data = { + "purpose": options.purpose, + "multi": options.multi, + "ttl": options.ttl + }; + if (options.permission) { + data.permission = JSON.stringify(options.permission); + } + if (options.canLogin) { + data.can_login = true; + } return $.ajax({ type: "GET", url: countlyCommon.API_URL + "/i/token/create", - data: { - "purpose": purpose, - "endpointquery": endpoint, - "multi": multi, - "apps": apps, - "ttl": ttl - }, + data: data, success: function(json) { //token created callback(null, json); diff --git a/frontend/express/public/javascripts/countly/vue/components/input.js b/frontend/express/public/javascripts/countly/vue/components/input.js index 028b5718bef..cf4fbbf013a 100755 --- a/frontend/express/public/javascripts/countly/vue/components/input.js +++ b/frontend/express/public/javascripts/countly/vue/components/input.js @@ -698,7 +698,12 @@ :key="option.value + \'-\' + index"\ v-for="(option, index) in searchedOptions">\
\ - {{option.label}}\ + \ + \ + \ + {{option.label}}\ + \ + \
\ \ \ diff --git a/frontend/express/public/javascripts/countly/vue/templates/selectx.html b/frontend/express/public/javascripts/countly/vue/templates/selectx.html index 9234f892523..e2929f28604 100644 --- a/frontend/express/public/javascripts/countly/vue/templates/selectx.html +++ b/frontend/express/public/javascripts/countly/vue/templates/selectx.html @@ -113,6 +113,9 @@ :options="getMatching(tab.options)" @change="handleValueChange" v-model="innerValue"> + .el-button{display:flex;align-items:center}.el-button-group>.el-select:first-child .el-button{display:flex;align-items:center}.cly-countly-icon-solid{width:20px;height:20px;background-image:url("../images/icons/countly-icon-solid.svg")}.cly-countly-icon-outline{width:20px;height:20px;background-image:url("../images/icons/countly-icon-outline.svg")}.cly-countly-icon-outline-trash-16px{background-position:99px 962px}.cly-countly-icon-outline-code-16px{background-position:99px 674px}.cly-countly-icon-outline-external-link-16px{background-position:419px 674px}.cly-countly-icon-outline-download-16px{background-position:299px 819px}.cly-countly-icon-outline-x-16px{background-position:139px 1107px}.cly-countly-icon-outline-document-download-16px{background-position:179px 1058px}.cly-countly-icon-outline-exclamation-circle-16px{background-position:339px 868px}.cly-countly-icon-outline-plus-circle-16px{background-position:299px 867px}.cly-countly-icon-outline-globe-16px{background-position:219px 1010px}.cly-countly-icon-outline-user-group-16px{background-position:300px 1202px}*{outline:none}html{height:100%;width:100%}body{height:100%;width:100%;margin:0;padding:0;overflow:auto;background-color:#f9f9f9;user-select:text;font-family:Inter,Ubuntu,Helvetica,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1{font-size:32px;line-height:48px;font-weight:500;margin:0;padding:10;color:#333c48}h2{font-size:24px;line-height:32px;font-weight:500;margin:0;padding:0;color:#333c48}h3{font-size:20px;line-height:30px;font-weight:500;margin:0;padding:0;color:#333c48}h4{font-size:16px;line-height:24px;font-weight:500;margin:0;padding:0;color:#333c48}h5{font-size:14px;line-height:20px;font-weight:500;margin:0;padding:0;color:#333c48}h6{font-size:14px;line-height:18px;font-weight:500;margin:0;padding:0;color:#333c48}textarea,input,p,h1,h2,h3,h4,h5,h6,button{font-family:Inter !important}.el-table th{padding:9px 0px;box-sizing:border-box;text-transform:uppercase;font-size:12px;font-style:normal;font-weight:500;border-bottom-width:2px}.el-table th .cell{line-height:16px;letter-spacing:.5px}.el-table th:hover .sort-caret.ascending{border-bottom-color:#005def}.el-table th:hover .sort-caret.descending{border-top-color:#005def}.el-table th.is-leaf{border-bottom-width:2px;border-top:1px solid #eef2f5}.el-table td{padding:14px 0px;font-style:normal;font-weight:normal;font-size:14px;box-sizing:border-box}.el-table td .cell{line-height:20px}.el-table.el-table--has-options td .cell{line-height:23px}.el-table th:first-child .cell,.el-table td:first-child .cell{padding-left:16px}.el-table th:last-child .cell,.el-table td:last-child .cell{padding-right:16px}.el-table .caret-wrapper{height:15px}.el-table .sort-caret{border-width:4px}.el-table .sort-caret.ascending{top:-2px}.el-table .sort-caret.descending{bottom:-1px}.el-table .ascending .sort-caret.ascending{border-bottom-color:#005def}.el-table .descending .sort-caret.descending{border-top-color:#005def}.el-table .cell{word-break:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}.el-table .cell>.el-checkbox,.el-table .cell>.el-switch,.el-table .cell>.el-radio{overflow:unset}.el-table table{width:0px}.el-radio.is-autosized{padding:16px;display:flex;width:100%;margin-right:0;height:unset;background-color:#fff}.el-radio.is-autosized.is-bordered+.el-radio.is-autosized.is-bordered{margin-left:10px}.el-radio.is-autosized.is-bordered{border-color:#cfd6e4;display:flex;align-items:center}.el-radio.is-autosized.is-bordered .el-radio__input{display:flex}.el-radio.is-autosized.is-bordered.is-multiline{align-items:flex-start}.el-radio.is-autosized.is-bordered.is-checked{border-color:#0166d6}.el-radio.is-autosized-vstack{padding:12px;display:flex;width:100%;margin-right:0;height:unset;background-color:#fff}.el-radio.is-autosized-vstack .el-radio__label{justify-content:space-between;display:flex;width:100%;align-items:baseline}.el-radio.is-autosized-vstack.is-bordered{align-items:center}.el-radio.is-autosized-vstack.is-bordered .el-radio__input{display:flex}.el-radio.is-autosized-vstack.is-bordered.is-multiline{align-items:flex-start}.el-radio.is-autosized-vstack.is-bordered.is-multiline .el-radio__label{padding-top:2px}.el-radio.is-autosized-vstack.is-bordered+.el-radio.is-autosized-vstack.is-bordered{margin-left:0;margin-top:7px}.el-radio.is-bordered{border:1px solid #cfd6e4}.el-radio:hover:not(.is-disabled) .el-radio__label{color:inherit}.el-radio.is-checked:not(.is-disabled):hover .el-radio__label{color:inherit}.el-radio__input.is-checked+.el-radio__label{color:inherit}.el-collapse.is-bordered-box{border:1px solid #ececec;box-sizing:border-box;box-shadow:0px 1px 0px #ececec;border-radius:4px}.el-collapse.is-bordered-box .el-collapse-item__header{border-radius:4px;padding:0rem 1.5rem 0rem 1.5rem}.el-collapse.is-bordered-box .el-collapse-item__header.is-active{border-bottom-color:#ececec}.el-collapse.is-bordered-box .el-collapse-item__wrap{background-color:rgba(0,0,0,0)}.el-collapse.is-bordered-box .el-collapse-item__content{margin:0rem 1.5rem 0rem 1.5rem}.el-switch__label *{font-weight:normal;color:#a7aeb8;font-size:12px}.el-input.is-adaptive .el-input__inner{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:10px;vertical-align:middle}.el-select .el-input.is-arrow .el-input__inner{padding-right:30px}.el-button.is-wireframe{background:rgba(0,0,0,0);border:1px dotted #0166d6;box-sizing:border-box;box-shadow:0px 2px 4px rgba(1,122,255,.07)}.el-button.el-button--text.is-danger{color:#d23f00}.el-button.el-button--text.is-danger:hover{background-color:#f6f6f6}.el-button.is-light-blue{box-shadow:none;-webkit-box-shadow:none;border:none;font-weight:500;height:32px}.el-button.is-light-blue:not(.is-disabled){color:#0166d6;background-color:#e1efff}.el-button.is-light-blue:not(.is-disabled):hover{background-color:#cce0f7}.el-button-group>.el-select{float:left}.el-button-group>.el-select:first-child .el-button{border-top-right-radius:0;border-bottom-right-radius:0;-webkit-box-shadow:none;box-shadow:none}.el-button-group>.el-select:not(:last-child){margin-right:-1px}.el-button-group>.el-select:active,.el-button-group>.el-select:focus,.el-button-group>.el-select:hover{z-index:1}.el-button-group>.el-select:active .el-button,.el-button-group>.el-select:focus .el-button,.el-button-group>.el-select:hover .el-button{z-index:1}.el-input-number.is-slim .el-input__inner{padding-left:5px;padding-right:26px}.el-input-number.is-slim.el-input-number--small .el-input-number__decrease,.el-input-number.is-slim.el-input-number--small .el-input-number__increase{width:22px;font-size:14px}.el-tabs--card>.el-tabs__header{margin:0}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap{margin-bottom:-2px}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap.is-scrollable{border-bottom:none;border-top-right-radius:4px;border-top-left-radius:4px}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-next{background:linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0.85) 100%)}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-prev{background:linear-gradient(270deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0.85) 100%)}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-next,.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-prev{opacity:.85}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-next:hover,.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-prev:hover{opacity:1}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-top-left-radius:3px}.el-tabs--card>.el-tabs__header .el-tabs__item:last-child{border-top-right-radius:3px}.el-tabs--card>.el-tabs__header .el-tabs__item{background-color:#fcfcfc;padding:0;height:auto;text-align:left;line-height:32px;border-bottom:2px solid #ececec}.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding:0}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#0166d6;background-color:#fff}.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding:0px}.el-radio-group.is-full{display:flex}.el-radio-group.is-full .el-radio-button{flex-grow:1}.el-radio-group.is-full .el-radio-button__inner{width:100%}.el-dropdown-menu__item{padding:7px 6px}.el-dropdown-menu__item>i.cly-icon-outline{padding:2px 7px}.el-select-dropdown .el-scrollbar__bar{opacity:1 !important;right:0}.el-select-dropdown__item{padding:7px 6px}.el-tiptap-editor{border-radius:4px}.el-tiptap-editor .el-tiptap-editor__menu-bar{border-color:#cfd6e4}.el-tiptap-editor .el-tiptap-editor__menu-bar::before{background-color:#cfd6e4}.el-tiptap-editor>.el-tiptap-editor__content{border:1px solid #cfd6e4 !important;border-top:0 !important;border-bottom:0 !important}.el-tiptap-editor>.el-tiptap-editor__content pre code{font-family:"Courier New"}.el-tiptap-editor .el-tiptap-editor__footer{border-color:#cfd6e4}.el-tiptap-editor .border-top-radius{border-top-left-radius:4px !important;border-top-right-radius:4px !important}.el-tiptap-editor .border-bottom-radius{border-bottom-left-radius:4px !important;border-bottom-right-radius:4px !important}.el-tiptap-editor .fa-icon{fill:#333c48}.el-tiptap-editor__content{overflow-x:unset !important}.el-tiptap-editor__content>pre>code{font-family:"Courier New" !important}.el-tiptap-editor__content span>span{background-color:inherit}.el-tiptap-editor__content hr{width:100%}.el-dialog.el-tiptap-edit-link-dialog .el-button.is-round{border-radius:4px}.el-dialog.el-tiptap-edit-link-dialog .el-button.el-button--primary{border-color:#12af51;background-color:#12af51}.el-checkbox__input .el-checkbox__inner{color:#fff;text-align:center}.el-checkbox__input.is-checked .el-checkbox__inner::before{content:"" !important;font-family:"countly-icons-outline" !important;border:none !important;position:absolute;top:0px;width:15px;left:0px}.el-checkbox__input.is-checked .el-checkbox__inner::after{border:none !important}.el-checkbox__input.is-checked.is-disabled .el-checkbox__inner{color:#d0d0d0}.el-checkbox__inner::after{display:none}.el-checkbox.is-checked .el-checkbox__inner::after{display:block}.el-upload__folder-preview-content{height:32px}.el-upload-dragger{background-color:#fcfcfc;display:flex;flex-direction:column;justify-content:center}.el-upload-dragger .fa-folder{line-height:0}.el-upload-list__item-name{background-color:#fff;width:100%;border:1px solid #ececec;border-radius:5px;height:68px;padding-left:unset;cursor:default !important}.el-upload-list__item-remove{margin-left:auto;margin-right:24px;cursor:pointer !important}.el-upload-list__item-preview .cly-io-document-text{font-size:20px;color:#000;margin-right:4px}.el-upload-list__item.is-success .el-upload-list__item-name:hover{color:#000}.bu-level:not(:last-child){margin-bottom:0px}.bu-tag.is-slim{padding:0 .5em;height:1.5em}.bu-tag.is-legend{padding:0;height:.5em;width:1em;border-radius:3px}.bu-tag.is-curved{border-radius:67px;height:16px;padding-left:4px;padding-right:4px;min-width:12px}.vjs-tree{padding:10px}.vjs-value-boolean,.vjs-value-number{color:#31acb4}.vjs-value-string{color:#6c47ff}.vjs-tree-node.is-highlight,.vjs-tree-node:hover{background-color:#e6f0fb}.cly-vue-theme-clydef{background-color:#f8f9fa}#main-views-container{height:100%;width:100%;overflow:auto}#main-views-container .main-view{min-width:768px;min-height:400px;width:100%;padding:0px 0 35px 0 !important;transition:width .55s;will-change:width;overflow:auto}.cly-vue-main{padding:0px 24px;margin:0}.cly-vue-section--has-configurator-skin .cly-vue-section__content{background:#fff;border:1px solid #ececec;box-sizing:border-box;box-shadow:0px 1px 0px #ececec;border-radius:4px}.cly-vue-section--has-configurator-skin .cly-vue-section__content .cly-vue-section__sub{border-top:none !important;border-bottom:1px solid #ececec;align-items:center;display:flex}.cly-vue-section--has-default-skin .cly-vue-section__content{border-top:1px solid #ececec;border-bottom:1px solid #ececec}.cly-vue-section .cly-vue-section__content-config{border-bottom:1px solid #ececec}.cly-vue-section:not(:last-child){margin-bottom:32px}.cly-vue-header{padding:24px 24px;margin-bottom:32px;border-bottom:1px solid #ececec;position:relative}.cly-vue-header__level{padding-top:8px;padding-bottom:12px}.cly-vue-header__level--no-pt{padding-top:0px}.cly-vue-header__level--no-pb{padding-bottom:0px}.cly-vue-header--no-mb{margin-bottom:0px}.cly-vue-header--no-bb{border-bottom:none}.cly-vue-header+div>.cly-vue-tabs{background-color:#fff;border-bottom:1px solid #ececec;margin-bottom:32px;padding:0px 24px}.cly-vue-select-x__pop .cly-vue-listbox{background-color:rgba(0,0,0,0) !important}.cly-vue-select-x__pop .el-tabs__active-bar{background-color:#0166d6}.cly-vue-select-x__pop .el-tabs__nav-wrap::after{background-color:rgba(0,0,0,0)}.cly-vue-select-x__pop .el-tabs__nav-scroll{margin:0 14px}.cly-vue-select-x__pop .el-tabs__header{margin:0 0 1px 0;z-index:2000}.cly-vue-select-x__pop .el-tabs__header::after{content:"";position:absolute;width:100%;height:12px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);top:40px;opacity:.12}.cly-vue-select-x__pop--hidden-tabs .el-tabs__header::after{top:0px}.cly-vue-select-x__pop--hidden-tabs .el-tabs__nav-scroll{display:none}.cly-vue-select-x__pop--hidden-header .cly-vue-select-x__header{padding:0}.cly-vue-select-x__pop--hidden-tabs.cly-vue-select-x__pop--hidden-header .el-tabs__header::after{background:none;height:0;width:0}.cly-vue-select-x__title{font-weight:500;font-size:16px;line-height:24px;color:#333c48;padding-bottom:16px}.cly-vue-select-x__header-slot{padding-bottom:16px}.cly-vue-select-x__header{padding:16px}.cly-vue-select-x__header input[type=text]{background-color:#f6f6f6}.cly-vue-select-x__footer{border-top:1px solid #ececec}.cly-vue-select-x__commit-section{float:right;margin:16px}.cly-vue-select-x__pop--has-single-option .cly-vue-select-x__header-slot,.cly-vue-select-x__pop--has-slim-header .cly-vue-select-x__header-slot{padding:0}.cly-vue-select-x__pop--has-single-option .el-tabs__header::after{content:none}.cly-vue-select-x .el-select-head{width:100%}.cly-vue-listbox{background-color:#fff}.cly-vue-listbox label.el-checkbox{width:100%;margin-left:8px}.cly-vue-listbox.is-expandable{transition:width 200ms;width:100%;z-index:100}.cly-vue-listbox.is-expanded{width:130%;position:relative;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.08);box-shadow:0 1px 4px rgba(0,0,0,.08)}.cly-vue-listbox.cly-vue-listbox--bordered{border-radius:4px;border:1px solid #ececec}.cly-vue-listbox .cly-vue-listbox__no-data{padding:8px;line-height:20px;font-weight:500;color:#333c48;font-size:14px}.cly-vue-listbox .el-checkbox-group{overflow:hidden}.cly-vue-listbox .el-checkbox__label{line-height:20px;font-weight:400;color:#333c48;max-width:330px !important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}.cly-vue-listbox .el-checkbox__input.is-checked+.el-checkbox__label{color:#333c48}.cly-vue-listbox .drag-handler{visibility:hidden;margin-right:8px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cly-vue-listbox__header{margin:0 0 1px 0;z-index:1997;border-bottom:1px solid #ececec;position:relative}.cly-vue-listbox__header::after{content:"";position:absolute;width:100%;height:12px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);top:57px;left:0;opacity:.12}.cly-vue-listbox .cly-vue-listbox__group{font-size:12px;line-height:14px;background-color:#f6f6f6;border-bottom:solid 1px #ececec;max-height:32px;display:flex;align-items:center;padding:8px}.cly-vue-listbox .cly-vue-listbox__item{display:flex;cursor:pointer;align-items:center;justify-content:space-between}.cly-vue-listbox .cly-vue-listbox__item.selected{background-color:#0166d6}.cly-vue-listbox .cly-vue-listbox__item.selected *{color:#fff}.cly-vue-listbox .cly-vue-listbox__item .cly-vue-listbox__remove-option{display:none}.cly-vue-listbox .cly-vue-listbox__item.hover .cly-vue-listbox__remove-option{display:block}.cly-vue-listbox .cly-vue-listbox__item-content{width:100%}.cly-vue-listbox__item-label{max-width:350px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cly-vue-listbox--disabled .cly-vue-listbox__item{background-color:#f6f6f6}.cly-vue-listbox--disabled .cly-vue-listbox__item *{color:#a7aeb8}.cly-vue-listbox--disabled .cly-vue-listbox__item.selected{opacity:.5}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover{background-color:#f6f6f6}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .drag-handler{visibility:visible}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-prefix,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-prefix *,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-label,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-label *,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-suffix,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-suffix *{color:#0166d6}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item .cly-vue-listbox__remove-option:hover{color:#0166d6}.cly-vue-listbox--has-default-skin .cly-vue-listbox__item{padding:8px;border-radius:4px;line-height:16px}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item{padding:8px;height:55px;box-sizing:border-box;border-bottom:1px solid #ececec}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item.selected{border-left:2px solid #0166d6;background-color:#f6f6f6}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item.selected *{color:#0166d6}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item:last-child{border-bottom:none}.cly-vue-listbox--has-margin .cly-vue-listbox__item{margin:8px}.cly-fields-select.default-skin{display:inline-block}.cly-fields-select.default-skin .el-input__inner::placeholder{color:#000}.cly-fields-select.default-skin .fields-select-body{box-shadow:0 3px 7px rgba(0,0,0,.08);background-color:#fff;position:absolute;top:0px;z-index:100;border:1px solid #d0d0d0;border-radius:2px;padding:16px;width:max-content}.cly-fields-select.default-skin .fields-select-body .controls{float:right;margin-top:10px}.cly-fields-select__title{color:#333c48;font-family:Inter;line-height:24px;font-weight:500;font-size:16px}.cly-fields-select__reset{float:right;font-size:12px;padding-bottom:26px;padding-top:0px;color:#d23f00;cursor:pointer;line-height:20px}.cly-fields-select__field{color:#333c48;font-family:Inter;line-height:20px;font-weight:500;font-size:12px;padding-bottom:3px}.cly-fields-select__field-dropdown{width:320px;height:32px;margin-bottom:16px}.cly-fields-select__confirm{background-color:#12af51;color:#fff}.cly-fields-select__cancel{background:#f6f6f6}.cly-fields-select__dropdown .el-input__inner::placeholder{color:#000}.cly-multi-select.default-skin{display:inline-block}.cly-multi-select.default-skin .el-input__inner::placeholder{color:#000}.cly-multi-select.default-skin .cly-multi-select__body{box-shadow:0 3px 7px rgba(0,0,0,.08);background-color:#fff;position:absolute;top:-2px;left:-2px;z-index:100;border:1px solid #d0d0d0;border-radius:4px;padding:16px;width:max-content}.cly-multi-select.default-skin .cly-multi-select__controls{float:right;margin-top:10px}.cly-multi-select__title-wrapper{margin-bottom:30px}.cly-multi-select__title{color:#333c48;font-family:Inter;line-height:24px;font-weight:500;font-size:16px}.cly-multi-select__reset{float:right;font-size:12px;padding:4px 8px;color:#d23f00;cursor:pointer;line-height:20px}.cly-multi-select__reset span{height:20px;line-height:20px}.cly-multi-select__reset:hover{background:#f6f6f6}.cly-multi-select__field{color:#333c48;font-family:Inter;line-height:20px;font-weight:500;font-size:12px;padding-bottom:3px}.cly-multi-select .cly-multi-select__confirm-button{margin-left:4px}.cly-multi-select__field-dropdown{width:320px;height:32px;margin-bottom:16px}.cly-multi-select__dropdown .el-input__inner::placeholder{color:#000}.cly-vue-dropdown__pop{padding:0px;width:fit-content}.cly-vue-dropdown__pop-container{word-break:initial}.cly-vue-dropdown__dropdown-icon{width:16px;line-height:16px;height:16px;background-size:16px 16px;border-radius:4px;background-position:center;background-color:#017aff;display:inline-block}.cly-vue-more-options .el-button{border:1px solid #cfd6e4}.cly-vue-more-options .el-button .is-reverse{-webkit-transform:rotateZ(0);transform:rotateZ(0)}.cly-vue-eldatatable.silent-loading .el-loading-spinner{visibility:hidden}.cly-vue-eldatatable.silent-loading .el-loading-mask{display:none !important}.cly-vue-eldatatable.display-mode--list .el-table{background-color:unset}.cly-vue-eldatatable.display-mode--list .el-table__body{border-collapse:separate;border-spacing:0 8px}.cly-vue-eldatatable.display-mode--list .el-table__row{cursor:pointer}.cly-vue-eldatatable.display-mode--list .el-table__row td{cursor:pointer;height:56px;border-top:1px solid #ececec !important;border-bottom:2px solid #ececec !important;background-color:#fff !important;padding:18px 0px}.cly-vue-eldatatable.display-mode--list .el-table__row td:first-child{border-left:1px solid #ececec !important;border-radius:4px 0 0 4px !important}.cly-vue-eldatatable.display-mode--list .el-table__row td:last-child{border-right:1px solid #ececec !important;border-radius:0 4px 4px 0 !important}.cly-vue-eldatatable.display-mode--list .el-table__row td .cell{line-height:20px}.cly-vue-eldatatable.display-mode--list .el-table__row td.drag-icon .cell{text-overflow:unset !important}.cly-vue-eldatatable.display-mode--list .el-table thead tr{background-color:unset !important}.cly-vue-eldatatable.display-mode--list .el-table th{background-color:unset !important;border:none !important;padding:0 !important;color:#81868d !important}.cly-vue-eldatatable.display-mode--list .el-table__fixed-right::before{height:0 !important}.cly-vue-eldatatable__header{height:56px;border-left:none;border-right:none;box-sizing:border-box;padding:12px;border-top:1px solid #ececec}.cly-vue-eldatatable__header--white{background-color:#fff;color:#333c48;font-size:13px}.cly-vue-eldatatable__export--extended::after{content:"";position:absolute;width:100%;height:12px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);top:185px;left:0;opacity:.12}.cly-vue-eldatatable__export--fileExport{border-top:1px solid #e2e4e8}.cly-vue-eldatatable__export--radio-button .el-radio-button{width:99px;display:inline-grid}.cly-vue-eldatatable__export--search input[type=text]{background-color:#f6f6f6}.cly-vue-eldatatable__search--grey input{background-color:#fbfdfe}.cly-vue-eldatatable__footer{height:40px;box-shadow:0px 2px 0px #eef2f5;border-left:none;border-right:none;box-sizing:border-box;padding:0px 12px}.cly-vue-eldatatable__footer--white{background-color:#fff;color:#808a9d;font-size:13px}.cly-vue-eldatatable .undo-row{position:absolute;left:0;padding:9px 0px;background-color:#ffd7d6;color:#2b2b2b;width:100%;height:100%;top:0;z-index:999;vertical-align:middle;display:flex;align-items:center;justify-content:center}.cly-vue-eldatatable .undo-row a{color:#017aff;cursor:pointer}.cly-vue-eldatatable .el-table-column--options .cly-vue-more-options{position:absolute;top:calc(50% - 16px);right:calc(50% - 20px)}.cly-vue-eldatatable div.cell{padding-left:16px;padding-right:8px}.cly-vue-eldatatable__empty-slot{height:200px}.cly-vue-table{width:100%}.cly-vue-table thead{background-color:#f6f6f6;border-top:1px solid #ececec;border-bottom:2px solid #ececec}.cly-vue-table thead th{color:#333c48;font-weight:500;font-size:12px;padding:9px;text-align:left}.cly-vue-table tbody td{padding:0px 9px}.cly-vue-chart{position:relative}.cly-vue-chart .bu-level{padding:0 20px 0 33px}.cly-progress-bar-container{width:100%}.cly-progress-bar-container.has-min-bradius .first-progress-bar-stack .el-progress-bar__outer{border-bottom-left-radius:4px;border-top-left-radius:4px}.cly-progress-bar-container.has-min-bradius .last-progress-bar-stack .el-progress-bar__outer{border-bottom-right-radius:4px;border-top-right-radius:4px}.cly-progress-bar-container.has-min-bradius .el-progress:not(.progress-bar-stack) .el-progress-bar__outer{border-radius:4px}.cly-progress-bar-container.has-min-bradius .el-progress:not(.progress-bar-stack) .el-progress-bar__inner{border-radius:4px}.cly-progress-bar-stack-container{float:left}.progress-bar-stack .el-progress-bar__inner{border-radius:0px}.first-progress-bar-stack .el-progress-bar__outer{border-bottom-right-radius:0px;border-top-right-radius:0px}.middle-progress-bar-stack .el-progress-bar__outer{border-radius:0px}.last-progress-bar-stack .el-progress-bar__outer{border-bottom-left-radius:0px;border-top-left-radius:0px}.cly-vue-tabs__primary-tab-list{padding:24px;box-sizing:border-box}.cly-vue-tabs__tab{height:34px;padding:10px 12px;display:inline-block;box-sizing:border-box}.cly-vue-tabs__tab--default{color:#81868d;line-height:14px;font-weight:500;font-size:14px;cursor:pointer}.cly-vue-tabs__tab--primary{border-radius:6px}.cly-vue-tabs__tab--primary:hover{color:#0166d6}.cly-vue-tabs__tab--secondary:hover{color:#333c48}.cly-vue-tabs__tab--primary-active{color:#0166d6;background-color:#e1efff}.cly-vue-tabs__tab--secondary-active{color:#333c48;border-bottom:2px solid #0166d6}.cly-vue-chart{width:100%;height:100%}.cly-vue-chart__echart{box-sizing:border-box;display:inline-block;width:100%}.cly-vue-chart--padded .cly-vue-chart__echart{padding-top:20px}.cly-vue-chart--full .cly-vue-chart__echart{padding:0}.cly-vue-chart-legend{display:inline-block;width:100%}.cly-vue-chart-legend__primary,.cly-vue-chart-legend__secondary{white-space:nowrap;overflow:auto;box-sizing:border-box;height:100%}.cly-vue-chart-legend__secondary--text-center{text-align:center}.cly-vue-chart-legend__p-series{display:inline-block;margin-left:64px;cursor:pointer}.cly-vue-chart-legend__p-series:first-child{margin-left:36px}.cly-vue-chart-legend__s-series{display:inline-block;margin-left:48px;cursor:pointer}.cly-vue-chart-legend__s-series:first-child{margin-left:0px}.cly-vue-chart-legend__s-series--deselected .cly-vue-chart-legend__s-rectangle,.cly-vue-chart-legend__s-series--deselected .cly-vue-chart-legend__s-title,.cly-vue-chart-legend__s-series--deselected .cly-vue-chart-legend__s-percentage{color:#a7aeb8}.cly-vue-chart-legend__second-row{margin-top:3px}.cly-vue-chart-legend__p-checkbox{height:16px;width:16px;border:1px solid #a7aeb8;display:inline-block;box-shadow:inset 0 0 0px 1.5px #fff;border-radius:4px;vertical-align:middle;box-sizing:border-box}.cly-vue-chart-legend__p-title{display:inline-block;vertical-align:middle;font-size:14px;color:#333c48}.cly-vue-chart-legend__p-tooltip{display:inline-block}.cly-vue-chart-legend__p-tooltip i{font-size:15px}.cly-vue-chart-legend__p-number{display:inline-block;font-size:24px;font-weight:500;color:#333c48}.cly-vue-chart-legend__p-trend{display:inline-block;font-size:14px;font-weight:500}.cly-vue-chart-legend__p-trend--trend-up{color:#12af51}.cly-vue-chart-legend__p-trend--trend-down{color:#d23f00}.cly-vue-chart-legend__s-rectangle{width:12px;height:6px;border-radius:3px;display:inline-block;vertical-align:middle;margin:0px 5px}.cly-vue-chart-legend__s-title,.cly-vue-chart-legend__s-percentage{display:inline-block;vertical-align:middle;margin-left:3px;font-size:12px;font-weight:400;color:#333c48}.cly-vue-chart-legend__bottom .cly-vue-chart-legend__primary,.cly-vue-chart-legend__bottom .cly-vue-chart-legend__secondary{padding:0px 24px 20px}.cly-vue-chart-legend__right{border-left:1px solid #ececec;width:265px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series{margin-left:0px;padding:3px 7px;box-sizing:border-box;width:100%;display:block;border-bottom:1px solid #ececec}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover{background-color:#f6f6f6;border-radius:0px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover .cly-vue-chart-legend__s-title,.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover .cly-vue-chart-legend__s-percentage{font-weight:500}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-title,.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-percentage{font-size:14px;line-height:30px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-title{width:calc(100% - 30px);text-align:left}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-percentage{min-width:50px;text-align:right}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:first-child{padding-top:10px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .__vuescroll .__view::before{content:"";position:absolute;width:100%;height:8px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);left:0;opacity:.12}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie{border-left:none}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary{padding-top:30px;padding-bottom:30px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series{border:none}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover{border-radius:8px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-title{width:calc(100% - 86px)}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:first-child{padding-top:3px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .__vuescroll .__view{display:flex;flex-direction:column;justify-content:center;padding-right:30px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .__vuescroll .__view::before{background:none;height:0}.chart-type-toggle-wrapper__select{width:126px}.chart-type-toggle-wrapper__title{margin-top:16px;margin-left:16px;letter-spacing:.5px}.chart-type-toggle-wrapper__type{font-family:Inter;font-style:normal;font-weight:500;font-size:13px;line-height:13px}.chart-type-annotation-wrapper__icon{margin-top:1px}.chart-type-annotation-wrapper__img{margin-bottom:-2px}.chart-zoom-button{margin-right:12px}.cly-vue-zoom__external .chart-zoom-button{margin:0}.chart-download-button{width:42px;height:32px}.shadow-container{position:relative}.shadow-container:after{display:block;content:"";position:absolute;width:100%;margin:0 auto;height:70px;border:0;bottom:0;background:linear-gradient(180deg, rgba(255, 255, 255, 0.7) 0%, #FFFFFF 100%)}.chart-tooltip{max-height:360px;overflow:auto;margin-bottom:2px;max-width:300px;white-space:nowrap}.chart-tooltip__header{letter-spacing:.5px;text-transform:uppercase}.chart-tooltip__body{background-color:#fff;margin-bottom:16px;margin-top:16px}.chart-tooltip__body:last-child{margin-bottom:0 !important}.chart-tooltip__body.chart-tooltip__single-row{display:flex;margin-bottom:14px;margin-top:14px}.chart-tooltip__body.chart-tooltip__single-row .chart-tooltip__bar{margin-top:6px;float:none}.chart-tooltip__body.chart-tooltip__single-row .chart-tooltip__series{display:flex;flex-direction:column;justify-content:space-between;width:calc(100% - 24px);margin-left:14px}.chart-tooltip__body.chart-tooltip__single-row .chart-tooltip__value{margin-top:-3px;margin-left:30px;display:flex;flex-direction:column;justify-content:space-between}.chart-tooltip__has-scroll .chart-tooltip__value{margin-right:10px}.chart-tooltip__bar{float:left;width:12px;min-width:12px;height:6px;border-radius:3px;margin-top:5px}.chart-tooltip__series{margin-left:24px;overflow:hidden}.chart-tooltip__series span{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.chart-tooltip__value{margin-top:2px;margin-left:24px}.graph-notes-tooltip{width:286px;max-height:118px;overflow:auto}.graph-notes-tooltip__header{width:100%}.graph-notes-tooltip__header .input-owner{font-weight:500}.graph-notes-tooltip__header .name-wrapper{margin-top:10px}.graph-notes-tooltip__header .el-icon-close{color:#898787;z-index:9999;cursor:pointer}.graph-notes-tooltip__header .el-icon-close:hover{font-size:14px;color:#898787;font-weight:600;opacity:.5;background-color:#f6f6f6}.graph-notes-tooltip__header .note-type{margin-top:12px}.graph-notes-tooltip__body{margin-top:12px}.graph-notes-tooltip__body .input-notes{word-break:break-word;display:inline-block;white-space:normal}.graph-notes-tooltip.minimize{width:180px}.graph-notes-tooltip.minimize .input-owner{font-size:12px}.graph-notes-tooltip.minimize__header{margin-top:0px}.graph-notes-tooltip.minimize .input-notes{font-size:12px}.graph-notes-tooltip.minimize .input-minimizer{margin-bottom:10px}.graph-notes-tooltip.minimize .graph-notes-tooltip__body{margin-top:5px}.graph-notes-tooltip.minimize .name-wrapper{margin-top:0px}.graph-notes-tooltip.minimize .note-date{font-size:11px}.graph-notes-tooltip.minimize .note-type{margin-top:2px;font-size:11px;margin-right:4px}.graph-tooltip-wrapper{margin-right:2px;margin-bottom:4px}.graph-tooltip-wrapper .el-icon-close{color:#898787;z-index:9999;cursor:pointer}.graph-tooltip-wrapper .el-icon-close:hover{font-size:14px;color:#898787;font-weight:600;opacity:.5}.graph-tooltip-wrapper__container{max-height:180px;overflow:hidden;overflow:auto}.cly-vue-tooltip-icon{font-size:17px;color:#a7aeb8;vertical-align:middle;width:18px;height:18px}.cly-in-page-notification{border-radius:4px;text-align:center;margin-bottom:32px}.cly-in-page-notification--light-informational{background-color:#e1efff;color:#333c48}.cly-in-page-notification--light-successful{background-color:#ebfaee;color:#333c48}.cly-in-page-notification--light-destructive{background-color:#fbece5;color:#333c48}.cly-in-page-notification--light-warning{background-color:#fcf5e5;color:#333c48}.cly-in-page-notification--dark-successful{background-color:#24292e;color:#fff}.cly-in-page-notification--dark-informational{background-color:#24292e;color:#fff}.cly-in-page-notification--dark-warning{background-color:#24292e;color:#fff}.cly-in-page-notification--dark-destructive{background-color:#24292e;color:#fff}.cly-in-page-notification a:hover{text-decoration:underline;cursor:hand}.cly-vue-back-link{cursor:pointer;color:#616161;font-size:12px;display:inline-block;text-transform:uppercase;margin-bottom:24px}.cly-vue-back-link:visited{cursor:pointer;color:#616161;font-size:12px;display:inline-block;text-transform:uppercase}.cly-vue-back-link:hover span{text-decoration:underline}.cly-divider{width:100%;border-top:1px solid #ececec}.cly-divider.is-2{border-top-width:2px}.cly-divider.is-dashed{border-top-style:dashed}.cly-divider.is-dotted{border-top-style:dotted}.cly-event-select .el-radio-group{display:flex}.cly-event-select .el-radio-button{flex-grow:1}.cly-event-select .el-radio-button__inner{width:100%}.cly-vue-color-tag__color-tag-wrapper{width:20%}.cly-vue-color-tag__color-tag{width:92%;height:37px;margin-bottom:5px;border-radius:4px}.cly-vue-color-tag__color-tag__selected{border:2px solid #0166d6}.cly-vue-color-tag__checkmark{color:#fff}.cly-vue-empty-view-framed{border:1px solid #e2e4e8;background-color:#f8fafd;border-radius:8px;background-image:url("../images/icons/empty-background.svg");background-position:100% 100%;background-repeat:no-repeat}.cly-vue-json-editor .el-dialog__header{text-align:left;padding:8px 16px 0 !important;color:#fff}.cly-vue-json-editor .el-dialog__body{padding:0 !important}.cly-vue-json-editor__textarea{width:100%;box-sizing:border-box;outline:none;border:none;border-top:1px solid #ececec;border-bottom:1px solid #ececec;background-color:#fcfcfc}.cly-breadcrumbs a:hover{text-decoration:underline}.cly-is-fullwidth{width:100%}.is-estimate:hover{color:#0166d6;text-decoration:underline;text-decoration-style:dotted}#cly-vue-sidebar{left:0;height:100%;z-index:2000;user-select:none;color:#d6d6d6}.cly-vue-sidebar{height:100%}.cly-vue-sidebar .tooltip{display:block !important;z-index:10000}.cly-vue-sidebar .tooltip .tooltip-inner{background:#fff;color:#000}.cly-vue-sidebar .tooltip .tooltip-arrow{display:none}.cly-vue-sidebar .tooltip[x-placement^=right]{margin-left:0px}.cly-vue-sidebar .cly-vue-listbox__item-label{width:350px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cly-vue-sidebar__slide-transition-enter-active{transition:all .3s ease}.cly-vue-sidebar__slide-transition-leave-active{transition:all .8s cubic-bezier(1, 0.5, 0.8, 1)}.cly-vue-sidebar__slide-transition-enter,.cly-vue-sidebar__slide-transition-leave-to{transform:translateX(-224px)}.cly-vue-sidebar ul{list-style:none;padding:0;margin:0}.cly-vue-sidebar ul li{padding:0px}.cly-vue-sidebar__app-selector__dialog_box .el-dialog__body{padding:0px !important}.cly-vue-sidebar .el-collapse{border:none}.cly-vue-sidebar__app-name{font-size:14px}.cly-vue-sidebar__app-name-type{width:120px}.cly-vue-sidebar__app-selector{border:1px solid #424243;box-sizing:border-box;box-shadow:0px 11px 14px rgba(0,0,0,.371635);border-radius:4px;height:50px}.cly-vue-sidebar__app-type{opacity:.57}.cly-vue-sidebar__menu-options{width:48px;height:100%;overflow:auto;color:#a7aeb8;z-index:2005;background-color:#191c20}.cly-vue-sidebar__menu-options ul:first-child li:first-child{margin-top:28px}.cly-vue-sidebar__menu-option{width:48px;margin-top:24px;font-size:20px;cursor:pointer}.cly-vue-sidebar__menu-option .el-popover__reference:hover{color:#fff !important}.cly-vue-sidebar__menu-option-toggle{height:48px;background-color:#24292e;border-top-left-radius:50%;border-bottom-left-radius:50%}.cly-vue-sidebar__menu-option-toggle--no-bg{background-color:rgba(0,0,0,0)}.cly-vue-sidebar__menu-option-toggle--rotate-right{transform:rotate(180deg);transition:transform .7s ease-in-out}.cly-vue-sidebar__menu-option-toggle--rotate-left{transform:rotate(0deg);transition:transform .7s ease-in-out}.cly-vue-sidebar__menu{width:224px;background-color:#24292e;color:#d6d6d6;box-sizing:border-box;height:100%;overflow:hidden;z-index:2004;position:relative}.cly-vue-sidebar__menu--with-banner .__view{padding-bottom:200px}.cly-vue-sidebar__menu-items,.cly-vue-sidebar__submenu-items,.cly-vue-sidebar .el-collapse-item__header{line-height:20px;height:20px;background-color:#24292e;color:#d6d6d6;border:none;font-size:14px;padding:0;font-weight:400;font-family:Inter;padding:6px 12px}.cly-vue-sidebar__menu-items .el-collapse-item__arrow,.cly-vue-sidebar__submenu-items .el-collapse-item__arrow,.cly-vue-sidebar .el-collapse-item__header .el-collapse-item__arrow{margin-right:0px}.cly-vue-sidebar__menu-items:hover,.cly-vue-sidebar__submenu-items:hover,.cly-vue-sidebar .el-collapse-item__header:hover{background-color:#383a3f;color:#fff;border-radius:4px}.cly-vue-sidebar__menu-items--selected,.cly-vue-sidebar__submenu-items--selected,.cly-vue-sidebar .el-collapse-item__header--selected{background-color:#12af51;border-radius:4px;color:#fff}.cly-vue-sidebar__menu-items--selected:hover,.cly-vue-sidebar__submenu-items--selected:hover,.cly-vue-sidebar .el-collapse-item__header--selected:hover{background-color:#12af51;border-radius:4px}.cly-vue-sidebar .el-collapse-item__header.focusing:focus:not(:hover){color:#d6d6d6}.cly-vue-sidebar .el-collapse-item .cly-vue-sidebar__submenu-items{padding-left:32px}.cly-vue-sidebar__el-collapse--selected .el-collapse-item__header{border-radius:4px;background-color:#383a3f;color:#fff}.cly-vue-sidebar .el-collapse-item__content{margin:0;padding:0}.cly-vue-sidebar .el-collapse-item__wrap{border-bottom:1px solid #424243;background-color:#24292e;box-shadow:0px 1px 0px rgba(0,0,0,.4)}.cly-vue-sidebar__header{padding:16px 12px;position:relative}.cly-vue-sidebar__header-search input{background-color:#383a3f;border:none;color:#d6d6d6}.cly-vue-sidebar__header-search input:focus{outline:none;box-shadow:none}.cly-vue-sidebar__header-title{font-size:16px;line-height:24px;font-weight:500;display:inline-block;margin-top:12px}.cly-vue-sidebar__header-button{float:right;top:5px;position:relative}.cly-vue-sidebar__main-menu{overflow:auto;height:calc(100% - 130px)}.cly-vue-sidebar__app-image{width:26px;height:26px;background-size:26px 26px;border-radius:4px;background-position:center;display:inline-block}.cly-vue-sidebar__user-popper{box-sizing:border-box;font-family:Inter;padding:14px 0px;box-shadow:0px 2px 7px rgba(0,0,0,.18815);color:#333c48}.cly-vue-sidebar__language-popper{font-family:Inter;padding:0px;min-width:130px;box-shadow:0px 2px 7px rgba(0,0,0,.18815)}.cly-vue-sidebar__user-details .bu-level{padding:6px 12px}.cly-vue-sidebar__user-links .bu-level{padding:6px;margin:0px 6px}.cly-vue-sidebar__user-links .bu-level:hover{background-color:#f1f3f4;color:#0166d6;border-radius:4px}.cly-vue-sidebar__user-links .bu-level:hover .bu-level-item i{color:#333c48}.cly-vue-sidebar__user-links .bu-level .bu-level-item:first-child{width:15px}.cly-vue-sidebar__user-links .bu-level .bu-level-item i{color:#a7aeb8}.cly-vue-sidebar__user-info{width:140px}.cly-vue-sidebar__user-info p{padding:0;margin:0}.cly-vue-sidebar__user-info p:first-child{font-size:16px;line-height:14px;font-weight:500}.cly-vue-sidebar__user-info p:last-child{margin-top:7px;line-height:17px;font-size:14px;font-weight:400}.cly-vue-sidebar__user-api{width:192px}.cly-vue-sidebar__user-api p{padding:0;margin:0}.cly-vue-sidebar__user-api p:first-child{font-size:12px;line-height:14px;font-weight:500}.cly-vue-sidebar__user-api p:last-child{margin-top:7px;line-height:17px;font-size:12px;font-weight:400}.cly-vue-sidebar__user-image{height:50px;width:50px;border-radius:25px;text-align:center}.cly-vue-sidebar__user-initials{text-transform:uppercase;color:#fff;font-size:16px}.cly-vue-sidebar__user-divider{border:1px solid #ececec;margin-top:5px;position:relative}.cly-vue-sidebar__version{position:absolute;bottom:0;height:48px;display:flex;align-items:center;justify-content:center;width:224px;font-size:10px;background-color:#24292e}.cly-vue-sidebar__version__banner{position:absolute;bottom:12px;left:12px;right:12px;z-index:2001;cursor:default;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;background-color:#191c20;border-radius:4px;padding:16px}.cly-vue-sidebar__version__banner .button{width:100%;margin-top:12px}.cly-vue-sidebar__version__banner .text{font-size:14px;color:#fff}.cly-vue-sidebar .cly-icon-sidebar-app{background-image:url("../images/sidebar/default/app.svg");height:24px;width:24px}.cly-vue-sidebar .cly-icon-sidebar-analytics{background-image:url("../images/sidebar/default/analytics-icon.svg");height:20px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-analytics:hover{background-image:url("../images/sidebar/hover/analytics-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-analytics.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/analytics-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-dashboards{background-image:url("../images/sidebar/default/dashboards-icon.svg");height:20px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-dashboards:hover{background-image:url("../images/sidebar/hover/dashboards-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-dashboards.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/dashboards-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-divider{background-image:url("../images/sidebar/default/divider.svg");width:32px;height:1px}.cly-vue-sidebar .cly-icon-sidebar-management{background-image:url("../images/sidebar/default/management.svg");height:20px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-management:hover{background-image:url("../images/sidebar/hover/management.svg")}.cly-vue-sidebar .cly-icon-sidebar-management.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/management.svg")}.cly-vue-sidebar .cly-icon-sidebar-report-manager{background-image:url("../images/sidebar/default/report-manager.svg");height:18px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-report-manager:hover{background-image:url("../images/sidebar/hover/report-manager.svg")}.cly-vue-sidebar .cly-icon-sidebar-report-manager.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/report-manager.svg")}.cly-vue-sidebar .cly-icon-sidebar-help-center{background-image:url("../images/sidebar/default/help-center.svg");height:25px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-help-center:hover{background-image:url("../images/sidebar/hover/help-center.svg")}.cly-vue-sidebar .cly-icon-sidebar-help-center.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/help-center.svg")}.cly-vue-sidebar .cly-icon-sidebar-notifications{background-image:url("../images/sidebar/default/notifications.svg");height:23px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-notifications:hover{background-image:url("../images/sidebar/hover/notifications.svg")}.cly-vue-sidebar .cly-icon-sidebar-notifications.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/notifications.svg")}.cly-vue-sidebar .cly-icon-sidebar-toggle-left{background-image:url("../images/sidebar/default/toggle-left.svg");height:15px;width:11px}.cly-vue-sidebar--floating{position:absolute;left:48px}.notification-toasts{position:absolute;right:24px;bottom:24px;z-index:600000}.notification-toasts__item{margin-top:12px;min-width:288px;max-width:288px}.notification-toasts__item--small{max-width:288px}.notification-toasts__item--large{max-width:611px}.font-weight-bold{font-weight:600 !important}.font-weight-normal{font-weight:400 !important}.font-italic{font-style:italic !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-big{color:#333c48;font-size:16px;font-weight:400;line-height:22px}.text-big.font-weight-bold{font-weight:500 !important}.text-medium{color:#333c48;font-size:14px;font-weight:400;line-height:20px}.text-medium.font-weight-bold{font-weight:500 !important}.text-smallish{color:#333c48;font-size:13px;font-weight:400;line-height:16px}.text-smallish.font-weight-bold{font-weight:500 !important}.text-small{color:#333c48;font-size:12px;font-weight:400;line-height:18px}.text-small.font-weight-bold{font-weight:500 !important}.text-heading.text-big{line-height:24px;font-weight:500 !important;margin-bottom:16px}.text-heading.text-medium{line-height:20px;font-weight:500 !important;margin-bottom:8px}.text-heading.text-small{line-height:18px;font-weight:500 !important;margin-bottom:4px}.text-smaller{color:#333c48;font-size:10px;font-weight:400;line-height:12px}.text-smaller.font-weight-bold{font-weight:500 !important}.text-smallest{color:#333c48;font-size:10px;font-weight:700;line-height:12px}.text-smallest.font-weight-bold{font-weight:500 !important}.text-button{color:#333c48;font-size:13px;font-weight:400;line-height:16px}.text-button.font-weight-bold{font-weight:500 !important}.cly-trend-up,.cly-trend-down,.cly-trend-neutral{font-size:14px;font-weight:500}.cly-trend-up,.cly-trend-down.negated{color:#12af51}.cly-trend-down,.cly-trend-up.negated{color:#d23f00}.cly-trend-neutral{color:#81868d}.cly-trend-up-icon,.cly-trend-down-icon,.cly-trend-neutral-icon{border-radius:50%;margin-right:4px;display:inline-block;width:17px;height:17px;text-align:center;line-height:17px}.cly-trend-up-icon{background-color:#ebfaee}.cly-trend-down-icon{background-color:#fbece5}.cly-trend-neutral-icon{background-color:#e2e4e8}.cly-trend-up.negated .cly-trend-up-icon{background-color:#fbece5}.cly-trend-down.negated .cly-trend-down-icon{background-color:#ebfaee}.cursor-pointer{cursor:pointer}.icon-size-1{font-size:6px}.icon-size-2{font-size:8px}.icon-size-3{font-size:10px}.icon-size-4{font-size:12px}.icon-size-5{font-size:14px}.icon-size-6{font-size:16px}.text-clickable{cursor:pointer}.color-white{color:#fff !important}.bg-white{background-color:#fff !important}.color-black{color:#000 !important}.bg-black{background-color:#000 !important}.color-warm-gray-100{color:#24292e !important}.bg-warm-gray-100{background-color:#24292e !important}.color-warm-gray-90{color:#24292e !important}.bg-warm-gray-90{background-color:#24292e !important}.color-warm-gray-80{color:#383a3f !important}.bg-warm-gray-80{background-color:#383a3f !important}.color-warm-gray-70{color:#424243 !important}.bg-warm-gray-70{background-color:#424243 !important}.color-warm-gray-60{color:#616165 !important}.bg-warm-gray-60{background-color:#616165 !important}.color-warm-gray-40{color:#d6d6d6 !important}.bg-warm-gray-40{background-color:#d6d6d6 !important}.color-warm-gray-30{color:#ececec !important}.bg-warm-gray-30{background-color:#ececec !important}.color-warm-gray-20{color:#f6f6f6 !important}.bg-warm-gray-20{background-color:#f6f6f6 !important}.color-warm-gray-10{color:#fcfcfc !important}.bg-warm-gray-10{background-color:#fcfcfc !important}.color-cool-gray-100{color:#333c48 !important}.bg-cool-gray-100{background-color:#333c48 !important}.color-cool-gray-50{color:#81868d !important}.bg-cool-gray-50{background-color:#81868d !important}.color-cool-gray-40{color:#a7aeb8 !important}.bg-cool-gray-40{background-color:#a7aeb8 !important}.color-cool-gray-30{color:#cfd6e4 !important}.bg-cool-gray-30{background-color:#cfd6e4 !important}.color-cool-gray-20{color:#e2e4e8 !important}.bg-cool-gray-20{background-color:#e2e4e8 !important}.color-cool-gray-10{color:#f8fafd !important}.bg-cool-gray-10{background-color:#f8fafd !important}.color-cool-gray-05{color:#fbfdfe !important}.bg-cool-gray-05{background-color:#fbfdfe !important}.color-charts-blue{color:#017aff !important}.bg-charts-blue{background-color:#017aff !important}.color-charts-turquoise{color:#39c0c8 !important}.bg-charts-turquoise{background-color:#39c0c8 !important}.color-charts-light-turquoise{color:#ebf9f9 !important}.bg-charts-light-turquoise{background-color:#ebf9f9 !important}.color-charts-amber{color:#f5c900 !important}.bg-charts-amber{background-color:#f5c900 !important}.color-charts-orange{color:#f96300 !important}.bg-charts-orange{background-color:#f96300 !important}.color-charts-light-orange{color:#feefe5 !important}.bg-charts-light-orange{background-color:#feefe5 !important}.color-charts-magenta{color:#f34971 !important}.bg-charts-magenta{background-color:#f34971 !important}.color-charts-light-magenta{color:#feedf1 !important}.bg-charts-light-magenta{background-color:#feedf1 !important}.color-charts-coral{color:#ff9382 !important}.bg-charts-coral{background-color:#ff9382 !important}.color-charts-violet{color:#da6efc !important}.bg-charts-violet{background-color:#da6efc !important}.color-charts-purple{color:#6c47ff !important}.bg-charts-purple{background-color:#6c47ff !important}.color-charts-light-purple{color:#f0edff !important}.bg-charts-light-purple{background-color:#f0edff !important}.color-charts-tan{color:#cdad7a !important}.bg-charts-tan{background-color:#cdad7a !important}.color-charts-olive{color:#aabb5d !important}.bg-charts-olive{background-color:#aabb5d !important}.color-charts-scarlet{color:#f62404 !important}.bg-charts-scarlet{background-color:#f62404 !important}.color-charts-pink{color:#ffb6f5 !important}.bg-charts-pink{background-color:#ffb6f5 !important}.color-yellow-100{color:#e49700 !important}.bg-yellow-100{background-color:#e49700 !important}.color-yellow-70{color:#ffc760 !important}.bg-yellow-70{background-color:#ffc760 !important}.color-yellow-20{color:#fff4ce !important}.bg-yellow-20{background-color:#fff4ce !important}.color-yellow-10{color:#fcf5e5 !important}.bg-yellow-10{background-color:#fcf5e5 !important}.color-blue-100{color:#0166d6 !important}.bg-blue-100{background-color:#0166d6 !important}.color-blue-90{color:#1a75da !important}.bg-blue-90{background-color:#1a75da !important}.color-blue-80{color:#3485de !important}.bg-blue-80{background-color:#3485de !important}.color-blue-70{color:#4d94e2 !important}.bg-blue-70{background-color:#4d94e2 !important}.color-blue-60{color:#67a3e6 !important}.bg-blue-60{background-color:#67a3e6 !important}.color-blue-50{color:#80b3eb !important}.bg-blue-50{background-color:#80b3eb !important}.color-blue-40{color:#99c2ef !important}.bg-blue-40{background-color:#99c2ef !important}.color-blue-30{color:#b3d1f3 !important}.bg-blue-30{background-color:#b3d1f3 !important}.color-blue-20{color:#cce0f7 !important}.bg-blue-20{background-color:#cce0f7 !important}.color-blue-10{color:#e6f0fb !important}.bg-blue-10{background-color:#e6f0fb !important}.color-sky-blue-100{color:#3f9aff !important}.bg-sky-blue-100{background-color:#3f9aff !important}.color-shadow-blue-100{color:#9cbce0 !important}.bg-shadow-blue-100{background-color:#9cbce0 !important}.color-light-blue-100{color:#e1efff !important}.bg-light-blue-100{background-color:#e1efff !important}.color-dark-blue-100{color:#32659d !important}.bg-dark-blue-100{background-color:#32659d !important}.color-teal{color:#00c3ca !important}.bg-teal{background-color:#00c3ca !important}.color-red-100{color:#d23f00 !important}.bg-red-100{background-color:#d23f00 !important}.color-red-10{color:#fbece5 !important}.bg-red-10{background-color:#fbece5 !important}.color-green-100{color:#10a14a !important}.bg-green-100{background-color:#10a14a !important}.color-green-80{color:#12af51 !important}.bg-green-80{background-color:#12af51 !important}.color-green-10{color:#ebfaee !important}.bg-green-10{background-color:#ebfaee !important}.color-primary{color:#0166d6 !important}.bg-primary{background-color:#0166d6 !important}.color-primary-green{color:#12af51 !important}.bg-primary-green{background-color:#12af51 !important}.color-success{color:#67c23a !important}.bg-success{background-color:#67c23a !important}.color-warning{color:#e49700 !important}.bg-warning{background-color:#e49700 !important}.color-danger{color:#d23f00 !important}.bg-danger{background-color:#d23f00 !important}.color-info{color:#909399 !important}.bg-info{background-color:#909399 !important}.color-text-secondary{color:#81868d !important}.bg-text-secondary{background-color:#81868d !important}.color-text-custom-1-active{color:#333c48 !important}.bg-text-custom-1-active{background-color:#333c48 !important}.color-text-placeholder{color:#81868d !important}.bg-text-placeholder{background-color:#81868d !important}.color-text-primary{color:#303133 !important}.bg-text-primary{background-color:#303133 !important}.color-text-regular{color:#333c48 !important}.bg-text-regular{background-color:#333c48 !important}.color-text-custom-1-passive{color:#858a91 !important}.bg-text-custom-1-passive{background-color:#858a91 !important}.color-action-blue{color:#0166d6 !important}.bg-action-blue{background-color:#0166d6 !important}.white-bg{background-color:#fff}.no-bg{background-color:none}.blinker{position:relative;top:-1px;width:6px;height:6px;border-radius:8px;background-color:#2fa732;display:inline-block;vertical-align:middle;margin-right:5px;animation:blinker 1.5s cubic-bezier(0.5, 0, 1, 1) infinite alternate}.has-tooltip{cursor:pointer}.has-ellipsis{word-break:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}.has-ellipsis::after{content:"";display:block}.user-activity-table__divider{width:16px;justify-content:space-evenly;color:#a7aeb8}.user-activity-table__data-item-container{flex-basis:75px;margin-right:14px}.user-activity-table-progress-bar-container{width:100%}.user-activity-table-progress-bar-container progress::-webkit-progress-value{background-color:#39c0c8}.session-overview-date-picker-container{margin-bottom:32px}.session-durations-date-picker-container{margin-bottom:32px}.session-frequency-date-picker-container{margin-bottom:32px}.cly-events-breakdown-horizontal-tile__wrapper{border-right:1px solid #ececec;border-top:1px solid #eef2f5;box-shadow:0px 2px 0px #eaecef;padding:24px 24px 0px 24px}.cly-events-breakdown-horizontal-tile__titleoverview{color:#333c48;font-size:14px;font-weight:400;line-height:20px;font-family:Inter}.cly-events-breakdown-horizontal-tile:nth-of-type(3n) .cly-events-breakdown-horizontal-tile__wrapper{border-right:0}.cly-events-breakdown-horizontal-tile__values-list{align-items:center}.cly-events-breakdown-horizontal-tile__item{height:33.3333333%;padding:0px 0px 24px 0px}.cly-events-breakdown-horizontal-tile__item-title{padding:12px 0px 8px 0px}.cly-events-breakdown-horizontal-tile__value{font-weight:500;font-size:20px;line-height:30px;font-family:Inter;color:#333c48;letter-spacing:.003em}.cly-events-breakdown-horizontal-tile__overviewvalue{font-weight:500;font-size:24px;line-height:32px}.cly-events-breakdown-horizontal-tile__percentage{font-size:14px;font-weight:400;line-height:20px;font-family:Inter;color:#333c48}.cly-events-breakdown-horizontal-tile__trend{font-weight:500;line-height:14px}.cly-events-breakdown-horizontal-tile__arrow{padding-right:3px}.cly-vue-events-overview-top-events{padding-left:24px;padding-bottom:16.5px}.cly-vue-events-overview-top-events--tooltip{margin-left:5.5px}.cly-vue-events-overview-top-events__description{font-weight:500;line-height:14px}.cly-vue-events-overview-top-events__trend{padding-right:3px}.cly-vue-events-overview-monitor-events__charts .cly-vue-chart__echart{padding:0px}.cly-vue-events-overview-monitor-events__empty{min-height:100px}.cly-vue-events-overview-monitor-events__empty__block{margin:32px auto 32px auto;max-width:586px}.cly-vue-events-overview-monitor-events__empty--image{width:242px;height:242px;background-image:url("../images/dashboard/events-overview-noevents.svg")}.cly-vue-events-overview-monitor-events--text{font-weight:500;font-size:12px;color:#a7aeb8;letter-spacing:.5px;font-family:Inter;line-height:14.52px}.cly-vue-events-overview-monitor-events .cly-vue-section__content{border:1px solid #ececec;border-radius:4px}.cly-vue-events-overview-monitor-events--grey{color:#a7aeb8}.cly-vue-events-overview-monitor-events__heading{padding:8px 0px 16px 0px}.cly-vue-events-overview-monitor-events__configure--position{float:right;margin-left:auto}.cly-events-overview-drawer__subheading{font-size:16px;line-height:24px;font-weight:500;font-family:Inter;color:#333c48}.cly-events-overview-drawer--dragicon{margin-right:18px}.cly-events-overview-drawer__event-records{border-radius:6px;width:100%;padding:14px 16px;background-color:#f8fafd}.cly-events-overview-drawer__basis{flex-basis:0}.cly-events-overview-drawer__dropdown{min-width:230px;width:100%}.cly-events-overview-drawer__close-icon{line-height:1;font-size:24px}.cly-monitor-events-breakdown-horizontal-tile__wrapper{border-right:1px solid #ececec;border-bottom:1px solid #ececec;padding:24px 24px 0 24px}.cly-monitor-events-breakdown-horizontal-tile__title{font-size:12px;font-weight:400;line-height:20px;padding-bottom:8px}.cly-monitor-events-breakdown-horizontal-tile__titleoverview{font-size:12px;font-weight:400;line-height:20px}.cly-monitor-events-breakdown-horizontal-tile__eventproperty{font-size:12px;font-weight:400;line-height:20px;color:#81868d}.cly-monitor-events-breakdown-horizontal-tile:nth-of-type(2n) .cly-monitor-events-breakdown-horizontal-tile__wrapper{border-right:none}.cly-monitor-events-breakdown-horizontal-tile:nth-child(1) .cly-monitor-events-breakdown-horizontal-tile__wrapper,.cly-monitor-events-breakdown-horizontal-tile:nth-child(2) .cly-monitor-events-breakdown-horizontal-tile__wrapper{border-top:1px solid #ececec}.cly-monitor-events-breakdown-horizontal-tile__values-list{align-items:center}.cly-monitor-events-breakdown-horizontal-tile__item{height:33.3333333%;padding:0px 0px 16px 0px}.cly-monitor-events-breakdown-horizontal-tile__item-title{padding-top:4px}.cly-monitor-events-breakdown-horizontal-tile__value{font-weight:500;font-size:20px;line-height:30px}.cly-monitor-events-breakdown-horizontal-tile__overviewvalue{font-weight:500;font-size:24px;line-height:32px}.cly-monitor-events-breakdown-horizontal-tile__percentage{font-size:14px;font-weight:300;line-height:20px}.cly-monitor-events-breakdown-horizontal-tile__trend{font-weight:500;line-height:14px}.cly-monitor-events-breakdown-horizontal-tile__arrow{padding-right:3px}.cly-monitor-events-breakdown-horizontal-tile__overview-name{z-index:1}.cly-monitor-events-breakdown-horizontal-tile__width-50{width:50%}.cly-vue-events-all{font-size:24px;line-height:32px;font-weight:500;padding-top:6px;letter-spacing:.003em;font-family:Inter;color:#333c48}.cly-vue-events-all__alerts a{text-decoration:underline}.cly-vue-events-all__subheading{font-size:14px;line-height:20px;color:#333c48}.cly-vue-events-all--padding{padding-top:32px}.cly-vue-events-all--margin{margin-right:32px}.cly-vue-events-group{color:#fff;background-color:#a7aeb8;font-size:10px;line-height:10px;font-weight:500;letter-spacing:.0042em;vertical-align:middle;padding:3px 4px;border-radius:4px}.cly-vue-events-all-placeholder-text{color:#81868d;font-size:12px;line-height:14.52px;font-weight:500;letter-spacing:.005em;white-space:nowrap}.cly-vue-events-all-date-picker{margin-right:26px}.cly-vue-events-all-tooltip{margin-left:10px}.cly-vue-overview-events-header{padding-right:9.5px;letter-spacing:.003em}.cly-vue-events-header{padding:8px 24px 24px 24px;letter-spacing:.003em;font-family:Inter;line-height:32px;border-bottom:1px solid #ececec;background-color:#fff}.cly-vue-events-header__subheading--widget{padding-right:5.33px;color:#81868d}.cly-vue-events-header__subheading--text{font-family:Inter;line-height:20px;font-size:14px;color:#81868d}.cly-vue-events-header--tooltip{padding-left:9.5px}.cly-vue-events-overview-subheadings{padding-right:9.5px;letter-spacing:.003em;font-family:Inter}.cly-vue-events-overview-subheadings--tooltip{padding-top:3px}.cly-vue-events-overview-subheadings--font{font-family:Inter}.cly-vue-listbox.is-expandable{z-index:2001}.cly-vue-events-omitted-segments__item{color:#81868d;font-size:14px;font-weight:500;line-height:16px;padding:8px;margin:8px}.cly-vue-events-omitted-segments__title{color:#a7aeb8;font-size:13px;font-weight:500;letter-spacing:.5px;text-transform:uppercase;line-height:16px;padding:8px;margin:8px}.technology-analytics-wrapper .allPlatformVersions{border-left:1px solid #ececec;height:264px;border-top:1px solid #ececec;border-bottom:1px solid #ececec;background-color:#fff}.technology-analytics-wrapper .emptyVersions{height:264px;position:relative;border-left:1px solid #ececec;border-top:1px solid #ececec;border-bottom:1px solid #ececec;background-color:#fff}.technology-analytics-wrapper .emptyVersions div{font-family:Inter;font-style:normal;font-weight:normal;font-size:14px;line-height:20px;color:#333c48;position:absolute;top:50%;text-align:center;width:100%;margin-top:-10px}.technology-analytics-wrapper .version-graph-title{font-family:Inter;font-style:normal;font-weight:normal;font-size:14px;line-height:20px;color:#333c48}.technology-analytics-wrapper .version-graph-title .divider{color:#a7aeb8}.technology-analytics-wrapper .version-graph-title div:first-child{text-align:left}.technology-analytics-wrapper .version-graph-title div:nth-child(2){text-align:right}.technology-analytics-wrapper .version-graph-block .version-graph-title{margin:0px;padding-bottom:4px}.technology-analytics-wrapper .version-graph-title .bu-column{padding:0px}.technology-analytics-wrapper .cly-vue-metric-card__wrapper .bu-is-flex-direction-column{margin-right:-1.5rem}.technology-analytics-wrapper .selector_wrapper{position:relative;top:-5px;margin-left:8px}.technology-analytics-wrapper .app-resolution-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .metricCardsWrapper{height:115px}.technology-analytics-wrapper .metricCardsWrapper .emptyCard{min-height:107px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;border-top:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper .emptyCard div{position:absolute;top:50%;text-align:center;width:100%;line-height:20px;margin-top:-10px}.technology-analytics-wrapper .platformVersionsWrapper .emptyCard{min-height:250px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;border-top:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper .platformVersionsWrapper{height:309px;padding-bottom:20px}.technology-analytics-wrapper .metricCardsWrapper .bu-columns{flex-wrap:nowrap}.technology-analytics-wrapper .metricCardsWrapper .__view{width:auto !important}.technology-analytics-wrapper .metricCardsWrapper .cly-vue-metric-cards{border-bottom:none}.technology-analytics-wrapper .metricCardsWrapper .cly-vue-metric-card{background-color:#fff;border-top:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper__items{height:115px}.technology-analytics-wrapper__versions{height:291px;padding-bottom:20px}.technology-analytics-wrapper__empty-card{min-height:107px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;top:50%;text-align:center;border-bottom:1px solid #ececec}.technology-analytics-wrapper__empty-card div{position:absolute;top:50%;text-align:center;width:100%;line-height:20px;margin-top:-10px}.technology-analytics-wrapper__versions-empty-card{min-height:250px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;border-top:1px solid #ececec;border-right:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper__versions-empty-card div{position:absolute;top:50%;text-align:center;width:100%;line-height:20px;margin-top:-10px}.technology-analytics-wrapper .app-version-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .app-version-date-picker-container+.cly-vue-notification__alert-box{margin-top:-8px}.technology-analytics-wrapper .app-resolution-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .app-carrier-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .devices-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .version-graph-block .version-graph-title{margin:0px;padding-bottom:4px}.technology-analytics-wrapper__metric-cards .bu-column:last-child{margin-right:unset !important}.technology-analytics-wrapper .technology-pie-graphs .cly-vue-chart__echart{padding-right:0px}.technology-analytics-wrapper .technology-pie-graphs .bu-level .bu-level-right{padding-right:5px}.technology-analytics-wrapper .technology-pie-graphs .bu-column:first-child .cly-vue-chart__echart .cly-vue-chart-legend__secondary{border-right:1px solid #ececec}.technology-analytics-wrapper .technology-pie-graphs .bu-column:first-child .cly-vue-chart__echart .bu-level .bu-level-right{border-right:1px solid #ececec}.technology-analytics-wrapper .technology-pie-graphs .cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .__vuescroll .__rail-is-vertical{right:5px !important}.version-graph-block:last-child{margin-bottom:12px}.technology-dashboard-widget>.bu-column:nth-child(odd)>.cly-vue-section{padding-left:4px}.technology-dashboard-widget>.bu-column:nth-child(even)>.cly-vue-section{padding-right:12px}.routename-analytics-geo .geo-date-picker-container{margin-bottom:32px}.routename-analytics-geo .radio-column{width:256px}.leaflet-noclick-map .leaflet-interactive{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-control-zoom{user-select:none;border-color:#dcdcdc !important}.leaflet-control-zoom a{color:#333c48;border-bottom-color:#dcdcdc}.geo-analytics-data .cly-vue-button{padding:6px 10px;border-radius:4px}.user-management-drawer-content__pp-options{height:fit-content;align-self:center;margin-left:15px;box-shadow:none}.user-management-drawer-content__pp-options .el-button{box-shadow:none;border:none}.user-management-drawer-content__user-pp{width:100px;height:100px;border-radius:50%}.user-management-drawer-content #user-drawer-dropzone{background-color:#f8fafd;border-radius:6px}.user-management-drawer-content__custom-options{box-shadow:none}.user-management-drawer-content__custom-options div button{box-shadow:none;padding:0px;background:none;border:none}.user-management-drawer-content__under-input-text{text-decoration:underline;cursor:pointer}.user-management-drawer-content__top-section{display:flex;flex-direction:row;justify-content:space-between}.user-management-drawer-content__bottom-section{display:flex;margin-top:28px;flex-direction:column;justify-content:space-between}.user-management-drawer-content__input-wrapper{margin-top:10px}.user-management-drawer-content__label{display:flex;justify-content:space-between;flex-direction:column}.user-management-drawer-content__label--text-secondary{color:#81868d}.user-management-drawer-content__label-text{font-style:normal;font-weight:normal;font-size:13px;line-height:16px;color:#333c48}.user-management-drawer-content__help-text{font-family:Inter;font-style:normal;font-weight:normal;font-size:12px;line-height:16px;color:#81868d}.user-management-drawer-content__help-menu-icon{font-family:Inter;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;color:#81868d}.user-management-drawer-content__light-button{background-color:#ddedff;color:#0166d6;font-weight:bold;padding:10px 20px;width:fit-content;margin-top:10px;font-size:13px;border-radius:5px}.user-management-drawer-content__profile-picture-area{margin-top:9px;margin-left:10px}.user-management-drawer-content__profile-picture-area__upload-section{border:1px dashed #81868d;padding:30px;border-radius:8px;display:flex;flex-direction:column;justify-content:center;align-items:center}.user-management-drawer-content__profile-picture-area__upload-section__img-box{background-color:#e2e4e8;height:100px;width:100px;border-radius:50%}.user-management-drawer-content__profile-picture-area__upload-section__description-box{font-size:12px;text-align:center;width:80%}.user-management-drawer-content__profile-picture-area__upload-section__description-box--bold{font-weight:500;color:#0166d6}.user-management-drawer-content__profile-picture-area__upload-section__description-box--gray{color:gray}.user-management-drawer-content__drawer-sub-section{border-radius:6px;background-color:#f8fafd;margin-top:10px;margin-bottom:24px}.user-management-drawer-content__drawer-sub-title{display:flex;flex-direction:row;justify-content:space-between;padding:5px 0px;font-weight:500;font-size:16px}.user-management-drawer-content__permission-table-header{display:flex;justify-content:space-between;margin-top:10px;font-size:12px;border-bottom:1px solid #ebebeb;padding-bottom:10px}.user-management-drawer-content__feature-search{width:calc(40% - 15px);margin-left:10px}.user-management-drawer-content__feature-search .el-input__inner{background-color:#fff !important}.user-management-drawer-content__permission-type-cols{width:75%;display:flex;justify-content:space-evenly}.user-management-drawer-content__permission-type{text-align:center;font-size:11px;width:25%;margin:auto}.user-management-drawer-content__permission-type .check-label{font-size:15px}.user-management-drawer-content__permission-type .check-text{margin-top:2px}.user-management-drawer-content__permission-type .el-checkbox__label{font-weight:medium !important}.user-management-drawer-content__permission-table-content{height:300px;overflow-y:scroll;padding:10px 0px}.user-management-drawer-content__permission-row{display:flex;flex-direction:row;height:30px;justify-content:center;align-items:center}.user-management-drawer-content__feature-name{width:calc(35% - 15px);text-align:left;padding-left:15px;display:flex;justify-content:flex-start;align-items:center;font-style:normal;font-weight:normal;font-size:14px;line-height:20px;color:#333c48}.user-management-drawer-content__feature-permissions{width:65%;display:flex;justify-content:space-evenly}.user-management-drawer-content__permission-box{text-align:center;width:35%}.user-management-drawer-content__section-title{display:flex;flex-direction:row;justify-content:space-between}.user-management-drawer-content__section-header{font-family:Inter;font-style:normal;font-weight:500;font-size:12px;line-height:15px;letter-spacing:.5px;text-transform:uppercase;color:#333c48}.user-management-drawer-content__role-section .check-text{margin-top:4px !important}.user-management-drawer-content__role-section .el-checkbox__label{font-weight:normal !important}.user-management-drawer-content__app-selector{width:100%}.user-management-drawer-content__add-permission-set{background-color:#e1efff;border-radius:4px;cursor:pointer;padding:9.5px 12px;font-weight:500;font-size:13px;line-height:13px;color:#0166d6;width:fit-content}.user-management-drawer-content__group-selector{width:100%}.user-management-drawer-content .check-label{color:#0166d6 !important}.user-management-drawer-content .el-tag--info{background-color:#81868d;color:#fff}.user-management-drawer-content .el-icon-close{background-color:#fff !important}.user-management-drawer-content .el-icon-close:hover{color:#81868d !important}.user-management-drawer-content .dropzone-custom-content{display:flex;flex-direction:column;justify-content:center;align-items:center}.user-management-drawer-content .dz-image>div{height:100px;width:100px;border-radius:50%}.user-management-drawer-content .dz-image-preview{display:flex !important;flex-direction:column;justify-content:center;align-items:center}.user-management-drawer-content .dz-image-preview .dz-remove{font-size:12px;font-weight:bold}.user-management-drawer-content .dz-image-preview .dz-image{display:block !important}.manage-users-table{margin:0px}.manage-users-table__groups{display:inline-flex;color:#32659d}.manage-users-table__groups a:hover{color:#1a75da;text-decoration:underline}.manage-users-table__groups-label{margin:0px 2px}.user-management-filter-form .select-full-width{width:100%}.user-management-view .el-tabs--button>.el-tabs__header{padding:24px 24px 0px 24px}.user-management-view .manage-users-action-btn{background-color:#12af51}#main_home_view .el-loading-parent--relative{min-height:100px}#main_home_view .bu-level-left:first-child>h2{font-size:20px !important}#main_home_view .cly-back-link i{color:#333c48}#main_home_view .componentBlock>div{padding-top:16px;padding-bottom:20px}.app-management-view h2{letter-spacing:.3px}.app-management-view h3{letter-spacing:.3px}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-drawer__steps-wrapper .cly-vue-drawer__steps-container{margin-top:12px}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-drawer__steps-wrapper .cly-vue-drawer__steps-container .cly-vue-form-step__section{padding-left:24px !important;padding-right:24px !important;padding-top:12px !important;padding-bottom:12px !important}.app-management-view .cly-vue-section__content{border:1px solid #ececec;border-radius:4px}.app-management-view .cly-vue-section__content .el-collapse{border:none}.app-management-view .cly-vue-section__content .app-section{margin-left:12px;margin-right:4px}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section{margin-bottom:24px !important;margin-left:0 !important;margin-right:0 !important;color:#333c48;display:flex;justify-content:space-between}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section .cly-vue-form-field__inner .el-form-item .el-form-item__content{line-height:0}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section div p{font-size:14px;line-height:14px;margin:0}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section div p+p{font-size:12px;line-height:16px;font-weight:400;margin-top:4px}.app-management-view .cly-vue-section__content .app-section span div .bu-columns{padding:0 !important;margin:0}.app-management-view .cly-vue-section__content .app-section span div .bu-columns .bu-is-4{width:50%;margin-right:48px}.app-management-view .cly-vue-section__content .app-section span div .bu-columns .bu-is-8{width:37.33%}.app-management-view .account-settings-avatar-box{padding:24px;overflow:auto;flex:auto}.app-management-view .account-settings-avatar-box .el-upload{display:block}.app-management-view .feedback-settings__upload .el-upload{display:block}.app-management-view .feedback-settings__upload img{width:165px;height:50px;margin-left:24px;cursor:default}.app-management-view .feedback-settings__colorpicker .cly-vue-color-picker__picker{overflow:auto}.app-management-view .feedback-settings__button:active,.app-management-view .feedback-settings__button:focus,.app-management-view .feedback-settings__button:hover{background-color:unset !important;border-color:unset !important}.app-management-view .app-management-list{max-height:681px;max-width:223px}.app-management-view .app-management-list .cly-vue-listbox__item span{margin-left:8px;position:relative;top:-8px}.app-management-view .app-management-details .bu-columns{margin:0 !important;padding:0 !important;border-bottom:1px solid #ececec}.app-management-view .app-management-details .bu-columns:last-child{border-bottom:none;margin-bottom:-20px}.app-management-view .app-management-details .el-collapse-item__header.is-active{border-bottom:1px solid #ececec}.app-management-view .app-management-details .el-collapse-item__content{padding-bottom:0}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-form-step__section{padding-left:48px !important;padding-right:24px !important;padding-top:12px !important;padding-bottom:12px !important}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-form-step__section div:nth-child(2) .el-form-item__content{display:flex !important;justify-content:right !important;float:right;line-height:inherit}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-form-step__section div:nth-child(2) .el-form-item__content>div{max-width:232px}.app-management-view .app-management-details .el-collapse-item__arrow{color:#81868d}.app-management-view .app-management-details .el-collapse-item__header{color:#333c48;padding-left:24px !important;text-transform:uppercase}.app-management-view .app-management-details .el-collapse-item__wrap{overflow:unset}.app-management-view .box-card{background-color:#e6f0fb;border:1px solid #0166d6;box-sizing:border-box;box-shadow:0px 1px 0px #ececec;border-radius:4px}.app-management-view .box-card .el-card__body{padding:24px}.app-management-view .box-card .el-card__body p a{color:#0166d6}.app-management-view .box-card .el-card__body p:nth-of-type(1){line-height:24px;font-weight:500;font-size:16px}.app-management-view .box-card .el-card__body p{font-size:14px}.el-dropdown-menu__item--divided__fix-broken{border:none}.el-dropdown-menu__item--divided__fix-broken::before{height:0px}.add-border{margin:0 8px;padding:0;border-top:1px solid #ececec}.add-border>a{display:none}@media(max-width: 2133px){.app-section span div div:nth-last-child(1) .cly-vue-form-step__section{display:block !important}.app-section span div div:nth-last-child(1) .cly-vue-form-step__section .el-input{width:268%}}@media(max-width: 1280px){.app-section span div div:nth-last-child(2) .cly-vue-form-step__section{display:block !important}.app-section span div div:nth-last-child(2) .cly-vue-form-step__section .el-input{width:177%}}.report-manager-filter-form .select-full-width{width:100%}.header{display:flex;flex-direction:column;gap:20px}.setup-logo{margin-top:38px;margin-left:72px;width:165px;height:50px;background-image:url("../images/pre-login/countly-logo-dark.svg");background-repeat:no-repeat;background-size:contain;position:fixed;left:0px;top:0px}.centered-content{min-height:100vh;display:flex;align-items:center;justify-content:center;margin-bottom:-35px}.setup-byline{color:#81868d;margin-top:12px;font-size:14px}.setup-form{width:520px}.setup-radio-grid{display:grid;grid-template-columns:1fr 1fr 1fr;column-gap:10px;row-gap:10px}.setup-radio .el-radio.is-checked{background-color:#e1efff}.setup-radio .el-radio{margin-left:0 !important}.setup-radio .is-autosized.is-bordered .el-radio__input{display:none !important}.setup-radio .el-radio__label{padding-left:0}.setup-radio-item{display:flex;align-items:center;font-size:24px}.setup-radio-icon-bg{width:40px;height:40px;display:flex;align-items:center;justify-content:center;background-color:#e1efff;border-radius:100%}.is-checked .setup-radio-icon-bg{background-color:#0166d6;color:#fff}.setup-radio-icon{background-image:url("../images/dashboard/onboarding/box.svg");background-position:center;background-repeat:no-repeat;width:100%;height:100%}.setup-radio-icon--app-type-mobile{background-image:url("../images/dashboard/onboarding/app-type-mobile.svg")}.setup-radio-icon--app-type-web{background-image:url("../images/dashboard/onboarding/app-type-web.svg")}.setup-radio-icon--app-type-desktop{background-image:url("../images/dashboard/onboarding/app-type-desktop.svg")}.is-checked .setup-radio-icon{filter:brightness(10)}.setup-video-container{max-width:800px}.consent-form{width:700px}.consent-blurb{color:#81868d;font-size:14px}.consent-question{font-size:14px}.initial-populator{width:800px}.quickstart-item{display:flex;align-items:flex-start}.quickstart-item-desc{color:#81868d;text-align:left;word-break:break-word}.quickstart-link,.quickstart-link:visited{color:#0166d6}.quickstart-link:hover{color:#333c48}.preset-list{display:inline-block;position:absolute;left:170px;border:1px solid #a7aeb8;border-radius:4px;background-color:#fff}.preset-list__header{z-index:1997;position:relative;padding:8px 16px}.preset-list__header::after{content:"";position:absolute;width:100%;height:16px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);left:0;opacity:.12;top:100%}.preset-list__header .el-button--primary{border:unset;font-weight:500;box-shadow:none}.preset-list__header .el-button--primary:hover{background-color:#cce0f7;color:#0166d6}.preset-list__header .el-button--text{font-weight:500;font-size:14px}.preset-list__body{background-color:#f9f9f9;height:400px;border-radius:4px}.preset-list__body::after{content:"";position:absolute;width:100%;height:16px;background:linear-gradient(0deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);opacity:.12;left:0;bottom:0}.preset-list__body .preset{display:flex;flex-direction:column;justify-content:center;width:300px;background-color:#fff;border:1px solid #ececec;border-radius:4px;padding:12px;margin-bottom:8px;cursor:pointer}.preset-list__body .preset:first-child{margin-top:16px}.preset-list__body .preset:last-child{margin-bottom:16px}.preset-list__body .preset:hover{border:1px solid #0166d6;background-color:#e1efff}.preset-list__body .preset--selected{border:1px solid #0166d6}.preset-list .sortable-chosen{background-color:#e1efff !important}#preset-drawer .el-radio__label{display:flex;align-items:center}#preset-drawer .el-radio__input{display:flex}#preset-drawer .el-checkbox{font-weight:400}#preset-drawer .el-checkbox__label{padding-left:8px;color:#333c48}#preset-drawer .el-checkbox:hover__label{color:#333c48}/*# sourceMappingURL=manifest.css.map */ +.cly-icon-btn{height:12px;width:12px;display:inline-block;background-position:center;background-repeat:no-repeat;background-size:100%}.cly-icon-prefix-icon{height:14px;width:12px;margin-left:7px;display:inline-block;background-position:center;background-repeat:no-repeat;background-size:100%}.cly-icon-input{height:20px;width:20px;display:inline-block;background-position:center;background-repeat:no-repeat;background-size:100%}.cly-icon-zoom{background-image:url("../images/icons/zoom-icon.svg")}.cly-icon-arrow-down{background-image:url("../images/icons/arrow_drop_down.svg")}.cly-icon-arrow-up{background-image:url("../images/icons/arrow_drop_up.svg")}.cly-icon-menu{background-image:url("../images/icons/menu.svg")}.cly-icon-date{background-image:url("../images/icons/date.svg")}.cly-icon-download{background-image:url("../images/icons/download-icon.svg")}.cly-icon-save{background-image:url("../images/icons/save.svg")}.cly-icon-load{background-image:url("../images/icons/load.svg")}.cly-icon-upload{background-image:url("../images/icons/upload.svg")}.cly-icon-arrow_forward-circled{background-image:url("../images/icons/arrow_forward-circled.svg")}.cly-icon-copy{background-image:url("../images/icons/copy.svg")}.cly-icon-copy_interactive{background-image:url("../images/icons/copy_inactive.svg")}.cly-icon-copy_interactive:hover{background-image:url("../images/icons/copy.svg")}.el-button [class*=ion-]+span,[class*=cly-icon-]+span{margin-left:6px}.el-button-group>.el-button{display:flex;align-items:center}.el-button-group>.el-select:first-child .el-button{display:flex;align-items:center}.cly-countly-icon-solid{width:20px;height:20px;background-image:url("../images/icons/countly-icon-solid.svg")}.cly-countly-icon-outline{width:20px;height:20px;background-image:url("../images/icons/countly-icon-outline.svg")}.cly-countly-icon-outline-trash-16px{background-position:99px 962px}.cly-countly-icon-outline-code-16px{background-position:99px 674px}.cly-countly-icon-outline-external-link-16px{background-position:419px 674px}.cly-countly-icon-outline-download-16px{background-position:299px 819px}.cly-countly-icon-outline-x-16px{background-position:139px 1107px}.cly-countly-icon-outline-document-download-16px{background-position:179px 1058px}.cly-countly-icon-outline-exclamation-circle-16px{background-position:339px 868px}.cly-countly-icon-outline-plus-circle-16px{background-position:299px 867px}.cly-countly-icon-outline-globe-16px{background-position:219px 1010px}.cly-countly-icon-outline-user-group-16px{background-position:300px 1202px}*{outline:none}html{height:100%;width:100%}body{height:100%;width:100%;margin:0;padding:0;overflow:auto;background-color:#f9f9f9;user-select:text;font-family:Inter,Ubuntu,Helvetica,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1{font-size:32px;line-height:48px;font-weight:500;margin:0;padding:10;color:#333c48}h2{font-size:24px;line-height:32px;font-weight:500;margin:0;padding:0;color:#333c48}h3{font-size:20px;line-height:30px;font-weight:500;margin:0;padding:0;color:#333c48}h4{font-size:16px;line-height:24px;font-weight:500;margin:0;padding:0;color:#333c48}h5{font-size:14px;line-height:20px;font-weight:500;margin:0;padding:0;color:#333c48}h6{font-size:14px;line-height:18px;font-weight:500;margin:0;padding:0;color:#333c48}textarea,input,p,h1,h2,h3,h4,h5,h6,button{font-family:Inter !important}.el-table th{padding:9px 0px;box-sizing:border-box;text-transform:uppercase;font-size:12px;font-style:normal;font-weight:500;border-bottom-width:2px}.el-table th .cell{line-height:16px;letter-spacing:.5px}.el-table th:hover .sort-caret.ascending{border-bottom-color:#005def}.el-table th:hover .sort-caret.descending{border-top-color:#005def}.el-table th.is-leaf{border-bottom-width:2px;border-top:1px solid #eef2f5}.el-table td{padding:14px 0px;font-style:normal;font-weight:normal;font-size:14px;box-sizing:border-box}.el-table td .cell{line-height:20px}.el-table.el-table--has-options td .cell{line-height:23px}.el-table th:first-child .cell,.el-table td:first-child .cell{padding-left:16px}.el-table th:last-child .cell,.el-table td:last-child .cell{padding-right:16px}.el-table .caret-wrapper{height:15px}.el-table .sort-caret{border-width:4px}.el-table .sort-caret.ascending{top:-2px}.el-table .sort-caret.descending{bottom:-1px}.el-table .ascending .sort-caret.ascending{border-bottom-color:#005def}.el-table .descending .sort-caret.descending{border-top-color:#005def}.el-table .cell{word-break:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}.el-table .cell>.el-checkbox,.el-table .cell>.el-switch,.el-table .cell>.el-radio{overflow:unset}.el-table table{width:0px}.el-radio.is-autosized{padding:16px;display:flex;width:100%;margin-right:0;height:unset;background-color:#fff}.el-radio.is-autosized.is-bordered+.el-radio.is-autosized.is-bordered{margin-left:10px}.el-radio.is-autosized.is-bordered{border-color:#cfd6e4;display:flex;align-items:center}.el-radio.is-autosized.is-bordered .el-radio__input{display:flex}.el-radio.is-autosized.is-bordered.is-multiline{align-items:flex-start}.el-radio.is-autosized.is-bordered.is-checked{border-color:#0166d6}.el-radio.is-autosized-vstack{padding:12px;display:flex;width:100%;margin-right:0;height:unset;background-color:#fff}.el-radio.is-autosized-vstack .el-radio__label{justify-content:space-between;display:flex;width:100%;align-items:baseline}.el-radio.is-autosized-vstack.is-bordered{align-items:center}.el-radio.is-autosized-vstack.is-bordered .el-radio__input{display:flex}.el-radio.is-autosized-vstack.is-bordered.is-multiline{align-items:flex-start}.el-radio.is-autosized-vstack.is-bordered.is-multiline .el-radio__label{padding-top:2px}.el-radio.is-autosized-vstack.is-bordered+.el-radio.is-autosized-vstack.is-bordered{margin-left:0;margin-top:7px}.el-radio.is-bordered{border:1px solid #cfd6e4}.el-radio:hover:not(.is-disabled) .el-radio__label{color:inherit}.el-radio.is-checked:not(.is-disabled):hover .el-radio__label{color:inherit}.el-radio__input.is-checked+.el-radio__label{color:inherit}.el-collapse.is-bordered-box{border:1px solid #ececec;box-sizing:border-box;box-shadow:0px 1px 0px #ececec;border-radius:4px}.el-collapse.is-bordered-box .el-collapse-item__header{border-radius:4px;padding:0rem 1.5rem 0rem 1.5rem}.el-collapse.is-bordered-box .el-collapse-item__header.is-active{border-bottom-color:#ececec}.el-collapse.is-bordered-box .el-collapse-item__wrap{background-color:rgba(0,0,0,0)}.el-collapse.is-bordered-box .el-collapse-item__content{margin:0rem 1.5rem 0rem 1.5rem}.el-switch__label *{font-weight:normal;color:#a7aeb8;font-size:12px}.el-input.is-adaptive .el-input__inner{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:10px;vertical-align:middle}.el-select .el-input.is-arrow .el-input__inner{padding-right:30px}.el-button.is-wireframe{background:rgba(0,0,0,0);border:1px dotted #0166d6;box-sizing:border-box;box-shadow:0px 2px 4px rgba(1,122,255,.07)}.el-button.el-button--text.is-danger{color:#d23f00}.el-button.el-button--text.is-danger:hover{background-color:#f6f6f6}.el-button.is-light-blue{box-shadow:none;-webkit-box-shadow:none;border:none;font-weight:500;height:32px}.el-button.is-light-blue:not(.is-disabled){color:#0166d6;background-color:#e1efff}.el-button.is-light-blue:not(.is-disabled):hover{background-color:#cce0f7}.el-button-group>.el-select{float:left}.el-button-group>.el-select:first-child .el-button{border-top-right-radius:0;border-bottom-right-radius:0;-webkit-box-shadow:none;box-shadow:none}.el-button-group>.el-select:not(:last-child){margin-right:-1px}.el-button-group>.el-select:active,.el-button-group>.el-select:focus,.el-button-group>.el-select:hover{z-index:1}.el-button-group>.el-select:active .el-button,.el-button-group>.el-select:focus .el-button,.el-button-group>.el-select:hover .el-button{z-index:1}.el-input-number.is-slim .el-input__inner{padding-left:5px;padding-right:26px}.el-input-number.is-slim.el-input-number--small .el-input-number__decrease,.el-input-number.is-slim.el-input-number--small .el-input-number__increase{width:22px;font-size:14px}.el-tabs--card>.el-tabs__header{margin:0}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap{margin-bottom:-2px}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap.is-scrollable{border-bottom:none;border-top-right-radius:4px;border-top-left-radius:4px}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-next{background:linear-gradient(90deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0.85) 100%)}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-prev{background:linear-gradient(270deg, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 0.7) 50%, rgba(255, 255, 255, 0.85) 100%)}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-next,.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-prev{opacity:.85}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-next:hover,.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap .el-tabs__nav-prev:hover{opacity:1}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-top-left-radius:3px}.el-tabs--card>.el-tabs__header .el-tabs__item:last-child{border-top-right-radius:3px}.el-tabs--card>.el-tabs__header .el-tabs__item{background-color:#fcfcfc;padding:0;height:auto;text-align:left;line-height:32px;border-bottom:2px solid #ececec}.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding:0}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:#0166d6;background-color:#fff}.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding:0px}.el-radio-group.is-full{display:flex}.el-radio-group.is-full .el-radio-button{flex-grow:1}.el-radio-group.is-full .el-radio-button__inner{width:100%}.el-dropdown-menu__item{padding:7px 6px}.el-dropdown-menu__item>i.cly-icon-outline{padding:2px 7px}.el-select-dropdown .el-scrollbar__bar{opacity:1 !important;right:0}.el-select-dropdown__item{padding:7px 6px}.el-tiptap-editor{border-radius:4px}.el-tiptap-editor .el-tiptap-editor__menu-bar{border-color:#cfd6e4}.el-tiptap-editor .el-tiptap-editor__menu-bar::before{background-color:#cfd6e4}.el-tiptap-editor>.el-tiptap-editor__content{border:1px solid #cfd6e4 !important;border-top:0 !important;border-bottom:0 !important}.el-tiptap-editor>.el-tiptap-editor__content pre code{font-family:"Courier New"}.el-tiptap-editor .el-tiptap-editor__footer{border-color:#cfd6e4}.el-tiptap-editor .border-top-radius{border-top-left-radius:4px !important;border-top-right-radius:4px !important}.el-tiptap-editor .border-bottom-radius{border-bottom-left-radius:4px !important;border-bottom-right-radius:4px !important}.el-tiptap-editor .fa-icon{fill:#333c48}.el-tiptap-editor__content{overflow-x:unset !important}.el-tiptap-editor__content>pre>code{font-family:"Courier New" !important}.el-tiptap-editor__content span>span{background-color:inherit}.el-tiptap-editor__content hr{width:100%}.el-dialog.el-tiptap-edit-link-dialog .el-button.is-round{border-radius:4px}.el-dialog.el-tiptap-edit-link-dialog .el-button.el-button--primary{border-color:#12af51;background-color:#12af51}.el-checkbox__input .el-checkbox__inner{color:#fff;text-align:center}.el-checkbox__input.is-checked .el-checkbox__inner::before{content:"" !important;font-family:"countly-icons-outline" !important;border:none !important;position:absolute;top:0px;width:15px;left:0px}.el-checkbox__input.is-checked .el-checkbox__inner::after{border:none !important}.el-checkbox__input.is-checked.is-disabled .el-checkbox__inner{color:#d0d0d0}.el-checkbox__inner::after{display:none}.el-checkbox.is-checked .el-checkbox__inner::after{display:block}.el-upload__folder-preview-content{height:32px}.el-upload-dragger{background-color:#fcfcfc;display:flex;flex-direction:column;justify-content:center}.el-upload-dragger .fa-folder{line-height:0}.el-upload-list__item-name{background-color:#fff;width:100%;border:1px solid #ececec;border-radius:5px;height:68px;padding-left:unset;cursor:default !important}.el-upload-list__item-remove{margin-left:auto;margin-right:24px;cursor:pointer !important}.el-upload-list__item-preview .cly-io-document-text{font-size:20px;color:#000;margin-right:4px}.el-upload-list__item.is-success .el-upload-list__item-name:hover{color:#000}.bu-level:not(:last-child){margin-bottom:0px}.bu-tag.is-slim{padding:0 .5em;height:1.5em}.bu-tag.is-legend{padding:0;height:.5em;width:1em;border-radius:3px}.bu-tag.is-curved{border-radius:67px;height:16px;padding-left:4px;padding-right:4px;min-width:12px}.vjs-tree{padding:10px}.vjs-value-boolean,.vjs-value-number{color:#31acb4}.vjs-value-string{color:#6c47ff}.vjs-tree-node.is-highlight,.vjs-tree-node:hover{background-color:#e6f0fb}.cly-vue-theme-clydef{background-color:#f8f9fa}#main-views-container{height:100%;width:100%;overflow:auto}#main-views-container .main-view{min-width:768px;min-height:400px;width:100%;padding:0px 0 35px 0 !important;transition:width .55s;will-change:width;overflow:auto}.cly-vue-main{padding:0px 24px;margin:0}.cly-vue-section--has-configurator-skin .cly-vue-section__content{background:#fff;border:1px solid #ececec;box-sizing:border-box;box-shadow:0px 1px 0px #ececec;border-radius:4px}.cly-vue-section--has-configurator-skin .cly-vue-section__content .cly-vue-section__sub{border-top:none !important;border-bottom:1px solid #ececec;align-items:center;display:flex}.cly-vue-section--has-default-skin .cly-vue-section__content{border-top:1px solid #ececec;border-bottom:1px solid #ececec}.cly-vue-section .cly-vue-section__content-config{border-bottom:1px solid #ececec}.cly-vue-section:not(:last-child){margin-bottom:32px}.cly-vue-header{padding:24px 24px;margin-bottom:32px;border-bottom:1px solid #ececec;position:relative}.cly-vue-header__level{padding-top:8px;padding-bottom:12px}.cly-vue-header__level--no-pt{padding-top:0px}.cly-vue-header__level--no-pb{padding-bottom:0px}.cly-vue-header--no-mb{margin-bottom:0px}.cly-vue-header--no-bb{border-bottom:none}.cly-vue-header+div>.cly-vue-tabs{background-color:#fff;border-bottom:1px solid #ececec;margin-bottom:32px;padding:0px 24px}.cly-vue-select-x__pop .cly-vue-checklistbox__item-content{display:inline-flex;align-items:center}.cly-vue-select-x__pop .cly-vue-checklistbox__item-prefix{display:inline-flex;align-items:center;margin-right:6px}.cly-vue-select-x__pop .cly-vue-listbox{background-color:rgba(0,0,0,0) !important}.cly-vue-select-x__pop .el-tabs__active-bar{background-color:#0166d6}.cly-vue-select-x__pop .el-tabs__nav-wrap::after{background-color:rgba(0,0,0,0)}.cly-vue-select-x__pop .el-tabs__nav-scroll{margin:0 14px}.cly-vue-select-x__pop .el-tabs__header{margin:0 0 1px 0;z-index:2000}.cly-vue-select-x__pop .el-tabs__header::after{content:"";position:absolute;width:100%;height:12px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);top:40px;opacity:.12}.cly-vue-select-x__pop--hidden-tabs .el-tabs__header::after{top:0px}.cly-vue-select-x__pop--hidden-tabs .el-tabs__nav-scroll{display:none}.cly-vue-select-x__pop--hidden-header .cly-vue-select-x__header{padding:0}.cly-vue-select-x__pop--hidden-tabs.cly-vue-select-x__pop--hidden-header .el-tabs__header::after{background:none;height:0;width:0}.cly-vue-select-x__title{font-weight:500;font-size:16px;line-height:24px;color:#333c48;padding-bottom:16px}.cly-vue-select-x__header-slot{padding-bottom:16px}.cly-vue-select-x__header{padding:16px}.cly-vue-select-x__header input[type=text]{background-color:#f6f6f6}.cly-vue-select-x__footer{border-top:1px solid #ececec}.cly-vue-select-x__commit-section{float:right;margin:16px}.cly-vue-select-x__pop--has-single-option .cly-vue-select-x__header-slot,.cly-vue-select-x__pop--has-slim-header .cly-vue-select-x__header-slot{padding:0}.cly-vue-select-x__pop--has-single-option .el-tabs__header::after{content:none}.cly-vue-select-x .el-select-head{width:100%}.cly-vue-listbox{background-color:#fff}.cly-vue-listbox label.el-checkbox{width:100%;margin-left:8px}.cly-vue-listbox.is-expandable{transition:width 200ms;width:100%;z-index:100}.cly-vue-listbox.is-expanded{width:130%;position:relative;-webkit-box-shadow:0 1px 4px rgba(0,0,0,.08);box-shadow:0 1px 4px rgba(0,0,0,.08)}.cly-vue-listbox.cly-vue-listbox--bordered{border-radius:4px;border:1px solid #ececec}.cly-vue-listbox .cly-vue-listbox__no-data{padding:8px;line-height:20px;font-weight:500;color:#333c48;font-size:14px}.cly-vue-listbox .el-checkbox-group{overflow:hidden}.cly-vue-listbox .el-checkbox__label{line-height:20px;font-weight:400;color:#333c48;max-width:330px !important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom}.cly-vue-listbox .el-checkbox__input.is-checked+.el-checkbox__label{color:#333c48}.cly-vue-listbox .drag-handler{visibility:hidden;margin-right:8px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cly-vue-listbox__header{margin:0 0 1px 0;z-index:1997;border-bottom:1px solid #ececec;position:relative}.cly-vue-listbox__header::after{content:"";position:absolute;width:100%;height:12px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);top:57px;left:0;opacity:.12}.cly-vue-listbox .cly-vue-listbox__group{font-size:12px;line-height:14px;background-color:#f6f6f6;border-bottom:solid 1px #ececec;max-height:32px;display:flex;align-items:center;padding:8px}.cly-vue-listbox .cly-vue-listbox__item{display:flex;cursor:pointer;align-items:center;justify-content:space-between}.cly-vue-listbox .cly-vue-listbox__item.selected{background-color:#0166d6}.cly-vue-listbox .cly-vue-listbox__item.selected *{color:#fff}.cly-vue-listbox .cly-vue-listbox__item .cly-vue-listbox__remove-option{display:none}.cly-vue-listbox .cly-vue-listbox__item.hover .cly-vue-listbox__remove-option{display:block}.cly-vue-listbox .cly-vue-listbox__item-content{width:100%}.cly-vue-listbox__item-label{max-width:350px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cly-vue-listbox--disabled .cly-vue-listbox__item{background-color:#f6f6f6}.cly-vue-listbox--disabled .cly-vue-listbox__item *{color:#a7aeb8}.cly-vue-listbox--disabled .cly-vue-listbox__item.selected{opacity:.5}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover{background-color:#f6f6f6}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .drag-handler{visibility:visible}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-prefix,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-prefix *,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-label,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-label *,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-suffix,.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item:not(.selected):hover .cly-vue-listbox__item-suffix *{color:#0166d6}.cly-vue-listbox:not(.cly-vue-listbox--disabled) .cly-vue-listbox__item .cly-vue-listbox__remove-option:hover{color:#0166d6}.cly-vue-listbox--has-default-skin .cly-vue-listbox__item{padding:8px;border-radius:4px;line-height:16px}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item{padding:8px;height:55px;box-sizing:border-box;border-bottom:1px solid #ececec}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item.selected{border-left:2px solid #0166d6;background-color:#f6f6f6}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item.selected *{color:#0166d6}.cly-vue-listbox--has-jumbo-skin .cly-vue-listbox__item:last-child{border-bottom:none}.cly-vue-listbox--has-margin .cly-vue-listbox__item{margin:8px}.cly-fields-select.default-skin{display:inline-block}.cly-fields-select.default-skin .el-input__inner::placeholder{color:#000}.cly-fields-select.default-skin .fields-select-body{box-shadow:0 3px 7px rgba(0,0,0,.08);background-color:#fff;position:absolute;top:0px;z-index:100;border:1px solid #d0d0d0;border-radius:2px;padding:16px;width:max-content}.cly-fields-select.default-skin .fields-select-body .controls{float:right;margin-top:10px}.cly-fields-select__title{color:#333c48;font-family:Inter;line-height:24px;font-weight:500;font-size:16px}.cly-fields-select__reset{float:right;font-size:12px;padding-bottom:26px;padding-top:0px;color:#d23f00;cursor:pointer;line-height:20px}.cly-fields-select__field{color:#333c48;font-family:Inter;line-height:20px;font-weight:500;font-size:12px;padding-bottom:3px}.cly-fields-select__field-dropdown{width:320px;height:32px;margin-bottom:16px}.cly-fields-select__confirm{background-color:#12af51;color:#fff}.cly-fields-select__cancel{background:#f6f6f6}.cly-fields-select__dropdown .el-input__inner::placeholder{color:#000}.cly-multi-select.default-skin{display:inline-block}.cly-multi-select.default-skin .el-input__inner::placeholder{color:#000}.cly-multi-select.default-skin .cly-multi-select__body{box-shadow:0 3px 7px rgba(0,0,0,.08);background-color:#fff;position:absolute;top:-2px;left:-2px;z-index:100;border:1px solid #d0d0d0;border-radius:4px;padding:16px;width:max-content}.cly-multi-select.default-skin .cly-multi-select__controls{float:right;margin-top:10px}.cly-multi-select__title-wrapper{margin-bottom:30px}.cly-multi-select__title{color:#333c48;font-family:Inter;line-height:24px;font-weight:500;font-size:16px}.cly-multi-select__reset{float:right;font-size:12px;padding:4px 8px;color:#d23f00;cursor:pointer;line-height:20px}.cly-multi-select__reset span{height:20px;line-height:20px}.cly-multi-select__reset:hover{background:#f6f6f6}.cly-multi-select__field{color:#333c48;font-family:Inter;line-height:20px;font-weight:500;font-size:12px;padding-bottom:3px}.cly-multi-select .cly-multi-select__confirm-button{margin-left:4px}.cly-multi-select__field-dropdown{width:320px;height:32px;margin-bottom:16px}.cly-multi-select__dropdown .el-input__inner::placeholder{color:#000}.cly-vue-dropdown__pop{padding:0px;width:fit-content}.cly-vue-dropdown__pop-container{word-break:initial}.cly-vue-dropdown__dropdown-icon{width:16px;line-height:16px;height:16px;background-size:16px 16px;border-radius:4px;background-position:center;background-color:#017aff;display:inline-block}.cly-vue-more-options .el-button{border:1px solid #cfd6e4}.cly-vue-more-options .el-button .is-reverse{-webkit-transform:rotateZ(0);transform:rotateZ(0)}.cly-vue-eldatatable.silent-loading .el-loading-spinner{visibility:hidden}.cly-vue-eldatatable.silent-loading .el-loading-mask{display:none !important}.cly-vue-eldatatable.display-mode--list .el-table{background-color:unset}.cly-vue-eldatatable.display-mode--list .el-table__body{border-collapse:separate;border-spacing:0 8px}.cly-vue-eldatatable.display-mode--list .el-table__row{cursor:pointer}.cly-vue-eldatatable.display-mode--list .el-table__row td{cursor:pointer;height:56px;border-top:1px solid #ececec !important;border-bottom:2px solid #ececec !important;background-color:#fff !important;padding:18px 0px}.cly-vue-eldatatable.display-mode--list .el-table__row td:first-child{border-left:1px solid #ececec !important;border-radius:4px 0 0 4px !important}.cly-vue-eldatatable.display-mode--list .el-table__row td:last-child{border-right:1px solid #ececec !important;border-radius:0 4px 4px 0 !important}.cly-vue-eldatatable.display-mode--list .el-table__row td .cell{line-height:20px}.cly-vue-eldatatable.display-mode--list .el-table__row td.drag-icon .cell{text-overflow:unset !important}.cly-vue-eldatatable.display-mode--list .el-table thead tr{background-color:unset !important}.cly-vue-eldatatable.display-mode--list .el-table th{background-color:unset !important;border:none !important;padding:0 !important;color:#81868d !important}.cly-vue-eldatatable.display-mode--list .el-table__fixed-right::before{height:0 !important}.cly-vue-eldatatable__header{height:56px;border-left:none;border-right:none;box-sizing:border-box;padding:12px;border-top:1px solid #ececec}.cly-vue-eldatatable__header--white{background-color:#fff;color:#333c48;font-size:13px}.cly-vue-eldatatable__export--extended::after{content:"";position:absolute;width:100%;height:12px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);top:185px;left:0;opacity:.12}.cly-vue-eldatatable__export--fileExport{border-top:1px solid #e2e4e8}.cly-vue-eldatatable__export--radio-button .el-radio-button{width:99px;display:inline-grid}.cly-vue-eldatatable__export--search input[type=text]{background-color:#f6f6f6}.cly-vue-eldatatable__search--grey input{background-color:#fbfdfe}.cly-vue-eldatatable__footer{height:40px;box-shadow:0px 2px 0px #eef2f5;border-left:none;border-right:none;box-sizing:border-box;padding:0px 12px}.cly-vue-eldatatable__footer--white{background-color:#fff;color:#808a9d;font-size:13px}.cly-vue-eldatatable .undo-row{position:absolute;left:0;padding:9px 0px;background-color:#ffd7d6;color:#2b2b2b;width:100%;height:100%;top:0;z-index:999;vertical-align:middle;display:flex;align-items:center;justify-content:center}.cly-vue-eldatatable .undo-row a{color:#017aff;cursor:pointer}.cly-vue-eldatatable .el-table-column--options .cly-vue-more-options{position:absolute;top:calc(50% - 16px);right:calc(50% - 20px)}.cly-vue-eldatatable div.cell{padding-left:16px;padding-right:8px}.cly-vue-eldatatable__empty-slot{height:200px}.cly-vue-table{width:100%}.cly-vue-table thead{background-color:#f6f6f6;border-top:1px solid #ececec;border-bottom:2px solid #ececec}.cly-vue-table thead th{color:#333c48;font-weight:500;font-size:12px;padding:9px;text-align:left}.cly-vue-table tbody td{padding:0px 9px}.cly-vue-chart{position:relative}.cly-vue-chart .bu-level{padding:0 20px 0 33px}.cly-progress-bar-container{width:100%}.cly-progress-bar-container.has-min-bradius .first-progress-bar-stack .el-progress-bar__outer{border-bottom-left-radius:4px;border-top-left-radius:4px}.cly-progress-bar-container.has-min-bradius .last-progress-bar-stack .el-progress-bar__outer{border-bottom-right-radius:4px;border-top-right-radius:4px}.cly-progress-bar-container.has-min-bradius .el-progress:not(.progress-bar-stack) .el-progress-bar__outer{border-radius:4px}.cly-progress-bar-container.has-min-bradius .el-progress:not(.progress-bar-stack) .el-progress-bar__inner{border-radius:4px}.cly-progress-bar-stack-container{float:left}.progress-bar-stack .el-progress-bar__inner{border-radius:0px}.first-progress-bar-stack .el-progress-bar__outer{border-bottom-right-radius:0px;border-top-right-radius:0px}.middle-progress-bar-stack .el-progress-bar__outer{border-radius:0px}.last-progress-bar-stack .el-progress-bar__outer{border-bottom-left-radius:0px;border-top-left-radius:0px}.cly-vue-tabs__primary-tab-list{padding:24px;box-sizing:border-box}.cly-vue-tabs__tab{height:34px;padding:10px 12px;display:inline-block;box-sizing:border-box}.cly-vue-tabs__tab--default{color:#81868d;line-height:14px;font-weight:500;font-size:14px;cursor:pointer}.cly-vue-tabs__tab--primary{border-radius:6px}.cly-vue-tabs__tab--primary:hover{color:#0166d6}.cly-vue-tabs__tab--secondary:hover{color:#333c48}.cly-vue-tabs__tab--primary-active{color:#0166d6;background-color:#e1efff}.cly-vue-tabs__tab--secondary-active{color:#333c48;border-bottom:2px solid #0166d6}.cly-vue-chart{width:100%;height:100%}.cly-vue-chart__echart{box-sizing:border-box;display:inline-block;width:100%}.cly-vue-chart--padded .cly-vue-chart__echart{padding-top:20px}.cly-vue-chart--full .cly-vue-chart__echart{padding:0}.cly-vue-chart-legend{display:inline-block;width:100%}.cly-vue-chart-legend__primary,.cly-vue-chart-legend__secondary{white-space:nowrap;overflow:auto;box-sizing:border-box;height:100%}.cly-vue-chart-legend__secondary--text-center{text-align:center}.cly-vue-chart-legend__p-series{display:inline-block;margin-left:64px;cursor:pointer}.cly-vue-chart-legend__p-series:first-child{margin-left:36px}.cly-vue-chart-legend__s-series{display:inline-block;margin-left:48px;cursor:pointer}.cly-vue-chart-legend__s-series:first-child{margin-left:0px}.cly-vue-chart-legend__s-series--deselected .cly-vue-chart-legend__s-rectangle,.cly-vue-chart-legend__s-series--deselected .cly-vue-chart-legend__s-title,.cly-vue-chart-legend__s-series--deselected .cly-vue-chart-legend__s-percentage{color:#a7aeb8}.cly-vue-chart-legend__second-row{margin-top:3px}.cly-vue-chart-legend__p-checkbox{height:16px;width:16px;border:1px solid #a7aeb8;display:inline-block;box-shadow:inset 0 0 0px 1.5px #fff;border-radius:4px;vertical-align:middle;box-sizing:border-box}.cly-vue-chart-legend__p-title{display:inline-block;vertical-align:middle;font-size:14px;color:#333c48}.cly-vue-chart-legend__p-tooltip{display:inline-block}.cly-vue-chart-legend__p-tooltip i{font-size:15px}.cly-vue-chart-legend__p-number{display:inline-block;font-size:24px;font-weight:500;color:#333c48}.cly-vue-chart-legend__p-trend{display:inline-block;font-size:14px;font-weight:500}.cly-vue-chart-legend__p-trend--trend-up{color:#12af51}.cly-vue-chart-legend__p-trend--trend-down{color:#d23f00}.cly-vue-chart-legend__s-rectangle{width:12px;height:6px;border-radius:3px;display:inline-block;vertical-align:middle;margin:0px 5px}.cly-vue-chart-legend__s-title,.cly-vue-chart-legend__s-percentage{display:inline-block;vertical-align:middle;margin-left:3px;font-size:12px;font-weight:400;color:#333c48}.cly-vue-chart-legend__bottom .cly-vue-chart-legend__primary,.cly-vue-chart-legend__bottom .cly-vue-chart-legend__secondary{padding:0px 24px 20px}.cly-vue-chart-legend__right{border-left:1px solid #ececec;width:265px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series{margin-left:0px;padding:3px 7px;box-sizing:border-box;width:100%;display:block;border-bottom:1px solid #ececec}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover{background-color:#f6f6f6;border-radius:0px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover .cly-vue-chart-legend__s-title,.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover .cly-vue-chart-legend__s-percentage{font-weight:500}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-title,.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-percentage{font-size:14px;line-height:30px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-title{width:calc(100% - 30px);text-align:left}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-percentage{min-width:50px;text-align:right}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:first-child{padding-top:10px}.cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .__vuescroll .__view::before{content:"";position:absolute;width:100%;height:8px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);left:0;opacity:.12}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie{border-left:none}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary{padding-top:30px;padding-bottom:30px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series{border:none}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:hover{border-radius:8px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series .cly-vue-chart-legend__s-title{width:calc(100% - 86px)}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .cly-vue-chart-legend__secondary .cly-vue-chart-legend__s-series:first-child{padding-top:3px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .__vuescroll .__view{display:flex;flex-direction:column;justify-content:center;padding-right:30px}.cly-vue-chart-legend__right.cly-vue-chart-legend__pie .__vuescroll .__view::before{background:none;height:0}.chart-type-toggle-wrapper__select{width:126px}.chart-type-toggle-wrapper__title{margin-top:16px;margin-left:16px;letter-spacing:.5px}.chart-type-toggle-wrapper__type{font-family:Inter;font-style:normal;font-weight:500;font-size:13px;line-height:13px}.chart-type-annotation-wrapper__icon{margin-top:1px}.chart-type-annotation-wrapper__img{margin-bottom:-2px}.chart-zoom-button{margin-right:12px}.cly-vue-zoom__external .chart-zoom-button{margin:0}.chart-download-button{width:42px;height:32px}.shadow-container{position:relative}.shadow-container:after{display:block;content:"";position:absolute;width:100%;margin:0 auto;height:70px;border:0;bottom:0;background:linear-gradient(180deg, rgba(255, 255, 255, 0.7) 0%, #FFFFFF 100%)}.chart-tooltip{max-height:360px;overflow:auto;margin-bottom:2px;max-width:300px;white-space:nowrap}.chart-tooltip__header{letter-spacing:.5px;text-transform:uppercase}.chart-tooltip__body{background-color:#fff;margin-bottom:16px;margin-top:16px}.chart-tooltip__body:last-child{margin-bottom:0 !important}.chart-tooltip__body.chart-tooltip__single-row{display:flex;margin-bottom:14px;margin-top:14px}.chart-tooltip__body.chart-tooltip__single-row .chart-tooltip__bar{margin-top:6px;float:none}.chart-tooltip__body.chart-tooltip__single-row .chart-tooltip__series{display:flex;flex-direction:column;justify-content:space-between;width:calc(100% - 24px);margin-left:14px}.chart-tooltip__body.chart-tooltip__single-row .chart-tooltip__value{margin-top:-3px;margin-left:30px;display:flex;flex-direction:column;justify-content:space-between}.chart-tooltip__has-scroll .chart-tooltip__value{margin-right:10px}.chart-tooltip__bar{float:left;width:12px;min-width:12px;height:6px;border-radius:3px;margin-top:5px}.chart-tooltip__series{margin-left:24px;overflow:hidden}.chart-tooltip__series span{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.chart-tooltip__value{margin-top:2px;margin-left:24px}.graph-notes-tooltip{width:286px;max-height:118px;overflow:auto}.graph-notes-tooltip__header{width:100%}.graph-notes-tooltip__header .input-owner{font-weight:500}.graph-notes-tooltip__header .name-wrapper{margin-top:10px}.graph-notes-tooltip__header .el-icon-close{color:#898787;z-index:9999;cursor:pointer}.graph-notes-tooltip__header .el-icon-close:hover{font-size:14px;color:#898787;font-weight:600;opacity:.5;background-color:#f6f6f6}.graph-notes-tooltip__header .note-type{margin-top:12px}.graph-notes-tooltip__body{margin-top:12px}.graph-notes-tooltip__body .input-notes{word-break:break-word;display:inline-block;white-space:normal}.graph-notes-tooltip.minimize{width:180px}.graph-notes-tooltip.minimize .input-owner{font-size:12px}.graph-notes-tooltip.minimize__header{margin-top:0px}.graph-notes-tooltip.minimize .input-notes{font-size:12px}.graph-notes-tooltip.minimize .input-minimizer{margin-bottom:10px}.graph-notes-tooltip.minimize .graph-notes-tooltip__body{margin-top:5px}.graph-notes-tooltip.minimize .name-wrapper{margin-top:0px}.graph-notes-tooltip.minimize .note-date{font-size:11px}.graph-notes-tooltip.minimize .note-type{margin-top:2px;font-size:11px;margin-right:4px}.graph-tooltip-wrapper{margin-right:2px;margin-bottom:4px}.graph-tooltip-wrapper .el-icon-close{color:#898787;z-index:9999;cursor:pointer}.graph-tooltip-wrapper .el-icon-close:hover{font-size:14px;color:#898787;font-weight:600;opacity:.5}.graph-tooltip-wrapper__container{max-height:180px;overflow:hidden;overflow:auto}.cly-vue-tooltip-icon{font-size:17px;color:#a7aeb8;vertical-align:middle;width:18px;height:18px}.cly-in-page-notification{border-radius:4px;text-align:center;margin-bottom:32px}.cly-in-page-notification--light-informational{background-color:#e1efff;color:#333c48}.cly-in-page-notification--light-successful{background-color:#ebfaee;color:#333c48}.cly-in-page-notification--light-destructive{background-color:#fbece5;color:#333c48}.cly-in-page-notification--light-warning{background-color:#fcf5e5;color:#333c48}.cly-in-page-notification--dark-successful{background-color:#24292e;color:#fff}.cly-in-page-notification--dark-informational{background-color:#24292e;color:#fff}.cly-in-page-notification--dark-warning{background-color:#24292e;color:#fff}.cly-in-page-notification--dark-destructive{background-color:#24292e;color:#fff}.cly-in-page-notification a:hover{text-decoration:underline;cursor:hand}.cly-vue-back-link{cursor:pointer;color:#616161;font-size:12px;display:inline-block;text-transform:uppercase;margin-bottom:24px}.cly-vue-back-link:visited{cursor:pointer;color:#616161;font-size:12px;display:inline-block;text-transform:uppercase}.cly-vue-back-link:hover span{text-decoration:underline}.cly-divider{width:100%;border-top:1px solid #ececec}.cly-divider.is-2{border-top-width:2px}.cly-divider.is-dashed{border-top-style:dashed}.cly-divider.is-dotted{border-top-style:dotted}.cly-event-select .el-radio-group{display:flex}.cly-event-select .el-radio-button{flex-grow:1}.cly-event-select .el-radio-button__inner{width:100%}.cly-vue-color-tag__color-tag-wrapper{width:20%}.cly-vue-color-tag__color-tag{width:92%;height:37px;margin-bottom:5px;border-radius:4px}.cly-vue-color-tag__color-tag__selected{border:2px solid #0166d6}.cly-vue-color-tag__checkmark{color:#fff}.cly-vue-empty-view-framed{border:1px solid #e2e4e8;background-color:#f8fafd;border-radius:8px;background-image:url("../images/icons/empty-background.svg");background-position:100% 100%;background-repeat:no-repeat}.cly-vue-json-editor .el-dialog__header{text-align:left;padding:8px 16px 0 !important;color:#fff}.cly-vue-json-editor .el-dialog__body{padding:0 !important}.cly-vue-json-editor__textarea{width:100%;box-sizing:border-box;outline:none;border:none;border-top:1px solid #ececec;border-bottom:1px solid #ececec;background-color:#fcfcfc}.cly-breadcrumbs a:hover{text-decoration:underline}.cly-is-fullwidth{width:100%}.is-estimate:hover{color:#0166d6;text-decoration:underline;text-decoration-style:dotted}#cly-vue-sidebar{left:0;height:100%;z-index:2000;user-select:none;color:#d6d6d6}.cly-vue-sidebar{height:100%}.cly-vue-sidebar .tooltip{display:block !important;z-index:10000}.cly-vue-sidebar .tooltip .tooltip-inner{background:#fff;color:#000}.cly-vue-sidebar .tooltip .tooltip-arrow{display:none}.cly-vue-sidebar .tooltip[x-placement^=right]{margin-left:0px}.cly-vue-sidebar .cly-vue-listbox__item-label{width:350px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cly-vue-sidebar__slide-transition-enter-active{transition:all .3s ease}.cly-vue-sidebar__slide-transition-leave-active{transition:all .8s cubic-bezier(1, 0.5, 0.8, 1)}.cly-vue-sidebar__slide-transition-enter,.cly-vue-sidebar__slide-transition-leave-to{transform:translateX(-224px)}.cly-vue-sidebar ul{list-style:none;padding:0;margin:0}.cly-vue-sidebar ul li{padding:0px}.cly-vue-sidebar__app-selector__dialog_box .el-dialog__body{padding:0px !important}.cly-vue-sidebar .el-collapse{border:none}.cly-vue-sidebar__app-name{font-size:14px}.cly-vue-sidebar__app-name-type{width:120px}.cly-vue-sidebar__app-selector{border:1px solid #424243;box-sizing:border-box;box-shadow:0px 11px 14px rgba(0,0,0,.371635);border-radius:4px;height:50px}.cly-vue-sidebar__app-type{opacity:.57}.cly-vue-sidebar__menu-options{width:48px;height:100%;overflow:auto;color:#a7aeb8;z-index:2005;background-color:#191c20}.cly-vue-sidebar__menu-options ul:first-child li:first-child{margin-top:28px}.cly-vue-sidebar__menu-option{width:48px;margin-top:24px;font-size:20px;cursor:pointer}.cly-vue-sidebar__menu-option .el-popover__reference:hover{color:#fff !important}.cly-vue-sidebar__menu-option-toggle{height:48px;background-color:#24292e;border-top-left-radius:50%;border-bottom-left-radius:50%}.cly-vue-sidebar__menu-option-toggle--no-bg{background-color:rgba(0,0,0,0)}.cly-vue-sidebar__menu-option-toggle--rotate-right{transform:rotate(180deg);transition:transform .7s ease-in-out}.cly-vue-sidebar__menu-option-toggle--rotate-left{transform:rotate(0deg);transition:transform .7s ease-in-out}.cly-vue-sidebar__menu{width:224px;background-color:#24292e;color:#d6d6d6;box-sizing:border-box;height:100%;overflow:hidden;z-index:2004;position:relative}.cly-vue-sidebar__menu--with-banner .__view{padding-bottom:200px}.cly-vue-sidebar__menu-items,.cly-vue-sidebar__submenu-items,.cly-vue-sidebar .el-collapse-item__header{line-height:20px;height:20px;background-color:#24292e;color:#d6d6d6;border:none;font-size:14px;padding:0;font-weight:400;font-family:Inter;padding:6px 12px}.cly-vue-sidebar__menu-items .el-collapse-item__arrow,.cly-vue-sidebar__submenu-items .el-collapse-item__arrow,.cly-vue-sidebar .el-collapse-item__header .el-collapse-item__arrow{margin-right:0px}.cly-vue-sidebar__menu-items:hover,.cly-vue-sidebar__submenu-items:hover,.cly-vue-sidebar .el-collapse-item__header:hover{background-color:#383a3f;color:#fff;border-radius:4px}.cly-vue-sidebar__menu-items--selected,.cly-vue-sidebar__submenu-items--selected,.cly-vue-sidebar .el-collapse-item__header--selected{background-color:#12af51;border-radius:4px;color:#fff}.cly-vue-sidebar__menu-items--selected:hover,.cly-vue-sidebar__submenu-items--selected:hover,.cly-vue-sidebar .el-collapse-item__header--selected:hover{background-color:#12af51;border-radius:4px}.cly-vue-sidebar .el-collapse-item__header.focusing:focus:not(:hover){color:#d6d6d6}.cly-vue-sidebar .el-collapse-item .cly-vue-sidebar__submenu-items{padding-left:32px}.cly-vue-sidebar__el-collapse--selected .el-collapse-item__header{border-radius:4px;background-color:#383a3f;color:#fff}.cly-vue-sidebar .el-collapse-item__content{margin:0;padding:0}.cly-vue-sidebar .el-collapse-item__wrap{border-bottom:1px solid #424243;background-color:#24292e;box-shadow:0px 1px 0px rgba(0,0,0,.4)}.cly-vue-sidebar__header{padding:16px 12px;position:relative}.cly-vue-sidebar__header-search input{background-color:#383a3f;border:none;color:#d6d6d6}.cly-vue-sidebar__header-search input:focus{outline:none;box-shadow:none}.cly-vue-sidebar__header-title{font-size:16px;line-height:24px;font-weight:500;display:inline-block;margin-top:12px}.cly-vue-sidebar__header-button{float:right;top:5px;position:relative}.cly-vue-sidebar__main-menu{overflow:auto;height:calc(100% - 130px)}.cly-vue-sidebar__app-image{width:26px;height:26px;background-size:26px 26px;border-radius:4px;background-position:center;display:inline-block}.cly-vue-sidebar__user-popper{box-sizing:border-box;font-family:Inter;padding:14px 0px;box-shadow:0px 2px 7px rgba(0,0,0,.18815);color:#333c48}.cly-vue-sidebar__language-popper{font-family:Inter;padding:0px;min-width:130px;box-shadow:0px 2px 7px rgba(0,0,0,.18815)}.cly-vue-sidebar__user-details .bu-level{padding:6px 12px}.cly-vue-sidebar__user-links .bu-level{padding:6px;margin:0px 6px}.cly-vue-sidebar__user-links .bu-level:hover{background-color:#f1f3f4;color:#0166d6;border-radius:4px}.cly-vue-sidebar__user-links .bu-level:hover .bu-level-item i{color:#333c48}.cly-vue-sidebar__user-links .bu-level .bu-level-item:first-child{width:15px}.cly-vue-sidebar__user-links .bu-level .bu-level-item i{color:#a7aeb8}.cly-vue-sidebar__user-info{width:140px}.cly-vue-sidebar__user-info p{padding:0;margin:0}.cly-vue-sidebar__user-info p:first-child{font-size:16px;line-height:14px;font-weight:500}.cly-vue-sidebar__user-info p:last-child{margin-top:7px;line-height:17px;font-size:14px;font-weight:400}.cly-vue-sidebar__user-api{width:192px}.cly-vue-sidebar__user-api p{padding:0;margin:0}.cly-vue-sidebar__user-api p:first-child{font-size:12px;line-height:14px;font-weight:500}.cly-vue-sidebar__user-api p:last-child{margin-top:7px;line-height:17px;font-size:12px;font-weight:400}.cly-vue-sidebar__user-image{height:50px;width:50px;border-radius:25px;text-align:center}.cly-vue-sidebar__user-initials{text-transform:uppercase;color:#fff;font-size:16px}.cly-vue-sidebar__user-divider{border:1px solid #ececec;margin-top:5px;position:relative}.cly-vue-sidebar__version{position:absolute;bottom:0;height:48px;display:flex;align-items:center;justify-content:center;width:224px;font-size:10px;background-color:#24292e}.cly-vue-sidebar__version__banner{position:absolute;bottom:12px;left:12px;right:12px;z-index:2001;cursor:default;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;background-color:#191c20;border-radius:4px;padding:16px}.cly-vue-sidebar__version__banner .button{width:100%;margin-top:12px}.cly-vue-sidebar__version__banner .text{font-size:14px;color:#fff}.cly-vue-sidebar .cly-icon-sidebar-app{background-image:url("../images/sidebar/default/app.svg");height:24px;width:24px}.cly-vue-sidebar .cly-icon-sidebar-analytics{background-image:url("../images/sidebar/default/analytics-icon.svg");height:20px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-analytics:hover{background-image:url("../images/sidebar/hover/analytics-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-analytics.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/analytics-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-dashboards{background-image:url("../images/sidebar/default/dashboards-icon.svg");height:20px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-dashboards:hover{background-image:url("../images/sidebar/hover/dashboards-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-dashboards.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/dashboards-icon.svg")}.cly-vue-sidebar .cly-icon-sidebar-divider{background-image:url("../images/sidebar/default/divider.svg");width:32px;height:1px}.cly-vue-sidebar .cly-icon-sidebar-management{background-image:url("../images/sidebar/default/management.svg");height:20px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-management:hover{background-image:url("../images/sidebar/hover/management.svg")}.cly-vue-sidebar .cly-icon-sidebar-management.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/management.svg")}.cly-vue-sidebar .cly-icon-sidebar-report-manager{background-image:url("../images/sidebar/default/report-manager.svg");height:18px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-report-manager:hover{background-image:url("../images/sidebar/hover/report-manager.svg")}.cly-vue-sidebar .cly-icon-sidebar-report-manager.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/report-manager.svg")}.cly-vue-sidebar .cly-icon-sidebar-help-center{background-image:url("../images/sidebar/default/help-center.svg");height:25px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-help-center:hover{background-image:url("../images/sidebar/hover/help-center.svg")}.cly-vue-sidebar .cly-icon-sidebar-help-center.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/help-center.svg")}.cly-vue-sidebar .cly-icon-sidebar-notifications{background-image:url("../images/sidebar/default/notifications.svg");height:23px;width:20px}.cly-vue-sidebar .cly-icon-sidebar-notifications:hover{background-image:url("../images/sidebar/hover/notifications.svg")}.cly-vue-sidebar .cly-icon-sidebar-notifications.cly-vue-sidebar__menu-option--selected{background-image:url("../images/sidebar/selected/notifications.svg")}.cly-vue-sidebar .cly-icon-sidebar-toggle-left{background-image:url("../images/sidebar/default/toggle-left.svg");height:15px;width:11px}.cly-vue-sidebar--floating{position:absolute;left:48px}.notification-toasts{position:absolute;right:24px;bottom:24px;z-index:600000}.notification-toasts__item{margin-top:12px;min-width:288px;max-width:288px}.notification-toasts__item--small{max-width:288px}.notification-toasts__item--large{max-width:611px}.font-weight-bold{font-weight:600 !important}.font-weight-normal{font-weight:400 !important}.font-italic{font-style:italic !important}.text-lowercase{text-transform:lowercase !important}.text-uppercase{text-transform:uppercase !important}.text-capitalize{text-transform:capitalize !important}.text-big{color:#333c48;font-size:16px;font-weight:400;line-height:22px}.text-big.font-weight-bold{font-weight:500 !important}.text-medium{color:#333c48;font-size:14px;font-weight:400;line-height:20px}.text-medium.font-weight-bold{font-weight:500 !important}.text-smallish{color:#333c48;font-size:13px;font-weight:400;line-height:16px}.text-smallish.font-weight-bold{font-weight:500 !important}.text-small{color:#333c48;font-size:12px;font-weight:400;line-height:18px}.text-small.font-weight-bold{font-weight:500 !important}.text-heading.text-big{line-height:24px;font-weight:500 !important;margin-bottom:16px}.text-heading.text-medium{line-height:20px;font-weight:500 !important;margin-bottom:8px}.text-heading.text-small{line-height:18px;font-weight:500 !important;margin-bottom:4px}.text-smaller{color:#333c48;font-size:10px;font-weight:400;line-height:12px}.text-smaller.font-weight-bold{font-weight:500 !important}.text-smallest{color:#333c48;font-size:10px;font-weight:700;line-height:12px}.text-smallest.font-weight-bold{font-weight:500 !important}.text-button{color:#333c48;font-size:13px;font-weight:400;line-height:16px}.text-button.font-weight-bold{font-weight:500 !important}.cly-trend-up,.cly-trend-down,.cly-trend-neutral{font-size:14px;font-weight:500}.cly-trend-up,.cly-trend-down.negated{color:#12af51}.cly-trend-down,.cly-trend-up.negated{color:#d23f00}.cly-trend-neutral{color:#81868d}.cly-trend-up-icon,.cly-trend-down-icon,.cly-trend-neutral-icon{border-radius:50%;margin-right:4px;display:inline-block;width:17px;height:17px;text-align:center;line-height:17px}.cly-trend-up-icon{background-color:#ebfaee}.cly-trend-down-icon{background-color:#fbece5}.cly-trend-neutral-icon{background-color:#e2e4e8}.cly-trend-up.negated .cly-trend-up-icon{background-color:#fbece5}.cly-trend-down.negated .cly-trend-down-icon{background-color:#ebfaee}.cursor-pointer{cursor:pointer}.icon-size-1{font-size:6px}.icon-size-2{font-size:8px}.icon-size-3{font-size:10px}.icon-size-4{font-size:12px}.icon-size-5{font-size:14px}.icon-size-6{font-size:16px}.text-clickable{cursor:pointer}.color-white{color:#fff !important}.bg-white{background-color:#fff !important}.color-black{color:#000 !important}.bg-black{background-color:#000 !important}.color-warm-gray-100{color:#24292e !important}.bg-warm-gray-100{background-color:#24292e !important}.color-warm-gray-90{color:#24292e !important}.bg-warm-gray-90{background-color:#24292e !important}.color-warm-gray-80{color:#383a3f !important}.bg-warm-gray-80{background-color:#383a3f !important}.color-warm-gray-70{color:#424243 !important}.bg-warm-gray-70{background-color:#424243 !important}.color-warm-gray-60{color:#616165 !important}.bg-warm-gray-60{background-color:#616165 !important}.color-warm-gray-40{color:#d6d6d6 !important}.bg-warm-gray-40{background-color:#d6d6d6 !important}.color-warm-gray-30{color:#ececec !important}.bg-warm-gray-30{background-color:#ececec !important}.color-warm-gray-20{color:#f6f6f6 !important}.bg-warm-gray-20{background-color:#f6f6f6 !important}.color-warm-gray-10{color:#fcfcfc !important}.bg-warm-gray-10{background-color:#fcfcfc !important}.color-cool-gray-100{color:#333c48 !important}.bg-cool-gray-100{background-color:#333c48 !important}.color-cool-gray-50{color:#81868d !important}.bg-cool-gray-50{background-color:#81868d !important}.color-cool-gray-40{color:#a7aeb8 !important}.bg-cool-gray-40{background-color:#a7aeb8 !important}.color-cool-gray-30{color:#cfd6e4 !important}.bg-cool-gray-30{background-color:#cfd6e4 !important}.color-cool-gray-20{color:#e2e4e8 !important}.bg-cool-gray-20{background-color:#e2e4e8 !important}.color-cool-gray-10{color:#f8fafd !important}.bg-cool-gray-10{background-color:#f8fafd !important}.color-cool-gray-05{color:#fbfdfe !important}.bg-cool-gray-05{background-color:#fbfdfe !important}.color-charts-blue{color:#017aff !important}.bg-charts-blue{background-color:#017aff !important}.color-charts-turquoise{color:#39c0c8 !important}.bg-charts-turquoise{background-color:#39c0c8 !important}.color-charts-light-turquoise{color:#ebf9f9 !important}.bg-charts-light-turquoise{background-color:#ebf9f9 !important}.color-charts-amber{color:#f5c900 !important}.bg-charts-amber{background-color:#f5c900 !important}.color-charts-orange{color:#f96300 !important}.bg-charts-orange{background-color:#f96300 !important}.color-charts-light-orange{color:#feefe5 !important}.bg-charts-light-orange{background-color:#feefe5 !important}.color-charts-magenta{color:#f34971 !important}.bg-charts-magenta{background-color:#f34971 !important}.color-charts-light-magenta{color:#feedf1 !important}.bg-charts-light-magenta{background-color:#feedf1 !important}.color-charts-coral{color:#ff9382 !important}.bg-charts-coral{background-color:#ff9382 !important}.color-charts-violet{color:#da6efc !important}.bg-charts-violet{background-color:#da6efc !important}.color-charts-purple{color:#6c47ff !important}.bg-charts-purple{background-color:#6c47ff !important}.color-charts-light-purple{color:#f0edff !important}.bg-charts-light-purple{background-color:#f0edff !important}.color-charts-tan{color:#cdad7a !important}.bg-charts-tan{background-color:#cdad7a !important}.color-charts-olive{color:#aabb5d !important}.bg-charts-olive{background-color:#aabb5d !important}.color-charts-scarlet{color:#f62404 !important}.bg-charts-scarlet{background-color:#f62404 !important}.color-charts-pink{color:#ffb6f5 !important}.bg-charts-pink{background-color:#ffb6f5 !important}.color-yellow-100{color:#e49700 !important}.bg-yellow-100{background-color:#e49700 !important}.color-yellow-70{color:#ffc760 !important}.bg-yellow-70{background-color:#ffc760 !important}.color-yellow-20{color:#fff4ce !important}.bg-yellow-20{background-color:#fff4ce !important}.color-yellow-10{color:#fcf5e5 !important}.bg-yellow-10{background-color:#fcf5e5 !important}.color-blue-100{color:#0166d6 !important}.bg-blue-100{background-color:#0166d6 !important}.color-blue-90{color:#1a75da !important}.bg-blue-90{background-color:#1a75da !important}.color-blue-80{color:#3485de !important}.bg-blue-80{background-color:#3485de !important}.color-blue-70{color:#4d94e2 !important}.bg-blue-70{background-color:#4d94e2 !important}.color-blue-60{color:#67a3e6 !important}.bg-blue-60{background-color:#67a3e6 !important}.color-blue-50{color:#80b3eb !important}.bg-blue-50{background-color:#80b3eb !important}.color-blue-40{color:#99c2ef !important}.bg-blue-40{background-color:#99c2ef !important}.color-blue-30{color:#b3d1f3 !important}.bg-blue-30{background-color:#b3d1f3 !important}.color-blue-20{color:#cce0f7 !important}.bg-blue-20{background-color:#cce0f7 !important}.color-blue-10{color:#e6f0fb !important}.bg-blue-10{background-color:#e6f0fb !important}.color-sky-blue-100{color:#3f9aff !important}.bg-sky-blue-100{background-color:#3f9aff !important}.color-shadow-blue-100{color:#9cbce0 !important}.bg-shadow-blue-100{background-color:#9cbce0 !important}.color-light-blue-100{color:#e1efff !important}.bg-light-blue-100{background-color:#e1efff !important}.color-dark-blue-100{color:#32659d !important}.bg-dark-blue-100{background-color:#32659d !important}.color-teal{color:#00c3ca !important}.bg-teal{background-color:#00c3ca !important}.color-red-100{color:#d23f00 !important}.bg-red-100{background-color:#d23f00 !important}.color-red-10{color:#fbece5 !important}.bg-red-10{background-color:#fbece5 !important}.color-green-100{color:#10a14a !important}.bg-green-100{background-color:#10a14a !important}.color-green-80{color:#12af51 !important}.bg-green-80{background-color:#12af51 !important}.color-green-10{color:#ebfaee !important}.bg-green-10{background-color:#ebfaee !important}.color-primary{color:#0166d6 !important}.bg-primary{background-color:#0166d6 !important}.color-primary-green{color:#12af51 !important}.bg-primary-green{background-color:#12af51 !important}.color-success{color:#67c23a !important}.bg-success{background-color:#67c23a !important}.color-warning{color:#e49700 !important}.bg-warning{background-color:#e49700 !important}.color-danger{color:#d23f00 !important}.bg-danger{background-color:#d23f00 !important}.color-info{color:#909399 !important}.bg-info{background-color:#909399 !important}.color-text-secondary{color:#81868d !important}.bg-text-secondary{background-color:#81868d !important}.color-text-custom-1-active{color:#333c48 !important}.bg-text-custom-1-active{background-color:#333c48 !important}.color-text-placeholder{color:#81868d !important}.bg-text-placeholder{background-color:#81868d !important}.color-text-primary{color:#303133 !important}.bg-text-primary{background-color:#303133 !important}.color-text-regular{color:#333c48 !important}.bg-text-regular{background-color:#333c48 !important}.color-text-custom-1-passive{color:#858a91 !important}.bg-text-custom-1-passive{background-color:#858a91 !important}.color-action-blue{color:#0166d6 !important}.bg-action-blue{background-color:#0166d6 !important}.white-bg{background-color:#fff}.no-bg{background-color:none}.blinker{position:relative;top:-1px;width:6px;height:6px;border-radius:8px;background-color:#2fa732;display:inline-block;vertical-align:middle;margin-right:5px;animation:blinker 1.5s cubic-bezier(0.5, 0, 1, 1) infinite alternate}.has-tooltip{cursor:pointer}.has-ellipsis{word-break:normal;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle}.has-ellipsis::after{content:"";display:block}.user-activity-table__divider{width:16px;justify-content:space-evenly;color:#a7aeb8}.user-activity-table__data-item-container{flex-basis:75px;margin-right:14px}.user-activity-table-progress-bar-container{width:100%}.user-activity-table-progress-bar-container progress::-webkit-progress-value{background-color:#39c0c8}.session-overview-date-picker-container{margin-bottom:32px}.session-durations-date-picker-container{margin-bottom:32px}.session-frequency-date-picker-container{margin-bottom:32px}.cly-events-breakdown-horizontal-tile__wrapper{border-right:1px solid #ececec;border-top:1px solid #eef2f5;box-shadow:0px 2px 0px #eaecef;padding:24px 24px 0px 24px}.cly-events-breakdown-horizontal-tile__titleoverview{color:#333c48;font-size:14px;font-weight:400;line-height:20px;font-family:Inter}.cly-events-breakdown-horizontal-tile:nth-of-type(3n) .cly-events-breakdown-horizontal-tile__wrapper{border-right:0}.cly-events-breakdown-horizontal-tile__values-list{align-items:center}.cly-events-breakdown-horizontal-tile__item{height:33.3333333%;padding:0px 0px 24px 0px}.cly-events-breakdown-horizontal-tile__item-title{padding:12px 0px 8px 0px}.cly-events-breakdown-horizontal-tile__value{font-weight:500;font-size:20px;line-height:30px;font-family:Inter;color:#333c48;letter-spacing:.003em}.cly-events-breakdown-horizontal-tile__overviewvalue{font-weight:500;font-size:24px;line-height:32px}.cly-events-breakdown-horizontal-tile__percentage{font-size:14px;font-weight:400;line-height:20px;font-family:Inter;color:#333c48}.cly-events-breakdown-horizontal-tile__trend{font-weight:500;line-height:14px}.cly-events-breakdown-horizontal-tile__arrow{padding-right:3px}.cly-vue-events-overview-top-events{padding-left:24px;padding-bottom:16.5px}.cly-vue-events-overview-top-events--tooltip{margin-left:5.5px}.cly-vue-events-overview-top-events__description{font-weight:500;line-height:14px}.cly-vue-events-overview-top-events__trend{padding-right:3px}.cly-vue-events-overview-monitor-events__charts .cly-vue-chart__echart{padding:0px}.cly-vue-events-overview-monitor-events__empty{min-height:100px}.cly-vue-events-overview-monitor-events__empty__block{margin:32px auto 32px auto;max-width:586px}.cly-vue-events-overview-monitor-events__empty--image{width:242px;height:242px;background-image:url("../images/dashboard/events-overview-noevents.svg")}.cly-vue-events-overview-monitor-events--text{font-weight:500;font-size:12px;color:#a7aeb8;letter-spacing:.5px;font-family:Inter;line-height:14.52px}.cly-vue-events-overview-monitor-events .cly-vue-section__content{border:1px solid #ececec;border-radius:4px}.cly-vue-events-overview-monitor-events--grey{color:#a7aeb8}.cly-vue-events-overview-monitor-events__heading{padding:8px 0px 16px 0px}.cly-vue-events-overview-monitor-events__configure--position{float:right;margin-left:auto}.cly-events-overview-drawer__subheading{font-size:16px;line-height:24px;font-weight:500;font-family:Inter;color:#333c48}.cly-events-overview-drawer--dragicon{margin-right:18px}.cly-events-overview-drawer__event-records{border-radius:6px;width:100%;padding:14px 16px;background-color:#f8fafd}.cly-events-overview-drawer__basis{flex-basis:0}.cly-events-overview-drawer__dropdown{min-width:230px;width:100%}.cly-events-overview-drawer__close-icon{line-height:1;font-size:24px}.cly-monitor-events-breakdown-horizontal-tile__wrapper{border-right:1px solid #ececec;border-bottom:1px solid #ececec;padding:24px 24px 0 24px}.cly-monitor-events-breakdown-horizontal-tile__title{font-size:12px;font-weight:400;line-height:20px;padding-bottom:8px}.cly-monitor-events-breakdown-horizontal-tile__titleoverview{font-size:12px;font-weight:400;line-height:20px}.cly-monitor-events-breakdown-horizontal-tile__eventproperty{font-size:12px;font-weight:400;line-height:20px;color:#81868d}.cly-monitor-events-breakdown-horizontal-tile:nth-of-type(2n) .cly-monitor-events-breakdown-horizontal-tile__wrapper{border-right:none}.cly-monitor-events-breakdown-horizontal-tile:nth-child(1) .cly-monitor-events-breakdown-horizontal-tile__wrapper,.cly-monitor-events-breakdown-horizontal-tile:nth-child(2) .cly-monitor-events-breakdown-horizontal-tile__wrapper{border-top:1px solid #ececec}.cly-monitor-events-breakdown-horizontal-tile__values-list{align-items:center}.cly-monitor-events-breakdown-horizontal-tile__item{height:33.3333333%;padding:0px 0px 16px 0px}.cly-monitor-events-breakdown-horizontal-tile__item-title{padding-top:4px}.cly-monitor-events-breakdown-horizontal-tile__value{font-weight:500;font-size:20px;line-height:30px}.cly-monitor-events-breakdown-horizontal-tile__overviewvalue{font-weight:500;font-size:24px;line-height:32px}.cly-monitor-events-breakdown-horizontal-tile__percentage{font-size:14px;font-weight:300;line-height:20px}.cly-monitor-events-breakdown-horizontal-tile__trend{font-weight:500;line-height:14px}.cly-monitor-events-breakdown-horizontal-tile__arrow{padding-right:3px}.cly-monitor-events-breakdown-horizontal-tile__overview-name{z-index:1}.cly-monitor-events-breakdown-horizontal-tile__width-50{width:50%}.cly-vue-events-all__alerts a{text-decoration:underline}.cly-vue-events-all{font-size:24px;line-height:32px;font-weight:500;padding-top:6px;letter-spacing:.003em;font-family:Inter;color:#333c48}.cly-vue-events-all__subheading{font-size:14px;line-height:20px;color:#333c48}.cly-vue-events-all--padding{padding-top:32px}.cly-vue-events-all--margin{margin-right:32px}.cly-vue-events-group{color:#fff;background-color:#a7aeb8;font-size:10px;line-height:10px;font-weight:500;letter-spacing:.0042em;vertical-align:middle;padding:3px 4px;border-radius:4px}.cly-vue-events-all-placeholder-text{color:#81868d;font-size:12px;line-height:14.52px;font-weight:500;letter-spacing:.005em;white-space:nowrap}.cly-vue-events-all-date-picker{margin-right:26px}.cly-vue-events-all-tooltip{margin-left:10px}.cly-vue-overview-events-header{padding-right:9.5px;letter-spacing:.003em}.cly-vue-events-header{padding:8px 24px 24px 24px;letter-spacing:.003em;font-family:Inter;line-height:32px;border-bottom:1px solid #ececec;background-color:#fff}.cly-vue-events-header__subheading--widget{padding-right:5.33px;color:#81868d}.cly-vue-events-header__subheading--text{font-family:Inter;line-height:20px;font-size:14px;color:#81868d}.cly-vue-events-header--tooltip{padding-left:9.5px}.cly-vue-events-overview-subheadings{padding-right:9.5px;letter-spacing:.003em;font-family:Inter}.cly-vue-events-overview-subheadings--tooltip{padding-top:3px}.cly-vue-events-overview-subheadings--font{font-family:Inter}.cly-vue-listbox.is-expandable{z-index:2001}.cly-vue-events-omitted-segments__item{color:#81868d;font-size:14px;font-weight:500;line-height:16px;padding:8px;margin:8px}.cly-vue-events-omitted-segments__title{color:#a7aeb8;font-size:13px;font-weight:500;letter-spacing:.5px;text-transform:uppercase;line-height:16px;padding:8px;margin:8px}.technology-analytics-wrapper .allPlatformVersions{border-left:1px solid #ececec;height:264px;border-top:1px solid #ececec;border-bottom:1px solid #ececec;background-color:#fff}.technology-analytics-wrapper .emptyVersions{height:264px;position:relative;border-left:1px solid #ececec;border-top:1px solid #ececec;border-bottom:1px solid #ececec;background-color:#fff}.technology-analytics-wrapper .emptyVersions div{font-family:Inter;font-style:normal;font-weight:normal;font-size:14px;line-height:20px;color:#333c48;position:absolute;top:50%;text-align:center;width:100%;margin-top:-10px}.technology-analytics-wrapper .version-graph-title{font-family:Inter;font-style:normal;font-weight:normal;font-size:14px;line-height:20px;color:#333c48}.technology-analytics-wrapper .version-graph-title .divider{color:#a7aeb8}.technology-analytics-wrapper .version-graph-title div:first-child{text-align:left}.technology-analytics-wrapper .version-graph-title div:nth-child(2){text-align:right}.technology-analytics-wrapper .version-graph-block .version-graph-title{margin:0px;padding-bottom:4px}.technology-analytics-wrapper .version-graph-title .bu-column{padding:0px}.technology-analytics-wrapper .cly-vue-metric-card__wrapper .bu-is-flex-direction-column{margin-right:-1.5rem}.technology-analytics-wrapper .selector_wrapper{position:relative;top:-5px;margin-left:8px}.technology-analytics-wrapper .app-resolution-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .metricCardsWrapper{height:115px}.technology-analytics-wrapper .metricCardsWrapper .emptyCard{min-height:107px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;border-top:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper .emptyCard div{position:absolute;top:50%;text-align:center;width:100%;line-height:20px;margin-top:-10px}.technology-analytics-wrapper .platformVersionsWrapper .emptyCard{min-height:250px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;border-top:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper .platformVersionsWrapper{height:309px;padding-bottom:20px}.technology-analytics-wrapper .metricCardsWrapper .bu-columns{flex-wrap:nowrap}.technology-analytics-wrapper .metricCardsWrapper .__view{width:auto !important}.technology-analytics-wrapper .metricCardsWrapper .cly-vue-metric-cards{border-bottom:none}.technology-analytics-wrapper .metricCardsWrapper .cly-vue-metric-card{background-color:#fff;border-top:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper__items{height:115px}.technology-analytics-wrapper__versions{height:291px;padding-bottom:20px}.technology-analytics-wrapper__empty-card{min-height:107px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;top:50%;text-align:center;border-bottom:1px solid #ececec}.technology-analytics-wrapper__empty-card div{position:absolute;top:50%;text-align:center;width:100%;line-height:20px;margin-top:-10px}.technology-analytics-wrapper__versions-empty-card{min-height:250px;border-left:1px solid #ececec;width:100%;position:relative;background-color:#fff;border-top:1px solid #ececec;border-right:1px solid #ececec;border-bottom:1px solid #ececec}.technology-analytics-wrapper__versions-empty-card div{position:absolute;top:50%;text-align:center;width:100%;line-height:20px;margin-top:-10px}.technology-analytics-wrapper .app-version-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .app-version-date-picker-container+.cly-vue-notification__alert-box{margin-top:-8px}.technology-analytics-wrapper .app-resolution-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .app-carrier-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .devices-date-picker-container{margin-bottom:32px}.technology-analytics-wrapper .version-graph-block .version-graph-title{margin:0px;padding-bottom:4px}.technology-analytics-wrapper__metric-cards .bu-column:last-child{margin-right:unset !important}.technology-analytics-wrapper .technology-pie-graphs .cly-vue-chart__echart{padding-right:0px}.technology-analytics-wrapper .technology-pie-graphs .bu-level .bu-level-right{padding-right:5px}.technology-analytics-wrapper .technology-pie-graphs .bu-column:first-child .cly-vue-chart__echart .cly-vue-chart-legend__secondary{border-right:1px solid #ececec}.technology-analytics-wrapper .technology-pie-graphs .bu-column:first-child .cly-vue-chart__echart .bu-level .bu-level-right{border-right:1px solid #ececec}.technology-analytics-wrapper .technology-pie-graphs .cly-vue-chart-legend__right .cly-vue-chart-legend__secondary .__vuescroll .__rail-is-vertical{right:5px !important}.version-graph-block:last-child{margin-bottom:12px}.technology-dashboard-widget>.bu-column:nth-child(odd)>.cly-vue-section{padding-left:4px}.technology-dashboard-widget>.bu-column:nth-child(even)>.cly-vue-section{padding-right:12px}.routename-analytics-geo .geo-date-picker-container{margin-bottom:32px}.routename-analytics-geo .radio-column{width:256px}.leaflet-noclick-map .leaflet-interactive{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-control-zoom{user-select:none;border-color:#dcdcdc !important}.leaflet-control-zoom a{color:#333c48;border-bottom-color:#dcdcdc}.geo-analytics-data .cly-vue-button{padding:6px 10px;border-radius:4px}.user-management-drawer-content__pp-options{height:fit-content;align-self:center;margin-left:15px;box-shadow:none}.user-management-drawer-content__pp-options .el-button{box-shadow:none;border:none}.user-management-drawer-content__user-pp{width:100px;height:100px;border-radius:50%}.user-management-drawer-content #user-drawer-dropzone{background-color:#f8fafd;border-radius:6px}.user-management-drawer-content__custom-options{box-shadow:none}.user-management-drawer-content__custom-options div button{box-shadow:none;padding:0px;background:none;border:none}.user-management-drawer-content__under-input-text{text-decoration:underline;cursor:pointer}.user-management-drawer-content__top-section{display:flex;flex-direction:row;justify-content:space-between}.user-management-drawer-content__bottom-section{display:flex;margin-top:28px;flex-direction:column;justify-content:space-between}.user-management-drawer-content__input-wrapper{margin-top:10px}.user-management-drawer-content__label{display:flex;justify-content:space-between;flex-direction:column}.user-management-drawer-content__label--text-secondary{color:#81868d}.user-management-drawer-content__label-text{font-style:normal;font-weight:normal;font-size:13px;line-height:16px;color:#333c48}.user-management-drawer-content__help-text{font-family:Inter;font-style:normal;font-weight:normal;font-size:12px;line-height:16px;color:#81868d}.user-management-drawer-content__help-menu-icon{font-family:Inter;font-style:normal;font-weight:normal;font-size:16px;line-height:16px;color:#81868d}.user-management-drawer-content__light-button{background-color:#ddedff;color:#0166d6;font-weight:bold;padding:10px 20px;width:fit-content;margin-top:10px;font-size:13px;border-radius:5px}.user-management-drawer-content__profile-picture-area{margin-top:9px;margin-left:10px}.user-management-drawer-content__profile-picture-area__upload-section{border:1px dashed #81868d;padding:30px;border-radius:8px;display:flex;flex-direction:column;justify-content:center;align-items:center}.user-management-drawer-content__profile-picture-area__upload-section__img-box{background-color:#e2e4e8;height:100px;width:100px;border-radius:50%}.user-management-drawer-content__profile-picture-area__upload-section__description-box{font-size:12px;text-align:center;width:80%}.user-management-drawer-content__profile-picture-area__upload-section__description-box--bold{font-weight:500;color:#0166d6}.user-management-drawer-content__profile-picture-area__upload-section__description-box--gray{color:gray}.user-management-drawer-content__drawer-sub-section{border-radius:6px;background-color:#f8fafd;margin-top:10px;margin-bottom:24px}.user-management-drawer-content__drawer-sub-title{display:flex;flex-direction:row;justify-content:space-between;padding:5px 0px;font-weight:500;font-size:16px}.user-management-drawer-content__permission-table-header{display:flex;justify-content:space-between;margin-top:10px;font-size:12px;border-bottom:1px solid #ebebeb;padding-bottom:10px}.user-management-drawer-content__feature-search{width:calc(40% - 15px);margin-left:10px}.user-management-drawer-content__feature-search .el-input__inner{background-color:#fff !important}.user-management-drawer-content__permission-type-cols{width:75%;display:flex;justify-content:space-evenly}.user-management-drawer-content__permission-type{text-align:center;font-size:11px;width:25%;margin:auto}.user-management-drawer-content__permission-type .check-label{font-size:15px}.user-management-drawer-content__permission-type .check-text{margin-top:2px}.user-management-drawer-content__permission-type .el-checkbox__label{font-weight:medium !important}.user-management-drawer-content__permission-table-content{height:300px;overflow-y:scroll;padding:10px 0px}.user-management-drawer-content__permission-row{display:flex;flex-direction:row;height:30px;justify-content:center;align-items:center}.user-management-drawer-content__feature-name{width:calc(35% - 15px);text-align:left;padding-left:15px;display:flex;justify-content:flex-start;align-items:center;font-style:normal;font-weight:normal;font-size:14px;line-height:20px;color:#333c48}.user-management-drawer-content__feature-permissions{width:65%;display:flex;justify-content:space-evenly}.user-management-drawer-content__permission-box{text-align:center;width:35%}.user-management-drawer-content__section-title{display:flex;flex-direction:row;justify-content:space-between}.user-management-drawer-content__section-header{font-family:Inter;font-style:normal;font-weight:500;font-size:12px;line-height:15px;letter-spacing:.5px;text-transform:uppercase;color:#333c48}.user-management-drawer-content__role-section .check-text{margin-top:4px !important}.user-management-drawer-content__role-section .el-checkbox__label{font-weight:normal !important}.user-management-drawer-content__app-selector{width:100%}.user-management-drawer-content__add-permission-set{background-color:#e1efff;border-radius:4px;cursor:pointer;padding:9.5px 12px;font-weight:500;font-size:13px;line-height:13px;color:#0166d6;width:fit-content}.user-management-drawer-content__group-selector{width:100%}.user-management-drawer-content .check-label{color:#0166d6 !important}.user-management-drawer-content .el-tag--info{background-color:#81868d;color:#fff}.user-management-drawer-content .el-icon-close{background-color:#fff !important}.user-management-drawer-content .el-icon-close:hover{color:#81868d !important}.user-management-drawer-content .dropzone-custom-content{display:flex;flex-direction:column;justify-content:center;align-items:center}.user-management-drawer-content .dz-image>div{height:100px;width:100px;border-radius:50%}.user-management-drawer-content .dz-image-preview{display:flex !important;flex-direction:column;justify-content:center;align-items:center}.user-management-drawer-content .dz-image-preview .dz-remove{font-size:12px;font-weight:bold}.user-management-drawer-content .dz-image-preview .dz-image{display:block !important}.manage-users-table{margin:0px}.manage-users-table__groups{display:inline-flex;color:#32659d}.manage-users-table__groups a:hover{color:#1a75da;text-decoration:underline}.manage-users-table__groups-label{margin:0px 2px}.user-management-filter-form .select-full-width{width:100%}.user-management-view .el-tabs--button>.el-tabs__header{padding:24px 24px 0px 24px}.user-management-view .manage-users-action-btn{background-color:#12af51}#main_home_view .el-loading-parent--relative{min-height:100px}#main_home_view .bu-level-left:first-child>h2{font-size:20px !important}#main_home_view .cly-back-link i{color:#333c48}#main_home_view .componentBlock>div{padding-top:16px;padding-bottom:20px}.app-management-view h2{letter-spacing:.3px}.app-management-view h3{letter-spacing:.3px}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-drawer__steps-wrapper .cly-vue-drawer__steps-container{margin-top:12px}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-drawer__steps-wrapper .cly-vue-drawer__steps-container .cly-vue-form-step__section{padding-left:24px !important;padding-right:24px !important;padding-top:12px !important;padding-bottom:12px !important}.app-management-view .cly-vue-section__content{border:1px solid #ececec;border-radius:4px}.app-management-view .cly-vue-section__content .el-collapse{border:none}.app-management-view .cly-vue-section__content .app-section{margin-left:12px;margin-right:4px}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section{margin-bottom:24px !important;margin-left:0 !important;margin-right:0 !important;color:#333c48;display:flex;justify-content:space-between}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section .cly-vue-form-field__inner .el-form-item .el-form-item__content{line-height:0}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section div p{font-size:14px;line-height:14px;margin:0}.app-management-view .cly-vue-section__content .app-section .cly-vue-form-step__section div p+p{font-size:12px;line-height:16px;font-weight:400;margin-top:4px}.app-management-view .cly-vue-section__content .app-section span div .bu-columns{padding:0 !important;margin:0}.app-management-view .cly-vue-section__content .app-section span div .bu-columns .bu-is-4{width:50%;margin-right:48px}.app-management-view .cly-vue-section__content .app-section span div .bu-columns .bu-is-8{width:37.33%}.app-management-view .account-settings-avatar-box{padding:24px;overflow:auto;flex:auto}.app-management-view .account-settings-avatar-box .el-upload{display:block}.app-management-view .feedback-settings__upload .el-upload{display:block}.app-management-view .feedback-settings__upload img{width:165px;height:50px;margin-left:24px;cursor:default}.app-management-view .feedback-settings__colorpicker .cly-vue-color-picker__picker{overflow:auto}.app-management-view .feedback-settings__button:active,.app-management-view .feedback-settings__button:focus,.app-management-view .feedback-settings__button:hover{background-color:unset !important;border-color:unset !important}.app-management-view .app-management-list{max-height:681px;max-width:223px}.app-management-view .app-management-list .cly-vue-listbox__item span{margin-left:8px;position:relative;top:-8px}.app-management-view .app-management-details .bu-columns{margin:0 !important;padding:0 !important;border-bottom:1px solid #ececec}.app-management-view .app-management-details .bu-columns:last-child{border-bottom:none;margin-bottom:-20px}.app-management-view .app-management-details .el-collapse-item__header.is-active{border-bottom:1px solid #ececec}.app-management-view .app-management-details .el-collapse-item__content{padding-bottom:0}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-form-step__section{padding-left:48px !important;padding-right:24px !important;padding-top:12px !important;padding-bottom:12px !important}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-form-step__section div:nth-child(2) .el-form-item__content{display:flex !important;justify-content:right !important;float:right;line-height:inherit}.app-management-view .app-management-details .el-collapse-item__content .cly-vue-form-step__section div:nth-child(2) .el-form-item__content>div{max-width:232px}.app-management-view .app-management-details .el-collapse-item__arrow{color:#81868d}.app-management-view .app-management-details .el-collapse-item__header{color:#333c48;padding-left:24px !important;text-transform:uppercase}.app-management-view .app-management-details .el-collapse-item__wrap{overflow:unset}.app-management-view .box-card{background-color:#e6f0fb;border:1px solid #0166d6;box-sizing:border-box;box-shadow:0px 1px 0px #ececec;border-radius:4px}.app-management-view .box-card .el-card__body{padding:24px}.app-management-view .box-card .el-card__body p a{color:#0166d6}.app-management-view .box-card .el-card__body p:nth-of-type(1){line-height:24px;font-weight:500;font-size:16px}.app-management-view .box-card .el-card__body p{font-size:14px}.el-dropdown-menu__item--divided__fix-broken{border:none}.el-dropdown-menu__item--divided__fix-broken::before{height:0px}.add-border{margin:0 8px;padding:0;border-top:1px solid #ececec}.add-border>a{display:none}@media(max-width: 2133px){.app-section span div div:nth-last-child(1) .cly-vue-form-step__section{display:block !important}.app-section span div div:nth-last-child(1) .cly-vue-form-step__section .el-input{width:268%}}@media(max-width: 1280px){.app-section span div div:nth-last-child(2) .cly-vue-form-step__section{display:block !important}.app-section span div div:nth-last-child(2) .cly-vue-form-step__section .el-input{width:177%}}.report-manager-filter-form .select-full-width{width:100%}.token-manager-drawer__choice .el-radio.is-autosized{flex:1 1 0;min-width:0}.token-manager-drawer__choice .el-radio__label{white-space:normal}.token-manager-drawer__permission-header,.token-manager-drawer__permission-row{display:flex;flex-direction:row;align-items:center}.token-manager-drawer__permission-header{border-bottom:1px solid #ebebeb;padding-bottom:10px;margin-bottom:10px}.token-manager-drawer__permission-row{height:30px}.token-manager-drawer__feature-name{width:35%;display:flex;align-items:center;justify-content:flex-start}.token-manager-drawer__feature-permissions{width:65%;display:flex}.token-manager-drawer__permission-box{flex:1 1 0;min-width:0;display:flex;align-items:center;justify-content:center}.token-manager-drawer__permission-box .el-checkbox{margin-right:0}.token-manager-drawer__permission-header .token-manager-drawer__permission-box .el-checkbox{display:flex;flex-direction:column-reverse;align-items:center}.token-manager-drawer__permission-header .token-manager-drawer__permission-box .el-checkbox__label{font-size:11px;padding-left:0;margin-bottom:4px;line-height:1}.header{display:flex;flex-direction:column;gap:20px}.setup-logo{margin-top:38px;margin-left:72px;width:165px;height:50px;background-image:url("../images/pre-login/countly-logo-dark.svg");background-repeat:no-repeat;background-size:contain;position:fixed;left:0px;top:0px}.centered-content{min-height:100vh;display:flex;align-items:center;justify-content:center;margin-bottom:-35px}.setup-byline{color:#81868d;margin-top:12px;font-size:14px}.setup-form{width:520px}.setup-radio-grid{display:grid;grid-template-columns:1fr 1fr 1fr;column-gap:10px;row-gap:10px}.setup-radio .el-radio.is-checked{background-color:#e1efff}.setup-radio .el-radio{margin-left:0 !important}.setup-radio .is-autosized.is-bordered .el-radio__input{display:none !important}.setup-radio .el-radio__label{padding-left:0}.setup-radio-item{display:flex;align-items:center;font-size:24px}.setup-radio-icon-bg{width:40px;height:40px;display:flex;align-items:center;justify-content:center;background-color:#e1efff;border-radius:100%}.is-checked .setup-radio-icon-bg{background-color:#0166d6;color:#fff}.setup-radio-icon{background-image:url("../images/dashboard/onboarding/box.svg");background-position:center;background-repeat:no-repeat;width:100%;height:100%}.setup-radio-icon--app-type-mobile{background-image:url("../images/dashboard/onboarding/app-type-mobile.svg")}.setup-radio-icon--app-type-web{background-image:url("../images/dashboard/onboarding/app-type-web.svg")}.setup-radio-icon--app-type-desktop{background-image:url("../images/dashboard/onboarding/app-type-desktop.svg")}.is-checked .setup-radio-icon{filter:brightness(10)}.setup-video-container{max-width:800px}.consent-form{width:700px}.consent-blurb{color:#81868d;font-size:14px}.consent-question{font-size:14px}.initial-populator{width:800px}.quickstart-item{display:flex;align-items:flex-start}.quickstart-item-desc{color:#81868d;text-align:left;word-break:break-word}.quickstart-link,.quickstart-link:visited{color:#0166d6}.quickstart-link:hover{color:#333c48}.preset-list{display:inline-block;position:absolute;left:170px;border:1px solid #a7aeb8;border-radius:4px;background-color:#fff}.preset-list__header{z-index:1997;position:relative;padding:8px 16px}.preset-list__header::after{content:"";position:absolute;width:100%;height:16px;background:linear-gradient(180deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);left:0;opacity:.12;top:100%}.preset-list__header .el-button--primary{border:unset;font-weight:500;box-shadow:none}.preset-list__header .el-button--primary:hover{background-color:#cce0f7;color:#0166d6}.preset-list__header .el-button--text{font-weight:500;font-size:14px}.preset-list__body{background-color:#f9f9f9;height:400px;border-radius:4px}.preset-list__body::after{content:"";position:absolute;width:100%;height:16px;background:linear-gradient(0deg, #333c48 0%, rgba(51, 60, 72, 0.0001) 100%);opacity:.12;left:0;bottom:0}.preset-list__body .preset{display:flex;flex-direction:column;justify-content:center;width:300px;background-color:#fff;border:1px solid #ececec;border-radius:4px;padding:12px;margin-bottom:8px;cursor:pointer}.preset-list__body .preset:first-child{margin-top:16px}.preset-list__body .preset:last-child{margin-bottom:16px}.preset-list__body .preset:hover{border:1px solid #0166d6;background-color:#e1efff}.preset-list__body .preset--selected{border:1px solid #0166d6}.preset-list .sortable-chosen{background-color:#e1efff !important}#preset-drawer .el-radio__label{display:flex;align-items:center}#preset-drawer .el-radio__input{display:flex}#preset-drawer .el-checkbox{font-weight:400}#preset-drawer .el-checkbox__label{padding-left:8px;color:#333c48}#preset-drawer .el-checkbox:hover__label{color:#333c48}/*# sourceMappingURL=manifest.css.map */ diff --git a/plugins/dashboards/api/api.js b/plugins/dashboards/api/api.js index 85585c4081f..e3bcc563450 100644 --- a/plugins/dashboards/api/api.js +++ b/plugins/dashboards/api/api.js @@ -1506,6 +1506,8 @@ plugins.setConfigs("dashboards", { purpose: "LoginAuthToken", temporary: true, ttl: 540, //9 minutes + //the headless renderer authenticates by redeeming this at /login/token + can_login: true, callback: function(er, token) { if (er) { return resolve(); diff --git a/test/2.api/16.token.manager.js b/test/2.api/16.token.manager.js index eceac22a375..f5b2e6e6bcf 100644 --- a/test/2.api/16.token.manager.js +++ b/test/2.api/16.token.manager.js @@ -201,10 +201,14 @@ describe('Testing token manager', function() { validate_token(token1, {"app": [APP_ID], "multi": true, "ttl": 300, "endpoint": ["/o/token"], "purpose": "My test token"}, 1, done); }); + //Deliberate change of behaviour: /o/token/list returns whole token documents, and a + //document's _id is the token itself, so listing hands the caller credentials that may be + //wider than the one it used. Token listing is therefore restricted to a full-permission + //credential, and this endpoint-scoped token is refused even though its endpoint matches. it('using token' + token1, function(done) { request .get('/o/token/list?auth_token=' + token1) - .expect(200) + .expect(403) .end(function(err, res) { if (err) { return done(err); @@ -379,4 +383,251 @@ describe('Testing token manager', function() { }); }); }); + + describe('Token permissions bound every grant to the creating credential', function() { + var limitedToken = ""; + var fullToken = ""; + var legacyRestrictedToken = ""; + + //a token allowed only to read the "core" feature of APP_ID, and nothing else + var readCorePermission = function(appId) { + var permission = {_: {a: [], u: [[appId]]}, c: {}, r: {}, u: {}, d: {}}; + permission.r[appId] = {all: false, allowed: {core: true}}; + return encodeURIComponent(JSON.stringify(permission)); + }; + + //a token that additionally claims delete on everything, which its creator must not pass on + var deleteAllPermission = function(appId) { + var permission = {_: {a: [], u: [[appId]]}, c: {}, r: {}, u: {}, d: {}}; + permission.r[appId] = {all: false, allowed: {core: true}}; + permission.d[appId] = {all: true, allowed: {}}; + return encodeURIComponent(JSON.stringify(permission)); + }; + + var deleteToken = function(id, done) { + if (!id) { + return done(); + } + request + .get('/i/token/delete?api_key=' + API_KEY_ADMIN + '&tokenid=' + id) + .end(function() { + done(); + }); + }; + + it('setup: api_key creates a token limited to reading core on one app', function(done) { + request + .get('/i/token/create?api_key=' + API_KEY_ADMIN + '&purpose=integration&multi=true&ttl=3600&permission=' + readCorePermission(APP_ID)) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var ob = JSON.parse(res.text); + ob.should.have.property('result'); + limitedToken = ob.result; + (limitedToken !== "").should.equal(true); + done(); + }); + }); + + it('the limited token can read the feature it was granted', function(done) { + request + .get('/o/users/permissions?app_id=' + APP_ID + '&auth_token=' + limitedToken) + .expect(200) + .end(function(err) { + if (err) { + return done(err); + } + done(); + }); + }); + + it('the limited token cannot act as a global admin, even though its owner is one', function(done) { + request + .get('/o/users/all?auth_token=' + limitedToken) + .expect(401) + .end(function(err) { + if (err) { + return done(err); + } + done(); + }); + }); + + it('the limited token cannot create an unrestricted child', function(done) { + request + .get('/i/token/create?auth_token=' + limitedToken + '&multi=true&ttl=300') + .expect(403) + .end(function(err, res) { + if (err) { + return done(err); + } + //an unrestricted child would carry the owner's full permissions + JSON.parse(res.text).should.have.property('result'); + done(); + }); + }); + + it('the limited token cannot grant a permission it does not hold', function(done) { + request + .get('/i/token/create?auth_token=' + limitedToken + '&multi=true&ttl=300&permission=' + deleteAllPermission(APP_ID)) + .expect(403) + .end(function(err, res) { + if (err) { + return done(err); + } + JSON.parse(res.text).should.have.property('result', "Token permissions must be a subset of the creating credential's permissions"); + done(); + }); + }); + + it('the limited token can create a child within its own permissions', function(done) { + request + .get('/i/token/create?auth_token=' + limitedToken + '&multi=true&ttl=300&permission=' + readCorePermission(APP_ID)) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + deleteToken(JSON.parse(res.text).result, done); + }); + }); + + it('the limited token cannot be granted login permission', function(done) { + //the permission the token already holds is supplied, so a narrowing child is the only + //thing being asked for beyond login - what is refused here is the login grant itself + request + .get('/i/token/create?auth_token=' + limitedToken + '&multi=true&ttl=300&can_login=true&permission=' + readCorePermission(APP_ID)) + .expect(403) + .end(function(err, res) { + if (err) { + return done(err); + } + JSON.parse(res.text).should.have.property('result', 'The creating credential cannot grant login permission'); + done(); + }); + }); + + it('the limited token cannot list the owner tokens, which would hand it their secrets', function(done) { + request + .get('/o/token/list?auth_token=' + limitedToken) + .expect(403) + .end(function(err, res) { + if (err) { + return done(err); + } + JSON.parse(res.text).should.have.property('result', 'A restricted token cannot list tokens'); + done(); + }); + }); + + it('the limited token cannot delete the owner tokens', function(done) { + request + .get('/i/token/delete?auth_token=' + limitedToken + '&tokenid=' + limitedToken) + .expect(403) + .end(function(err) { + if (err) { + return done(err); + } + done(); + }); + }); + + it('a purpose string alone never grants login permission', function(done) { + request + .get('/i/token/create?api_key=' + API_KEY_ADMIN + '&purpose=LoggedInAuth&multi=true&ttl=300') + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var forged = JSON.parse(res.text).result; + //the token exists, but carries no login permission, so it cannot open a session + request + .get('/login/token/' + forged) + .expect(302) + .end(function(err2, res2) { + if (err2) { + return done(err2); + } + //rejected logins are redirected back to the login page + res2.headers.location.should.not.containEql('/dashboard'); + deleteToken(forged, done); + }); + }); + }); + + it('api_key can still create a token, and grant it login permission', function(done) { + request + .get('/i/token/create?api_key=' + API_KEY_ADMIN + '&multi=true&ttl=300&can_login=true') + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + fullToken = JSON.parse(res.text).result; + (fullToken !== "").should.equal(true); + done(); + }); + }); + + it('a full-permission token can create a child, as the dashboard session does', function(done) { + request + .get('/i/token/create?auth_token=' + fullToken + '&purpose=child&multi=true&ttl=300') + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + deleteToken(JSON.parse(res.text).result, done); + }); + }); + + it('setup: a token restricted the legacy way, by app', function(done) { + request + .get('/i/token/create?api_key=' + API_KEY_ADMIN + '&purpose=legacy&multi=true&ttl=3600&apps=' + APP_ID) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + legacyRestrictedToken = JSON.parse(res.text).result; + done(); + }); + }); + + it('a legacy app-restricted token cannot create tokens', function(done) { + request + .get('/i/token/create?auth_token=' + legacyRestrictedToken + '&multi=true&ttl=300') + .expect(403) + .end(function(err, res) { + if (err) { + return done(err); + } + JSON.parse(res.text).should.have.property('result', 'A restricted token cannot create tokens'); + done(); + }); + }); + + it('an endpoint restriction cannot be combined with permissions', function(done) { + request + .get('/i/token/create?api_key=' + API_KEY_ADMIN + '&multi=true&ttl=300&endpoint=/o/users&permission=' + readCorePermission(APP_ID)) + .expect(400) + .end(function(err) { + if (err) { + return done(err); + } + done(); + }); + }); + + it('cleanup: remove the tokens created here', function(done) { + deleteToken(limitedToken, function() { + deleteToken(fullToken, function() { + deleteToken(legacyRestrictedToken, done); + }); + }); + }); + }); }); \ No newline at end of file diff --git a/test/unit-tests/api.utils.authorizer.tokenFields.js b/test/unit-tests/api.utils.authorizer.tokenFields.js new file mode 100644 index 00000000000..3e86a0af786 --- /dev/null +++ b/test/unit-tests/api.utils.authorizer.tokenFields.js @@ -0,0 +1,179 @@ +require("should"); +var authorizer = require("../../api/utils/authorizer.js"); + +var OWNER = "60e42efa5c23ee7ec6259af0"; + +/** +* Minimal in-memory stand-in for the countly db handle, so the token record can be exercised +* without a running MongoDB. +* @param {object[]} tokens - documents the auth_tokens collection starts with +* @returns {object} db stub, with the stored documents on .stored +*/ +function dbStub(tokens) { + var stored = tokens || []; + return { + stored: stored, + ObjectID: function(id) { + return id; + }, + collection: function(name) { + return { + findOne: function(query, callback) { + if (name === "members") { + return callback(null, {_id: OWNER}); + } + var found = stored.filter(function(doc) { + return doc._id === query._id; + })[0]; + callback(null, found || null); + }, + insert: function(doc, callback) { + stored.push(doc); + callback(null, doc); + }, + remove: function() { }, + update: function() { }, + findAndModify: function(rules, sort, update, callback) { + callback(null, null); + } + }; + } + }; +} + +describe("authorizer token record", function() { + it("stores can_login and token_permission when they are asked for", function(done) { + var db = dbStub([]); + var permission = {_: {a: [], u: [["app1"]]}, c: {}, r: {app1: {all: false, allowed: {core: true}}}, u: {}, d: {}}; + authorizer.save({ + db: db, + owner: OWNER, + ttl: 300, + token_permission: permission, + can_login: true, + callback: function(err) { + (!err).should.equal(true); + db.stored.length.should.equal(1); + db.stored[0].can_login.should.equal(true); + db.stored[0].token_permission.should.eql(permission); + done(); + } + }); + }); + + it("defaults to no login permission and no permission scope", function(done) { + var db = dbStub([]); + authorizer.save({ + db: db, + owner: OWNER, + ttl: 300, + callback: function() { + //login permission is never acquired by omission + db.stored[0].can_login.should.equal(false); + db.stored[0].should.not.have.property("token_permission"); + done(); + } + }); + }); + + it("never treats a truthy non-true value as login permission", function(done) { + var db = dbStub([]); + authorizer.save({ + db: db, + owner: OWNER, + ttl: 300, + can_login: "yes", + callback: function() { + db.stored[0].can_login.should.equal(false); + done(); + } + }); + }); + + describe("verify_token", function() { + var future = Math.round(Date.now() / 1000) + 3600; + + it("still applies the endpoint regex to a legacy token", function(done) { + var db = dbStub([{ + _id: "legacy", + ttl: 300, + ends: future, + multi: true, + owner: OWNER, + app: "", + endpoint: ["^/o/users"], + purpose: "legacy" + }]); + authorizer.verify_return({ + db: db, + token: "legacy", + req_path: "/o/apps/all", + return_data: true, + callback: function(valid) { + //path outside the allowed regex is refused, as before + (valid === false).should.equal(true); + authorizer.verify_return({ + db: db, + token: "legacy", + req_path: "/o/users/all", + return_data: true, + callback: function(valid2) { + valid2.should.have.property("_id", "legacy"); + done(); + } + }); + } + }); + }); + + it("ignores the endpoint regex once a token carries permissions", function(done) { + //the regex was never an authorization boundary; permissions are enforced in rights.js + var db = dbStub([{ + _id: "scoped", + ttl: 300, + ends: future, + multi: true, + owner: OWNER, + app: "", + endpoint: ["^/o/users"], + purpose: "integration", + token_permission: {_: {a: [], u: [[]]}, c: {}, r: {}, u: {}, d: {}} + }]); + authorizer.verify_return({ + db: db, + token: "scoped", + req_path: "/o/apps/all", + return_data: true, + callback: function(valid) { + valid.should.have.property("_id", "scoped"); + valid.should.have.property("token_permission"); + done(); + } + }); + }); + + it("still refuses a request for an app the token is not scoped to", function(done) { + var db = dbStub([{ + _id: "appscoped", + ttl: 300, + ends: future, + multi: true, + owner: OWNER, + app: ["app1"], + endpoint: "", + purpose: "integration" + }]); + authorizer.verify_return({ + db: db, + token: "appscoped", + req_path: "/o", + qstring: {app_id: "app2"}, + return_data: true, + callback: function(valid) { + (valid === false).should.equal(true); + done(); + } + }); + }); + }); +}); diff --git a/test/unit-tests/api.utils.rights.tokenPermissions.js b/test/unit-tests/api.utils.rights.tokenPermissions.js new file mode 100644 index 00000000000..df3671067ba --- /dev/null +++ b/test/unit-tests/api.utils.rights.tokenPermissions.js @@ -0,0 +1,159 @@ +require("should"); +var rights = require("../../api/utils/rights.js"); + +var APP_A = "aaaaaaaaaaaaaaaaaaaaaaaa"; +var APP_B = "bbbbbbbbbbbbbbbbbbbbbbbb"; + +/** +* Build a permission object of the shape the permission editor and the token manager produce. +* @param {object} spec - {adminApps, userApps, grants:[{type, app, all, allowed}]} +* @returns {object} permission object +*/ +function permission(spec) { + var perm = {_: {a: spec.adminApps || [], u: [spec.userApps || []]}, c: {}, r: {}, u: {}, d: {}}; + (spec.grants || []).forEach(function(grant) { + perm[grant.type][grant.app] = grant.all ? {all: true, allowed: {}} : {all: false, allowed: grant.allowed}; + }); + return perm; +} + +var globalAdmin = {global_admin: true}; + +//can create+read core on A, read core and events on A, read core on B +var member = { + global_admin: false, + permission: permission({ + userApps: [APP_A, APP_B], + grants: [ + {type: "c", app: APP_A, allowed: {core: true}}, + {type: "r", app: APP_A, allowed: {core: true, events: true}}, + {type: "r", app: APP_B, allowed: {core: true}} + ] + }) +}; + +var adminOfA = {global_admin: false, permission: permission({adminApps: [APP_A]})}; + +var readCoreOnA = permission({userApps: [APP_A], grants: [{type: "r", app: APP_A, allowed: {core: true}}]}); + +describe("token permissions", function() { + describe("isPermissionSubset", function() { + it("allows a grant the ceiling holds", function() { + rights.isPermissionSubset(readCoreOnA, member).should.equal(true); + }); + + it("refuses a grant on an app the ceiling cannot reach", function() { + //the escalation this model exists to prevent: the owner has A and B, but a token + //scoped to A must not be able to produce a child that reaches B + var childOnB = permission({userApps: [APP_B], grants: [{type: "r", app: APP_B, allowed: {core: true}}]}); + rights.isPermissionSubset(childOnB, {permission: readCoreOnA}).should.equal(false); + rights.isPermissionSubset(childOnB, member).should.equal(true); + }); + + it("refuses a feature the ceiling does not hold", function() { + var update = permission({userApps: [APP_A], grants: [{type: "u", app: APP_A, allowed: {core: true}}]}); + rights.isPermissionSubset(update, member).should.equal(false); + }); + + it("refuses an all grant from a ceiling that holds only named features", function() { + //"all" covers features that do not exist yet, so only an "all" holder may pass it on + var readAll = permission({userApps: [APP_A], grants: [{type: "r", app: APP_A, all: true}]}); + rights.isPermissionSubset(readAll, member).should.equal(false); + rights.isPermissionSubset(readAll, adminOfA).should.equal(true); + rights.isPermissionSubset(readAll, globalAdmin).should.equal(true); + }); + + it("refuses app administration unless the ceiling administers that app", function() { + var adminChild = permission({adminApps: [APP_A]}); + rights.isPermissionSubset(adminChild, member).should.equal(false); + rights.isPermissionSubset(adminChild, adminOfA).should.equal(true); + }); + + it("ignores entries that grant nothing", function() { + //the permission editor emits an entry for every visible app, most of them empty + var editorShaped = permission({userApps: [APP_A], grants: [{type: "r", app: APP_A, allowed: {core: true}}]}); + editorShaped.d[APP_B] = {all: false, allowed: {}}; + editorShaped.c[APP_B] = {all: false, allowed: {core: false}}; + rights.isPermissionSubset(editorShaped, {permission: readCoreOnA}).should.equal(true); + }); + + it("refuses anything that is not a permission object", function() { + rights.isPermissionSubset(undefined, member).should.equal(false); + rights.isPermissionSubset([], member).should.equal(false); + }); + }); + + describe("intersectPermission", function() { + it("strips global admin, so a scoped token cannot act as one", function() { + var scoped = rights.intersectPermission(globalAdmin, readCoreOnA); + scoped.global_admin.should.equal(false); + rights.hasReadRight("core", APP_A, scoped).should.equal(true); + rights.hasReadRight("core", APP_B, scoped).should.equal(false); + }); + + it("keeps only what the token and the owner both allow", function() { + //the token asks for everything; the owner has only named features + var wide = permission({ + userApps: [APP_A], + grants: [ + {type: "r", app: APP_A, all: true}, + {type: "d", app: APP_A, all: true} + ] + }); + var scoped = rights.intersectPermission(member, wide); + rights.hasReadRight("core", APP_A, scoped).should.equal(true); + rights.hasReadRight("events", APP_A, scoped).should.equal(true); + rights.hasDeleteRight("core", APP_A, scoped).should.equal(false); + }); + + it("drops access the owner lost after the token was created", function() { + var reduced = { + global_admin: false, + permission: permission({ + userApps: [APP_A], + grants: [{type: "r", app: APP_A, allowed: {core: true}}] + }) + }; + var tokenOnBoth = permission({ + userApps: [APP_A, APP_B], + grants: [ + {type: "r", app: APP_A, allowed: {core: true}}, + {type: "r", app: APP_B, allowed: {core: true}} + ] + }); + var scoped = rights.intersectPermission(reduced, tokenOnBoth); + rights.hasReadRight("core", APP_A, scoped).should.equal(true); + rights.hasReadRight("core", APP_B, scoped).should.equal(false); + rights.getUserApps(scoped).indexOf(APP_B).should.equal(-1); + }); + + it("does not leave an app administrator administering it through a narrower token", function() { + var scoped = rights.intersectPermission(adminOfA, readCoreOnA); + rights.hasAdminAccess(scoped, APP_A).should.equal(false); + rights.hasReadRight("core", APP_A, scoped).should.equal(true); + rights.hasDeleteRight("core", APP_A, scoped).should.equal(false); + }); + + it("reports only the apps the token reaches", function() { + var scoped = rights.intersectPermission(member, readCoreOnA); + rights.getUserApps(scoped).should.eql([APP_A]); + }); + }); + + describe("isScopedCredential", function() { + it("treats an api_key request as unscoped", function() { + rights.isScopedCredential({}).should.equal(false); + }); + + it("treats a session style token as unscoped", function() { + rights.isScopedCredential({token_data: {app: "", endpoint: ""}}).should.equal(false); + rights.isScopedCredential({token_data: {app: [], endpoint: []}}).should.equal(false); + }); + + it("treats a permission scoped or legacy restricted token as scoped", function() { + rights.isScopedCredential({token_data: {token_permission: readCoreOnA}}).should.equal(true); + rights.isScopedCredential({token_data: {app: [APP_A], endpoint: ""}}).should.equal(true); + rights.isScopedCredential({token_data: {app: "", endpoint: ["^/o/users"]}}).should.equal(true); + }); + }); +});