mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-01 01:14:03 +08:00
Merge pull request #949 from pranshuchittora/pranshuchittora/feat/ttl-s3
feat(FE): TTL/s3 integration
This commit is contained in:
commit
6e446dc0ab
@ -1,3 +1,18 @@
|
|||||||
{
|
{
|
||||||
"monitor_signup": "Monitor your applications. Find what is causing issues."
|
"monitor_signup": "Monitor your applications. Find what is causing issues.",
|
||||||
|
"routes": {
|
||||||
|
"general": "General",
|
||||||
|
"alert_channels": "Alert Channels"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"total_retention_period": "Total Retention Period",
|
||||||
|
"move_to_s3": "Move to S3\n(should be lower than total retention period)",
|
||||||
|
"retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.",
|
||||||
|
"retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io",
|
||||||
|
"retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io",
|
||||||
|
"retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.",
|
||||||
|
"retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below",
|
||||||
|
"retention_confirmation": "Are you sure you want to change the retention period?",
|
||||||
|
"retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
24
frontend/src/api/disks/getDisks.ts
Normal file
24
frontend/src/api/disks/getDisks.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps } from 'types/api/disks/getDisks';
|
||||||
|
|
||||||
|
const getDisks = async (): Promise<
|
||||||
|
SuccessResponse<PayloadProps> | ErrorResponse
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`/disks`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getDisks;
|
@ -9,7 +9,11 @@ const setRetention = async (
|
|||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post<PayloadProps>(
|
const response = await axios.post<PayloadProps>(
|
||||||
`/settings/ttl?duration=${props.duration}&type=${props.type}`,
|
`/settings/ttl?duration=${props.totalDuration}&type=${props.type}${
|
||||||
|
props.coldStorage
|
||||||
|
? `&coldStorage=${props.coldStorage};toColdDuration=${props.toColdDuration}`
|
||||||
|
: ''
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -1,108 +1,118 @@
|
|||||||
import { DownOutlined } from '@ant-design/icons';
|
import { Col, Row, Select } from 'antd';
|
||||||
import { Button, Menu } from 'antd';
|
import { find } from 'lodash-es';
|
||||||
import React from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { SettingPeroid } from '.';
|
|
||||||
import {
|
import {
|
||||||
Dropdown,
|
|
||||||
Input,
|
Input,
|
||||||
RetentionContainer,
|
RetentionContainer,
|
||||||
TextContainer,
|
RetentionFieldInputContainer,
|
||||||
Typography,
|
RetentionFieldLabel,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
import {
|
||||||
|
convertHoursValueToRelevantUnit,
|
||||||
|
SettingPeriod,
|
||||||
|
TimeUnits,
|
||||||
|
} from './utils';
|
||||||
|
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
function Retention({
|
function Retention({
|
||||||
retentionValue,
|
retentionValue,
|
||||||
setRentionValue,
|
setRetentionValue,
|
||||||
selectedRetentionPeroid,
|
|
||||||
setSelectedRetentionPeroid,
|
|
||||||
text,
|
text,
|
||||||
}: RetentionProps): JSX.Element {
|
hide,
|
||||||
const options: Option[] = [
|
}: RetentionProps): JSX.Element | null {
|
||||||
{
|
const {
|
||||||
key: 'hr',
|
value: initialValue,
|
||||||
value: 'Hrs',
|
timeUnitValue: initialTimeUnitValue,
|
||||||
},
|
} = convertHoursValueToRelevantUnit(Number(retentionValue));
|
||||||
{
|
const [selectedTimeUnit, setSelectTimeUnit] = useState(initialTimeUnitValue);
|
||||||
key: 'day',
|
const [selectedValue, setSelectedValue] = useState<number | null>(
|
||||||
value: 'Days',
|
initialValue,
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'month',
|
|
||||||
value: 'Months',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const onClickHandler = (
|
|
||||||
e: { key: string },
|
|
||||||
func: React.Dispatch<React.SetStateAction<SettingPeroid>>,
|
|
||||||
): void => {
|
|
||||||
const selected = e.key as SettingPeroid;
|
|
||||||
func(selected);
|
|
||||||
};
|
|
||||||
|
|
||||||
const menu = (
|
|
||||||
<Menu onClick={(e): void => onClickHandler(e, setSelectedRetentionPeroid)}>
|
|
||||||
{options.map((option) => (
|
|
||||||
<Menu.Item key={option.key}>{option.value}</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
);
|
||||||
|
const interacted = useRef(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!interacted.current) setSelectedValue(initialValue);
|
||||||
|
}, [initialValue]);
|
||||||
|
|
||||||
const currentSelectedOption = (option: SettingPeroid): string | undefined => {
|
useEffect(() => {
|
||||||
return options.find((e) => e.key === option)?.value;
|
if (!interacted.current) setSelectTimeUnit(initialTimeUnitValue);
|
||||||
|
}, [initialTimeUnitValue]);
|
||||||
|
|
||||||
|
const menuItems = TimeUnits.map((option) => (
|
||||||
|
<Option key={option.value} value={option.value}>
|
||||||
|
{option.key}
|
||||||
|
</Option>
|
||||||
|
));
|
||||||
|
|
||||||
|
const currentSelectedOption = (option: SettingPeriod): void => {
|
||||||
|
const selectedValue = find(TimeUnits, (e) => e.value === option)?.value;
|
||||||
|
if (selectedValue) setSelectTimeUnit(selectedValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const inverseMultiplier = find(
|
||||||
|
TimeUnits,
|
||||||
|
(timeUnit) => timeUnit.value === selectedTimeUnit,
|
||||||
|
)?.multiplier;
|
||||||
|
if (!selectedValue) setRetentionValue(null);
|
||||||
|
if (selectedValue && inverseMultiplier) {
|
||||||
|
setRetentionValue(selectedValue * (1 / inverseMultiplier));
|
||||||
|
}
|
||||||
|
}, [selectedTimeUnit, selectedValue, setRetentionValue]);
|
||||||
|
|
||||||
const onChangeHandler = (
|
const onChangeHandler = (
|
||||||
e: React.ChangeEvent<HTMLInputElement>,
|
e: React.ChangeEvent<HTMLInputElement>,
|
||||||
func: React.Dispatch<React.SetStateAction<string>>,
|
func: React.Dispatch<React.SetStateAction<number | null>>,
|
||||||
): void => {
|
): void => {
|
||||||
|
interacted.current = true;
|
||||||
const { value } = e.target;
|
const { value } = e.target;
|
||||||
const integerValue = parseInt(value, 10);
|
const integerValue = parseInt(value, 10);
|
||||||
|
|
||||||
if (value.length > 0 && integerValue.toString() === value) {
|
if (value.length > 0 && integerValue.toString() === value) {
|
||||||
const parsedValue = Math.abs(integerValue).toString();
|
const parsedValue = Math.abs(integerValue);
|
||||||
func(parsedValue);
|
func(parsedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.length === 0) {
|
if (value.length === 0) {
|
||||||
func('');
|
func(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (hide) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<RetentionContainer>
|
<RetentionContainer>
|
||||||
<TextContainer>
|
<Row justify="space-between">
|
||||||
<Typography>{text}</Typography>
|
<Col flex={1} style={{ display: 'flex' }}>
|
||||||
</TextContainer>
|
<RetentionFieldLabel>{text}</RetentionFieldLabel>
|
||||||
|
</Col>
|
||||||
<Input
|
<Col flex="150px">
|
||||||
value={retentionValue}
|
<RetentionFieldInputContainer>
|
||||||
onChange={(e): void => onChangeHandler(e, setRentionValue)}
|
<Input
|
||||||
/>
|
value={selectedValue && selectedValue >= 0 ? selectedValue : ''}
|
||||||
|
onChange={(e): void => onChangeHandler(e, setSelectedValue)}
|
||||||
<Dropdown overlay={menu}>
|
style={{ width: 75 }}
|
||||||
<Button>
|
/>
|
||||||
{currentSelectedOption(selectedRetentionPeroid)} <DownOutlined />
|
<Select
|
||||||
</Button>
|
value={selectedTimeUnit}
|
||||||
</Dropdown>
|
onChange={currentSelectedOption}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
>
|
||||||
|
{menuItems}
|
||||||
|
</Select>
|
||||||
|
</RetentionFieldInputContainer>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</RetentionContainer>
|
</RetentionContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
|
||||||
key: SettingPeroid;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RetentionProps {
|
interface RetentionProps {
|
||||||
retentionValue: string;
|
retentionValue: number | null;
|
||||||
text: string;
|
text: string;
|
||||||
setRentionValue: React.Dispatch<React.SetStateAction<string>>;
|
setRetentionValue: React.Dispatch<React.SetStateAction<number | null>>;
|
||||||
selectedRetentionPeroid: SettingPeroid;
|
hide: boolean;
|
||||||
setSelectedRetentionPeroid: React.Dispatch<
|
|
||||||
React.SetStateAction<SettingPeroid>
|
|
||||||
>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Retention;
|
export default Retention;
|
||||||
|
@ -1,43 +1,69 @@
|
|||||||
import { Button, Modal, notification, Typography } from 'antd';
|
import { Button, Col, Modal, notification, Row, Typography } from 'antd';
|
||||||
import getRetentionperoidApi from 'api/settings/getRetention';
|
import getDisks from 'api/disks/getDisks';
|
||||||
|
import getRetentionPeriodApi from 'api/settings/getRetention';
|
||||||
import setRetentionApi from 'api/settings/setRetention';
|
import setRetentionApi from 'api/settings/setRetention';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import TextToolTip from 'components/TextToolTip';
|
import TextToolTip from 'components/TextToolTip';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
import convertIntoHr from 'lib/convertIntoHr';
|
import { find } from 'lodash-es';
|
||||||
import getSettingsPeroid from 'lib/getSettingsPeroid';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { IDiskType } from 'types/api/disks/getDisks';
|
||||||
import { PayloadProps } from 'types/api/settings/getRetention';
|
import { PayloadProps } from 'types/api/settings/getRetention';
|
||||||
|
|
||||||
import Retention from './Retention';
|
import Retention from './Retention';
|
||||||
import {
|
import {
|
||||||
ButtonContainer,
|
ButtonContainer,
|
||||||
Container,
|
|
||||||
ErrorText,
|
ErrorText,
|
||||||
ErrorTextContainer,
|
ErrorTextContainer,
|
||||||
ToolTipContainer,
|
ToolTipContainer,
|
||||||
} from './styles';
|
} from './styles';
|
||||||
|
|
||||||
function GeneralSettings(): JSX.Element {
|
function GeneralSettings(): JSX.Element {
|
||||||
const [
|
const { t } = useTranslation();
|
||||||
selectedMetricsPeroid,
|
|
||||||
setSelectedMetricsPeroid,
|
|
||||||
] = useState<SettingPeroid>('month');
|
|
||||||
const [notifications, Element] = notification.useNotification();
|
const [notifications, Element] = notification.useNotification();
|
||||||
|
|
||||||
const [retentionPeroidMetrics, setRetentionPeroidMetrics] = useState<string>(
|
|
||||||
'',
|
|
||||||
);
|
|
||||||
const [modal, setModal] = useState<boolean>(false);
|
const [modal, setModal] = useState<boolean>(false);
|
||||||
const [postApiLoading, setPostApiLoading] = useState<boolean>(false);
|
const [postApiLoading, setPostApiLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const [selectedTracePeroid, setSelectedTracePeroid] = useState<SettingPeroid>(
|
const [availableDisks, setAvailableDisks] = useState<IDiskType[] | null>(null);
|
||||||
'hr',
|
|
||||||
);
|
|
||||||
|
|
||||||
const [retentionPeroidTrace, setRetentionPeroidTrace] = useState<string>('');
|
useEffect(() => {
|
||||||
const [isDefaultMetrics, setIsDefaultMetrics] = useState<boolean>(false);
|
getDisks().then((response) => setAvailableDisks(response.payload));
|
||||||
const [isDefaultTrace, setIsDefaultTrace] = useState<boolean>(false);
|
}, []);
|
||||||
|
|
||||||
|
const { payload: currentTTLValues, loading, error, errorMessage } = useFetch<
|
||||||
|
PayloadProps,
|
||||||
|
undefined
|
||||||
|
>(getRetentionPeriodApi, undefined);
|
||||||
|
const [metricsTotalRetentionPeriod, setMetricsTotalRetentionPeriod] = useState<
|
||||||
|
number | null
|
||||||
|
>(null);
|
||||||
|
const [metricsS3RetentionPeriod, setMetricsS3RetentionPeriod] = useState<
|
||||||
|
number | null
|
||||||
|
>(null);
|
||||||
|
const [tracesTotalRetentionPeriod, setTracesTotalRetentionPeriod] = useState<
|
||||||
|
number | null
|
||||||
|
>(null);
|
||||||
|
const [tracesS3RetentionPeriod, setTracesS3RetentionPeriod] = useState<
|
||||||
|
number | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (currentTTLValues) {
|
||||||
|
setMetricsTotalRetentionPeriod(currentTTLValues.metrics_ttl_duration_hrs);
|
||||||
|
setMetricsS3RetentionPeriod(
|
||||||
|
currentTTLValues.metrics_move_ttl_duration_hrs
|
||||||
|
? currentTTLValues.metrics_move_ttl_duration_hrs
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
setTracesTotalRetentionPeriod(currentTTLValues.traces_ttl_duration_hrs);
|
||||||
|
setTracesS3RetentionPeriod(
|
||||||
|
currentTTLValues.traces_move_ttl_duration_hrs
|
||||||
|
? currentTTLValues.traces_move_ttl_duration_hrs
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [currentTTLValues]);
|
||||||
|
|
||||||
const onModalToggleHandler = (): void => {
|
const onModalToggleHandler = (): void => {
|
||||||
setModal((modal) => !modal);
|
setModal((modal) => !modal);
|
||||||
@ -47,142 +73,182 @@ function GeneralSettings(): JSX.Element {
|
|||||||
onModalToggleHandler();
|
onModalToggleHandler();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { payload, loading, error, errorMessage } = useFetch<
|
const s3Enabled = useMemo(
|
||||||
PayloadProps,
|
() => !!find(availableDisks, (disks: IDiskType) => disks?.type === 's3'),
|
||||||
undefined
|
[availableDisks],
|
||||||
>(getRetentionperoidApi, undefined);
|
);
|
||||||
|
|
||||||
const checkMetricTraceDefault = (trace: number, metric: number): void => {
|
const renderConfig = [
|
||||||
if (metric === -1) {
|
{
|
||||||
setIsDefaultMetrics(true);
|
name: 'Metrics',
|
||||||
} else {
|
retentionFields: [
|
||||||
setIsDefaultMetrics(false);
|
{
|
||||||
|
name: t('settings.total_retention_period'),
|
||||||
|
value: metricsTotalRetentionPeriod,
|
||||||
|
setValue: setMetricsTotalRetentionPeriod,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('settings.move_to_s3'),
|
||||||
|
value: metricsS3RetentionPeriod,
|
||||||
|
setValue: setMetricsS3RetentionPeriod,
|
||||||
|
hide: !s3Enabled,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Traces',
|
||||||
|
retentionFields: [
|
||||||
|
{
|
||||||
|
name: t('settings.total_retention_period'),
|
||||||
|
value: tracesTotalRetentionPeriod,
|
||||||
|
setValue: setTracesTotalRetentionPeriod,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: t('settings.move_to_s3'),
|
||||||
|
value: tracesS3RetentionPeriod,
|
||||||
|
setValue: setTracesS3RetentionPeriod,
|
||||||
|
hide: !s3Enabled,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
].map((category): JSX.Element | null => {
|
||||||
|
if (
|
||||||
|
Array.isArray(category.retentionFields) &&
|
||||||
|
category.retentionFields.length > 0
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<Col flex="40%" style={{ minWidth: 475 }} key={category.name}>
|
||||||
|
<Typography.Title level={3}>{category.name}</Typography.Title>
|
||||||
|
|
||||||
|
{category.retentionFields.map((retentionField) => (
|
||||||
|
<Retention
|
||||||
|
key={retentionField.name}
|
||||||
|
text={retentionField.name}
|
||||||
|
retentionValue={retentionField.value}
|
||||||
|
setRetentionValue={retentionField.setValue}
|
||||||
|
hide={!!retentionField.hide}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
if (trace === -1) {
|
});
|
||||||
setIsDefaultTrace(true);
|
|
||||||
} else {
|
|
||||||
setIsDefaultTrace(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loading && payload !== undefined) {
|
|
||||||
const {
|
|
||||||
metrics_ttl_duration_hrs: metricTllDuration,
|
|
||||||
traces_ttl_duration_hrs: traceTllDuration,
|
|
||||||
} = payload;
|
|
||||||
|
|
||||||
checkMetricTraceDefault(traceTllDuration, metricTllDuration);
|
|
||||||
|
|
||||||
const traceValue = getSettingsPeroid(traceTllDuration);
|
|
||||||
const metricsValue = getSettingsPeroid(metricTllDuration);
|
|
||||||
|
|
||||||
setRetentionPeroidTrace(traceValue.value.toString());
|
|
||||||
setSelectedTracePeroid(traceValue.peroid);
|
|
||||||
|
|
||||||
setRetentionPeroidMetrics(metricsValue.value.toString());
|
|
||||||
setSelectedMetricsPeroid(metricsValue.peroid);
|
|
||||||
}
|
|
||||||
}, [setSelectedMetricsPeroid, loading, payload]);
|
|
||||||
|
|
||||||
const onOkHandler = async (): Promise<void> => {
|
const onOkHandler = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setPostApiLoading(true);
|
setPostApiLoading(true);
|
||||||
const retentionTraceValue =
|
const [metricsTTLApiResponse, tracesTTLApiResponse] = await Promise.all([
|
||||||
retentionPeroidTrace === '0' && (payload?.traces_ttl_duration_hrs || 0) < 0
|
|
||||||
? payload?.traces_ttl_duration_hrs || 0
|
|
||||||
: parseInt(retentionPeroidTrace, 10);
|
|
||||||
|
|
||||||
const retentionMetricsValue =
|
|
||||||
retentionPeroidMetrics === '0' &&
|
|
||||||
(payload?.metrics_ttl_duration_hrs || 0) < 0
|
|
||||||
? payload?.metrics_ttl_duration_hrs || 0
|
|
||||||
: parseInt(retentionPeroidMetrics, 10);
|
|
||||||
|
|
||||||
const [tracesResponse, metricsResponse] = await Promise.all([
|
|
||||||
setRetentionApi({
|
setRetentionApi({
|
||||||
duration: `${convertIntoHr(retentionTraceValue, selectedTracePeroid)}h`,
|
type: 'metrics',
|
||||||
type: 'traces',
|
totalDuration: `${metricsTotalRetentionPeriod || -1}h`,
|
||||||
|
coldStorage: s3Enabled ? 's3' : null,
|
||||||
|
toColdDuration: `${metricsS3RetentionPeriod || -1}h`,
|
||||||
}),
|
}),
|
||||||
setRetentionApi({
|
setRetentionApi({
|
||||||
duration: `${convertIntoHr(
|
type: 'traces',
|
||||||
retentionMetricsValue,
|
totalDuration: `${tracesTotalRetentionPeriod || -1}h`,
|
||||||
selectedMetricsPeroid,
|
coldStorage: s3Enabled ? 's3' : null,
|
||||||
)}h`,
|
toColdDuration: `${tracesS3RetentionPeriod || -1}h`,
|
||||||
type: 'metrics',
|
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
[
|
||||||
|
{
|
||||||
|
apiResponse: metricsTTLApiResponse,
|
||||||
|
name: 'metrics',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
apiResponse: tracesTTLApiResponse,
|
||||||
|
name: 'traces',
|
||||||
|
},
|
||||||
|
].forEach(({ apiResponse, name }) => {
|
||||||
|
if (apiResponse.statusCode === 200) {
|
||||||
|
notifications.success({
|
||||||
|
message: 'Success!',
|
||||||
|
placement: 'topRight',
|
||||||
|
|
||||||
if (
|
description: t('settings.retention_success_message', { name }),
|
||||||
tracesResponse.statusCode === 200 &&
|
});
|
||||||
metricsResponse.statusCode === 200
|
} else {
|
||||||
) {
|
notifications.error({
|
||||||
notifications.success({
|
message: 'Error',
|
||||||
message: 'Success!',
|
description: t('settings.retention_error_message', { name }),
|
||||||
placement: 'topRight',
|
placement: 'topRight',
|
||||||
description: 'Congrats. The retention periods were updated correctly.',
|
});
|
||||||
});
|
}
|
||||||
|
});
|
||||||
checkMetricTraceDefault(retentionTraceValue, retentionMetricsValue);
|
onModalToggleHandler();
|
||||||
|
|
||||||
onModalToggleHandler();
|
|
||||||
} else {
|
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description:
|
|
||||||
'There was an issue in changing the retention period. Please try again or reach out to support@signoz.io',
|
|
||||||
placement: 'topRight',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setPostApiLoading(false);
|
setPostApiLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description:
|
description: t('settings.retention_failed_message'),
|
||||||
'There was an issue in changing the retention period. Please try again or reach out to support@signoz.io',
|
|
||||||
placement: 'topRight',
|
placement: 'topRight',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
setModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [isDisabled, errorText] = useMemo(() => {
|
||||||
|
// Various methods to return dynamic error message text.
|
||||||
|
const messages = {
|
||||||
|
compareError: (name: string | number): string =>
|
||||||
|
t('settings.retention_comparison_error', { name }),
|
||||||
|
nullValueError: (name: string | number): string =>
|
||||||
|
t('settings.retention_null_value_error', { name }),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Defaults to button not disabled and empty error message text.
|
||||||
|
let isDisabled = false;
|
||||||
|
let errorText = '';
|
||||||
|
|
||||||
|
if (s3Enabled) {
|
||||||
|
if (
|
||||||
|
(metricsTotalRetentionPeriod || metricsS3RetentionPeriod) &&
|
||||||
|
Number(metricsTotalRetentionPeriod) <= Number(metricsS3RetentionPeriod)
|
||||||
|
) {
|
||||||
|
isDisabled = true;
|
||||||
|
errorText = messages.compareError('metrics');
|
||||||
|
} else if (
|
||||||
|
(tracesTotalRetentionPeriod || tracesS3RetentionPeriod) &&
|
||||||
|
Number(tracesTotalRetentionPeriod) <= Number(tracesS3RetentionPeriod)
|
||||||
|
) {
|
||||||
|
isDisabled = true;
|
||||||
|
errorText = messages.compareError('traces');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!metricsTotalRetentionPeriod || !tracesTotalRetentionPeriod) {
|
||||||
|
isDisabled = true;
|
||||||
|
if (!metricsTotalRetentionPeriod && !tracesTotalRetentionPeriod) {
|
||||||
|
errorText = messages.nullValueError('metrics and traces');
|
||||||
|
} else if (!metricsTotalRetentionPeriod) {
|
||||||
|
errorText = messages.nullValueError('metrics');
|
||||||
|
} else if (!tracesTotalRetentionPeriod) {
|
||||||
|
errorText = messages.nullValueError('traces');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [isDisabled, errorText];
|
||||||
|
}, [
|
||||||
|
metricsS3RetentionPeriod,
|
||||||
|
metricsTotalRetentionPeriod,
|
||||||
|
s3Enabled,
|
||||||
|
t,
|
||||||
|
tracesS3RetentionPeriod,
|
||||||
|
tracesTotalRetentionPeriod,
|
||||||
|
]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return <Typography>{errorMessage}</Typography>;
|
return <Typography>{errorMessage}</Typography>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading || payload === undefined) {
|
if (loading || currentTTLValues === undefined) {
|
||||||
return <Spinner tip="Loading.." height="70vh" />;
|
return <Spinner tip="Loading.." height="70vh" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getErrorText = (): string => {
|
|
||||||
const getValue = (value: string): string =>
|
|
||||||
`Retention Peroid for ${value} is not set yet. Please set by choosing below`;
|
|
||||||
|
|
||||||
if (!isDefaultMetrics && !isDefaultTrace) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDefaultMetrics && !isDefaultTrace) {
|
|
||||||
return `${getValue('Metrics')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isDefaultMetrics && isDefaultTrace) {
|
|
||||||
return `${getValue('Trace')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${getValue('Trace , Metrics')}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDisabledHandler = (): boolean => {
|
|
||||||
return !!(retentionPeroidTrace === '' || retentionPeroidMetrics === '');
|
|
||||||
};
|
|
||||||
|
|
||||||
const errorText = getErrorText();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Col xs={24} md={22} xl={20} xxl={18} style={{ margin: 'auto' }}>
|
||||||
{Element}
|
{Element}
|
||||||
|
|
||||||
{errorText ? (
|
{errorText ? (
|
||||||
<ErrorTextContainer>
|
<ErrorTextContainer>
|
||||||
<ErrorText>{errorText}</ErrorText>
|
<ErrorText>{errorText}</ErrorText>
|
||||||
@ -204,25 +270,10 @@ function GeneralSettings(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
</ToolTipContainer>
|
</ToolTipContainer>
|
||||||
)}
|
)}
|
||||||
|
<Row justify="space-around">{renderConfig}</Row>
|
||||||
<Retention
|
|
||||||
text="Retention Period for Metrics"
|
|
||||||
selectedRetentionPeroid={selectedMetricsPeroid}
|
|
||||||
setRentionValue={setRetentionPeroidMetrics}
|
|
||||||
retentionValue={retentionPeroidMetrics}
|
|
||||||
setSelectedRetentionPeroid={setSelectedMetricsPeroid}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Retention
|
|
||||||
text="Retention Period for Traces"
|
|
||||||
selectedRetentionPeroid={selectedTracePeroid}
|
|
||||||
setRentionValue={setRetentionPeroidTrace}
|
|
||||||
retentionValue={retentionPeroidTrace}
|
|
||||||
setSelectedRetentionPeroid={setSelectedTracePeroid}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="Are you sure you want to change the retention period?"
|
title={t('settings.retention_confirmation')}
|
||||||
focusTriggerAfterClose
|
focusTriggerAfterClose
|
||||||
forceRender
|
forceRender
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
@ -233,24 +284,18 @@ function GeneralSettings(): JSX.Element {
|
|||||||
visible={modal}
|
visible={modal}
|
||||||
confirmLoading={postApiLoading}
|
confirmLoading={postApiLoading}
|
||||||
>
|
>
|
||||||
<Typography>
|
<Typography>{t('settings.retention_confirmation_description')}</Typography>
|
||||||
This will change the amount of storage needed for saving metrics & traces.
|
|
||||||
</Typography>
|
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<ButtonContainer>
|
<ButtonContainer>
|
||||||
<Button
|
<Button onClick={onClickSaveHandler} disabled={isDisabled} type="primary">
|
||||||
onClick={onClickSaveHandler}
|
|
||||||
disabled={isDisabledHandler()}
|
|
||||||
type="primary"
|
|
||||||
>
|
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
</Container>
|
</Col>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SettingPeroid = 'hr' | 'day' | 'month';
|
export type SettingPeriod = 'hr' | 'day' | 'month';
|
||||||
|
|
||||||
export default GeneralSettings;
|
export default GeneralSettings;
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
|
Col,
|
||||||
Dropdown as DropDownComponent,
|
Dropdown as DropDownComponent,
|
||||||
Input as InputComponent,
|
Input as InputComponent,
|
||||||
Typography as TypographyComponent,
|
Typography as TypographyComponent,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
export const RetentionContainer = styled.div`
|
export const RetentionContainer = styled(Col)`
|
||||||
width: 50%;
|
margin: 0.75rem 0;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Input = styled(InputComponent)`
|
export const Input = styled(InputComponent)`
|
||||||
@ -37,13 +34,6 @@ export const ButtonContainer = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Container = styled.div`
|
|
||||||
&&& {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const Dropdown = styled(DropDownComponent)`
|
export const Dropdown = styled(DropDownComponent)`
|
||||||
&&& {
|
&&& {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -90,3 +80,12 @@ export const ErrorText = styled(TypographyComponent)`
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const RetentionFieldLabel = styled(TypographyComponent)`
|
||||||
|
vertical-align: middle;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RetentionFieldInputContainer = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
`;
|
||||||
|
42
frontend/src/container/GeneralSettings/utils.ts
Normal file
42
frontend/src/container/GeneralSettings/utils.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
export type SettingPeriod = 'hr' | 'day' | 'month';
|
||||||
|
|
||||||
|
export interface ITimeUnit {
|
||||||
|
value: SettingPeriod;
|
||||||
|
key: string;
|
||||||
|
multiplier: number;
|
||||||
|
}
|
||||||
|
export const TimeUnits: ITimeUnit[] = [
|
||||||
|
{
|
||||||
|
value: 'hr',
|
||||||
|
key: 'Hours',
|
||||||
|
multiplier: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'day',
|
||||||
|
key: 'Days',
|
||||||
|
multiplier: 1 / 24,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'month',
|
||||||
|
key: 'Months',
|
||||||
|
multiplier: 1 / (24 * 30),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const convertHoursValueToRelevantUnit = (
|
||||||
|
value: number,
|
||||||
|
): { value: number; timeUnitValue: SettingPeriod } => {
|
||||||
|
if (value)
|
||||||
|
for (let idx = TimeUnits.length - 1; idx >= 0; idx -= 1) {
|
||||||
|
const timeUnit = TimeUnits[idx];
|
||||||
|
const convertedValue = timeUnit.multiplier * value;
|
||||||
|
|
||||||
|
if (
|
||||||
|
convertedValue >= 1 &&
|
||||||
|
convertedValue === parseInt(`${convertedValue}`, 10)
|
||||||
|
) {
|
||||||
|
return { value: convertedValue, timeUnitValue: timeUnit.value };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { value, timeUnitValue: TimeUnits[0].value };
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import { SettingPeroid } from 'container/GeneralSettings';
|
import { SettingPeriod } from 'container/GeneralSettings';
|
||||||
|
|
||||||
const converIntoHr = (value: number, peroid: SettingPeroid): number => {
|
const converIntoHr = (value: number, peroid: SettingPeriod): number => {
|
||||||
if (peroid === 'day') {
|
if (peroid === 'day') {
|
||||||
return value * 24;
|
return value * 24;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { SettingPeroid } from 'container/GeneralSettings';
|
import { SettingPeriod } from 'container/GeneralSettings';
|
||||||
|
|
||||||
const getSettingsPeroid = (hr: number): PayloadProps => {
|
const getSettingsPeroid = (hr: number): PayloadProps => {
|
||||||
if (hr <= 0) {
|
if (hr <= 0) {
|
||||||
@ -30,7 +30,7 @@ const getSettingsPeroid = (hr: number): PayloadProps => {
|
|||||||
|
|
||||||
interface PayloadProps {
|
interface PayloadProps {
|
||||||
value: number;
|
value: number;
|
||||||
peroid: SettingPeroid;
|
peroid: SettingPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default getSettingsPeroid;
|
export default getSettingsPeroid;
|
||||||
|
@ -5,29 +5,32 @@ import CreateAlertChannels from 'container/CreateAlertChannels';
|
|||||||
import GeneralSettings from 'container/GeneralSettings';
|
import GeneralSettings from 'container/GeneralSettings';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function SettingsPage(): JSX.Element {
|
function SettingsPage(): JSX.Element {
|
||||||
const pathName = history.location.pathname;
|
const pathName = history.location.pathname;
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<RouteTab
|
<RouteTab
|
||||||
{...{
|
{...{
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
Component: GeneralSettings,
|
Component: GeneralSettings,
|
||||||
name: 'General Settings',
|
name: t('routes.general'),
|
||||||
route: ROUTES.SETTINGS,
|
route: ROUTES.SETTINGS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: (): JSX.Element => {
|
Component: (): JSX.Element => {
|
||||||
return <CreateAlertChannels preType="slack" />;
|
return <CreateAlertChannels preType="slack" />;
|
||||||
},
|
},
|
||||||
name: 'Alert Channels',
|
name: t('routes.alert_channels'),
|
||||||
route: ROUTES.ALL_CHANNELS,
|
route: ROUTES.ALL_CHANNELS,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
activeKey:
|
activeKey:
|
||||||
pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels',
|
pathName === ROUTES.SETTINGS
|
||||||
|
? t('routes.general')
|
||||||
|
: t('routes.alert_channels'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -4,27 +4,30 @@ import AlertChannels from 'container/AllAlertChannels';
|
|||||||
import GeneralSettings from 'container/GeneralSettings';
|
import GeneralSettings from 'container/GeneralSettings';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function AllAlertChannels(): JSX.Element {
|
function AllAlertChannels(): JSX.Element {
|
||||||
const pathName = history.location.pathname;
|
const pathName = history.location.pathname;
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<RouteTab
|
<RouteTab
|
||||||
{...{
|
{...{
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
Component: GeneralSettings,
|
Component: GeneralSettings,
|
||||||
name: 'General Settings',
|
name: t('routes.general'),
|
||||||
route: ROUTES.SETTINGS,
|
route: ROUTES.SETTINGS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: AlertChannels,
|
Component: AlertChannels,
|
||||||
name: 'Alert Channels',
|
name: t('routes.alert_channels'),
|
||||||
route: ROUTES.ALL_CHANNELS,
|
route: ROUTES.ALL_CHANNELS,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
activeKey:
|
activeKey:
|
||||||
pathName === ROUTES.SETTINGS ? 'General Settings' : 'Alert Channels',
|
pathName === ROUTES.SETTINGS
|
||||||
|
? t('routes.general')
|
||||||
|
: t('routes.alert_channels'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -14,7 +14,7 @@ function SettingsPage(): JSX.Element {
|
|||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
Component: GeneralSettings,
|
Component: GeneralSettings,
|
||||||
name: 'General Settings',
|
name: 'General',
|
||||||
route: ROUTES.SETTINGS,
|
route: ROUTES.SETTINGS,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -23,8 +23,7 @@ function SettingsPage(): JSX.Element {
|
|||||||
route: ROUTES.ALL_CHANNELS,
|
route: ROUTES.ALL_CHANNELS,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
activeKey:
|
activeKey: pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General',
|
||||||
pathName === ROUTES.ALL_CHANNELS ? 'Alert Channels' : 'General Settings',
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
5
frontend/src/types/api/disks/getDisks.ts
Normal file
5
frontend/src/types/api/disks/getDisks.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export type PayloadProps = IDiskType[];
|
||||||
|
export interface IDiskType {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
export interface PayloadProps {
|
export interface PayloadProps {
|
||||||
metrics_ttl_duration_hrs: number;
|
metrics_ttl_duration_hrs: number;
|
||||||
|
metrics_move_ttl_duration_hrs?: number;
|
||||||
traces_ttl_duration_hrs: number;
|
traces_ttl_duration_hrs: number;
|
||||||
|
traces_move_ttl_duration_hrs?: number;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export interface Props {
|
export interface Props {
|
||||||
type: 'metrics' | 'traces';
|
type: 'metrics' | 'traces';
|
||||||
duration: string;
|
totalDuration: string;
|
||||||
|
coldStorage?: 's3' | null;
|
||||||
|
toColdDuration?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PayloadProps {
|
export interface PayloadProps {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user