From b00531137f5f060492ade515f58d3f642d4380fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Sat, 13 Jul 2024 19:27:47 +0200 Subject: [PATCH] fix logs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan-Otto Kröpke --- .ci/config/tls/app.yaml | 1 + .editorconfig | 10 ++ docker-compose.yaml | 5 + pkg/plugin/app.go | 2 +- pkg/plugin/internal/chrome/local.go | 4 +- pkg/plugin/internal/chrome/main.go | 3 +- pkg/plugin/internal/chrome/remote.go | 39 +++++++ pkg/plugin/internal/chrome/tab.go | 9 +- pkg/plugin/internal/client/client_test.go | 6 +- pkg/plugin/internal/config/settings.go | 3 + pkg/plugin/resources.go | 22 ++-- pkg/plugin/resources_test.go | 1 - provisioning/plugins/app.yaml | 3 + src/components/AppConfig/AppConfig.tsx | 123 +++++++++++++++++++--- src/components/testIds.ts | 3 + 15 files changed, 202 insertions(+), 32 deletions(-) create mode 100644 .editorconfig create mode 100644 pkg/plugin/internal/chrome/remote.go diff --git a/.ci/config/tls/app.yaml b/.ci/config/tls/app.yaml index e5ba953..36fa8a0 100644 --- a/.ci/config/tls/app.yaml +++ b/.ci/config/tls/app.yaml @@ -18,3 +18,4 @@ apps: logo: '' maxRenderWorkers: 2 persistData: false + remoteChromeAddr: '' diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..be6b706 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true + +[*.tsx] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 28f12c8..7e857fc 100755 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -51,3 +51,8 @@ services: - IGNORE_HTTPS_ERRORS=true ports: - 8081 + chrome: + image: chromedp/headless-shell:latest + shm_size: 2G + ports: + - 9222 \ No newline at end of file diff --git a/pkg/plugin/app.go b/pkg/plugin/app.go index 53d0d66..dc75901 100755 --- a/pkg/plugin/app.go +++ b/pkg/plugin/app.go @@ -55,7 +55,7 @@ func NewDashboardReporterApp(ctx context.Context, settings backend.AppInstanceSe return nil, fmt.Errorf("error in http client options: %w", err) } - if opts.TLS != nil { + if opts.TLS == nil { opts.TLS = &httpclient.TLSOptions{} } diff --git a/pkg/plugin/internal/chrome/local.go b/pkg/plugin/internal/chrome/local.go index d787d69..06dc621 100644 --- a/pkg/plugin/internal/chrome/local.go +++ b/pkg/plugin/internal/chrome/local.go @@ -5,6 +5,7 @@ import ( "github.com/chromedp/chromedp" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/mahendrapaipuri/grafana-dashboard-reporter-app/pkg/plugin/internal/config" "golang.org/x/net/context" ) @@ -56,7 +57,6 @@ func NewLocalBrowserInstance(ctx context.Context, logger log.Logger, insecureSki // start a browser (and an empty tab) so we can add more tabs to the browser chromeLogger := logger.With("subsystem", "chromium") browserCtx, browserCtxCancel := chromedp.NewContext(allocCtx, - chromedp.WithDebugf(chromeLogger.Debug), chromedp.WithErrorf(chromeLogger.Error), chromedp.WithLogf(chromeLogger.Debug), ) @@ -72,7 +72,7 @@ func NewLocalBrowserInstance(ctx context.Context, logger log.Logger, insecureSki }, nil } -func (i *LocalInstance) NewTab(_ context.Context, _ log.Logger) *Tab { +func (i *LocalInstance) NewTab(ctx context.Context, logger log.Logger, _ config.Config) *Tab { // start a browser (and an empty tab) so we can add more tabs to the browser ctx, cancel := chromedp.NewContext(i.browserCtx) diff --git a/pkg/plugin/internal/chrome/main.go b/pkg/plugin/internal/chrome/main.go index a24b574..1a170b9 100644 --- a/pkg/plugin/internal/chrome/main.go +++ b/pkg/plugin/internal/chrome/main.go @@ -7,6 +7,7 @@ import ( "github.com/chromedp/cdproto/page" "github.com/chromedp/chromedp" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/mahendrapaipuri/grafana-dashboard-reporter-app/pkg/plugin/internal/config" "golang.org/x/net/context" ) @@ -29,7 +30,7 @@ type PDFOptions struct { } type Instance interface { - NewTab(ctx context.Context, logger log.Logger) *Tab + NewTab(ctx context.Context, logger log.Logger, conf config.Config) *Tab Close() } diff --git a/pkg/plugin/internal/chrome/remote.go b/pkg/plugin/internal/chrome/remote.go new file mode 100644 index 0000000..78d10df --- /dev/null +++ b/pkg/plugin/internal/chrome/remote.go @@ -0,0 +1,39 @@ +package chrome + +import ( + "github.com/chromedp/chromedp" + "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/mahendrapaipuri/grafana-dashboard-reporter-app/pkg/plugin/internal/config" + "golang.org/x/net/context" +) + +type RemoteInstance struct { + browserCtx context.Context + + browserCtxCancel context.CancelFunc + allocCtxCancel context.CancelFunc +} + +// NewRemoteBrowserInstance creates a new remote browser instance +func NewRemoteBrowserInstance(_ context.Context, _ log.Logger, _ bool) (*RemoteInstance, error) { + // we don't need to do anything here. We are connecting to a remote browser if needed. + return &RemoteInstance{}, nil +} + +func (i *RemoteInstance) NewTab(ctx context.Context, logger log.Logger, conf config.Config) *Tab { + allocCtx, allocCtxCancel := chromedp.NewRemoteAllocator(ctx, conf.RemoteChromeAddr) + + chromeLogger := logger.With("subsystem", "chromium") + browserCtx, browserCtxCancel := chromedp.NewContext(allocCtx, + chromedp.WithErrorf(chromeLogger.Error), + chromedp.WithLogf(chromeLogger.Debug), + ) + + return &Tab{ + parentCtxCancel: allocCtxCancel, + ctx: browserCtx, + cancel: browserCtxCancel, + } +} + +func (i *RemoteInstance) Close() {} diff --git a/pkg/plugin/internal/chrome/tab.go b/pkg/plugin/internal/chrome/tab.go index eb6b3e7..b73ac44 100644 --- a/pkg/plugin/internal/chrome/tab.go +++ b/pkg/plugin/internal/chrome/tab.go @@ -11,14 +11,19 @@ import ( ) type Tab struct { - ctx context.Context - cancel context.CancelFunc + parentCtxCancel context.CancelFunc + ctx context.Context + cancel context.CancelFunc } func (t *Tab) Close() { if t.cancel != nil { t.cancel() } + + if t.parentCtxCancel != nil { + t.cancel() + } } func (t *Tab) Run(actions chromedp.Action) error { diff --git a/pkg/plugin/internal/client/client_test.go b/pkg/plugin/internal/client/client_test.go index a8722f5..bbff7df 100644 --- a/pkg/plugin/internal/client/client_test.go +++ b/pkg/plugin/internal/client/client_test.go @@ -24,7 +24,7 @@ func init() { getPanelRetrySleepTime = time.Duration(1) * time.Millisecond } -func TestGrafanaClientFetchesDashboard(t *testing.T) { +func TestGrafanaClientFetchesDashboardWithLocalChrome(t *testing.T) { // Skip test if chrome is not available _, err := exec.LookPath("chrome") if err != nil { @@ -44,7 +44,7 @@ func TestGrafanaClientFetchesDashboard(t *testing.T) { defer ts.Close() Convey("When using the Grafana httpClient", func() { - credential := Credential{HeaderName: backend.CookiesHeaderName, HeaderValue: requestCookie} + credential := Credential{HeaderName: backend.CookiesHeaderName, HeaderValue: "cookie"} conf := &config.Config{ Layout: "simple", DashboardMode: "default", @@ -77,7 +77,7 @@ func TestGrafanaClientFetchesPanelPNG(t *testing.T) { })) defer ts.Close() - credential := Credential{HeaderName: backend.OAuthIdentityTokenHeaderName, HeaderValue: "token"} + credential := Credential{HeaderName: backend.OAuthIdentityTokenHeaderName, HeaderValue: "Bearer token"} conf := &config.Config{ Layout: "simple", DashboardMode: "default", diff --git a/pkg/plugin/internal/config/settings.go b/pkg/plugin/internal/config/settings.go index b75a67b..f025ed0 100644 --- a/pkg/plugin/internal/config/settings.go +++ b/pkg/plugin/internal/config/settings.go @@ -9,12 +9,15 @@ import ( // Config contains plugin settings type Config struct { + URL string `json:"url"` + TLSSkipVerify string `json:"tlsSkipVerify"` Orientation string `json:"orientation"` Layout string `json:"layout"` DashboardMode string `json:"dashboardMode"` TimeZone string `json:"timeZone"` EncodedLogo string `json:"logo"` MaxRenderWorkers int `json:"maxRenderWorkers"` + RemoteChromeAddr string `json:"remoteChromeAddr"` IncludePanelIDs []int ExcludePanelIDs []int diff --git a/pkg/plugin/resources.go b/pkg/plugin/resources.go index 87e912f..6531946 100755 --- a/pkg/plugin/resources.go +++ b/pkg/plugin/resources.go @@ -65,15 +65,6 @@ func (a *App) handleReport(w http.ResponseWriter, req *http.Request) { // Get Grafana config from context grafanaConfig := backend.GrafanaConfigFromContext(req.Context()) - - grafanaAppURL, err := grafanaConfig.AppURL() - if err != nil { - ctxLogger.Error("failed to get app URL", "err", err) - http.Error(w, "failed to get app URL", http.StatusInternalServerError) - - return - } - conf, err := config.Load( pluginConfig.AppInstanceSettings.JSONData, pluginConfig.AppInstanceSettings.DecryptedSecureJSONData, @@ -85,6 +76,19 @@ func (a *App) handleReport(w http.ResponseWriter, req *http.Request) { return } + var grafanaAppURL string + if conf.URL != "" { + grafanaAppURL = conf.URL + } else { + grafanaAppURL, err = grafanaConfig.AppURL() + if err != nil { + ctxLogger.Error("failed to get app URL", "err", err) + http.Error(w, "failed to get app URL", http.StatusInternalServerError) + + return + } + } + var credential client.Credential switch { diff --git a/pkg/plugin/resources_test.go b/pkg/plugin/resources_test.go index e33ef77..73a8597 100755 --- a/pkg/plugin/resources_test.go +++ b/pkg/plugin/resources_test.go @@ -37,7 +37,6 @@ func (m mockReport) Title() string { return "title" } func TestReportResource(t *testing.T) { // Set appURL env variable t.Setenv("GF_APP_URL", "http://localhost:3000") - t.Setenv("GF_PATHS_DATA", t.TempDir()) // Initialize app inst, err := NewDashboardReporterApp(context.Background(), backend.AppInstanceSettings{}) diff --git a/provisioning/plugins/app.yaml b/provisioning/plugins/app.yaml index 2a451e6..24f6715 100755 --- a/provisioning/plugins/app.yaml +++ b/provisioning/plugins/app.yaml @@ -103,3 +103,6 @@ apps: # applies globally to all generated reports # maxRenderWorkers: 2 + + # A remote address for a running chrome instance. If empty, a local chrome browser will be executed + remoteChromeAddr: 'ws://chrome:9222/' \ No newline at end of file diff --git a/src/components/AppConfig/AppConfig.tsx b/src/components/AppConfig/AppConfig.tsx index fc3c1bb..88f2875 100755 --- a/src/components/AppConfig/AppConfig.tsx +++ b/src/components/AppConfig/AppConfig.tsx @@ -21,17 +21,26 @@ import { getBackendSrv } from "@grafana/runtime"; import { testIds } from "../testIds"; export type JsonData = { - appUrl?: string; - skipTlsCheck?: boolean; + url?: string; + tlsSkipVerify?: boolean; orientation?: string; layout?: string; dashboardMode?: string; timeZone?: string; logo?: string; maxRenderWorkers?: number; + remoteChromeAddr?: string; }; type State = { + // URL of grafana (override auto-detection) + url: string; + // If url has changed + urlChanged: boolean; + // Skip TLS verification to grafana + tlsSkipVerify: boolean; + // If tlsSkipVerify has changed + tlsSkipVerifyChanged: boolean; // PDF report orientation (portrait or landscape) orientation: string; // If orientation has changed @@ -56,6 +65,10 @@ type State = { maxRenderWorkers: number; // If maxRenderWorkers has changed maxRenderWorkersChanged: boolean; + // Address of an chrome remote instance + remoteChromeAddr: string; + // If remoteChromeAddrChanged has changed + remoteChromeAddrChanged: boolean; // Tells us if the Service Account's token is set. // Set to `true` ONLY if it has already been set and haven't been changed. // (We unfortunately need an auxiliray variable for this, as `secureJsonData` is never exposed to the browser after it is set) @@ -70,6 +83,12 @@ export const AppConfig = ({ plugin }: Props) => { const s = useStyles2(getStyles); const { enabled, pinned, jsonData, secureJsonFields } = plugin.meta; const [state, setState] = useState({ + url: jsonData?.url || "", + urlChanged: false, + tlsSkipVerify: jsonData?.tlsSkipVerify || false, + tlsSkipVerifyChanged: false, + remoteChromeAddr: jsonData?.remoteChromeAddr || 2, + remoteChromeAddrChanged: false, orientation: jsonData?.orientation || "portrait", orientationChanged: false, layout: jsonData?.layout || "simple", @@ -82,14 +101,12 @@ export const AppConfig = ({ plugin }: Props) => { logoChanged: false, maxRenderWorkers: jsonData?.maxRenderWorkers || 2, maxRenderWorkersChanged: false, + remoteChromeAddr: jsonData?.remoteChromeAddr || 2, + remoteChromeAddrChanged: false, saToken: "", isSaTokenSet: Boolean(secureJsonFields?.saToken), }); - // Pass through appUrl and SkipTlsCheck as they cannot be configured via UI - const appUrl = jsonData?.appUrl; - const skipTlsCheck = jsonData?.skipTlsCheck; - const orientationOptions = [ { label: "Portrait", value: "portrait", icon: "gf-portrait" }, { label: "Landscape", value: "landscape", icon: "gf-landscape" }, @@ -105,6 +122,22 @@ export const AppConfig = ({ plugin }: Props) => { { label: "Full", value: "full" }, ]; + const onChangeURL = (value: string) => { + setState({ + ...state, + url: value, + urlChanged: true, + }); + }; + + const onChangeTLSSkipVerify = (event: ChangeEvent) => { + setState({ + ...state, + tlsSkipVerify: event.target.checked,, + tlsSkipVerifyChanged: true, + }); + }; + const onChangeLayout = (value: string) => { setState({ ...state, @@ -153,6 +186,14 @@ export const AppConfig = ({ plugin }: Props) => { }); }; + const onChangeRemoteChromeAddr = (event: ChangeEvent) => { + setState({ + ...state, + remoteChromeAddr: event.target.value, + remoteChromeAddrChanged: true, + }); + }; + const onResetSaToken = () => setState({ ...state, @@ -184,14 +225,15 @@ export const AppConfig = ({ plugin }: Props) => { enabled: true, pinned: true, jsonData: { - appUrl: appUrl, - skipTlsCheck: skipTlsCheck, + url: state.url, + tlsSkipVerify: state.tlsSkipVerify, maxRenderWorkers: state.maxRenderWorkers, orientation: state.orientation, layout: state.layout, dashboardMode: state.dashboardMode, timeZone: state.timeZone, logo: state.logo, + remoteChromeAddr: state.remoteChromeAddr, }, // This cannot be queried later by the frontend. // We don't want to override it in case it was set previously and left untouched now. @@ -220,14 +262,15 @@ export const AppConfig = ({ plugin }: Props) => { enabled: false, pinned: false, jsonData: { - appUrl: appUrl, - skipTlsCheck: skipTlsCheck, + url: state.url, + tlsSkipVerify: state.tlsSkipVerify, maxRenderWorkers: state.maxRenderWorkers, orientation: state.orientation, layout: state.layout, dashboardMode: state.dashboardMode, timeZone: state.timeZone, logo: state.logo, + remoteChromeAddr: state.remoteChromeAddr, }, // This cannot be queried later by the frontend. // We don't want to override it in case it was set previously and left untouched now. @@ -380,6 +423,56 @@ export const AppConfig = ({ plugin }: Props) => { onChange={onChangeMaxWorkers} /> + + {/* Grafana Hostname */} + + + + + {/* Skip TLS verification */} + + + + + {/* Remote Chrome Addr */} + + +
@@ -391,14 +484,15 @@ export const AppConfig = ({ plugin }: Props) => { enabled, pinned, jsonData: { - appUrl: appUrl, - skipTlsCheck: skipTlsCheck, + url: state.url, + tlsSkipVerify: state.tlsSkipVerify, maxRenderWorkers: state.maxRenderWorkers, orientation: state.orientation, layout: state.layout, dashboardMode: state.dashboardMode, timeZone: state.timeZone, logo: state.logo, + remoteChromeAddr: state.remoteChromeAddr, }, // This cannot be queried later by the frontend. // We don't want to override it in case it was set previously and left untouched now. @@ -410,12 +504,15 @@ export const AppConfig = ({ plugin }: Props) => { }) } disabled={Boolean( - !state.layoutChanged && + !state.url && + !state.tlsSkipVerify && + !state.layoutChanged && !state.orientationChanged && !state.dashboardModeChanged && !state.timeZoneChanged && !state.logoChanged && !state.maxRenderWorkersChanged && + !state.remoteChromeAddr && !state.saToken )} > diff --git a/src/components/testIds.ts b/src/components/testIds.ts index a405b5d..c3cc634 100755 --- a/src/components/testIds.ts +++ b/src/components/testIds.ts @@ -1,5 +1,7 @@ export const testIds = { appConfig: { + url: "data-testid ac-url", + tlsSkipVerify: "data-testid ac-tls-skip-verify", saToken: "data-testid ac-sa-token", container: "data-testid ac-container", layout: "data-testid ac-layout", @@ -8,6 +10,7 @@ export const testIds = { tz: "data-testid ac-timezone", logo: "data-testid ac-logo", maxWorkers: "data-testid ac-max-workers", + remoteChromeAddr: "data-testid ac-remote-chrome-addr", submit: "data-testid ac-submit-form", }, Status: {