mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-18 18:41:31 +08:00
Merge branch 'develop' into tag-value-suggestion
This commit is contained in:
commit
eb0d3374d5
34
.github/workflows/build.yaml
vendored
34
.github/workflows/build.yaml
vendored
@ -2,11 +2,12 @@ name: build-pipeline
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
- develop
|
||||||
- main
|
- main
|
||||||
- v*
|
- v*
|
||||||
paths:
|
paths:
|
||||||
- 'pkg/**'
|
- "pkg/**"
|
||||||
- 'frontend/**'
|
- "frontend/**"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
get_filters:
|
get_filters:
|
||||||
@ -17,17 +18,17 @@ jobs:
|
|||||||
query-service: ${{ steps.filter.outputs.query-service }}
|
query-service: ${{ steps.filter.outputs.query-service }}
|
||||||
flattener: ${{ steps.filter.outputs.flattener }}
|
flattener: ${{ steps.filter.outputs.flattener }}
|
||||||
steps:
|
steps:
|
||||||
# For pull requests it's not necessary to checkout the code
|
# For pull requests it's not necessary to checkout the code
|
||||||
- uses: dorny/paths-filter@v2
|
- uses: dorny/paths-filter@v2
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
frontend:
|
frontend:
|
||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
query-service:
|
query-service:
|
||||||
- 'pkg/query-service/**'
|
- 'pkg/query-service/**'
|
||||||
flattener:
|
flattener:
|
||||||
- 'pkg/processors/flattener/**'
|
- 'pkg/processors/flattener/**'
|
||||||
|
|
||||||
build-frontend:
|
build-frontend:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -39,12 +40,11 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: cd frontend && yarn install
|
run: cd frontend && yarn install
|
||||||
- name: Run Prettier
|
|
||||||
run: cd frontend && npm run prettify
|
|
||||||
continue-on-error: true
|
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
run: cd frontend && npm run lint
|
run: cd frontend && npm run lint
|
||||||
continue-on-error: true
|
- name: TSC
|
||||||
|
run: yarn tsc
|
||||||
|
working-directory: ./frontend
|
||||||
- name: Build frontend docker image
|
- name: Build frontend docker image
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -17,19 +17,20 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:0.5.0
|
image: signoz/alertmanager:0.6.0
|
||||||
volumes:
|
volumes:
|
||||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
|
||||||
- ./data/alertmanager:/data
|
- ./data/alertmanager:/data
|
||||||
command:
|
command:
|
||||||
- '--config.file=/prometheus/alertmanager.yml'
|
- --queryService.url=http://query-service:8080
|
||||||
- '--storage.path=/data'
|
- --storage.path=/data
|
||||||
|
depends_on:
|
||||||
|
- query-service
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.7.3
|
image: signoz/query-service:0.7.4
|
||||||
command: ["-config=/root/config/prometheus.yml"]
|
command: ["-config=/root/config/prometheus.yml"]
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
@ -48,10 +49,10 @@ services:
|
|||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
- clickhouse
|
- clickhouse
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:0.7.3
|
image: signoz/frontend:0.7.4
|
||||||
depends_on:
|
depends_on:
|
||||||
- query-service
|
- query-service
|
||||||
ports:
|
ports:
|
||||||
|
@ -15,16 +15,17 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:0.5.0
|
image: signoz/alertmanager:0.6.0
|
||||||
volumes:
|
volumes:
|
||||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
|
||||||
- ./data/alertmanager:/data
|
- ./data/alertmanager:/data
|
||||||
|
depends_on:
|
||||||
|
- query-service
|
||||||
command:
|
command:
|
||||||
- '--config.file=/prometheus/alertmanager.yml'
|
- --queryService.url=http://query-service:8080
|
||||||
- '--storage.path=/data'
|
- --storage.path=/data
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.7.3
|
image: signoz/query-service:0.7.4
|
||||||
container_name: query-service
|
container_name: query-service
|
||||||
command: ["-config=/root/config/prometheus.yml"]
|
command: ["-config=/root/config/prometheus.yml"]
|
||||||
volumes:
|
volumes:
|
||||||
@ -44,7 +45,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:0.7.3
|
image: signoz/frontend:0.7.4
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
depends_on:
|
depends_on:
|
||||||
- query-service
|
- query-service
|
||||||
@ -66,7 +67,7 @@ services:
|
|||||||
# - "14268:14268" # Jaeger receiver
|
# - "14268:14268" # Jaeger receiver
|
||||||
# - "55678:55678" # OpenCensus receiver
|
# - "55678:55678" # OpenCensus receiver
|
||||||
# - "55679:55679" # zpages extension
|
# - "55679:55679" # zpages extension
|
||||||
# - "55680:55680" # OTLP gRPC legacy port
|
# - "55680:55680" # OTLP gRPC legacy receiver
|
||||||
# - "55681:55681" # OTLP HTTP legacy receiver
|
# - "55681:55681" # OTLP HTTP legacy receiver
|
||||||
mem_limit: 2000m
|
mem_limit: 2000m
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
@ -93,7 +94,7 @@ services:
|
|||||||
max-file: "3"
|
max-file: "3"
|
||||||
command: ["all"]
|
command: ["all"]
|
||||||
environment:
|
environment:
|
||||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||||
|
|
||||||
load-hotrod:
|
load-hotrod:
|
||||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||||
|
@ -15,19 +15,19 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:0.5.0
|
image: signoz/alertmanager:0.6.0
|
||||||
volumes:
|
volumes:
|
||||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
|
||||||
- ./data/alertmanager:/data
|
- ./data/alertmanager:/data
|
||||||
|
depends_on:
|
||||||
|
- query-service
|
||||||
command:
|
command:
|
||||||
- '--config.file=/prometheus/alertmanager.yml'
|
- --queryService.url=http://query-service:8080
|
||||||
- '--storage.path=/data'
|
- --storage.path=/data
|
||||||
|
|
||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
|
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.7.3
|
image: signoz/query-service:0.7.4
|
||||||
container_name: query-service
|
container_name: query-service
|
||||||
command: ["-config=/root/config/prometheus.yml"]
|
command: ["-config=/root/config/prometheus.yml"]
|
||||||
volumes:
|
volumes:
|
||||||
@ -40,14 +40,13 @@ services:
|
|||||||
- GODEBUG=netdns=go
|
- GODEBUG=netdns=go
|
||||||
- TELEMETRY_ENABLED=true
|
- TELEMETRY_ENABLED=true
|
||||||
- DEPLOYMENT_TYPE=docker-standalone-amd
|
- DEPLOYMENT_TYPE=docker-standalone-amd
|
||||||
|
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
clickhouse:
|
clickhouse:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:0.7.3
|
image: signoz/frontend:0.7.4
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
depends_on:
|
depends_on:
|
||||||
- query-service
|
- query-service
|
||||||
@ -69,7 +68,7 @@ services:
|
|||||||
# - "14268:14268" # Jaeger receiver
|
# - "14268:14268" # Jaeger receiver
|
||||||
# - "55678:55678" # OpenCensus receiver
|
# - "55678:55678" # OpenCensus receiver
|
||||||
# - "55679:55679" # zpages extension
|
# - "55679:55679" # zpages extension
|
||||||
# - "55680:55680" # OTLP gRPC legacy port
|
# - "55680:55680" # OTLP gRPC legacy receiver
|
||||||
# - "55681:55681" # OTLP HTTP legacy receiver
|
# - "55681:55681" # OTLP HTTP legacy receiver
|
||||||
mem_limit: 2000m
|
mem_limit: 2000m
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ant-design/colors": "^6.0.0",
|
||||||
"@ant-design/icons": "^4.6.2",
|
"@ant-design/icons": "^4.6.2",
|
||||||
"@grafana/data": "^8.4.3",
|
"@grafana/data": "^8.4.3",
|
||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "^4.3.1",
|
||||||
|
29
frontend/src/api/alerts/getTriggered.ts
Normal file
29
frontend/src/api/alerts/getTriggered.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { AxiosAlertManagerInstance } from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps, Props } from 'types/api/alerts/getTriggered';
|
||||||
|
|
||||||
|
const getTriggered = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
const queryParams = convertObjectIntoParams(props);
|
||||||
|
|
||||||
|
const response = await AxiosAlertManagerInstance.get(
|
||||||
|
`/alerts?${queryParams}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: response.data.status,
|
||||||
|
payload: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getTriggered;
|
51
frontend/src/api/channels/createWebhook.ts
Normal file
51
frontend/src/api/channels/createWebhook.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
|
||||||
|
|
||||||
|
const create = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
let httpConfig = {};
|
||||||
|
|
||||||
|
if (props.username !== '' && props.password !== '') {
|
||||||
|
httpConfig = {
|
||||||
|
basic_auth: {
|
||||||
|
username: props.username,
|
||||||
|
password: props.password,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (props.username === '' && props.password !== '') {
|
||||||
|
httpConfig = {
|
||||||
|
authorization: {
|
||||||
|
type: 'bearer',
|
||||||
|
credentials: props.password,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post('/channels', {
|
||||||
|
name: props.name,
|
||||||
|
webhook_configs: [
|
||||||
|
{
|
||||||
|
send_resolved: true,
|
||||||
|
url: props.api_url,
|
||||||
|
http_config: httpConfig,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default create;
|
50
frontend/src/api/channels/editWebhook.ts
Normal file
50
frontend/src/api/channels/editWebhook.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
import { PayloadProps, Props } from 'types/api/channels/editWebhook';
|
||||||
|
|
||||||
|
const editWebhook = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||||
|
try {
|
||||||
|
let httpConfig = {};
|
||||||
|
if (props.username !== '' && props.password !== '') {
|
||||||
|
httpConfig = {
|
||||||
|
basic_auth: {
|
||||||
|
username: props.username,
|
||||||
|
password: props.password,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else if (props.username === '' && props.password !== '') {
|
||||||
|
httpConfig = {
|
||||||
|
authorization: {
|
||||||
|
type: 'bearer',
|
||||||
|
credentials: props.password,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.put(`/channels/${props.id}`, {
|
||||||
|
name: props.name,
|
||||||
|
webhook_configs: [
|
||||||
|
{
|
||||||
|
send_resolved: true,
|
||||||
|
url: props.api_url,
|
||||||
|
http_config: httpConfig,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
error: null,
|
||||||
|
message: 'Success',
|
||||||
|
payload: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return ErrorResponseHandler(error as AxiosError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default editWebhook;
|
@ -79,6 +79,7 @@ function Graph({
|
|||||||
return 'rgba(231,233,237,0.8)';
|
return 'rgba(231,233,237,0.8)';
|
||||||
}, [currentTheme]);
|
}, [currentTheme]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
const buildChart = useCallback(() => {
|
const buildChart = useCallback(() => {
|
||||||
if (lineChartRef.current !== undefined) {
|
if (lineChartRef.current !== undefined) {
|
||||||
lineChartRef.current.destroy();
|
lineChartRef.current.destroy();
|
||||||
|
@ -30,7 +30,8 @@ function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: 'Error',
|
||||||
description: error instanceof Error ? error.toString() : 'Something went wrong',
|
description:
|
||||||
|
error instanceof Error ? error.toString() : 'Something went wrong',
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,12 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [isLoggedIn, isSignUpPage]);
|
}, [isLoggedIn, isSignUpPage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isLoggedIn && pathname === ROUTES.SIGN_UP) {
|
||||||
|
history.push(ROUTES.APPLICATION);
|
||||||
|
}
|
||||||
|
}, [isLoggedIn, pathname]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
{!isSignUpPage && <SideNav />}
|
{!isSignUpPage && <SideNav />}
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
export interface SlackChannel {
|
export interface Channel {
|
||||||
send_resolved: boolean;
|
send_resolved?: boolean;
|
||||||
api_url: string;
|
|
||||||
channel: string;
|
|
||||||
title: string;
|
|
||||||
text: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChannelType = 'slack' | 'email';
|
export interface SlackChannel extends Channel {
|
||||||
|
api_url?: string;
|
||||||
|
channel?: string;
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WebhookChannel extends Channel {
|
||||||
|
api_url?: string;
|
||||||
|
// basic auth
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ChannelType = 'slack' | 'email' | 'webhook';
|
||||||
|
export const SlackType: ChannelType = 'slack';
|
||||||
|
export const WebhookType: ChannelType = 'webhook';
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
import { Form, notification } from 'antd';
|
import { Form, notification } from 'antd';
|
||||||
import createSlackApi from 'api/channels/createSlack';
|
import createSlackApi from 'api/channels/createSlack';
|
||||||
|
import createWebhookApi from 'api/channels/createWebhook';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import FormAlertChannels from 'container/FormAlertChannels';
|
import FormAlertChannels from 'container/FormAlertChannels';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { ChannelType, SlackChannel } from './config';
|
import {
|
||||||
|
ChannelType,
|
||||||
|
SlackChannel,
|
||||||
|
SlackType,
|
||||||
|
WebhookChannel,
|
||||||
|
WebhookType,
|
||||||
|
} from './config';
|
||||||
|
|
||||||
function CreateAlertChannels({
|
function CreateAlertChannels({
|
||||||
preType = 'slack',
|
preType = 'slack',
|
||||||
}: CreateAlertChannelsProps): JSX.Element {
|
}: CreateAlertChannelsProps): JSX.Element {
|
||||||
const [formInstance] = Form.useForm();
|
const [formInstance] = Form.useForm();
|
||||||
const [selectedConfig, setSelectedConfig] = useState<Partial<SlackChannel>>({
|
const [selectedConfig, setSelectedConfig] = useState<
|
||||||
|
Partial<SlackChannel & WebhookChannel>
|
||||||
|
>({
|
||||||
text: ` {{ range .Alerts -}}
|
text: ` {{ range .Alerts -}}
|
||||||
*Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
*Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - {{ .Labels.severity }}{{ end }}
|
||||||
|
|
||||||
@ -73,17 +82,93 @@ function CreateAlertChannels({
|
|||||||
}
|
}
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description:
|
||||||
|
'An unexpected error occurred while creating this channel, please try again',
|
||||||
|
});
|
||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
}
|
}
|
||||||
}, [notifications, selectedConfig]);
|
}, [notifications, selectedConfig]);
|
||||||
|
|
||||||
|
const onWebhookHandler = useCallback(async () => {
|
||||||
|
// initial api request without auth params
|
||||||
|
let request: WebhookChannel = {
|
||||||
|
api_url: selectedConfig?.api_url || '',
|
||||||
|
name: selectedConfig?.name || '',
|
||||||
|
send_resolved: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
setSavingState(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (selectedConfig?.username !== '' || selectedConfig?.password !== '') {
|
||||||
|
if (selectedConfig?.username !== '') {
|
||||||
|
// if username is not null then password must be passed
|
||||||
|
if (selectedConfig?.password !== '') {
|
||||||
|
request = {
|
||||||
|
...request,
|
||||||
|
username: selectedConfig.username,
|
||||||
|
password: selectedConfig.password,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: 'A Password must be provided with user name',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (selectedConfig?.password !== '') {
|
||||||
|
// only password entered, set bearer token
|
||||||
|
request = {
|
||||||
|
...request,
|
||||||
|
username: '',
|
||||||
|
password: selectedConfig.password,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await createWebhookApi(request);
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
notifications.success({
|
||||||
|
message: 'Success',
|
||||||
|
description: 'Successfully created the channel',
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
history.replace(ROUTES.SETTINGS);
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: response.error || 'Error while creating the channel',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description:
|
||||||
|
'An unexpected error occurred while creating this channel, please try again',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setSavingState(false);
|
||||||
|
}, [notifications, selectedConfig]);
|
||||||
|
|
||||||
const onSaveHandler = useCallback(
|
const onSaveHandler = useCallback(
|
||||||
async (value: ChannelType) => {
|
async (value: ChannelType) => {
|
||||||
if (value === 'slack') {
|
switch (value) {
|
||||||
onSlackHandler();
|
case SlackType:
|
||||||
|
onSlackHandler();
|
||||||
|
break;
|
||||||
|
case WebhookType:
|
||||||
|
onWebhookHandler();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: 'channel type selected is invalid',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onSlackHandler],
|
[onSlackHandler, onWebhookHandler, notifications],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -108,11 +193,7 @@ function CreateAlertChannels({
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface CreateAlertChannelsProps {
|
interface CreateAlertChannelsProps {
|
||||||
preType?: ChannelType;
|
preType: ChannelType;
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateAlertChannels.defaultProps = {
|
|
||||||
preType: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CreateAlertChannels;
|
export default CreateAlertChannels;
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
import { Form, notification } from 'antd';
|
import { Form, notification } from 'antd';
|
||||||
import editSlackApi from 'api/channels/editSlack';
|
import editSlackApi from 'api/channels/editSlack';
|
||||||
|
import editWebhookApi from 'api/channels/editWebhook';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import {
|
import {
|
||||||
ChannelType,
|
ChannelType,
|
||||||
SlackChannel,
|
SlackChannel,
|
||||||
|
SlackType,
|
||||||
|
WebhookChannel,
|
||||||
|
WebhookType,
|
||||||
} from 'container/CreateAlertChannels/config';
|
} from 'container/CreateAlertChannels/config';
|
||||||
import FormAlertChannels from 'container/FormAlertChannels';
|
import FormAlertChannels from 'container/FormAlertChannels';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -14,14 +18,18 @@ function EditAlertChannels({
|
|||||||
initialValue,
|
initialValue,
|
||||||
}: EditAlertChannelsProps): JSX.Element {
|
}: EditAlertChannelsProps): JSX.Element {
|
||||||
const [formInstance] = Form.useForm();
|
const [formInstance] = Form.useForm();
|
||||||
const [selectedConfig, setSelectedConfig] = useState<Partial<SlackChannel>>({
|
const [selectedConfig, setSelectedConfig] = useState<
|
||||||
|
Partial<SlackChannel & WebhookChannel>
|
||||||
|
>({
|
||||||
...initialValue,
|
...initialValue,
|
||||||
});
|
});
|
||||||
const [savingState, setSavingState] = useState<boolean>(false);
|
const [savingState, setSavingState] = useState<boolean>(false);
|
||||||
const [notifications, NotificationElement] = notification.useNotification();
|
const [notifications, NotificationElement] = notification.useNotification();
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|
||||||
const [type, setType] = useState<ChannelType>('slack');
|
const [type, setType] = useState<ChannelType>(
|
||||||
|
initialValue?.type ? (initialValue.type as ChannelType) : SlackType,
|
||||||
|
);
|
||||||
|
|
||||||
const onTypeChangeHandler = useCallback((value: string) => {
|
const onTypeChangeHandler = useCallback((value: string) => {
|
||||||
setType(value as ChannelType);
|
setType(value as ChannelType);
|
||||||
@ -57,13 +65,62 @@ function EditAlertChannels({
|
|||||||
setSavingState(false);
|
setSavingState(false);
|
||||||
}, [selectedConfig, notifications, id]);
|
}, [selectedConfig, notifications, id]);
|
||||||
|
|
||||||
|
const onWebhookEditHandler = useCallback(async () => {
|
||||||
|
setSavingState(true);
|
||||||
|
const { name, username, password } = selectedConfig;
|
||||||
|
|
||||||
|
const showError = (msg: string): void => {
|
||||||
|
notifications.error({
|
||||||
|
message: 'Error',
|
||||||
|
description: msg,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (selectedConfig?.api_url === '') {
|
||||||
|
showError('Webhook URL is mandatory');
|
||||||
|
setSavingState(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username && (!password || password === '')) {
|
||||||
|
showError('Please enter a password');
|
||||||
|
setSavingState(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await editWebhookApi({
|
||||||
|
api_url: selectedConfig?.api_url || '',
|
||||||
|
name: name || '',
|
||||||
|
send_resolved: true,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
notifications.success({
|
||||||
|
message: 'Success',
|
||||||
|
description: 'Channels Edited Successfully',
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
history.replace(ROUTES.SETTINGS);
|
||||||
|
}, 2000);
|
||||||
|
} else {
|
||||||
|
showError(response.error || 'error while updating the Channels');
|
||||||
|
}
|
||||||
|
setSavingState(false);
|
||||||
|
}, [selectedConfig, notifications, id]);
|
||||||
|
|
||||||
const onSaveHandler = useCallback(
|
const onSaveHandler = useCallback(
|
||||||
(value: ChannelType) => {
|
(value: ChannelType) => {
|
||||||
if (value === 'slack') {
|
if (value === SlackType) {
|
||||||
onSlackEditHandler();
|
onSlackEditHandler();
|
||||||
|
} else if (value === WebhookType) {
|
||||||
|
onWebhookEditHandler();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[onSlackEditHandler],
|
[onSlackEditHandler, onWebhookEditHandler],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onTestHandler = useCallback(() => {
|
const onTestHandler = useCallback(() => {
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
import { Input } from 'antd';
|
||||||
|
import FormItem from 'antd/lib/form/FormItem';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { WebhookChannel } from '../../CreateAlertChannels/config';
|
||||||
|
|
||||||
|
function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FormItem name="api_url" label="Webhook URL">
|
||||||
|
<Input
|
||||||
|
onChange={(event): void => {
|
||||||
|
setSelectedConfig((value) => ({
|
||||||
|
...value,
|
||||||
|
api_url: event.target.value,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="username"
|
||||||
|
label="User Name (optional)"
|
||||||
|
help="Leave empty for bearer auth or when authentication is not necessary."
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
onChange={(event): void => {
|
||||||
|
setSelectedConfig((value) => ({
|
||||||
|
...value,
|
||||||
|
username: event.target.value,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
name="password"
|
||||||
|
label="Password (optional)"
|
||||||
|
help="Specify a password or bearer token"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
onChange={(event): void => {
|
||||||
|
setSelectedConfig((value) => ({
|
||||||
|
...value,
|
||||||
|
password: event.target.value,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebhookProps {
|
||||||
|
setSelectedConfig: React.Dispatch<
|
||||||
|
React.SetStateAction<Partial<WebhookChannel>>
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WebhookSettings;
|
@ -5,11 +5,14 @@ import ROUTES from 'constants/routes';
|
|||||||
import {
|
import {
|
||||||
ChannelType,
|
ChannelType,
|
||||||
SlackChannel,
|
SlackChannel,
|
||||||
|
SlackType,
|
||||||
|
WebhookType,
|
||||||
} from 'container/CreateAlertChannels/config';
|
} from 'container/CreateAlertChannels/config';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import SlackSettings from './Settings/Slack';
|
import SlackSettings from './Settings/Slack';
|
||||||
|
import WebhookSettings from './Settings/Webhook';
|
||||||
import { Button } from './styles';
|
import { Button } from './styles';
|
||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
@ -28,6 +31,16 @@ function FormAlertChannels({
|
|||||||
initialValue,
|
initialValue,
|
||||||
nameDisable = false,
|
nameDisable = false,
|
||||||
}: FormAlertChannelsProps): JSX.Element {
|
}: FormAlertChannelsProps): JSX.Element {
|
||||||
|
const renderSettings = (): React.ReactElement | null => {
|
||||||
|
switch (type) {
|
||||||
|
case SlackType:
|
||||||
|
return <SlackSettings setSelectedConfig={setSelectedConfig} />;
|
||||||
|
case WebhookType:
|
||||||
|
return <WebhookSettings setSelectedConfig={setSelectedConfig} />;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{NotificationElement}
|
{NotificationElement}
|
||||||
@ -52,14 +65,13 @@ function FormAlertChannels({
|
|||||||
<Option value="slack" key="slack">
|
<Option value="slack" key="slack">
|
||||||
Slack
|
Slack
|
||||||
</Option>
|
</Option>
|
||||||
|
<Option value="webhook" key="webhook">
|
||||||
|
Webhook
|
||||||
|
</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
||||||
<FormItem>
|
<FormItem>{renderSettings()}</FormItem>
|
||||||
{type === 'slack' && (
|
|
||||||
<SlackSettings setSelectedConfig={setSelectedConfig} />
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
|
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<Button
|
<Button
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { Button } from 'antd';
|
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { bindActionCreators, Dispatch } from 'redux';
|
import { bindActionCreators, Dispatch } from 'redux';
|
||||||
@ -7,6 +6,7 @@ import { DeleteDashboard, DeleteDashboardProps } from 'store/actions';
|
|||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
|
|
||||||
import { Data } from '../index';
|
import { Data } from '../index';
|
||||||
|
import { TableLinkText } from './styles';
|
||||||
|
|
||||||
function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element {
|
function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element {
|
||||||
const onClickHandler = useCallback(() => {
|
const onClickHandler = useCallback(() => {
|
||||||
@ -15,11 +15,7 @@ function DeleteButton({ deleteDashboard, id }: DeleteButtonProps): JSX.Element {
|
|||||||
});
|
});
|
||||||
}, [id, deleteDashboard]);
|
}, [id, deleteDashboard]);
|
||||||
|
|
||||||
return (
|
return <TableLinkText onClick={onClickHandler}>Delete</TableLinkText>;
|
||||||
<Button onClick={onClickHandler} type="link">
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DispatchProps {
|
interface DispatchProps {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Button } from 'antd';
|
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { generatePath } from 'react-router-dom';
|
import { generatePath } from 'react-router-dom';
|
||||||
|
|
||||||
import { Data } from '..';
|
import { Data } from '..';
|
||||||
|
import { TableLinkText } from './styles';
|
||||||
|
|
||||||
function Name(name: Data['name'], data: Data): JSX.Element {
|
function Name(name: Data['name'], data: Data): JSX.Element {
|
||||||
const onClickHandler = (): void => {
|
const onClickHandler = (): void => {
|
||||||
@ -17,11 +17,7 @@ function Name(name: Data['name'], data: Data): JSX.Element {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return <TableLinkText onClick={onClickHandler}>{name}</TableLinkText>;
|
||||||
<Button onClick={onClickHandler} type="link">
|
|
||||||
{name}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Name;
|
export default Name;
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { blue } from '@ant-design/colors';
|
||||||
|
import { Typography } from 'antd';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const TableLinkText = styled(Typography.Text)`
|
||||||
|
color: ${blue.primary} !important;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
@ -157,7 +157,7 @@ function ListOfAllDashboard(): JSX.Element {
|
|||||||
<TextToolTip
|
<TextToolTip
|
||||||
{...{
|
{...{
|
||||||
text: `More details on how to create dashboards`,
|
text: `More details on how to create dashboards`,
|
||||||
url: 'https://signoz.io/docs/userguide/metrics-dashboard',
|
url: 'https://signoz.io/docs/userguide/dashboards',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ function Query({
|
|||||||
<TextToolTip
|
<TextToolTip
|
||||||
{...{
|
{...{
|
||||||
text: `More details on how to plot metrics graphs`,
|
text: `More details on how to plot metrics graphs`,
|
||||||
url: 'https://signoz.io/docs/userguide/prometheus-metrics/',
|
url: 'https://signoz.io/docs/userguide/send-metrics/#related-videos',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
|
@ -75,9 +75,10 @@ function Timeline({
|
|||||||
{intervals &&
|
{intervals &&
|
||||||
intervals.map((interval, index) => (
|
intervals.map((interval, index) => (
|
||||||
<TimelineInterval
|
<TimelineInterval
|
||||||
transform={`translate(${TimelineHSpacing +
|
transform={`translate(${
|
||||||
|
TimelineHSpacing +
|
||||||
(interval.percentage * (width - 2 * TimelineHSpacing)) / 100
|
(interval.percentage * (width - 2 * TimelineHSpacing)) / 100
|
||||||
},0)`}
|
},0)`}
|
||||||
key={`${interval.label + interval.percentage + index}`}
|
key={`${interval.label + interval.percentage + index}`}
|
||||||
>
|
>
|
||||||
<text y={13} fill={isDarkMode ? 'white' : 'black'}>
|
<text y={13} fill={isDarkMode ? 'white' : 'black'}>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import getGroupApi from 'api/alerts/getGroup';
|
import getTriggeredApi from 'api/alerts/getTriggered';
|
||||||
import useInterval from 'hooks/useInterval';
|
import useInterval from 'hooks/useInterval';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Alerts } from 'types/api/alerts/getAll';
|
import { Alerts } from 'types/api/alerts/getAll';
|
||||||
@ -13,20 +13,22 @@ function TriggeredAlerts({ allAlerts }: TriggeredAlertsProps): JSX.Element {
|
|||||||
|
|
||||||
useInterval(() => {
|
useInterval(() => {
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
const response = await getGroupApi({
|
const response = await getTriggeredApi({
|
||||||
active: true,
|
active: true,
|
||||||
inhibited: true,
|
inhibited: true,
|
||||||
silenced: false,
|
silenced: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200 && response.payload !== null) {
|
if (response.statusCode === 200 && response.payload !== null) {
|
||||||
const initialAlerts: Alerts[] = [];
|
// commented reduce() call as we no longer use /alerts/groups
|
||||||
|
// from alertmanager which needed re-grouping on client side
|
||||||
|
// const initialAlerts: Alerts[] = [];
|
||||||
|
|
||||||
const allAlerts: Alerts[] = response.payload.reduce((acc, cur) => {
|
// const allAlerts: Alerts[] = response.payload.reduce((acc, cur) => {
|
||||||
return [...acc, ...cur.alerts];
|
// return [...acc, ...cur.alerts];
|
||||||
}, initialAlerts);
|
// }, initialAlerts);
|
||||||
|
|
||||||
setInitialAlerts(allAlerts);
|
setInitialAlerts(response.payload);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import getGroupApi from 'api/alerts/getGroup';
|
import getTriggeredApi from 'api/alerts/getTriggered';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { State } from 'hooks/useFetch';
|
import { State } from 'hooks/useFetch';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { Alerts } from 'types/api/alerts/getAll';
|
import { Alerts } from 'types/api/alerts/getAll';
|
||||||
import { PayloadProps } from 'types/api/alerts/getGroups';
|
import { PayloadProps } from 'types/api/alerts/getTriggered';
|
||||||
|
|
||||||
import TriggerComponent from './TriggeredAlert';
|
import TriggerComponent from './TriggeredAlert';
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ function TriggeredAlerts(): JSX.Element {
|
|||||||
loading: true,
|
loading: true,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const response = await getGroupApi({
|
const response = await getTriggeredApi({
|
||||||
active: true,
|
active: true,
|
||||||
inhibited: true,
|
inhibited: true,
|
||||||
silenced: false,
|
silenced: false,
|
||||||
@ -65,13 +65,16 @@ function TriggeredAlerts(): JSX.Element {
|
|||||||
return <Spinner height="75vh" tip="Loading Alerts..." />;
|
return <Spinner height="75vh" tip="Loading Alerts..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialAlerts: Alerts[] = [];
|
// commented the reduce() call as we no longer use /alerts/groups
|
||||||
|
// API from alert manager, which returns a group for each receiver
|
||||||
|
|
||||||
const allAlerts: Alerts[] = groupState.payload.reduce((acc, curr) => {
|
// const initialAlerts: Alerts[] = [];
|
||||||
return [...acc, ...curr.alerts];
|
|
||||||
}, initialAlerts);
|
|
||||||
|
|
||||||
return <TriggerComponent allAlerts={allAlerts} />;
|
// const allAlerts: Alerts[] = groupState.payload.reduce((acc, curr) => {
|
||||||
|
// return [...acc, ...curr.alerts];
|
||||||
|
// }, initialAlerts);
|
||||||
|
|
||||||
|
return <TriggerComponent allAlerts={groupState.payload} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TriggeredAlerts;
|
export default TriggeredAlerts;
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
//@ts-nocheck
|
//@ts-nocheck
|
||||||
|
|
||||||
|
import { Card } from 'antd';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import { ForceGraph2D } from 'react-force-graph';
|
import { ForceGraph2D } from 'react-force-graph';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
import { RouteComponentProps, withRouter } from 'react-router-dom';
|
||||||
import { getDetailedServiceMapItems, getServiceMapItems } from 'store/actions';
|
import { getDetailedServiceMapItems, ServiceMapStore } from 'store/actions';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { GlobalTime } from 'types/actions/globalTime';
|
import { GlobalTime } from 'types/actions/globalTime';
|
||||||
@ -31,9 +32,8 @@ const Container = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
interface ServiceMapProps extends RouteComponentProps<any> {
|
interface ServiceMapProps extends RouteComponentProps<any> {
|
||||||
serviceMap: serviceMapStore;
|
serviceMap: ServiceMapStore;
|
||||||
globalTime: GlobalTime;
|
globalTime: GlobalTime;
|
||||||
getServiceMapItems: (time: GlobalTime) => void;
|
|
||||||
getDetailedServiceMapItems: (time: GlobalTime) => void;
|
getDetailedServiceMapItems: (time: GlobalTime) => void;
|
||||||
}
|
}
|
||||||
interface graphNode {
|
interface graphNode {
|
||||||
@ -53,29 +53,32 @@ export interface graphDataType {
|
|||||||
function ServiceMap(props: ServiceMapProps): JSX.Element {
|
function ServiceMap(props: ServiceMapProps): JSX.Element {
|
||||||
const fgRef = useRef();
|
const fgRef = useRef();
|
||||||
|
|
||||||
const {
|
const { getDetailedServiceMapItems, globalTime, serviceMap } = props;
|
||||||
getDetailedServiceMapItems,
|
|
||||||
getServiceMapItems,
|
|
||||||
globalTime,
|
|
||||||
serviceMap,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
/*
|
/*
|
||||||
Call the apis only when the route is loaded.
|
Call the apis only when the route is loaded.
|
||||||
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
Check this issue: https://github.com/SigNoz/signoz/issues/110
|
||||||
*/
|
*/
|
||||||
getServiceMapItems(globalTime);
|
|
||||||
getDetailedServiceMapItems(globalTime);
|
getDetailedServiceMapItems(globalTime);
|
||||||
}, [globalTime, getServiceMapItems, getDetailedServiceMapItems]);
|
}, [globalTime, getDetailedServiceMapItems]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fgRef.current && fgRef.current.d3Force('charge').strength(-400);
|
fgRef.current && fgRef.current.d3Force('charge').strength(-400);
|
||||||
});
|
});
|
||||||
if (!serviceMap.items.length || !serviceMap.services.length) {
|
|
||||||
|
if (serviceMap.loading) {
|
||||||
return <Spinner size="large" tip="Loading..." />;
|
return <Spinner size="large" tip="Loading..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!serviceMap.loading && serviceMap.items.length === 0) {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Card>No Service Found</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const zoomToService = (value: string): void => {
|
const zoomToService = (value: string): void => {
|
||||||
fgRef &&
|
fgRef &&
|
||||||
fgRef.current &&
|
fgRef.current &&
|
||||||
@ -149,7 +152,6 @@ const mapStateToProps = (
|
|||||||
|
|
||||||
export default withRouter(
|
export default withRouter(
|
||||||
connect(mapStateToProps, {
|
connect(mapStateToProps, {
|
||||||
getServiceMapItems,
|
|
||||||
getDetailedServiceMapItems,
|
getDetailedServiceMapItems,
|
||||||
})(ServiceMap),
|
})(ServiceMap),
|
||||||
);
|
);
|
||||||
|
@ -20,7 +20,7 @@ function SettingsPage(): JSX.Element {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
Component: (): JSX.Element => {
|
Component: (): JSX.Element => {
|
||||||
return <CreateAlertChannels />;
|
return <CreateAlertChannels preType="slack" />;
|
||||||
},
|
},
|
||||||
name: 'Alert Channels',
|
name: 'Alert Channels',
|
||||||
route: ROUTES.ALL_CHANNELS,
|
route: ROUTES.ALL_CHANNELS,
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import get from 'api/channels/get';
|
import get from 'api/channels/get';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { SlackChannel } from 'container/CreateAlertChannels/config';
|
import {
|
||||||
|
SlackChannel,
|
||||||
|
SlackType,
|
||||||
|
WebhookChannel,
|
||||||
|
WebhookType,
|
||||||
|
} from 'container/CreateAlertChannels/config';
|
||||||
import EditAlertChannels from 'container/EditAlertChannels';
|
import EditAlertChannels from 'container/EditAlertChannels';
|
||||||
import useFetch from 'hooks/useFetch';
|
import useFetch from 'hooks/useFetch';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@ -29,15 +34,36 @@ function ChannelsEdit(): JSX.Element {
|
|||||||
const { data } = payload;
|
const { data } = payload;
|
||||||
|
|
||||||
const value = JSON.parse(data);
|
const value = JSON.parse(data);
|
||||||
|
let type = '';
|
||||||
|
let channel: SlackChannel & WebhookChannel = { name: '' };
|
||||||
|
|
||||||
const channel: SlackChannel = value.slack_configs[0];
|
if (value && 'slack_configs' in value) {
|
||||||
|
const slackConfig = value.slack_configs[0];
|
||||||
|
channel = slackConfig;
|
||||||
|
type = SlackType;
|
||||||
|
} else if (value && 'webhook_configs' in value) {
|
||||||
|
const webhookConfig = value.webhook_configs[0];
|
||||||
|
channel = webhookConfig;
|
||||||
|
channel.api_url = webhookConfig.url;
|
||||||
|
|
||||||
|
if ('http_config' in webhookConfig) {
|
||||||
|
const httpConfig = webhookConfig.http_config;
|
||||||
|
if ('basic_auth' in httpConfig) {
|
||||||
|
channel.username = webhookConfig.http_config?.basic_auth?.username;
|
||||||
|
channel.password = webhookConfig.http_config?.basic_auth?.password;
|
||||||
|
} else if ('authorization' in httpConfig) {
|
||||||
|
channel.password = webhookConfig.http_config?.authorization?.credentials;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type = WebhookType;
|
||||||
|
}
|
||||||
|
console.log('channel:', channel);
|
||||||
return (
|
return (
|
||||||
<EditAlertChannels
|
<EditAlertChannels
|
||||||
{...{
|
{...{
|
||||||
initialValue: {
|
initialValue: {
|
||||||
...channel,
|
...channel,
|
||||||
type: 'slack',
|
type,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
@ -7,6 +7,7 @@ import { ActionTypes } from './types';
|
|||||||
export interface ServiceMapStore {
|
export interface ServiceMapStore {
|
||||||
items: ServicesMapItem[];
|
items: ServicesMapItem[];
|
||||||
services: ServicesItem[];
|
services: ServicesItem[];
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServicesItem {
|
export interface ServicesItem {
|
||||||
@ -37,38 +38,39 @@ export interface ServicesAction {
|
|||||||
payload: ServicesItem[];
|
payload: ServicesItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServiceMapItems = (globalTime: GlobalTime) => {
|
export interface ServiceMapLoading {
|
||||||
return async (dispatch: Dispatch): Promise<void> => {
|
type: ActionTypes.serviceMapLoading;
|
||||||
dispatch<ServiceMapItemAction>({
|
payload: {
|
||||||
type: ActionTypes.getServiceMapItems,
|
loading: ServiceMapStore['loading'];
|
||||||
payload: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const requestString = `/serviceMapDependencies?start=${globalTime.minTime}&end=${globalTime.maxTime}`;
|
|
||||||
|
|
||||||
const response = await api.get<ServicesMapItem[]>(requestString);
|
|
||||||
|
|
||||||
dispatch<ServiceMapItemAction>({
|
|
||||||
type: ActionTypes.getServiceMapItems,
|
|
||||||
payload: response.data,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
|
export const getDetailedServiceMapItems = (globalTime: GlobalTime) => {
|
||||||
return async (dispatch: Dispatch): Promise<void> => {
|
return async (dispatch: Dispatch): Promise<void> => {
|
||||||
dispatch<ServicesAction>({
|
|
||||||
type: ActionTypes.getServices,
|
|
||||||
payload: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const requestString = `/services?start=${globalTime.minTime}&end=${globalTime.maxTime}`;
|
const requestString = `/services?start=${globalTime.minTime}&end=${globalTime.maxTime}`;
|
||||||
|
|
||||||
const response = await api.get<ServicesItem[]>(requestString);
|
const serviceMapDependencies = `/serviceMapDependencies?start=${globalTime.minTime}&end=${globalTime.maxTime}`;
|
||||||
|
|
||||||
|
const [serviceMapDependenciesResponse, response] = await Promise.all([
|
||||||
|
api.get<ServicesMapItem[]>(serviceMapDependencies),
|
||||||
|
api.get<ServicesItem[]>(requestString),
|
||||||
|
]);
|
||||||
|
|
||||||
dispatch<ServicesAction>({
|
dispatch<ServicesAction>({
|
||||||
type: ActionTypes.getServices,
|
type: ActionTypes.getServices,
|
||||||
payload: response.data,
|
payload: response.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatch<ServiceMapItemAction>({
|
||||||
|
type: ActionTypes.getServiceMapItems,
|
||||||
|
payload: serviceMapDependenciesResponse.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch<ServiceMapLoading>({
|
||||||
|
type: ActionTypes.serviceMapLoading,
|
||||||
|
payload: {
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import { ServiceMapItemAction, ServicesAction } from './serviceMap';
|
import {
|
||||||
|
ServiceMapItemAction,
|
||||||
|
ServiceMapLoading,
|
||||||
|
ServicesAction,
|
||||||
|
} from './serviceMap';
|
||||||
import { GetUsageDataAction } from './usage';
|
import { GetUsageDataAction } from './usage';
|
||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
updateTraceFilters = 'UPDATE_TRACES_FILTER',
|
|
||||||
updateTimeInterval = 'UPDATE_TIME_INTERVAL',
|
updateTimeInterval = 'UPDATE_TIME_INTERVAL',
|
||||||
getServiceMapItems = 'GET_SERVICE_MAP_ITEMS',
|
getServiceMapItems = 'GET_SERVICE_MAP_ITEMS',
|
||||||
getServices = 'GET_SERVICES',
|
getServices = 'GET_SERVICES',
|
||||||
getUsageData = 'GET_USAGE_DATE',
|
getUsageData = 'GET_USAGE_DATE',
|
||||||
fetchTraces = 'FETCH_TRACES',
|
fetchTraces = 'FETCH_TRACES',
|
||||||
fetchTraceItem = 'FETCH_TRACE_ITEM',
|
fetchTraceItem = 'FETCH_TRACE_ITEM',
|
||||||
|
serviceMapLoading = 'UPDATE_SERVICE_MAP_LOADING',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Action = GetUsageDataAction | ServicesAction | ServiceMapItemAction;
|
export type Action =
|
||||||
|
| GetUsageDataAction
|
||||||
|
| ServicesAction
|
||||||
|
| ServiceMapItemAction
|
||||||
|
| ServiceMapLoading;
|
||||||
|
@ -3,6 +3,7 @@ import { Action, ActionTypes, ServiceMapStore } from 'store/actions';
|
|||||||
const initialState: ServiceMapStore = {
|
const initialState: ServiceMapStore = {
|
||||||
items: [],
|
items: [],
|
||||||
services: [],
|
services: [],
|
||||||
|
loading: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ServiceMapReducer = (
|
export const ServiceMapReducer = (
|
||||||
@ -20,6 +21,12 @@ export const ServiceMapReducer = (
|
|||||||
...state,
|
...state,
|
||||||
services: action.payload,
|
services: action.payload,
|
||||||
};
|
};
|
||||||
|
case ActionTypes.serviceMapLoading: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
loading: action.payload.loading,
|
||||||
|
};
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
10
frontend/src/types/api/alerts/getTriggered.ts
Normal file
10
frontend/src/types/api/alerts/getTriggered.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Alerts } from './getAll';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
silenced: boolean;
|
||||||
|
inhibited: boolean;
|
||||||
|
active: boolean;
|
||||||
|
[key: string]: string | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PayloadProps = Alerts[] | [];
|
8
frontend/src/types/api/channels/createWebhook.ts
Normal file
8
frontend/src/types/api/channels/createWebhook.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { WebhookChannel } from 'container/CreateAlertChannels/config';
|
||||||
|
|
||||||
|
export type Props = WebhookChannel;
|
||||||
|
|
||||||
|
export interface PayloadProps {
|
||||||
|
data: string;
|
||||||
|
status: string;
|
||||||
|
}
|
10
frontend/src/types/api/channels/editWebhook.ts
Normal file
10
frontend/src/types/api/channels/editWebhook.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { WebhookChannel } from 'container/CreateAlertChannels/config';
|
||||||
|
|
||||||
|
export interface Props extends WebhookChannel {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PayloadProps {
|
||||||
|
data: string;
|
||||||
|
status: string;
|
||||||
|
}
|
@ -13,6 +13,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -44,6 +45,7 @@ import (
|
|||||||
"github.com/prometheus/prometheus/util/strutil"
|
"github.com/prometheus/prometheus/util/strutil"
|
||||||
|
|
||||||
"go.signoz.io/query-service/constants"
|
"go.signoz.io/query-service/constants"
|
||||||
|
am "go.signoz.io/query-service/integrations/alertManager"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/query-service/model"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -74,6 +76,7 @@ type ClickHouseReader struct {
|
|||||||
remoteStorage *remote.Storage
|
remoteStorage *remote.Storage
|
||||||
ruleManager *rules.Manager
|
ruleManager *rules.Manager
|
||||||
promConfig *config.Config
|
promConfig *config.Config
|
||||||
|
alertManager am.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTraceReader returns a TraceReader for the database
|
// NewTraceReader returns a TraceReader for the database
|
||||||
@ -88,9 +91,12 @@ func NewReader(localDB *sqlx.DB) *ClickHouseReader {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alertManager := am.New("")
|
||||||
|
|
||||||
return &ClickHouseReader{
|
return &ClickHouseReader{
|
||||||
db: db,
|
db: db,
|
||||||
localDB: localDB,
|
localDB: localDB,
|
||||||
|
alertManager: alertManager,
|
||||||
operationsTable: options.primary.OperationsTable,
|
operationsTable: options.primary.OperationsTable,
|
||||||
indexTable: options.primary.IndexTable,
|
indexTable: options.primary.IndexTable,
|
||||||
errorTable: options.primary.ErrorTable,
|
errorTable: options.primary.ErrorTable,
|
||||||
@ -651,7 +657,7 @@ func (r *ClickHouseReader) LoadRule(rule model.RuleResponseItem) *model.ApiError
|
|||||||
|
|
||||||
func (r *ClickHouseReader) LoadChannel(channel *model.ChannelItem) *model.ApiError {
|
func (r *ClickHouseReader) LoadChannel(channel *model.ChannelItem) *model.ApiError {
|
||||||
|
|
||||||
receiver := &model.Receiver{}
|
receiver := &am.Receiver{}
|
||||||
if err := json.Unmarshal([]byte(channel.Data), receiver); err != nil { // Parse []byte to go struct pointer
|
if err := json.Unmarshal([]byte(channel.Data), receiver); err != nil { // Parse []byte to go struct pointer
|
||||||
return &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
return &model.ApiError{Typ: model.ErrorBadData, Err: err}
|
||||||
}
|
}
|
||||||
@ -723,32 +729,10 @@ func (r *ClickHouseReader) DeleteChannel(id string) *model.ApiError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
values := map[string]string{"name": channelToDelete.Name}
|
apiError := r.alertManager.DeleteRoute(channelToDelete.Name)
|
||||||
jsonValue, _ := json.Marshal(values)
|
if apiError != nil {
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodDelete, constants.GetAlertManagerApiPrefix()+"v1/receivers", bytes.NewBuffer(jsonValue))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Errorf("Error in creating new delete request to alertmanager/v1/receivers\n", err)
|
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
return apiError
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
response, err := client.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Errorf("Error in delete API call to alertmanager/v1/receivers\n", err)
|
|
||||||
tx.Rollback()
|
|
||||||
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
|
||||||
}
|
|
||||||
if response.StatusCode > 299 {
|
|
||||||
err := fmt.Errorf("Error in getting 2xx response in API call to delete alertmanager/v1/receivers\n", response.Status)
|
|
||||||
zap.S().Error(err)
|
|
||||||
tx.Rollback()
|
|
||||||
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
@ -780,7 +764,7 @@ func (r *ClickHouseReader) GetChannels() (*[]model.ChannelItem, *model.ApiError)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getChannelType(receiver *model.Receiver) string {
|
func getChannelType(receiver *am.Receiver) string {
|
||||||
|
|
||||||
if receiver.EmailConfigs != nil {
|
if receiver.EmailConfigs != nil {
|
||||||
return "email"
|
return "email"
|
||||||
@ -813,7 +797,7 @@ func getChannelType(receiver *model.Receiver) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClickHouseReader) EditChannel(receiver *model.Receiver, id string) (*model.Receiver, *model.ApiError) {
|
func (r *ClickHouseReader) EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError) {
|
||||||
|
|
||||||
idInt, _ := strconv.Atoi(id)
|
idInt, _ := strconv.Atoi(id)
|
||||||
|
|
||||||
@ -851,30 +835,10 @@ func (r *ClickHouseReader) EditChannel(receiver *model.Receiver, id string) (*mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(http.MethodPut, constants.GetAlertManagerApiPrefix()+"v1/receivers", bytes.NewBuffer(receiverString))
|
apiError := r.alertManager.EditRoute(receiver)
|
||||||
|
if apiError != nil {
|
||||||
if err != nil {
|
|
||||||
zap.S().Errorf("Error in creating new update request to alertmanager/v1/receivers\n", err)
|
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
return nil, apiError
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
response, err := client.Do(req)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
zap.S().Errorf("Error in update API call to alertmanager/v1/receivers\n", err)
|
|
||||||
tx.Rollback()
|
|
||||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
if response.StatusCode > 299 {
|
|
||||||
err := fmt.Errorf("Error in getting 2xx response in API call to alertmanager/v1/receivers\n", response.Status)
|
|
||||||
zap.S().Error(err)
|
|
||||||
tx.Rollback()
|
|
||||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
@ -887,7 +851,7 @@ func (r *ClickHouseReader) EditChannel(receiver *model.Receiver, id string) (*mo
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClickHouseReader) CreateChannel(receiver *model.Receiver) (*model.Receiver, *model.ApiError) {
|
func (r *ClickHouseReader) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) {
|
||||||
|
|
||||||
tx, err := r.localDB.Begin()
|
tx, err := r.localDB.Begin()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -897,6 +861,8 @@ func (r *ClickHouseReader) CreateChannel(receiver *model.Receiver) (*model.Recei
|
|||||||
channel_type := getChannelType(receiver)
|
channel_type := getChannelType(receiver)
|
||||||
receiverString, _ := json.Marshal(receiver)
|
receiverString, _ := json.Marshal(receiver)
|
||||||
|
|
||||||
|
// todo: check if the channel name already exists, raise an error if so
|
||||||
|
|
||||||
{
|
{
|
||||||
stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
|
stmt, err := tx.Prepare(`INSERT INTO notification_channels (created_at, updated_at, name, type, data) VALUES($1,$2,$3,$4,$5);`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -913,18 +879,10 @@ func (r *ClickHouseReader) CreateChannel(receiver *model.Receiver) (*model.Recei
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := http.Post(constants.GetAlertManagerApiPrefix()+"v1/receivers", "application/json", bytes.NewBuffer(receiverString))
|
apiError := r.alertManager.AddRoute(receiver)
|
||||||
|
if apiError != nil {
|
||||||
if err != nil {
|
|
||||||
zap.S().Errorf("Error in getting response of API call to alertmanager/v1/receivers\n", err)
|
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
return nil, apiError
|
||||||
}
|
|
||||||
if response.StatusCode > 299 {
|
|
||||||
err := fmt.Errorf("Error in getting 2xx response in API call to alertmanager/v1/receivers\n", response.Status)
|
|
||||||
zap.S().Error(err)
|
|
||||||
tx.Rollback()
|
|
||||||
return nil, &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit()
|
err = tx.Commit()
|
||||||
@ -2644,7 +2602,6 @@ func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *mo
|
|||||||
fmt.Errorf("error while getting disks. Err=%v", err)}
|
fmt.Errorf("error while getting disks. Err=%v", err)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
zap.S().Infof("Got response: %+v\n", diskItems)
|
zap.S().Infof("Got response: %+v\n", diskItems)
|
||||||
|
|
||||||
return &diskItems, nil
|
return &diskItems, nil
|
||||||
@ -2652,29 +2609,33 @@ func (r *ClickHouseReader) GetDisks(ctx context.Context) (*[]model.DiskItem, *mo
|
|||||||
|
|
||||||
func (r *ClickHouseReader) GetTTL(ctx context.Context, ttlParams *model.GetTTLParams) (*model.GetTTLResponseItem, *model.ApiError) {
|
func (r *ClickHouseReader) GetTTL(ctx context.Context, ttlParams *model.GetTTLParams) (*model.GetTTLResponseItem, *model.ApiError) {
|
||||||
|
|
||||||
parseTTL := func(queryResp string) int {
|
parseTTL := func(queryResp string) (int, int) {
|
||||||
values := strings.Split(queryResp, " ")
|
|
||||||
N := len(values)
|
|
||||||
ttlIdx := -1
|
|
||||||
|
|
||||||
for i := 0; i < N; i++ {
|
zap.S().Debugf("Parsing TTL from: %s", queryResp)
|
||||||
if strings.Contains(values[i], "toIntervalSecond") {
|
deleteTTLExp := regexp.MustCompile(`toIntervalSecond\(([0-9]*)\)`)
|
||||||
ttlIdx = i
|
moveTTLExp := regexp.MustCompile(`toIntervalSecond\(([0-9]*)\) TO VOLUME`)
|
||||||
break
|
|
||||||
|
var delTTL, moveTTL int = -1, -1
|
||||||
|
|
||||||
|
m := deleteTTLExp.FindStringSubmatch(queryResp)
|
||||||
|
if len(m) > 1 {
|
||||||
|
seconds_int, err := strconv.Atoi(m[1])
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1
|
||||||
}
|
}
|
||||||
}
|
delTTL = seconds_int / 3600
|
||||||
if ttlIdx == -1 {
|
|
||||||
return ttlIdx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
output := strings.SplitN(values[ttlIdx], "(", 2)
|
m = moveTTLExp.FindStringSubmatch(queryResp)
|
||||||
timePart := strings.Trim(output[1], ")")
|
if len(m) > 1 {
|
||||||
seconds_int, err := strconv.Atoi(timePart)
|
seconds_int, err := strconv.Atoi(m[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1
|
return -1, -1
|
||||||
|
}
|
||||||
|
moveTTL = seconds_int / 3600
|
||||||
}
|
}
|
||||||
ttl_hrs := seconds_int / 3600
|
|
||||||
return ttl_hrs
|
return delTTL, moveTTL
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetricsTTL := func() (*model.DBResponseTTL, *model.ApiError) {
|
getMetricsTTL := func() (*model.DBResponseTTL, *model.ApiError) {
|
||||||
@ -2713,7 +2674,8 @@ func (r *ClickHouseReader) GetTTL(ctx context.Context, ttlParams *model.GetTTLPa
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &model.GetTTLResponseItem{TracesTime: parseTTL(dbResp.EngineFull)}, nil
|
delTTL, moveTTL := parseTTL(dbResp.EngineFull)
|
||||||
|
return &model.GetTTLResponseItem{TracesTime: delTTL, TracesMoveTime: moveTTL}, nil
|
||||||
|
|
||||||
case constants.MetricsTTL:
|
case constants.MetricsTTL:
|
||||||
dbResp, err := getMetricsTTL()
|
dbResp, err := getMetricsTTL()
|
||||||
@ -2721,7 +2683,9 @@ func (r *ClickHouseReader) GetTTL(ctx context.Context, ttlParams *model.GetTTLPa
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &model.GetTTLResponseItem{MetricsTime: parseTTL(dbResp.EngineFull)}, nil
|
delTTL, moveTTL := parseTTL(dbResp.EngineFull)
|
||||||
|
return &model.GetTTLResponseItem{MetricsTime: delTTL, MetricsMoveTime: moveTTL}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
db1, err := getTracesTTL()
|
db1, err := getTracesTTL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2732,9 +2696,15 @@ func (r *ClickHouseReader) GetTTL(ctx context.Context, ttlParams *model.GetTTLPa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
tracesDelTTL, tracesMoveTTL := parseTTL(db1.EngineFull)
|
||||||
|
metricsDelTTL, metricsMoveTTL := parseTTL(db2.EngineFull)
|
||||||
|
|
||||||
return &model.GetTTLResponseItem{TracesTime: parseTTL(db1.EngineFull), MetricsTime: parseTTL(db2.EngineFull)}, nil
|
return &model.GetTTLResponseItem{
|
||||||
|
TracesTime: tracesDelTTL,
|
||||||
|
TracesMoveTime: tracesMoveTTL,
|
||||||
|
MetricsTime: metricsDelTTL,
|
||||||
|
MetricsMoveTime: metricsMoveTTL,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ClickHouseReader) GetErrors(ctx context.Context, queryParams *model.GetErrorsParams) (*[]model.Error, *model.ApiError) {
|
func (r *ClickHouseReader) GetErrors(ctx context.Context, queryParams *model.GetErrorsParams) (*[]model.Error, *model.ApiError) {
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"go.signoz.io/query-service/druidQuery"
|
"go.signoz.io/query-service/druidQuery"
|
||||||
"go.signoz.io/query-service/godruid"
|
"go.signoz.io/query-service/godruid"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/query-service/model"
|
||||||
|
am "go.signoz.io/query-service/integrations/alertManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DruidReader struct {
|
type DruidReader struct {
|
||||||
@ -65,12 +66,12 @@ func (druid *DruidReader) GetChannel(id string) (*model.ChannelItem, *model.ApiE
|
|||||||
func (druid *DruidReader) GetChannels() (*[]model.ChannelItem, *model.ApiError) {
|
func (druid *DruidReader) GetChannels() (*[]model.ChannelItem, *model.ApiError) {
|
||||||
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")}
|
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")}
|
||||||
}
|
}
|
||||||
func (druid *DruidReader) CreateChannel(receiver *model.Receiver) (*model.Receiver, *model.ApiError) {
|
func (druid *DruidReader) CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError) {
|
||||||
|
|
||||||
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")}
|
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")}
|
||||||
|
|
||||||
}
|
}
|
||||||
func (druid *DruidReader) EditChannel(receiver *model.Receiver, id string) (*model.Receiver, *model.ApiError) {
|
func (druid *DruidReader) EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError) {
|
||||||
|
|
||||||
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")}
|
return nil, &model.ApiError{model.ErrorNotImplemented, fmt.Errorf("Druid does not support notification channel for alerts")}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/query-service/model"
|
||||||
"go.signoz.io/query-service/telemetry"
|
"go.signoz.io/query-service/telemetry"
|
||||||
"go.signoz.io/query-service/version"
|
"go.signoz.io/query-service/version"
|
||||||
|
am "go.signoz.io/query-service/integrations/alertManager"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -467,7 +468,7 @@ func (aH *APIHandler) editChannel(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver := &model.Receiver{}
|
receiver := &am.Receiver{}
|
||||||
if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer
|
if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer
|
||||||
zap.S().Errorf("Error in parsing req body of editChannel API\n", err)
|
zap.S().Errorf("Error in parsing req body of editChannel API\n", err)
|
||||||
aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
@ -495,7 +496,7 @@ func (aH *APIHandler) createChannel(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
receiver := &model.Receiver{}
|
receiver := &am.Receiver{}
|
||||||
if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer
|
if err := json.Unmarshal(body, receiver); err != nil { // Parse []byte to go struct pointer
|
||||||
zap.S().Errorf("Error in parsing req body of createChannel API\n", err)
|
zap.S().Errorf("Error in parsing req body of createChannel API\n", err)
|
||||||
aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
aH.respondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, nil)
|
||||||
|
@ -6,14 +6,15 @@ import (
|
|||||||
"github.com/prometheus/prometheus/promql"
|
"github.com/prometheus/prometheus/promql"
|
||||||
"github.com/prometheus/prometheus/util/stats"
|
"github.com/prometheus/prometheus/util/stats"
|
||||||
"go.signoz.io/query-service/model"
|
"go.signoz.io/query-service/model"
|
||||||
|
am "go.signoz.io/query-service/integrations/alertManager"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reader interface {
|
type Reader interface {
|
||||||
GetChannel(id string) (*model.ChannelItem, *model.ApiError)
|
GetChannel(id string) (*model.ChannelItem, *model.ApiError)
|
||||||
GetChannels() (*[]model.ChannelItem, *model.ApiError)
|
GetChannels() (*[]model.ChannelItem, *model.ApiError)
|
||||||
DeleteChannel(id string) *model.ApiError
|
DeleteChannel(id string) *model.ApiError
|
||||||
CreateChannel(receiver *model.Receiver) (*model.Receiver, *model.ApiError)
|
CreateChannel(receiver *am.Receiver) (*am.Receiver, *model.ApiError)
|
||||||
EditChannel(receiver *model.Receiver, id string) (*model.Receiver, *model.ApiError)
|
EditChannel(receiver *am.Receiver, id string) (*am.Receiver, *model.ApiError)
|
||||||
|
|
||||||
GetRule(id string) (*model.RuleResponseItem, *model.ApiError)
|
GetRule(id string) (*model.RuleResponseItem, *model.ApiError)
|
||||||
ListRulesFromProm() (*model.AlertDiscovery, *model.ApiError)
|
ListRulesFromProm() (*model.AlertDiscovery, *model.ApiError)
|
||||||
|
@ -914,7 +914,7 @@ func parseTTLParams(r *http.Request) (*model.TTLParams, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Not a valid toCold TTL duration %v", toColdDuration)
|
return nil, fmt.Errorf("Not a valid toCold TTL duration %v", toColdDuration)
|
||||||
}
|
}
|
||||||
if toColdParsed.Seconds() >= durationParsed.Seconds() {
|
if toColdParsed.Seconds() != 0 && toColdParsed.Seconds() >= durationParsed.Seconds() {
|
||||||
return nil, fmt.Errorf("Delete TTL should be greater than cold storage move TTL.")
|
return nil, fmt.Errorf("Delete TTL should be greater than cold storage move TTL.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,10 @@ func GetAlertManagerApiPrefix() string {
|
|||||||
return "http://alertmanager:9093/api/"
|
return "http://alertmanager:9093/api/"
|
||||||
}
|
}
|
||||||
|
|
||||||
const RELATIONAL_DATASOURCE_PATH = "/var/lib/signoz/signoz.db"
|
// Alert manager channel subpath
|
||||||
|
var AmChannelApiPath = GetOrDefaultEnv("ALERTMANAGER_API_CHANNEL_PATH", "v1/routes")
|
||||||
|
|
||||||
|
var RELATIONAL_DATASOURCE_PATH = GetOrDefaultEnv("SIGNOZ_LOCAL_DB_PATH", "/var/lib/signoz/signoz.db")
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ServiceName = "serviceName"
|
ServiceName = "serviceName"
|
||||||
@ -43,3 +46,12 @@ const (
|
|||||||
OperationDB = "name"
|
OperationDB = "name"
|
||||||
OperationRequest = "operation"
|
OperationRequest = "operation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func GetOrDefaultEnv(key string, fallback string) string {
|
||||||
|
v := os.Getenv(key)
|
||||||
|
if len(v) == 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
@ -62,7 +62,7 @@ type QueryScan struct {
|
|||||||
Limit int64 `json:"limit,omitempty"`
|
Limit int64 `json:"limit,omitempty"`
|
||||||
Offset int64 `json:"offset,omitempty"`
|
Offset int64 `json:"offset,omitempty"`
|
||||||
BatchSize int64 `json:"batchSize,omitempty"`
|
BatchSize int64 `json:"batchSize,omitempty"`
|
||||||
Order string `json:"order",omitempty`
|
Order string `json:"order,omitempty"`
|
||||||
ResultFormat string `json:"resultFormat"`
|
ResultFormat string `json:"resultFormat"`
|
||||||
Context map[string]interface{} `json:"context,omitempty"`
|
Context map[string]interface{} `json:"context,omitempty"`
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ type TimeBoundaryItem struct {
|
|||||||
|
|
||||||
type TimeBoundary struct {
|
type TimeBoundary struct {
|
||||||
MinTime string `json:"minTime"`
|
MinTime string `json:"minTime"`
|
||||||
MaxTime string `json:"minTime"`
|
MaxTime string `json:"maxTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QueryTimeBoundary) setup() { q.QueryType = "timeBoundary" }
|
func (q *QueryTimeBoundary) setup() { q.QueryType = "timeBoundary" }
|
||||||
|
129
pkg/query-service/integrations/alertManager/manager.go
Normal file
129
pkg/query-service/integrations/alertManager/manager.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package alertManager
|
||||||
|
|
||||||
|
// Wrapper to connect and process alert manager functions
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"encoding/json"
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.signoz.io/query-service/constants"
|
||||||
|
"go.signoz.io/query-service/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const contentType = "application/json"
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
AddRoute(receiver *Receiver) *model.ApiError
|
||||||
|
EditRoute(receiver *Receiver) *model.ApiError
|
||||||
|
DeleteRoute(name string) *model.ApiError
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(url string) Manager{
|
||||||
|
|
||||||
|
if url == ""{
|
||||||
|
url = constants.GetAlertManagerApiPrefix()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &manager {
|
||||||
|
url: url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func prepareAmChannelApiURL() string {
|
||||||
|
basePath := constants.GetAlertManagerApiPrefix()
|
||||||
|
AmChannelApiPath := constants.AmChannelApiPath
|
||||||
|
|
||||||
|
if len(AmChannelApiPath) > 0 && rune(AmChannelApiPath[0]) == rune('/') {
|
||||||
|
AmChannelApiPath = AmChannelApiPath[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s%s", basePath, AmChannelApiPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) AddRoute(receiver *Receiver) (*model.ApiError) {
|
||||||
|
|
||||||
|
receiverString, _ := json.Marshal(receiver)
|
||||||
|
|
||||||
|
amURL := prepareAmChannelApiURL()
|
||||||
|
response, err := http.Post(amURL, contentType, bytes.NewBuffer(receiverString))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(POST %s)\n", amURL), err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode > 299 {
|
||||||
|
err := fmt.Errorf(fmt.Sprintf("Error in getting 2xx response in API call to alertmanager(POST %s)\n", amURL), response.Status)
|
||||||
|
zap.S().Error(err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) EditRoute(receiver *Receiver) *model.ApiError {
|
||||||
|
receiverString, _ := json.Marshal(receiver)
|
||||||
|
|
||||||
|
amURL := prepareAmChannelApiURL()
|
||||||
|
req, err := http.NewRequest(http.MethodPut, amURL, bytes.NewBuffer(receiverString))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf(fmt.Sprintf("Error creating new update request for API call to alertmanager(PUT %s)\n", amURL), err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", contentType)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
response, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(PUT %s)\n", amURL), err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode > 299 {
|
||||||
|
err := fmt.Errorf(fmt.Sprintf("Error in getting 2xx response in PUT API call to alertmanager(PUT %s)\n", amURL), response.Status)
|
||||||
|
zap.S().Error(err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) DeleteRoute(name string) *model.ApiError {
|
||||||
|
values := map[string]string{"name": name}
|
||||||
|
requestData, _ := json.Marshal(values)
|
||||||
|
|
||||||
|
amURL := prepareAmChannelApiURL()
|
||||||
|
req, err := http.NewRequest(http.MethodDelete, amURL, bytes.NewBuffer(requestData))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf("Error in creating new delete request to alertmanager/v1/receivers\n", err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Add("Content-Type", contentType)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
response, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.S().Errorf(fmt.Sprintf("Error in getting response of API call to alertmanager(DELETE %s)\n", amURL), err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.StatusCode > 299 {
|
||||||
|
err := fmt.Errorf(fmt.Sprintf("Error in getting 2xx response in PUT API call to alertmanager(DELETE %s)\n", amURL), response.Status)
|
||||||
|
zap.S().Error(err)
|
||||||
|
return &model.ApiError{Typ: model.ErrorInternal, Err: err}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
22
pkg/query-service/integrations/alertManager/model.go
Normal file
22
pkg/query-service/integrations/alertManager/model.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package alertManager
|
||||||
|
|
||||||
|
// Receiver configuration provides configuration on how to contact a receiver.
|
||||||
|
type Receiver struct {
|
||||||
|
// A unique identifier for this receiver.
|
||||||
|
Name string `yaml:"name" json:"name"`
|
||||||
|
|
||||||
|
EmailConfigs interface{} `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
|
||||||
|
PagerdutyConfigs interface{} `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
|
||||||
|
SlackConfigs interface{} `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
|
||||||
|
WebhookConfigs interface{} `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
|
||||||
|
OpsGenieConfigs interface{} `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
|
||||||
|
WechatConfigs interface{} `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
|
||||||
|
PushoverConfigs interface{} `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
|
||||||
|
VictorOpsConfigs interface{} `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
|
||||||
|
SNSConfigs interface{} `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiverResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Data Receiver `json:"data"`
|
||||||
|
}
|
@ -51,27 +51,6 @@ type ChannelItem struct {
|
|||||||
Data string `json:"data" db:"data"`
|
Data string `json:"data" db:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Receiver configuration provides configuration on how to contact a receiver.
|
|
||||||
type Receiver struct {
|
|
||||||
// A unique identifier for this receiver.
|
|
||||||
Name string `yaml:"name" json:"name"`
|
|
||||||
|
|
||||||
EmailConfigs interface{} `yaml:"email_configs,omitempty" json:"email_configs,omitempty"`
|
|
||||||
PagerdutyConfigs interface{} `yaml:"pagerduty_configs,omitempty" json:"pagerduty_configs,omitempty"`
|
|
||||||
SlackConfigs interface{} `yaml:"slack_configs,omitempty" json:"slack_configs,omitempty"`
|
|
||||||
WebhookConfigs interface{} `yaml:"webhook_configs,omitempty" json:"webhook_configs,omitempty"`
|
|
||||||
OpsGenieConfigs interface{} `yaml:"opsgenie_configs,omitempty" json:"opsgenie_configs,omitempty"`
|
|
||||||
WechatConfigs interface{} `yaml:"wechat_configs,omitempty" json:"wechat_configs,omitempty"`
|
|
||||||
PushoverConfigs interface{} `yaml:"pushover_configs,omitempty" json:"pushover_configs,omitempty"`
|
|
||||||
VictorOpsConfigs interface{} `yaml:"victorops_configs,omitempty" json:"victorops_configs,omitempty"`
|
|
||||||
SNSConfigs interface{} `yaml:"sns_configs,omitempty" json:"sns_configs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReceiverResponse struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Data Receiver `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AlertDiscovery has info for all active alerts.
|
// AlertDiscovery has info for all active alerts.
|
||||||
type AlertDiscovery struct {
|
type AlertDiscovery struct {
|
||||||
Alerts []*AlertingRuleResponse `json:"rules"`
|
Alerts []*AlertingRuleResponse `json:"rules"`
|
||||||
@ -304,8 +283,10 @@ type DBResponseTTL struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GetTTLResponseItem struct {
|
type GetTTLResponseItem struct {
|
||||||
MetricsTime int `json:"metrics_ttl_duration_hrs"`
|
MetricsTime int `json:"metrics_ttl_duration_hrs,omitempty"`
|
||||||
TracesTime int `json:"traces_ttl_duration_hrs"`
|
MetricsMoveTime int `json:"metrics_move_ttl_duration_hrs,omitempty"`
|
||||||
|
TracesTime int `json:"traces_ttl_duration_hrs,omitempty"`
|
||||||
|
TracesMoveTime int `json:"traces_move_ttl_duration_hrs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBResponseMinMaxDuration struct {
|
type DBResponseMinMaxDuration struct {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package tests
|
package tests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -8,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"go.signoz.io/query-service/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -102,6 +104,76 @@ func TestSetTTL(t *testing.T) {
|
|||||||
fmt.Printf("=== Found %d objects in Minio\n", count)
|
fmt.Printf("=== Found %d objects in Minio\n", count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getTTL(t *testing.T, table string) *model.GetTTLResponseItem {
|
||||||
|
req := endpoint + fmt.Sprintf("/api/v1/settings/ttl?type=%s", table)
|
||||||
|
if len(table) == 0 {
|
||||||
|
req = endpoint + "/api/v1/settings/ttl"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Get(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
res := &model.GetTTLResponseItem{}
|
||||||
|
require.NoError(t, json.Unmarshal(b, res))
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTTL(t *testing.T) {
|
||||||
|
r, err := setTTL("traces", "s3", "3600s", "7200s")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(r), "successfully set up")
|
||||||
|
|
||||||
|
resp := getTTL(t, "traces")
|
||||||
|
require.Equal(t, 1, resp.TracesMoveTime)
|
||||||
|
require.Equal(t, 2, resp.TracesTime)
|
||||||
|
|
||||||
|
r, err = setTTL("metrics", "s3", "3600s", "7200s")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(r), "successfully set up")
|
||||||
|
|
||||||
|
resp = getTTL(t, "metrics")
|
||||||
|
require.Equal(t, 1, resp.MetricsMoveTime)
|
||||||
|
require.Equal(t, 2, resp.MetricsTime)
|
||||||
|
|
||||||
|
r, err = setTTL("traces", "s3", "36000s", "72000s")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(r), "successfully set up")
|
||||||
|
|
||||||
|
resp = getTTL(t, "")
|
||||||
|
require.Equal(t, 10, resp.TracesMoveTime)
|
||||||
|
require.Equal(t, 20, resp.TracesTime)
|
||||||
|
require.Equal(t, 1, resp.MetricsMoveTime)
|
||||||
|
require.Equal(t, 2, resp.MetricsTime)
|
||||||
|
|
||||||
|
r, err = setTTL("metrics", "s3", "15h", "50h")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(r), "successfully set up")
|
||||||
|
|
||||||
|
resp = getTTL(t, "")
|
||||||
|
require.Equal(t, 10, resp.TracesMoveTime)
|
||||||
|
require.Equal(t, 20, resp.TracesTime)
|
||||||
|
require.Equal(t, 15, resp.MetricsMoveTime)
|
||||||
|
require.Equal(t, 50, resp.MetricsTime)
|
||||||
|
|
||||||
|
r, err = setTTL("metrics", "s3", "0s", "0s")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(r), "successfully set up")
|
||||||
|
|
||||||
|
r, err = setTTL("traces", "s3", "0s", "0s")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, string(r), "successfully set up")
|
||||||
|
|
||||||
|
resp = getTTL(t, "")
|
||||||
|
require.Equal(t, 0, resp.TracesMoveTime)
|
||||||
|
require.Equal(t, 0, resp.TracesTime)
|
||||||
|
require.Equal(t, 0, resp.MetricsMoveTime)
|
||||||
|
require.Equal(t, 0, resp.MetricsTime)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
if err := startCluster(); err != nil {
|
if err := startCluster(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
BIN
pkg/query-service/tests/test-deploy/data/signoz.db
Normal file
BIN
pkg/query-service/tests/test-deploy/data/signoz.db
Normal file
Binary file not shown.
@ -5,7 +5,6 @@ services:
|
|||||||
image: altinity/clickhouse-server:21.12.3.32.altinitydev.arm
|
image: altinity/clickhouse-server:21.12.3.32.altinitydev.arm
|
||||||
volumes:
|
volumes:
|
||||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||||
@ -14,13 +13,12 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:0.5.0
|
image: signoz/alertmanager:0.6.0
|
||||||
volumes:
|
depends_on:
|
||||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
- query-service
|
||||||
- ./data/alertmanager:/data
|
|
||||||
command:
|
command:
|
||||||
- '--config.file=/prometheus/alertmanager.yml'
|
- --queryService.url=http://query-service:8080
|
||||||
- '--storage.path=/data'
|
- --storage.path=/data
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:latest
|
image: signoz/query-service:latest
|
||||||
@ -29,9 +27,9 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/root/config/prometheus.yml
|
- ./prometheus.yml:/root/config/prometheus.yml
|
||||||
- ../dashboards:/root/config/dashboards
|
- ../dashboards:/root/config/dashboards
|
||||||
- ./data/signoz/:/var/lib/signoz/
|
- ./data:/var/lib/signoz
|
||||||
ports:
|
ports:
|
||||||
- "8180:8080"
|
- "8180:8080"
|
||||||
environment:
|
environment:
|
||||||
- ClickHouseUrl=tcp://clickhouse:9000
|
- ClickHouseUrl=tcp://clickhouse:9000
|
||||||
- STORAGE=clickhouse
|
- STORAGE=clickhouse
|
||||||
@ -72,7 +70,7 @@ services:
|
|||||||
max-file: "3"
|
max-file: "3"
|
||||||
command: ["all"]
|
command: ["all"]
|
||||||
environment:
|
environment:
|
||||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||||
|
|
||||||
load-hotrod:
|
load-hotrod:
|
||||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||||
@ -87,4 +85,4 @@ services:
|
|||||||
QUIET_MODE: "${QUIET_MODE:-false}"
|
QUIET_MODE: "${QUIET_MODE:-false}"
|
||||||
LOCUST_OPTS: "--headless -u 10 -r 1"
|
LOCUST_OPTS: "--headless -u 10 -r 1"
|
||||||
volumes:
|
volumes:
|
||||||
- ../common/locust-scripts:/locust
|
- ../../../../deploy/docker/common/locust-scripts:/locust
|
||||||
|
@ -13,22 +13,25 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
image: signoz/alertmanager:0.5.0
|
image: signoz/alertmanager:0.6.0
|
||||||
volumes:
|
depends_on:
|
||||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
- query-service
|
||||||
command:
|
command:
|
||||||
- '--config.file=/prometheus/alertmanager.yml'
|
- --queryService.url=http://query-service:8080
|
||||||
- '--storage.path=/data'
|
- --storage.path=/data
|
||||||
|
|
||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
|
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:latest
|
image: signoz/query-service:latest
|
||||||
|
container_name: query-service
|
||||||
command: ["-config=/root/config/prometheus.yml"]
|
command: ["-config=/root/config/prometheus.yml"]
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/root/config/prometheus.yml
|
- ./prometheus.yml:/root/config/prometheus.yml
|
||||||
- ../dashboards:/root/config/dashboards
|
- ../dashboards:/root/config/dashboards
|
||||||
|
- ./data:/var/lib/signoz
|
||||||
|
ports:
|
||||||
|
- "8180:8080"
|
||||||
environment:
|
environment:
|
||||||
- ClickHouseUrl=tcp://clickhouse:9000
|
- ClickHouseUrl=tcp://clickhouse:9000
|
||||||
- STORAGE=clickhouse
|
- STORAGE=clickhouse
|
||||||
@ -37,13 +40,6 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
clickhouse:
|
clickhouse:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
ports:
|
|
||||||
- "8180:8080"
|
|
||||||
volumes:
|
|
||||||
- type: bind
|
|
||||||
source: ./data
|
|
||||||
target: /var/lib/signoz
|
|
||||||
read_only: false
|
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/otelcontribcol:0.43.0
|
image: signoz/otelcontribcol:0.43.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user