mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 02:09:04 +08:00
fix(license): OSS UI build failing due to restrictive active license check (#7345)
* fix: ui breaking due to licenses issue * feat: handle navigations in case of oss in homepage (#7347) * feat: handle navigations in case of oss in homepage * fix: skip datasource and redirect to get-started from services table --------- Co-authored-by: makeavish <makeavish786@gmail.com> --------- Co-authored-by: makeavish <makeavish786@gmail.com>
This commit is contained in:
parent
7118829107
commit
f992ba9106
@ -44,7 +44,6 @@ function App(): JSX.Element {
|
||||
trialInfo,
|
||||
activeLicenseV3,
|
||||
isFetchingActiveLicenseV3,
|
||||
activeLicenseV3FetchError,
|
||||
userFetchError,
|
||||
licensesFetchError,
|
||||
featureFlagsFetchError,
|
||||
@ -264,12 +263,7 @@ function App(): JSX.Element {
|
||||
// if the user is in logged in state
|
||||
if (isLoggedInState) {
|
||||
// if the setup calls are loading then return a spinner
|
||||
if (
|
||||
isFetchingLicenses ||
|
||||
isFetchingUser ||
|
||||
isFetchingFeatureFlags ||
|
||||
isFetchingActiveLicenseV3
|
||||
) {
|
||||
if (isFetchingLicenses || isFetchingUser || isFetchingFeatureFlags) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
@ -277,7 +271,7 @@ function App(): JSX.Element {
|
||||
// this needs to be on top of data missing error because if there is an error, data will never be loaded and it will
|
||||
// move to indefinitive loading
|
||||
if (
|
||||
(userFetchError || licensesFetchError || activeLicenseV3FetchError) &&
|
||||
(userFetchError || licensesFetchError) &&
|
||||
pathname !== ROUTES.SOMETHING_WENT_WRONG
|
||||
) {
|
||||
history.replace(ROUTES.SOMETHING_WENT_WRONG);
|
||||
@ -287,8 +281,7 @@ function App(): JSX.Element {
|
||||
if (
|
||||
(!licenses || !user.email || !featureFlags) &&
|
||||
!userFetchError &&
|
||||
!licensesFetchError &&
|
||||
!activeLicenseV3FetchError
|
||||
!licensesFetchError
|
||||
) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
@ -6,7 +6,11 @@ import { useGetDeploymentsData } from 'hooks/CustomDomain/useGetDeploymentsData'
|
||||
import history from 'lib/history';
|
||||
import { Globe, Link2 } from 'lucide-react';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
|
||||
import { DOCS_LINKS } from '../constants';
|
||||
|
||||
function DataSourceInfo({
|
||||
dataSentToSigNoz,
|
||||
@ -15,6 +19,8 @@ function DataSourceInfo({
|
||||
dataSentToSigNoz: boolean;
|
||||
isLoading: boolean;
|
||||
}): JSX.Element {
|
||||
const { activeLicenseV3 } = useAppContext();
|
||||
|
||||
const notSendingData = !dataSentToSigNoz;
|
||||
|
||||
const {
|
||||
@ -77,12 +83,36 @@ function DataSourceInfo({
|
||||
tabIndex={0}
|
||||
onClick={(): void => {
|
||||
logEvent('Homepage: Connect dataSource clicked', {});
|
||||
history.push(ROUTES.GET_STARTED);
|
||||
|
||||
if (
|
||||
activeLicenseV3 &&
|
||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter') {
|
||||
logEvent('Homepage: Connect dataSource clicked', {});
|
||||
history.push(ROUTES.GET_STARTED);
|
||||
|
||||
if (
|
||||
activeLicenseV3 &&
|
||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -1,11 +1,14 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import './HomeChecklist.styles.scss';
|
||||
|
||||
import { Button } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { ArrowRight, ArrowRightToLine, BookOpenText } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
export type ChecklistItem = {
|
||||
@ -14,6 +17,7 @@ export type ChecklistItem = {
|
||||
description: string;
|
||||
completed: boolean;
|
||||
isSkipped: boolean;
|
||||
isSkippable?: boolean;
|
||||
skippedPreferenceKey?: string;
|
||||
toRoute?: string;
|
||||
docsLink?: string;
|
||||
@ -28,7 +32,7 @@ function HomeChecklist({
|
||||
onSkip: (item: ChecklistItem) => void;
|
||||
isLoading: boolean;
|
||||
}): JSX.Element {
|
||||
const { user } = useAppContext();
|
||||
const { user, activeLicenseV3 } = useAppContext();
|
||||
|
||||
const [completedChecklistItems, setCompletedChecklistItems] = useState<
|
||||
ChecklistItem[]
|
||||
@ -79,19 +83,32 @@ function HomeChecklist({
|
||||
{user?.role !== USER_ROLES.VIEWER && (
|
||||
<div className="whats-next-checklist-item-action-buttons">
|
||||
<div className="whats-next-checklist-item-action-buttons-container">
|
||||
<Link to={item.toRoute || ''}>
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn secondary"
|
||||
onClick={(): void => {
|
||||
logEvent('Welcome Checklist: Get started clicked', {
|
||||
step: item.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Started <ArrowRight size={16} />
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn secondary"
|
||||
onClick={(): void => {
|
||||
logEvent('Welcome Checklist: Get started clicked', {
|
||||
step: item.id,
|
||||
});
|
||||
|
||||
if (item.toRoute !== ROUTES.GET_STARTED_WITH_CLOUD) {
|
||||
history.push(item.toRoute || '');
|
||||
} else if (
|
||||
activeLicenseV3 &&
|
||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
||||
) {
|
||||
history.push(item.toRoute || '');
|
||||
} else {
|
||||
window?.open(
|
||||
item.docsLink || '',
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Get Started <ArrowRight size={16} />
|
||||
</Button>
|
||||
|
||||
{item.docsLink && (
|
||||
<Button
|
||||
@ -102,7 +119,7 @@ function HomeChecklist({
|
||||
step: item.id,
|
||||
});
|
||||
|
||||
window?.open(item.docsLink, '_blank');
|
||||
window?.open(item.docsLink, '_blank', 'noopener noreferrer');
|
||||
}}
|
||||
>
|
||||
<BookOpenText size={16} />
|
||||
@ -110,7 +127,7 @@ function HomeChecklist({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!item.isSkipped && (
|
||||
{!item.isSkipped && item.isSkippable && (
|
||||
<div className="whats-next-checklist-item-action-buttons-container">
|
||||
<Button
|
||||
type="link"
|
||||
|
@ -11,6 +11,7 @@ import useGetTopLevelOperations from 'hooks/useGetTopLevelOperations';
|
||||
import useResourceAttribute from 'hooks/useResourceAttribute';
|
||||
import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute/utils';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import history from 'lib/history';
|
||||
import { ArrowRight, ArrowUpRight } from 'lucide-react';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@ -20,18 +21,29 @@ import { QueryKey } from 'react-query';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import {
|
||||
LicensePlatform,
|
||||
LicenseV3ResModel,
|
||||
} from 'types/api/licensesV3/getActive';
|
||||
import { ServicesList } from 'types/api/metrics/getService';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import { DOCS_LINKS } from '../constants';
|
||||
import { columns, TIME_PICKER_OPTIONS } from './constants';
|
||||
|
||||
const homeInterval = 30 * 60 * 1000;
|
||||
|
||||
// Extracted EmptyState component
|
||||
const EmptyState = memo(
|
||||
({ user }: { user: IUser }): JSX.Element => (
|
||||
({
|
||||
user,
|
||||
activeLicenseV3,
|
||||
}: {
|
||||
user: IUser;
|
||||
activeLicenseV3: LicenseV3ResModel | null;
|
||||
}): JSX.Element => (
|
||||
<div className="empty-state-container">
|
||||
<div className="empty-state-content-container">
|
||||
<div className="empty-state-content">
|
||||
@ -48,19 +60,31 @@ const EmptyState = memo(
|
||||
|
||||
{user?.role !== USER_ROLES.VIEWER && (
|
||||
<div className="empty-actions-container">
|
||||
<Link to={ROUTES.GET_STARTED}>
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn secondary"
|
||||
onClick={(): void => {
|
||||
logEvent('Homepage: Get Started clicked', {
|
||||
source: 'Service Metrics',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Started <ArrowRight size={16} />
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn secondary"
|
||||
onClick={(): void => {
|
||||
logEvent('Homepage: Get Started clicked', {
|
||||
source: 'Service Metrics',
|
||||
});
|
||||
|
||||
if (
|
||||
activeLicenseV3 &&
|
||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Get Started <ArrowRight size={16} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="link"
|
||||
className="learn-more-link"
|
||||
@ -122,7 +146,7 @@ function ServiceMetrics({
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
|
||||
const { user } = useAppContext();
|
||||
const { user, activeLicenseV3 } = useAppContext();
|
||||
|
||||
const [timeRange, setTimeRange] = useState(() => {
|
||||
const now = new Date().getTime();
|
||||
@ -311,7 +335,7 @@ function ServiceMetrics({
|
||||
{servicesExist ? (
|
||||
<ServicesListTable services={top5Services} onRowClick={handleRowClick} />
|
||||
) : (
|
||||
<EmptyState user={user} />
|
||||
<EmptyState user={user} activeLicenseV3={activeLicenseV3} />
|
||||
)}
|
||||
</Card.Content>
|
||||
|
||||
|
@ -3,6 +3,7 @@ import logEvent from 'api/common/logEvent';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useQueryService } from 'hooks/useQueryService';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import history from 'lib/history';
|
||||
import { ArrowRight, ArrowUpRight } from 'lucide-react';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@ -10,10 +11,12 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { ServicesList } from 'types/api/metrics/getService';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import { DOCS_LINKS } from '../constants';
|
||||
import { columns, TIME_PICKER_OPTIONS } from './constants';
|
||||
|
||||
const homeInterval = 30 * 60 * 1000;
|
||||
@ -29,7 +32,7 @@ export default function ServiceTraces({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const { user } = useAppContext();
|
||||
const { user, activeLicenseV3 } = useAppContext();
|
||||
|
||||
const now = new Date().getTime();
|
||||
const [timeRange, setTimeRange] = useState({
|
||||
@ -112,19 +115,30 @@ export default function ServiceTraces({
|
||||
|
||||
{user?.role !== USER_ROLES.VIEWER && (
|
||||
<div className="empty-actions-container">
|
||||
<Link to={ROUTES.GET_STARTED}>
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn secondary"
|
||||
onClick={(): void => {
|
||||
logEvent('Homepage: Get Started clicked', {
|
||||
source: 'Service Traces',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Started <ArrowRight size={16} />
|
||||
</Button>
|
||||
</Link>
|
||||
<Button
|
||||
type="default"
|
||||
className="periscope-btn secondary"
|
||||
onClick={(): void => {
|
||||
logEvent('Homepage: Get Started clicked', {
|
||||
source: 'Service Traces',
|
||||
});
|
||||
|
||||
if (
|
||||
activeLicenseV3 &&
|
||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Get Started <ArrowRight size={16} />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="link"
|
||||
@ -146,7 +160,7 @@ export default function ServiceTraces({
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
[user?.role],
|
||||
[user?.role, activeLicenseV3],
|
||||
);
|
||||
|
||||
const renderDashboardsList = useCallback(
|
||||
|
@ -15,6 +15,7 @@ export const checkListStepToPreferenceKeyMap = {
|
||||
};
|
||||
|
||||
export const DOCS_LINKS = {
|
||||
ADD_DATA_SOURCE: 'https://signoz.io/docs/instrumentation/overview/',
|
||||
SEND_LOGS: 'https://signoz.io/docs/userguide/logs/',
|
||||
SEND_TRACES: 'https://signoz.io/docs/userguide/traces/',
|
||||
SEND_INFRA_METRICS:
|
||||
@ -32,6 +33,7 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
description: '',
|
||||
completed: true,
|
||||
isSkipped: false,
|
||||
isSkippable: false,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.SETUP_WORKSPACE,
|
||||
},
|
||||
{
|
||||
@ -41,7 +43,9 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
completed: false,
|
||||
isSkipped: false,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.ADD_DATA_SOURCE,
|
||||
toRoute: ROUTES.GET_STARTED,
|
||||
toRoute: ROUTES.GET_STARTED_WITH_CLOUD,
|
||||
docsLink: DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
isSkippable: false,
|
||||
},
|
||||
{
|
||||
id: 'SEND_LOGS',
|
||||
@ -50,8 +54,9 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
'Send your logs to SigNoz to get more visibility into how your resources interact.',
|
||||
completed: false,
|
||||
isSkipped: false,
|
||||
isSkippable: true,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.SEND_LOGS,
|
||||
toRoute: ROUTES.GET_STARTED,
|
||||
toRoute: ROUTES.GET_STARTED_WITH_CLOUD,
|
||||
docsLink: DOCS_LINKS.SEND_LOGS,
|
||||
},
|
||||
{
|
||||
@ -61,8 +66,9 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
'Send your traces to SigNoz to get more visibility into how your resources interact.',
|
||||
completed: false,
|
||||
isSkipped: false,
|
||||
isSkippable: true,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.SEND_TRACES,
|
||||
toRoute: ROUTES.GET_STARTED,
|
||||
toRoute: ROUTES.GET_STARTED_WITH_CLOUD,
|
||||
docsLink: DOCS_LINKS.SEND_TRACES,
|
||||
},
|
||||
{
|
||||
@ -72,8 +78,9 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
'Send your infra metrics to SigNoz to get more visibility into your infrastructure.',
|
||||
completed: false,
|
||||
isSkipped: false,
|
||||
isSkippable: true,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.SEND_INFRA_METRICS,
|
||||
toRoute: ROUTES.GET_STARTED,
|
||||
toRoute: ROUTES.GET_STARTED_WITH_CLOUD,
|
||||
docsLink: DOCS_LINKS.SEND_INFRA_METRICS,
|
||||
},
|
||||
{
|
||||
@ -83,6 +90,7 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
'Setup alerts to get notified when your resources are not performing as expected.',
|
||||
completed: false,
|
||||
isSkipped: false,
|
||||
isSkippable: true,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.SETUP_ALERTS,
|
||||
toRoute: ROUTES.ALERTS_NEW,
|
||||
docsLink: DOCS_LINKS.SETUP_ALERTS,
|
||||
@ -94,6 +102,7 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
'Save your views to get a quick overview of your data and share it with your team.',
|
||||
completed: false,
|
||||
isSkipped: false,
|
||||
isSkippable: true,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.SETUP_SAVED_VIEWS,
|
||||
toRoute: ROUTES.LOGS_EXPLORER,
|
||||
docsLink: DOCS_LINKS.SETUP_SAVED_VIEWS,
|
||||
@ -105,6 +114,7 @@ export const defaultChecklistItemsState: ChecklistItem[] = [
|
||||
'Create dashboards to visualize your data and share it with your team.',
|
||||
completed: false,
|
||||
isSkipped: false,
|
||||
isSkippable: true,
|
||||
skippedPreferenceKey: checkListStepToPreferenceKeyMap.SETUP_DASHBOARDS,
|
||||
toRoute: ROUTES.ALL_DASHBOARD,
|
||||
docsLink: DOCS_LINKS.SETUP_DASHBOARDS,
|
||||
|
@ -11,10 +11,10 @@ function WorkspaceAccessRestricted(): JSX.Element {
|
||||
const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
|
||||
const isTerminated = activeLicenseV3.state === LicenseState.TERMINATED;
|
||||
const isExpired = activeLicenseV3.state === LicenseState.EXPIRED;
|
||||
const isCancelled = activeLicenseV3.state === LicenseState.CANCELLED;
|
||||
if (!isFetchingActiveLicenseV3) {
|
||||
const isTerminated = activeLicenseV3?.state === LicenseState.TERMINATED;
|
||||
const isExpired = activeLicenseV3?.state === LicenseState.EXPIRED;
|
||||
const isCancelled = activeLicenseV3?.state === LicenseState.CANCELLED;
|
||||
|
||||
const isWorkspaceAccessRestricted = isTerminated || isExpired || isCancelled;
|
||||
|
||||
@ -22,7 +22,7 @@ function WorkspaceAccessRestricted(): JSX.Element {
|
||||
!isWorkspaceAccessRestricted ||
|
||||
activeLicenseV3.platform === LicensePlatform.SELF_HOSTED
|
||||
) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
history.push(ROUTES.HOME);
|
||||
}
|
||||
}
|
||||
}, [isFetchingActiveLicenseV3, activeLicenseV3]);
|
||||
|
@ -77,7 +77,7 @@ export default function WorkspaceBlocked(): JSX.Element {
|
||||
!shouldBlockWorkspace ||
|
||||
activeLicenseV3?.platform === LicensePlatform.SELF_HOSTED
|
||||
) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
history.push(ROUTES.HOME);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
|
@ -56,15 +56,15 @@ function WorkspaceSuspended(): JSX.Element {
|
||||
}, [manageCreditCard]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
|
||||
if (!isFetchingActiveLicenseV3) {
|
||||
const shouldSuspendWorkspace =
|
||||
activeLicenseV3.state === LicenseState.DEFAULTED;
|
||||
activeLicenseV3?.state === LicenseState.DEFAULTED;
|
||||
|
||||
if (
|
||||
!shouldSuspendWorkspace ||
|
||||
activeLicenseV3?.platform === LicensePlatform.SELF_HOSTED
|
||||
) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
history.push(ROUTES.HOME);
|
||||
}
|
||||
}
|
||||
}, [isFetchingActiveLicenseV3, activeLicenseV3]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user