Compare commits

...

2 Commits

Author SHA1 Message Date
Nikhil Soni
d2d2ee01d4 fix(apm): use dynamic stepInterval for API monitoring time range on endpoints page
Replace hardcoded stepInterval: 300 (5 min) with the computed step value in
all four onViewAPIMonitoringPopupClick calls in External.tsx. Also fix the
endTime formula in util.ts — endTime should be `timestamp` (bucket end), not
`timestamp + stepInterval`, so the destination time range covers exactly the
clicked bar bucket [bucket_start, bucket_end].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 16:58:43 +05:30
Vikrant Gupta
3ffb5bd43b feat(web): add support web settings (#11444)
* feat(web): add support web settings in index.html

* feat(web): remove settings from global config

* feat(web): fix openapi schemas

* feat(web): fix formatting issues

* feat(web): fix formatting issues

* feat(web): remove frontend script changes

* feat(web): remove the redundant test

* feat(web): update defaults
2026-05-25 09:24:12 +00:00
9 changed files with 97 additions and 14 deletions

View File

@@ -60,6 +60,14 @@ web:
index: index.html
# The directory containing the static build files.
directory: /etc/signoz/web
# Settings exposed to the web.
settings:
posthog:
# Whether to enable PostHog in web.
enabled: true
appcues:
# Whether to enable Appcues in web.
enabled: true
##################### Cache #####################
cache:

View File

@@ -263,7 +263,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address || '',
isError: true,
stepInterval: 300,
stepInterval,
safeNavigate,
})}
/>
@@ -306,7 +306,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
stepInterval,
safeNavigate,
})}
/>
@@ -352,7 +352,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
stepInterval,
safeNavigate,
})}
/>
@@ -395,7 +395,7 @@ function External(): JSX.Element {
timestamp: selectedTimeStamp,
domainName: selectedData?.address,
isError: false,
stepInterval: 300,
stepInterval,
safeNavigate,
})}
/>

View File

@@ -151,7 +151,7 @@ export function onViewAPIMonitoringPopupClick({
safeNavigate,
}: OnViewAPIMonitoringPopupClickProps): (e?: React.MouseEvent) => void {
return (e?: React.MouseEvent): void => {
const endTime = timestamp + (stepInterval || 60);
const endTime = timestamp;
const startTime = timestamp - (stepInterval || 60);
const filters = {
items: [

View File

@@ -14,6 +14,24 @@ type Config struct {
// The directory from which to serve the web files.
Directory string `mapstructure:"directory"`
// Settings that are exposed to the web.
Settings Settings `mapstructure:"settings"`
}
// Settings that are exposed to the web.
type Settings struct {
Posthog Posthog `mapstructure:"posthog"`
Appcues Appcues `mapstructure:"appcues"`
}
type Posthog struct {
Enabled bool `mapstructure:"enabled"`
}
type Appcues struct {
Enabled bool `mapstructure:"enabled"`
}
func NewConfigFactory() factory.ConfigFactory {
@@ -25,6 +43,14 @@ func newConfig() factory.Config {
Enabled: true,
Index: "index.html",
Directory: "/etc/signoz/web",
Settings: Settings{
Posthog: Posthog{
Enabled: true,
},
Appcues: Appcues{
Enabled: true,
},
},
}
}

View File

@@ -38,6 +38,7 @@ func TestNewWithEnvProvider(t *testing.T) {
Enabled: false,
Index: def.Index,
Directory: def.Directory,
Settings: def.Settings,
}
assert.Equal(t, expected, actual)

View File

@@ -2,6 +2,8 @@ package routerweb
import (
"context"
"encoding/json"
"html/template"
"net/http"
"os"
"path/filepath"
@@ -42,8 +44,16 @@ func New(ctx context.Context, settings factory.ProviderSettings, config web.Conf
return nil, errors.WrapInvalidInputf(err, errors.CodeInvalidInput, "cannot read %q in web directory", config.Index)
}
settingsJSON, err := json.Marshal(config.Settings)
if err != nil {
return nil, errors.WrapInternalf(err, errors.CodeInternal, "cannot marshal web settings to JSON")
}
logger := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/web/routerweb").Logger()
indexContents := web.NewIndex(ctx, logger, config.Index, raw, web.TemplateData{BaseHref: globalConfig.ExternalPathTrailing()})
indexContents := web.NewIndex(ctx, logger, config.Index, raw, web.TemplateData{
BaseHref: globalConfig.ExternalPathTrailing(),
Settings: template.JS(settingsJSON),
})
return &provider{
config: config,

View File

@@ -2,6 +2,7 @@ package routerweb
import (
"context"
"encoding/json"
"io"
"net"
"net/http"
@@ -19,6 +20,11 @@ import (
"github.com/stretchr/testify/require"
)
func expectedHTML(baseHref string, settings web.Settings) string {
settingsJSON, _ := json.Marshal(settings)
return `<html><head><base href="` + baseHref + `" /></head><body><script>window.signozBootData={settings:` + string(settingsJSON) + `}</script>Welcome to test data!!!</body></html>`
}
func startServer(t *testing.T, config web.Config, globalConfig global.Config) string {
t.Helper()
@@ -54,53 +60,79 @@ func httpGet(t *testing.T, url string) string {
func TestServeTemplatedIndex(t *testing.T) {
t.Parallel()
emptySettings := web.Settings{}
testCases := []struct {
name string
path string
globalConfig global.Config
webConfig web.Config
expected string
}{
{
name: "RootBaseHrefAtRoot",
path: "/",
globalConfig: global.Config{},
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
},
{
name: "RootBaseHrefAtNonExistentPath",
path: "/does-not-exist",
globalConfig: global.Config{},
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
},
{
name: "RootBaseHrefAtDirectory",
path: "/assets",
globalConfig: global.Config{},
expected: `<html><head><base href="/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/", emptySettings),
},
{
name: "SubPathBaseHrefAtRoot",
path: "/",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
},
{
name: "SubPathBaseHrefAtNonExistentPath",
path: "/does-not-exist",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
},
{
name: "SubPathBaseHrefAtDirectory",
path: "/assets",
globalConfig: global.Config{ExternalURL: &url.URL{Scheme: "https", Host: "example.com", Path: "/signoz"}},
expected: `<html><head><base href="/signoz/" /></head><body>Welcome to test data!!!</body></html>`,
webConfig: web.Config{Index: "valid_template.html", Directory: "testdata"},
expected: expectedHTML("/signoz/", emptySettings),
},
{
name: "WithPopulatedSettings",
path: "/",
globalConfig: global.Config{},
webConfig: web.Config{
Index: "valid_template.html",
Directory: "testdata",
Settings: web.Settings{
Posthog: web.Posthog{Enabled: true},
Appcues: web.Appcues{Enabled: true},
},
},
expected: expectedHTML("/", web.Settings{
Posthog: web.Posthog{Enabled: true},
Appcues: web.Appcues{Enabled: true},
}),
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
base := startServer(t, web.Config{Index: "valid_template.html", Directory: "testdata"}, testCase.globalConfig)
base := startServer(t, testCase.webConfig, testCase.globalConfig)
assert.Equal(t, testCase.expected, strings.TrimSuffix(httpGet(t, base+testCase.path), "\n"))
})

View File

@@ -1 +1 @@
<html><head><base href="[[.BaseHref]]" /></head><body>Welcome to test data!!!</body></html>
<html><head><base href="[[.BaseHref]]" /></head><body><script>window.signozBootData={settings:[[.Settings]]}</script>Welcome to test data!!!</body></html>

View File

@@ -11,8 +11,14 @@ import (
// Field names map to the HTML attributes they populate in the template:
// - BaseHref → <base href="[[.BaseHref]]" />
// - Settings → window.signozBootData = { settings: [[.Settings]] }
type TemplateData struct {
BaseHref string
// Settings is the pre-serialized JSON of web.Settings for injection into a
// <script> block. The template.JS type prevents html/template from
// HTML-escaping the value.
Settings template.JS
}
// If the template cannot be parsed or executed, the raw bytes are