Compare commits
12 Commits
fix/query-
...
fix/pipeli
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99edf96910 | ||
|
|
552d44d208 | ||
|
|
497315579f | ||
|
|
bfaac15ccb | ||
|
|
5e18be6a23 | ||
|
|
1793706f87 | ||
|
|
da2a3c738a | ||
|
|
d17dab9a1d | ||
|
|
88b75d4e72 | ||
|
|
6327ab5ec6 | ||
|
|
5b09490ad7 | ||
|
|
b50127b567 |
@@ -174,7 +174,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.88.1
|
||||
image: signoz/signoz:v0.89.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
|
||||
@@ -115,7 +115,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.88.1
|
||||
image: signoz/signoz:v0.89.0
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
ports:
|
||||
|
||||
@@ -177,7 +177,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.88.1}
|
||||
image: signoz/signoz:${VERSION:-v0.89.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
|
||||
@@ -110,7 +110,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.88.1}
|
||||
image: signoz/signoz:${VERSION:-v0.89.0}
|
||||
container_name: signoz
|
||||
command:
|
||||
- --config=/root/config/prometheus.yml
|
||||
|
||||
@@ -213,7 +213,9 @@
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "^7.0.4",
|
||||
"image-webpack-loader": "8.1.0",
|
||||
"image-minimizer-webpack-plugin": "^4.0.0",
|
||||
"imagemin": "^8.0.1",
|
||||
"imagemin-svgo": "^10.0.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"lint-staged": "^12.5.0",
|
||||
@@ -230,6 +232,7 @@
|
||||
"redux-mock-store": "1.5.4",
|
||||
"sass": "1.66.1",
|
||||
"sass-loader": "13.3.2",
|
||||
"sharp": "^0.33.4",
|
||||
"ts-jest": "^27.1.5",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "5.0.1",
|
||||
@@ -254,6 +257,7 @@
|
||||
"cross-spawn": "7.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"serialize-javascript": "6.0.2",
|
||||
"prismjs": "1.30.0"
|
||||
"prismjs": "1.30.0",
|
||||
"got": "11.8.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import dayjs from 'dayjs';
|
||||
import { useGetDeploymentsData } from 'hooks/CustomDomain/useGetDeploymentsData';
|
||||
import { useGetAllIngestionsKeys } from 'hooks/IngestionKeys/useGetAllIngestionKeys';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isNil, isUndefined } from 'lodash-es';
|
||||
import {
|
||||
@@ -167,6 +168,8 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
|
||||
const [totalIngestionKeys, setTotalIngestionKeys] = useState(0);
|
||||
|
||||
const { isEnterpriseSelfHostedUser } = useGetTenantLicense();
|
||||
|
||||
const [
|
||||
hasCreateLimitForIngestionKeyError,
|
||||
setHasCreateLimitForIngestionKeyError,
|
||||
@@ -293,7 +296,7 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
isLoading: isLoadingDeploymentsData,
|
||||
isFetching: isFetchingDeploymentsData,
|
||||
isError: isErrorDeploymentsData,
|
||||
} = useGetDeploymentsData(true);
|
||||
} = useGetDeploymentsData(!isEnterpriseSelfHostedUser);
|
||||
|
||||
const {
|
||||
mutate: createIngestionKey,
|
||||
@@ -1308,7 +1311,8 @@ function MultiIngestionSettings(): JSX.Element {
|
||||
|
||||
{!isErrorDeploymentsData &&
|
||||
!isLoadingDeploymentsData &&
|
||||
!isFetchingDeploymentsData && (
|
||||
!isFetchingDeploymentsData &&
|
||||
deploymentsData && (
|
||||
<div className="ingestion-setup-details-links">
|
||||
<div className="ingestion-key-url-container">
|
||||
<div className="ingestion-key-url-label">Ingestion URL</div>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const TitleWrapper = styled.span`
|
||||
user-select: text !important;
|
||||
cursor: text;
|
||||
|
||||
.hover-reveal {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
@@ -71,8 +71,13 @@ function BodyTitleRenderer({
|
||||
onClick: onClickHandler,
|
||||
};
|
||||
|
||||
const handleTextSelection = (e: React.MouseEvent): void => {
|
||||
// Prevent tree node click when user is trying to select text
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<TitleWrapper>
|
||||
<TitleWrapper onMouseDown={handleTextSelection}>
|
||||
<Dropdown menu={menu} trigger={['click']}>
|
||||
<SettingOutlined style={{ marginRight: 8 }} className="hover-reveal" />
|
||||
</Dropdown>
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.selectable-tree {
|
||||
.ant-tree-node-content-wrapper {
|
||||
user-select: text !important;
|
||||
cursor: text !important;
|
||||
}
|
||||
|
||||
.ant-tree-title {
|
||||
user-select: text !important;
|
||||
cursor: text !important;
|
||||
}
|
||||
}
|
||||
|
||||
.table-view-actions-content {
|
||||
.ant-popover-inner {
|
||||
border-radius: 4px;
|
||||
|
||||
@@ -53,7 +53,12 @@ const convert = new Convert();
|
||||
|
||||
// Memoized Tree Component
|
||||
const MemoizedTree = React.memo<{ treeData: any[] }>(({ treeData }) => (
|
||||
<Tree defaultExpandAll showLine treeData={treeData} />
|
||||
<Tree
|
||||
defaultExpandAll
|
||||
showLine
|
||||
treeData={treeData}
|
||||
className="selectable-tree"
|
||||
/>
|
||||
));
|
||||
|
||||
MemoizedTree.displayName = 'MemoizedTree';
|
||||
|
||||
@@ -3,9 +3,11 @@ import { useGetMetricMeta } from 'hooks/apDex/useGetMetricMeta';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { FeatureKeys } from '../../../../../constants/features';
|
||||
import { useAppContext } from '../../../../../providers/App/App';
|
||||
import { WidgetKeys } from '../../../constant';
|
||||
import { IServiceName } from '../../types';
|
||||
import ApDexMetrics from './ApDexMetrics';
|
||||
import { metricMeta } from './constants';
|
||||
import { ApDexDataSwitcherProps } from './types';
|
||||
|
||||
function ApDexMetricsApplication({
|
||||
@@ -18,7 +20,19 @@ function ApDexMetricsApplication({
|
||||
const { servicename: encodedServiceName } = useParams<IServiceName>();
|
||||
const servicename = decodeURIComponent(encodedServiceName);
|
||||
|
||||
const { data, isLoading, error } = useGetMetricMeta(metricMeta, servicename);
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
|
||||
?.active || false;
|
||||
|
||||
const signozLatencyBucketMetrics = dotMetricsEnabled
|
||||
? WidgetKeys.Signoz_latency_bucket
|
||||
: WidgetKeys.Signoz_latency_bucket_norm;
|
||||
|
||||
const { data, isLoading, error } = useGetMetricMeta(
|
||||
signozLatencyBucketMetrics,
|
||||
servicename,
|
||||
);
|
||||
useErrorNotification(error);
|
||||
|
||||
if (isLoading) {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const metricMeta = 'signoz_latency_bucket';
|
||||
@@ -215,6 +215,27 @@ export function SpanDuration({
|
||||
setHasActionButtons(false);
|
||||
};
|
||||
|
||||
// Calculate text positioning to handle overflow cases
|
||||
const textStyle = useMemo(() => {
|
||||
const spanRightEdge = leftOffset + width;
|
||||
const textWidthApprox = 8; // Approximate text width in percentage
|
||||
|
||||
// If span would cause text overflow, right-align text to span end
|
||||
if (leftOffset > 100 - textWidthApprox) {
|
||||
return {
|
||||
right: `${100 - spanRightEdge}%`,
|
||||
color,
|
||||
textAlign: 'right' as const,
|
||||
};
|
||||
}
|
||||
|
||||
// Default: left-align text to span start
|
||||
return {
|
||||
left: `${leftOffset}%`,
|
||||
color,
|
||||
};
|
||||
}, [leftOffset, width, color]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
@@ -270,7 +291,7 @@ export function SpanDuration({
|
||||
<Typography.Text
|
||||
className="span-line-text"
|
||||
ellipsis
|
||||
style={{ left: `${leftOffset}%`, color }}
|
||||
style={textStyle}
|
||||
>{`${toFixed(time, 2)} ${timeUnitName}`}</Typography.Text>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -311,6 +332,16 @@ function getWaterfallColumns({
|
||||
/>
|
||||
),
|
||||
size: 450,
|
||||
/**
|
||||
* Note: The TanStack table currently does not support percentage-based column sizing.
|
||||
* Therefore, we specify both `minSize` and `maxSize` for the "span-name" column to ensure
|
||||
* that its width remains between 240px and 900px. Setting a `maxSize` here is important
|
||||
* because the "span-duration" column has column resizing disabled, making it difficult
|
||||
* to enforce a minimum width for that column. By constraining the "span-name" column,
|
||||
* we indirectly control the minimum width available for the "span-duration" column.
|
||||
*/
|
||||
minSize: 240,
|
||||
maxSize: 900,
|
||||
}),
|
||||
columnDefHelper.display({
|
||||
id: 'span-duration',
|
||||
|
||||
@@ -110,7 +110,7 @@ function SettingsPage(): JSX.Element {
|
||||
item.key === ROUTES.INTEGRATIONS ||
|
||||
item.key === ROUTES.API_KEYS ||
|
||||
item.key === ROUTES.ORG_SETTINGS ||
|
||||
item.key === ROUTES.SHORTCUTS
|
||||
item.key === ROUTES.INGESTION_SETTINGS
|
||||
? true
|
||||
: item.isEnabled,
|
||||
}));
|
||||
@@ -120,7 +120,11 @@ function SettingsPage(): JSX.Element {
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
updatedItems = updatedItems.map((item) => ({
|
||||
...item,
|
||||
isEnabled: item.key === ROUTES.INTEGRATIONS ? true : item.isEnabled,
|
||||
isEnabled:
|
||||
item.key === ROUTES.INTEGRATIONS ||
|
||||
item.key === ROUTES.INGESTION_SETTINGS
|
||||
? true
|
||||
: item.isEnabled,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -130,9 +134,7 @@ function SettingsPage(): JSX.Element {
|
||||
updatedItems = updatedItems.map((item) => ({
|
||||
...item,
|
||||
isEnabled:
|
||||
item.key === ROUTES.API_KEYS ||
|
||||
item.key === ROUTES.ORG_SETTINGS ||
|
||||
item.key === ROUTES.SHORTCUTS
|
||||
item.key === ROUTES.API_KEYS || item.key === ROUTES.ORG_SETTINGS
|
||||
? true
|
||||
: item.isEnabled,
|
||||
}));
|
||||
|
||||
@@ -119,24 +119,8 @@ const config = {
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
},
|
||||
{
|
||||
loader: 'image-webpack-loader',
|
||||
options: {
|
||||
bypassOnDebug: true,
|
||||
optipng: {
|
||||
optimizationLevel: 7,
|
||||
},
|
||||
gifsicle: {
|
||||
interlaced: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
type: 'asset',
|
||||
},
|
||||
{
|
||||
test: /\.(ttf|eot|woff|woff2)$/,
|
||||
|
||||
@@ -14,6 +14,7 @@ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin');
|
||||
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -135,24 +136,8 @@ const config = {
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
},
|
||||
{
|
||||
loader: 'image-webpack-loader',
|
||||
options: {
|
||||
bypassOnDebug: true,
|
||||
optipng: {
|
||||
optimizationLevel: 7,
|
||||
},
|
||||
gifsicle: {
|
||||
interlaced: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
test: /\.(jpe?g|png|gif|svg)$/i,
|
||||
type: 'asset',
|
||||
},
|
||||
|
||||
{
|
||||
@@ -212,6 +197,55 @@ const config = {
|
||||
},
|
||||
}),
|
||||
new CssMinimizerPlugin(),
|
||||
new ImageMinimizerPlugin({
|
||||
minimizer: [
|
||||
{
|
||||
implementation: ImageMinimizerPlugin.sharpMinify,
|
||||
options: {
|
||||
encodeOptions: {
|
||||
jpeg: {
|
||||
quality: 80,
|
||||
},
|
||||
webp: {
|
||||
lossless: true,
|
||||
},
|
||||
avif: {
|
||||
lossless: true,
|
||||
},
|
||||
png: {},
|
||||
gif: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
implementation: ImageMinimizerPlugin.imageminMinify,
|
||||
options: {
|
||||
plugins: [
|
||||
[
|
||||
'svgo',
|
||||
{
|
||||
plugins: [
|
||||
{
|
||||
name: 'preset-default',
|
||||
params: {
|
||||
overrides: {
|
||||
removeViewBox: false,
|
||||
addAttributesToSVGElement: {
|
||||
params: {
|
||||
attributes: [{ xmlns: 'http://www.w3.org/2000/svg' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
performance: {
|
||||
|
||||
1459
frontend/yarn.lock
1459
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -289,43 +289,6 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
existingUser, err := h.module.GetUserByID(ctx, claims.OrgID, id)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// only displayName, role can be updated
|
||||
if user.DisplayName == "" {
|
||||
user.DisplayName = existingUser.DisplayName
|
||||
}
|
||||
|
||||
if user.Role == "" {
|
||||
user.Role = existingUser.Role
|
||||
}
|
||||
|
||||
if user.Role != existingUser.Role && claims.Role != types.RoleAdmin {
|
||||
render.Error(w, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles"))
|
||||
return
|
||||
}
|
||||
|
||||
// Make sure that the request is not demoting the last admin user.
|
||||
// also an admin user can only change role of their own or other user
|
||||
if user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin.String() {
|
||||
adminUsers, err := h.module.GetUsersByRoleInOrg(ctx, claims.OrgID, types.RoleAdmin)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(adminUsers) == 1 {
|
||||
render.Error(w, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot demote the last admin"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
updatedUser, err := h.module.UpdateUser(ctx, claims.OrgID, id, &user, claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
|
||||
@@ -176,18 +176,69 @@ func (m *Module) ListUsers(ctx context.Context, orgID string) ([]*types.Gettable
|
||||
}
|
||||
|
||||
func (m *Module) UpdateUser(ctx context.Context, orgID string, id string, user *types.User, updatedBy string) (*types.User, error) {
|
||||
user, err := m.store.UpdateUser(ctx, orgID, id, user)
|
||||
|
||||
existingUser, err := m.GetUserByID(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
traits := types.NewTraitsFromUser(user)
|
||||
requestor, err := m.GetUserByID(ctx, orgID, updatedBy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// only displayName, role can be updated
|
||||
if user.DisplayName == "" {
|
||||
user.DisplayName = existingUser.DisplayName
|
||||
}
|
||||
|
||||
if user.Role == "" {
|
||||
user.Role = existingUser.Role
|
||||
}
|
||||
|
||||
if user.Role != existingUser.Role && requestor.Role != types.RoleAdmin.String() {
|
||||
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles")
|
||||
}
|
||||
|
||||
// Make sure that the request is not demoting the last admin user.
|
||||
// also an admin user can only change role of their own or other user
|
||||
if user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin.String() {
|
||||
adminUsers, err := m.GetUsersByRoleInOrg(ctx, orgID, types.RoleAdmin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(adminUsers) == 1 {
|
||||
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot demote the last admin")
|
||||
}
|
||||
}
|
||||
|
||||
user.UpdatedAt = time.Now()
|
||||
|
||||
updatedUser, err := m.store.UpdateUser(ctx, orgID, id, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
traits := types.NewTraitsFromUser(updatedUser)
|
||||
m.analytics.IdentifyUser(ctx, user.OrgID, user.ID.String(), traits)
|
||||
|
||||
traits["updated_by"] = updatedBy
|
||||
m.analytics.TrackUser(ctx, user.OrgID, user.ID.String(), "User Updated", traits)
|
||||
|
||||
return user, nil
|
||||
// if the role is updated then send an email
|
||||
if existingUser.Role != updatedUser.Role {
|
||||
if err := m.emailing.SendHTML(ctx, existingUser.Email, "Your Role is updated in SigNoz", emailtypes.TemplateNameUpdateRole, map[string]any{
|
||||
"CustomerName": existingUser.DisplayName,
|
||||
"UpdatedByEmail": requestor.Email,
|
||||
"OldRole": existingUser.Role,
|
||||
"NewRole": updatedUser.Role,
|
||||
}); err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to send email", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return updatedUser, nil
|
||||
}
|
||||
|
||||
func (m *Module) DeleteUser(ctx context.Context, orgID string, id string, deletedBy string) error {
|
||||
|
||||
@@ -2858,11 +2858,11 @@ func (r *ClickHouseReader) GetMetricMetadata(ctx context.Context, orgID valuer.U
|
||||
WHERE metric_name = $1
|
||||
AND unix_milli >= $2
|
||||
AND type = 'Histogram'
|
||||
AND JSONExtractString(labels, 'service_name') = $3
|
||||
AND (JSONExtractString(labels, 'service_name') = $3 OR JSONExtractString(labels, 'service.name') = $4)
|
||||
GROUP BY le
|
||||
ORDER BY le`, signozMetricDBName, signozTSTableNameV41Day)
|
||||
|
||||
rows, err := r.db.Query(ctx, query, metricName, unixMilli, serviceName)
|
||||
rows, err := r.db.Query(ctx, query, metricName, unixMilli, serviceName, serviceName)
|
||||
if err != nil {
|
||||
zap.L().Error("Error while querying histogram buckets", zap.Error(err))
|
||||
return nil, fmt.Errorf("error while querying histogram buckets: %s", err.Error())
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
"multiSelect": false,
|
||||
"name": "Account",
|
||||
"order": 0,
|
||||
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'cloud.account.id') AS cloud.account.id FROM signoz_metrics.distributed_time_series_v4_1day WHERE metric_name = 'aws_ElastiCache_CPUUtilization_max' GROUP BY cloud.account.id",
|
||||
"queryValue": "SELECT DISTINCT JSONExtractString(labels, 'cloud.account.id') AS `cloud.account.id` FROM signoz_metrics.distributed_time_series_v4_1day WHERE metric_name = 'aws_ElastiCache_CPUUtilization_max' GROUP BY `cloud.account.id`",
|
||||
"showALLOption": false,
|
||||
"sort": "DISABLED",
|
||||
"textboxValue": "",
|
||||
|
||||
@@ -4041,7 +4041,7 @@ func (aH *APIHandler) PreviewLogsPipelinesHandler(w http.ResponseWriter, r *http
|
||||
req := logparsingpipeline.PipelinesPreviewRequest{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4050,7 +4050,7 @@ func (aH *APIHandler) PreviewLogsPipelinesHandler(w http.ResponseWriter, r *http
|
||||
)
|
||||
|
||||
if apiErr != nil {
|
||||
RespondError(w, apiErr, nil)
|
||||
render.Error(w, apiErr)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4072,7 +4072,7 @@ func (aH *APIHandler) ListLogsPipelinesHandler(w http.ResponseWriter, r *http.Re
|
||||
|
||||
version, err := parseAgentConfigVersion(r)
|
||||
if err != nil {
|
||||
RespondError(w, model.WrapApiError(err, "Failed to parse agent config version"), nil)
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4086,7 +4086,7 @@ func (aH *APIHandler) ListLogsPipelinesHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
if apierr != nil {
|
||||
RespondError(w, apierr, payload)
|
||||
render.Error(w, apierr)
|
||||
return
|
||||
}
|
||||
aH.Respond(w, payload)
|
||||
@@ -4164,7 +4164,7 @@ func (aH *APIHandler) CreateLogsPipeline(w http.ResponseWriter, r *http.Request)
|
||||
req := pipelinetypes.PostablePipelines{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4186,7 +4186,7 @@ func (aH *APIHandler) CreateLogsPipeline(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
res, err := createPipeline(r.Context(), req.Pipelines)
|
||||
if err != nil {
|
||||
RespondError(w, err, nil)
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,12 @@ import (
|
||||
var (
|
||||
// Templates is a list of all the templates that are supported by the emailing service.
|
||||
// This list should be updated whenever a new template is added.
|
||||
Templates = []TemplateName{TemplateNameInvitationEmail}
|
||||
Templates = []TemplateName{TemplateNameInvitationEmail, TemplateNameUpdateRole}
|
||||
)
|
||||
|
||||
var (
|
||||
TemplateNameInvitationEmail = TemplateName{valuer.NewString("invitation_email")}
|
||||
TemplateNameUpdateRole = TemplateName{valuer.NewString("update_role")}
|
||||
)
|
||||
|
||||
type TemplateName struct{ valuer.String }
|
||||
@@ -25,6 +26,8 @@ func NewTemplateName(name string) (TemplateName, error) {
|
||||
switch name {
|
||||
case TemplateNameInvitationEmail.StringValue():
|
||||
return TemplateNameInvitationEmail, nil
|
||||
case TemplateNameUpdateRole.StringValue():
|
||||
return TemplateNameUpdateRole, nil
|
||||
default:
|
||||
return TemplateName{}, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid template name: %s", name)
|
||||
}
|
||||
|
||||
@@ -106,6 +106,8 @@ func detectPlatform() string {
|
||||
return "render"
|
||||
case os.Getenv("COOLIFY_RESOURCE_UUID") != "":
|
||||
return "coolify"
|
||||
case os.Getenv("RAILWAY_SERVICE_ID") != "":
|
||||
return "railway"
|
||||
}
|
||||
|
||||
// Try to detect cloud provider through metadata endpoints
|
||||
|
||||
23
templates/email/update_role.gotmpl
Normal file
23
templates/email/update_role.gotmpl
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<p>Hi {{.CustomerName}},</p>
|
||||
|
||||
<p>We wanted to inform you that your role in the <strong>SigNoz</strong> project has been updated by <strong>{{.UpdatedByEmail}}</strong>.</p>
|
||||
|
||||
<p>
|
||||
<strong>Previous Role:</strong> {{.OldRole}}<br>
|
||||
<strong>New Role:</strong> {{.NewRole}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Please note that you will need to <strong>log out and log back in</strong> for the changes to take effect.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you were not expecting this change or have any questions, please reach out to your project administrator or contact us at <a href="mailto:support@signoz.io">support@signoz.io</a>.
|
||||
</p>
|
||||
|
||||
<p>Thanks,<br/>The SigNoz Team</p>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user