mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 00:38:59 +08:00
Feat: dynamic tooltip (#1705)
* feat: integrate config service with query service * feat: add tooltip checkpoint * feat: add support for dark and light mode icons Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
parent
73706d872f
commit
a50d7f227c
24
frontend/src/api/dynamicConfigs/getDynamicConfigs.ts
Normal file
24
frontend/src/api/dynamicConfigs/getDynamicConfigs.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/dynamicConfigs/getDynamicConfigs';
|
||||
|
||||
const getDynamicConfigs = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/configs`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getDynamicConfigs;
|
@ -1,4 +1,5 @@
|
||||
import { notification } from 'antd';
|
||||
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
|
||||
import getFeaturesFlags from 'api/features/getFeatureFlags';
|
||||
import getUserLatestVersion from 'api/user/getLatestVersion';
|
||||
import getUserVersion from 'api/user/getVersion';
|
||||
@ -14,6 +15,7 @@ import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
UPDATE_CONFIGS,
|
||||
UPDATE_CURRENT_ERROR,
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_FEATURE_FLAGS,
|
||||
@ -33,6 +35,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
getUserVersionResponse,
|
||||
getUserLatestVersionResponse,
|
||||
getFeaturesResponse,
|
||||
getDynamicConfigsResponse,
|
||||
] = useQueries([
|
||||
{
|
||||
queryFn: getUserVersion,
|
||||
@ -48,6 +51,10 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
queryFn: getFeaturesFlags,
|
||||
queryKey: 'getFeatureFlags',
|
||||
},
|
||||
{
|
||||
queryFn: getDynamicConfigs,
|
||||
queryKey: 'getDynamicConfigs',
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -65,11 +72,15 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
if (getFeaturesResponse.status === 'idle') {
|
||||
getFeaturesResponse.refetch();
|
||||
}
|
||||
if (getDynamicConfigsResponse.status === 'idle') {
|
||||
getDynamicConfigsResponse.refetch();
|
||||
}
|
||||
}, [
|
||||
getFeaturesResponse,
|
||||
getUserLatestVersionResponse,
|
||||
getUserVersionResponse,
|
||||
isLoggedIn,
|
||||
getDynamicConfigsResponse,
|
||||
]);
|
||||
|
||||
const { children } = props;
|
||||
@ -78,6 +89,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const latestCurrentCounter = useRef(0);
|
||||
const latestVersionCounter = useRef(0);
|
||||
const latestConfigCounter = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@ -170,6 +182,23 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
getDynamicConfigsResponse.isFetched &&
|
||||
getDynamicConfigsResponse.isSuccess &&
|
||||
getDynamicConfigsResponse.data &&
|
||||
getDynamicConfigsResponse.data.payload &&
|
||||
latestConfigCounter.current === 0
|
||||
) {
|
||||
latestConfigCounter.current = 1;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_CONFIGS,
|
||||
payload: {
|
||||
configs: getDynamicConfigsResponse.data.payload,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
isLoggedIn,
|
||||
@ -187,6 +216,9 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
getFeaturesResponse.isFetched,
|
||||
getFeaturesResponse.isSuccess,
|
||||
getFeaturesResponse.data,
|
||||
getDynamicConfigsResponse.data,
|
||||
getDynamicConfigsResponse.isFetched,
|
||||
getDynamicConfigsResponse.isSuccess,
|
||||
]);
|
||||
|
||||
const isToDisplayLayout = isLoggedIn;
|
||||
|
33
frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx
Normal file
33
frontend/src/container/ConfigDropdown/Config/ErrorLink.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
class ErrorLink extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = { hasError: false };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(): State {
|
||||
return { hasError: true };
|
||||
}
|
||||
|
||||
render(): JSX.Element {
|
||||
const { children } = this.props;
|
||||
const { hasError } = this.state;
|
||||
|
||||
if (hasError) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorLink;
|
23
frontend/src/container/ConfigDropdown/Config/Link.tsx
Normal file
23
frontend/src/container/ConfigDropdown/Config/Link.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
function LinkContainer({ children, href }: LinkContainerProps): JSX.Element {
|
||||
const isInternalLink = href.startsWith('/');
|
||||
|
||||
if (isInternalLink) {
|
||||
return <Link to={href}>{children}</Link>;
|
||||
}
|
||||
|
||||
return (
|
||||
<a rel="noreferrer" target="_blank" href={href}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
interface LinkContainerProps {
|
||||
children: React.ReactNode;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export default LinkContainer;
|
51
frontend/src/container/ConfigDropdown/Config/index.tsx
Normal file
51
frontend/src/container/ConfigDropdown/Config/index.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { Menu, Space } from 'antd';
|
||||
import Spinner from 'components/Spinner';
|
||||
import React, { Suspense, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { ConfigProps } from 'types/api/dynamicConfigs/getDynamicConfigs';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import ErrorLink from './ErrorLink';
|
||||
import LinkContainer from './Link';
|
||||
|
||||
function HelpToolTip({ config }: HelpToolTipProps): JSX.Element {
|
||||
const sortedConfig = useMemo(
|
||||
() => config.components.sort((a, b) => a.position - b.position),
|
||||
[config.components],
|
||||
);
|
||||
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
return (
|
||||
<Menu.ItemGroup>
|
||||
{sortedConfig.map((item) => {
|
||||
const iconName = `${isDarkMode ? item.darkIcon : item.lightIcon}`;
|
||||
|
||||
const Component = React.lazy(
|
||||
() => import(`@ant-design/icons/es/icons/${iconName}.js`),
|
||||
);
|
||||
return (
|
||||
<ErrorLink key={item.text + item.href}>
|
||||
<Suspense fallback={<Spinner height="5vh" />}>
|
||||
<Menu.Item>
|
||||
<LinkContainer href={item.href}>
|
||||
<Space size="small" align="start">
|
||||
<Component />
|
||||
{item.text}
|
||||
</Space>
|
||||
</LinkContainer>
|
||||
</Menu.Item>
|
||||
</Suspense>
|
||||
</ErrorLink>
|
||||
);
|
||||
})}
|
||||
</Menu.ItemGroup>
|
||||
);
|
||||
}
|
||||
|
||||
interface HelpToolTipProps {
|
||||
config: ConfigProps;
|
||||
}
|
||||
|
||||
export default HelpToolTip;
|
67
frontend/src/container/ConfigDropdown/index.tsx
Normal file
67
frontend/src/container/ConfigDropdown/index.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
CaretDownFilled,
|
||||
CaretUpFilled,
|
||||
QuestionCircleFilled,
|
||||
QuestionCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Dropdown, Menu, Space } from 'antd';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import HelpToolTip from './Config';
|
||||
|
||||
function DynamicConfigDropdown({
|
||||
frontendId,
|
||||
}: DynamicConfigDropdownProps): JSX.Element {
|
||||
const { configs, isDarkMode } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
);
|
||||
const [isHelpDropDownOpen, setIsHelpDropDownOpen] = useState<boolean>(false);
|
||||
|
||||
const config = useMemo(
|
||||
() =>
|
||||
Object.values(configs).find(
|
||||
(config) => config.frontendPositionId === frontendId,
|
||||
),
|
||||
[frontendId, configs],
|
||||
);
|
||||
|
||||
const onToggleHandler = (): void => {
|
||||
setIsHelpDropDownOpen(!isHelpDropDownOpen);
|
||||
};
|
||||
|
||||
if (!config) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
const Icon = isDarkMode ? QuestionCircleOutlined : QuestionCircleFilled;
|
||||
const DropDownIcon = isHelpDropDownOpen ? CaretUpFilled : CaretDownFilled;
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
onVisibleChange={onToggleHandler}
|
||||
trigger={['click']}
|
||||
overlay={
|
||||
<Menu>
|
||||
<HelpToolTip config={config} />
|
||||
</Menu>
|
||||
}
|
||||
visible={isHelpDropDownOpen}
|
||||
>
|
||||
<Space align="center">
|
||||
<Icon
|
||||
style={{ fontSize: 26, color: 'white', paddingTop: 20, cursor: 'pointer' }}
|
||||
/>
|
||||
<DropDownIcon style={{ color: 'white' }} />
|
||||
</Space>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
interface DynamicConfigDropdownProps {
|
||||
frontendId: string;
|
||||
}
|
||||
|
||||
export default DynamicConfigDropdown;
|
@ -14,8 +14,9 @@ import {
|
||||
} from 'antd';
|
||||
import { Logout } from 'api/utils';
|
||||
import ROUTES from 'constants/routes';
|
||||
import Config from 'container/ConfigDropdown';
|
||||
import setTheme, { AppMode } from 'lib/theme/setTheme';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
||||
import { connect, useSelector } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { bindActionCreators } from 'redux';
|
||||
@ -34,7 +35,8 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
const { isDarkMode, user, currentVersion } = useSelector<AppState, AppReducer>(
|
||||
(state) => state.app,
|
||||
);
|
||||
const [isUserDropDownOpen, setIsUserDropDownOpen] = useState<boolean>();
|
||||
|
||||
const [isUserDropDownOpen, setIsUserDropDownOpen] = useState<boolean>(false);
|
||||
|
||||
const onToggleThemeHandler = useCallback(() => {
|
||||
const preMode: AppMode = isDarkMode ? 'lightMode' : 'darkMode';
|
||||
@ -57,22 +59,21 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
};
|
||||
}, [toggleDarkMode, isDarkMode]);
|
||||
|
||||
const onArrowClickHandler: VoidFunction = () => {
|
||||
setIsUserDropDownOpen((state) => !state);
|
||||
};
|
||||
|
||||
const onClickLogoutHandler = (): void => {
|
||||
Logout();
|
||||
};
|
||||
const onToggleHandler = useCallback(
|
||||
(functionToExecute: Dispatch<SetStateAction<boolean>>) => (): void => {
|
||||
functionToExecute((state) => !state);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const menu = (
|
||||
<Menu style={{ padding: '1rem' }}>
|
||||
<Menu.ItemGroup>
|
||||
<SignedInAS />
|
||||
<Divider />
|
||||
<CurrentOrganization onToggle={onArrowClickHandler} />
|
||||
<CurrentOrganization onToggle={onToggleHandler(setIsUserDropDownOpen)} />
|
||||
<Divider />
|
||||
<ManageLicense onToggle={onArrowClickHandler} />
|
||||
<ManageLicense onToggle={onToggleHandler(setIsUserDropDownOpen)} />
|
||||
<Divider />
|
||||
<LogoutContainer>
|
||||
<LogoutOutlined />
|
||||
@ -80,11 +81,11 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
tabIndex={0}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter' || e.key === 'Space') {
|
||||
onClickLogoutHandler();
|
||||
Logout();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
onClick={onClickLogoutHandler}
|
||||
onClick={Logout}
|
||||
>
|
||||
<Typography.Link>Logout</Typography.Link>
|
||||
</div>
|
||||
@ -94,23 +95,18 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
);
|
||||
|
||||
return (
|
||||
<Layout.Header
|
||||
style={{
|
||||
paddingLeft: '1.125rem',
|
||||
paddingRight: '1.125rem',
|
||||
}}
|
||||
>
|
||||
<Layout.Header>
|
||||
<Container>
|
||||
<NavLink
|
||||
style={{ display: 'flex', alignItems: 'center', gap: '8px' }}
|
||||
to={ROUTES.APPLICATION}
|
||||
>
|
||||
<NavLink to={ROUTES.APPLICATION}>
|
||||
<img src={`/signoz.svg?currentVersion=${currentVersion}`} alt="SigNoz" />
|
||||
<Typography.Title style={{ margin: 0, color: '#DBDBDB' }} level={4}>
|
||||
SigNoz
|
||||
</Typography.Title>
|
||||
</NavLink>
|
||||
<Space align="center">
|
||||
|
||||
<Space style={{ height: '100%' }} align="center">
|
||||
<Config frontendId="tooltip" />
|
||||
|
||||
<ToggleButton
|
||||
checked={isDarkMode}
|
||||
onChange={onToggleThemeHandler}
|
||||
@ -120,26 +116,14 @@ function HeaderContainer({ toggleDarkMode }: Props): JSX.Element {
|
||||
/>
|
||||
|
||||
<Dropdown
|
||||
onVisibleChange={onArrowClickHandler}
|
||||
onVisibleChange={onToggleHandler(setIsUserDropDownOpen)}
|
||||
trigger={['click']}
|
||||
overlay={menu}
|
||||
visible={isUserDropDownOpen}
|
||||
>
|
||||
<Space>
|
||||
<Avatar shape="circle">{user?.name[0]}</Avatar>
|
||||
{!isUserDropDownOpen ? (
|
||||
<CaretDownFilled
|
||||
style={{
|
||||
color: '#DBDBDB',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CaretUpFilled
|
||||
style={{
|
||||
color: '#DBDBDB',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isUserDropDownOpen ? <CaretDownFilled /> : <CaretUpFilled />}
|
||||
</Space>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
LOGGED_IN,
|
||||
SIDEBAR_COLLAPSE,
|
||||
SWITCH_DARK_MODE,
|
||||
UPDATE_CONFIGS,
|
||||
UPDATE_CURRENT_ERROR,
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_FEATURE_FLAGS,
|
||||
@ -56,6 +57,7 @@ const InitialValue: InitialValueTypes = {
|
||||
isUserFetchingError: false,
|
||||
org: null,
|
||||
role: null,
|
||||
configs: {},
|
||||
};
|
||||
|
||||
const appReducer = (
|
||||
@ -210,6 +212,13 @@ const appReducer = (
|
||||
};
|
||||
}
|
||||
|
||||
case UPDATE_CONFIGS: {
|
||||
return {
|
||||
...state,
|
||||
configs: action.payload.configs,
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ export const UPDATE_USER = 'UPDATE_USER';
|
||||
export const UPDATE_ORG_NAME = 'UPDATE_ORG_NAME';
|
||||
export const UPDATE_ORG = 'UPDATE_ORG';
|
||||
export const UPDATE_FEATURE_FLAGS = 'UPDATE_FEATURE_FLAGS';
|
||||
export const UPDATE_CONFIGS = 'UPDATE_CONFIGS';
|
||||
|
||||
export interface SwitchDarkMode {
|
||||
type: typeof SWITCH_DARK_MODE;
|
||||
@ -115,6 +116,12 @@ export interface UpdateOrg {
|
||||
org: AppReducer['org'];
|
||||
};
|
||||
}
|
||||
export interface UpdateConfigs {
|
||||
type: typeof UPDATE_CONFIGS;
|
||||
payload: {
|
||||
configs: AppReducer['configs'];
|
||||
};
|
||||
}
|
||||
|
||||
export type AppAction =
|
||||
| SwitchDarkMode
|
||||
@ -129,4 +136,5 @@ export type AppAction =
|
||||
| UpdateUser
|
||||
| UpdateOrgName
|
||||
| UpdateOrg
|
||||
| UpdateFeatureFlags;
|
||||
| UpdateFeatureFlags
|
||||
| UpdateConfigs;
|
||||
|
14
frontend/src/types/api/dynamicConfigs/getDynamicConfigs.ts
Normal file
14
frontend/src/types/api/dynamicConfigs/getDynamicConfigs.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export interface ConfigProps {
|
||||
enabled: boolean;
|
||||
frontendPositionId: string;
|
||||
components: Array<{
|
||||
href: string;
|
||||
darkIcon: string;
|
||||
lightIcon: string;
|
||||
position: 1;
|
||||
text: string;
|
||||
}>;
|
||||
}
|
||||
export interface PayloadProps {
|
||||
[key: string]: ConfigProps;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { PayloadProps as ConfigPayload } from 'types/api/dynamicConfigs/getDynamicConfigs';
|
||||
import { PayloadProps as FeatureFlagPayload } from 'types/api/features/getFeaturesFlags';
|
||||
import { PayloadProps as OrgPayload } from 'types/api/user/getOrganization';
|
||||
import { PayloadProps as UserPayload } from 'types/api/user/getUser';
|
||||
@ -26,4 +27,5 @@ export default interface AppReducer {
|
||||
role: ROLES | null;
|
||||
org: OrgPayload | null;
|
||||
featureFlags: null | FeatureFlagPayload;
|
||||
configs: ConfigPayload;
|
||||
}
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
|
||||
"go.signoz.io/signoz/pkg/query-service/dao"
|
||||
am "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||
signozio "go.signoz.io/signoz/pkg/query-service/integrations/signozio"
|
||||
"go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
"go.signoz.io/signoz/pkg/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/rules"
|
||||
@ -360,6 +361,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router) {
|
||||
|
||||
router.HandleFunc("/api/v1/version", OpenAccess(aH.getVersion)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/featureFlags", OpenAccess(aH.getFeatureFlags)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/configs", OpenAccess(aH.getConfigs)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/getSpanFilters", ViewAccess(aH.getSpanFilters)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/getTagFilters", ViewAccess(aH.getTagFilters)).Methods(http.MethodPost)
|
||||
@ -1583,6 +1585,16 @@ func (aH *APIHandler) CheckFeature(f string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (aH *APIHandler) getConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
configs, err := signozio.FetchDynamicConfigs()
|
||||
if err != nil {
|
||||
aH.HandleError(w, err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, configs)
|
||||
}
|
||||
|
||||
// inviteUser is used to invite a user. It is used by an admin api.
|
||||
func (aH *APIHandler) inviteUser(w http.ResponseWriter, r *http.Request) {
|
||||
req, err := parseInviteRequest(r)
|
||||
|
@ -13,6 +13,8 @@ const (
|
||||
DebugHttpPort = "0.0.0.0:6060" // Address to serve http (pprof)
|
||||
)
|
||||
|
||||
var ConfigSignozIo = "https://config.signoz.io/api/v1"
|
||||
|
||||
var DEFAULT_TELEMETRY_ANONYMOUS = false
|
||||
|
||||
func IsTelemetryEnabled() bool {
|
||||
|
75
pkg/query-service/integrations/signozio/dynamic_config.go
Normal file
75
pkg/query-service/integrations/signozio/dynamic_config.go
Normal file
@ -0,0 +1,75 @@
|
||||
package signozio
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
"go.signoz.io/signoz/pkg/query-service/constants"
|
||||
)
|
||||
|
||||
var C *Client
|
||||
|
||||
const (
|
||||
POST = "POST"
|
||||
APPLICATION_JSON = "application/json"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func New() *Client {
|
||||
return &Client{
|
||||
Prefix: constants.ConfigSignozIo,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
C = New()
|
||||
}
|
||||
|
||||
// FetchDynamicConfigs fetches configs from config server
|
||||
func FetchDynamicConfigs() (map[string]Config, *model.ApiError) {
|
||||
|
||||
client := http.Client{Timeout: 5 * time.Second}
|
||||
req, err := http.NewRequest(http.MethodGet, C.Prefix+"/configs", http.NoBody)
|
||||
if err != nil {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
req.SetBasicAuth("admin", "SigNoz@adm1n")
|
||||
httpResponse, err := client.Do(req)
|
||||
if err != nil {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
|
||||
defer httpResponse.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
|
||||
httpBody, err := ioutil.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
|
||||
// read api request result
|
||||
result := ConfigResult{}
|
||||
err = json.Unmarshal(httpBody, &result)
|
||||
if err != nil {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
|
||||
switch httpResponse.StatusCode {
|
||||
case 200, 201:
|
||||
return result.Data, nil
|
||||
case 400, 401:
|
||||
return DefaultConfig, nil
|
||||
default:
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
|
||||
}
|
54
pkg/query-service/integrations/signozio/response.go
Normal file
54
pkg/query-service/integrations/signozio/response.go
Normal file
@ -0,0 +1,54 @@
|
||||
package signozio
|
||||
|
||||
type status string
|
||||
|
||||
type ConfigResult struct {
|
||||
Status status `json:"status"`
|
||||
Data map[string]Config `json:"data,omitempty"`
|
||||
ErrorType string `json:"errorType,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
FrontendPositionId string `json:"frontendPositionId"`
|
||||
Components []ComponentProps `json:"components"`
|
||||
}
|
||||
|
||||
type ComponentProps struct {
|
||||
Text string `json:"text"`
|
||||
Position int `json:"position"`
|
||||
DarkIcon string `json:"darkIcon"`
|
||||
LightIcon string `json:"lightIcon"`
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
var DefaultConfig = map[string]Config{
|
||||
"helpConfig": {
|
||||
Enabled: true,
|
||||
FrontendPositionId: "tooltip",
|
||||
Components: []ComponentProps{
|
||||
{
|
||||
Text: "How to use SigNoz in production",
|
||||
Position: 1,
|
||||
LightIcon: "RiseOutlined",
|
||||
DarkIcon: "RiseOutlined",
|
||||
Href: "https://signoz.io/docs/production-readiness",
|
||||
},
|
||||
{
|
||||
Text: "Create an issue in GitHub",
|
||||
Position: 2,
|
||||
LightIcon: "GithubFilled",
|
||||
DarkIcon: "GithubOutlined",
|
||||
Href: "https://github.com/SigNoz/signoz/issues/new/choose",
|
||||
},
|
||||
{
|
||||
Text: "Read the docs",
|
||||
Position: 3,
|
||||
LightIcon: "FileTextFilled",
|
||||
DarkIcon: "FileTextOutlined",
|
||||
Href: "https://signoz.io/docs",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user