import React, { useState } from 'react';
import { dateTime } from '@grafana/data';
import { config } from '@grafana/runtime';
import { addBodyRenderHook } from 'app/AppWrapper';
import { contextSrv } from 'app/core/services/context_srv';
import store from 'app/core/store';
import { OrgRole } from 'app/types';
import { AccessControlAction } from '../types';
import { IsInvalid, HasExpired, ExpiresSoon, TokenExpiresSoon, MaxUsersReached } from './LicenseWarning';
import {
DISMISS_WARNING_FOR_DAYS,
LICENSE_WARNING_DISMISS_UNTIL_KEY,
WARNING_CLOSE_TIMEOUT_SEC,
LIMIT_BY_USERS,
} from './constants';
import { refreshLicenseStats } from './state/api';
import { ActiveUserStats } from './types';
interface LicensingSettings {
activeAdminsAndEditors?: number;
activeViewers?: number;
activeUsers?: number;
limitBy?: string;
includedAdmins?: number;
includedViewers?: number;
includedUsers?: number;
slug?: string;
licenseExpiry?: number;
licenseExpiryWarnDays?: number;
tokenExpiry?: number;
tokenExpiryWarnDays?: number;
usageBilling?: boolean;
isTrial?: boolean;
}
export function initLicenseWarnings() {
addBodyRenderHook(LicenseWarning);
}
export function LicenseWarning() {
const [isClosed, setIsClosed] = useState(false);
const [settings, updateSettings] = useState((config as any).licensing as LicensingSettings);
const dismissUntil = store.get(LICENSE_WARNING_DISMISS_UNTIL_KEY);
const hasDismissed = dismissUntil && dismissUntil > dateTime().valueOf();
// true if the user has licensing:read permission if RBAC is enabled or is an organisation admin if RBAC is disabled
const isLicensingReaderOrAdmin = contextSrv.hasAccess(
AccessControlAction.LicensingRead,
contextSrv.hasRole(OrgRole.Admin)
);
// true if the user has licensing:read permission if RBAC is enabled or is a Grafana server admin if RBAC is disabled
const isLicensingReaderOrGrafanaAdmin = contextSrv.hasAccess(
AccessControlAction.LicensingRead,
contextSrv.isGrafanaAdmin
);
const showExpireSoon = isLicensingReaderOrAdmin && willExpireSoon() && !hasDismissed;
const showTokenExpireSoon = isLicensingReaderOrAdmin && tokenWillExpireSoon() && !hasDismissed;
const usageBillingDisabled = isUsageBillingDisabled();
let showMaxUsersReached = false;
let activeUsers = 0;
let maxUsers = 0;
if (settings.limitBy === LIMIT_BY_USERS) {
const maxUsersReached = numberOfActiveUsersReached(settings.includedUsers, settings.activeUsers);
showMaxUsersReached =
usageBillingDisabled && maxUsersReached && (isLicensingReaderOrAdmin || isLicensingReaderOrGrafanaAdmin);
activeUsers = settings.activeUsers!;
maxUsers = settings.includedUsers!;
}
if (isRenderingPanel() || isLicenseAdminPage()) {
return null;
}
const onCloseWarning = () => {
const dismissTill = dateTime().add(DISMISS_WARNING_FOR_DAYS, 'd').valueOf();
store.set(LICENSE_WARNING_DISMISS_UNTIL_KEY, dismissTill);
setIsClosed(true);
};
const onRefreshWarning = async () => {
const activeUserStats: ActiveUserStats | null = await refreshLicenseStats().catch((err) => null);
if (activeUserStats) {
const update = {
...settings,
activeUsers: activeUserStats.active_users,
};
// update on config object as well
(config as any).licensing = update;
updateSettings(update);
}
};
if (isClosed) {
return null;
}
if (isInvalid()) {
return ;
} else if (hasExpired()) {
return ;
} else if (showMaxUsersReached) {
return (
);
} else if (showExpireSoon) {
const expiresIn = willExpireInDays();
// auto hide expire warning in case it's a TV monitor with admin permissions
setTimeout(onCloseWarning, 1000 * WARNING_CLOSE_TIMEOUT_SEC);
return (
);
} else if (showTokenExpireSoon) {
const expiresIn = tokenWillExpireInDays();
// auto hide expire warning in case it's a TV monitor with admin permissions
setTimeout(onCloseWarning, 1000 * WARNING_CLOSE_TIMEOUT_SEC);
return (
);
}
return null;
}
export function isInvalid(): boolean {
const { expiry, hasLicense } = (config as any).licenseInfo;
return hasLicense && !expiry;
}
export function willExpireSoon(): boolean {
const { licenseExpiry, licenseExpiryWarnDays = 30 } = (config as any).licensing;
return licenseExpiry > 0 && dateTime(licenseExpiry * 1000) < dateTime().add(licenseExpiryWarnDays, 'd');
}
export function willExpireInDays(): number {
const { licenseExpiry } = (config as any).licensing;
return Math.ceil((licenseExpiry - dateTime().unix()) / 3600 / 24);
}
export function hasExpired(): boolean {
const { licenseExpiry } = (config as any).licensing;
return licenseExpiry > 0 && dateTime(licenseExpiry * 1000) < dateTime();
}
function tokenWillExpireSoon(): boolean {
const { tokenExpiry, tokenExpiryWarnDays = 3 } = (config as any).licensing;
return tokenExpiry > 0 && dateTime(tokenExpiry * 1000) < dateTime().add(tokenExpiryWarnDays, 'd');
}
function tokenWillExpireInDays(): number {
const { tokenExpiry } = (config as any).licensing;
return Math.ceil((tokenExpiry - dateTime().unix()) / 3600 / 24);
}
export function numberOfActiveUsersReached(includedUsers?: number, activeUsers?: number): boolean {
if (includedUsers === undefined || activeUsers === undefined) {
return false;
}
return includedUsers !== -1 && activeUsers > includedUsers;
}
function isUsageBillingDisabled(): boolean {
const settings = (config as any).licensing as LicensingSettings;
return !settings.usageBilling;
}
function isSoloPanel(): boolean {
const soloPanelPattern = /\/d-solo\//;
const path = window.location.pathname;
return soloPanelPattern.test(path);
}
function isRenderingPanel(): boolean {
return isSoloPanel();
}
function isLicenseAdminPage(): boolean {
const pattern = /\/admin\/licensing$/;
const path = window.location.pathname;
return pattern.test(path);
}