Compare commits

...

4 Commits

Author SHA1 Message Date
konsalex
639d3745d4 fix: nginx config and namespace in bootdata 2025-12-15 15:55:13 +01:00
konsalex
29f1080cff chore: add feature flags for search 2025-12-12 11:06:24 +01:00
konsalex
0ccd091c8a chore: more changes 2025-12-12 11:01:35 +01:00
konsalex
5b6d2e642d wip: add skeleton for cloud shim mt-frontend 2025-12-10 16:03:21 +01:00
6 changed files with 509 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
# Local Cloud Shim
This is a local development shim, that forwards MT-Front-end requests to MT-tilt back-end setup.
This is still very much work in progress, so things will change a lot.
```
┌──────────────────┐
│ │
┌─────────▶ MT Front-end │
│ │ │
┌──────────────────┐ ┌──────────────────┐ │ └──────────────────┘
│ │ │ │ │
│ User request │────────▶ Nginx ├─────┼─┐ ┌──────────────────────┐
│ │ │ │ │ │ │ │
└──────────────────┘ └──────────────────┘ │ └───────▶ Downstream MT-tilt │
│ │ │
│ └──────────────────────┘
│ ┌──────────────────────┐
│ │ │
└─────────▶ Mocked responses │
│ │
└──────────────────────┘
```
## Proxying
Currently, the proxying is done by an Nginx handling the requests in the following way:
1. Most requests are forwarded to the MT-Front-end
2. All the requests that match this pattern `apis|logout|swagger|openapi/v3|login` are forwarded to the MT-tilt back-end
3. Some requests that are breaking the front-end are mocked. For now `/bootdata` and `/api/user/orgs` are mocked. The mock data are stored under the `./mock-data` folder.
## Dev setup
The Tiltfile and the docker compose file, are simplified to avoid spinning up all services, as most are not needed for the shim setup to work.
The rest of the downstream services are handled by `mt-tilt` setup in Enterprise repo.

View File

@@ -0,0 +1,32 @@
# --- Frontend Build (One-time, no watch)
local_resource(
'frontend-assets',
cmd='yarn install && yarn dev',
dir='../../..',
allow_parallel=True,
labels=["local"]
)
# --- Backend Build (One-time, no watch)
local_resource(
'backend-build',
cmd='mkdir -p build && bash ./build-grafana.sh',
dir='..',
allow_parallel=True,
labels=["local"]
)
# --- Docker Compose
docker_compose('./docker-compose.yaml')
dc_resource("proxy",
resource_deps=["frontend-service"],
labels=["services"]
)
dc_resource("frontend-service",
resource_deps=["frontend-assets", "backend-build"],
labels=["services"],
trigger_mode = TRIGGER_MODE_MANUAL
)

View File

@@ -0,0 +1,47 @@
name: grafana-fs-dev-shim
services:
proxy:
image: grafana-proxy
build:
context: ../
dockerfile: proxy.dockerfile
volumes:
- ../../../public/build:/cdn/public/build
- ../../../public/app/plugins:/cdn/public/app/plugins
- ../../../public/fonts:/cdn/public/fonts
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- ./mock-data:/etc/nginx/mock-data:ro
ports:
- "12345:80" # Gateway
- "3010:81" # CDN
depends_on:
- frontend-service
labels:
- "alloy.logs=true"
frontend-service:
image: grafana-fs-dev
build:
context: ../../..
dockerfile: devenv/frontend-service/grafana-fs-dev.dockerfile
entrypoint: ["bin/grafana", "server", "target"]
volumes:
- ../configs/frontend-service.local.ini:/grafana/conf/custom.ini
# Port 3000 is only accessible via the proxy, not directly exposed
labels:
- "alloy.logs=true"
environment:
OTEL_BSP_SCHEDULE_DELAY: 500
GF_DATABASE_SKIP_MIGRATIONS: true
GF_DATABASE_ENSURE_DEFAULT_ORG_AND_USER: false
GF_DEFAULT_APP_MODE: development
GF_DEFAULT_TARGET: frontend-server
GF_SECURITY_CONTENT_SECURITY_POLICY: false
GF_FEATURE_TOGGLES_ENABLE: enableNativeHTTPHistogram
GF_SERVER_CDN_URL: http://localhost:3010
GF_SERVER_ROUTER_LOGGING: true
GF_LOG_LEVEL: info
OTEL_SERVICE_NAME: frontend-service
GF_TRACING_OPENTELEMETRY_OTLP_ADDRESS: "alloy:4317"
GF_TRACING_OPENTELEMETRY_OTLP_PROPAGATION: jaeger,w3c

View File

@@ -0,0 +1,218 @@
{
"user": {
"isSignedIn": true,
"id": 0,
"uid": "",
"login": "",
"email": "",
"name": "",
"theme": "dark",
"lightTheme": false,
"orgCount": 1,
"orgId": 1,
"orgName": "Main Org.",
"orgRole": "Viewer",
"isGrafanaAdmin": false,
"gravatarUrl": "",
"timezone": "browser",
"weekStart": "browser",
"locale": "en-US",
"regionalFormat": "",
"language": "en-US",
"helpFlags1": 0,
"hasEditPermissionInFolders": false,
"authenticatedBy": "",
"permissions": {
"datasources:explore": true,
"datasources:query": true,
"datasources:read": true,
"datasources:create": true,
"datasources:write": true,
"datasources:delete": true
},
"analytics": {
"identifier": ""
}
},
"settings": {
"namespace": "stacks-11",
"defaultDatasource": "-- Grafana --",
"datasources": {},
"minRefreshInterval": "5s",
"panels": {},
"apps": {},
"appUrl": "http://localhost:12345/",
"appSubUrl": "",
"allowOrgCreate": false,
"authProxyEnabled": false,
"ldapEnabled": false,
"jwtHeaderName": "",
"jwtUrlLogin": false,
"liveEnabled": true,
"autoAssignOrg": true,
"verifyEmailEnabled": false,
"sigV4AuthEnabled": false,
"azureAuthEnabled": false,
"rbacEnabled": true,
"exploreEnabled": true,
"helpEnabled": true,
"profileEnabled": true,
"newsFeedEnabled": true,
"queryHistoryEnabled": true,
"googleAnalyticsId": "",
"googleAnalytics4Id": "",
"rudderstackWriteKey": "",
"rudderstackDataPlaneUrl": "",
"feedbackLinksEnabled": true,
"disableLoginForm": false,
"disableUserSignUp": true,
"loginHint": "",
"passwordHint": "",
"viewersCanEdit": false,
"disableSanitizeHtml": false,
"auth": {
"disableLogin": false
},
"buildInfo": {
"hideVersion": false,
"version": "12.4.0-dev",
"commit": "dev",
"commitShort": "dev",
"buildstamp": 0,
"edition": "Open Source",
"latestVersion": "",
"hasUpdate": false,
"env": "development"
},
"licenseInfo": {
"expiry": 0,
"stateInfo": "",
"licenseUrl": "",
"edition": "Open Source"
},
"featureToggles": {
"unifiedStorageSearch": true,
"unifiedStorageSearchUI": true
},
"anonymousEnabled": false,
"rendererAvailable": false,
"http2Enabled": false,
"pluginCatalogURL": "https://grafana.com/grafana/plugins/",
"pluginAdminEnabled": true,
"pluginAdminExternalManageEnabled": true,
"pluginCatalogHiddenPlugins": [],
"expressionsEnabled": true,
"awsAllowedAuthProviders": ["keys"],
"awsAssumeRoleEnabled": false,
"azure": {
"cloud": "AzureCloud"
},
"caching": {
"enabled": false
},
"unifiedAlertingEnabled": true,
"unifiedAlerting": {
"minInterval": "10s"
},
"oauth": {},
"samlEnabled": false,
"samlName": "",
"dateFormats": {
"fullDate": "YYYY-MM-DD HH:mm:ss",
"useBrowserLocale": false,
"interval": {
"second": "HH:mm:ss",
"minute": "HH:mm",
"hour": "MM/DD HH:mm",
"day": "MM/DD",
"month": "YYYY-MM",
"year": "YYYY"
},
"defaultTimezone": "browser",
"defaultWeekStart": "browser"
},
"licensing": {
"usageBilling": {
"enabled": false
}
}
},
"navTree": [
{
"id": "home",
"text": "Home",
"icon": "home-alt",
"url": "/",
"sortWeight": -4000
},
{
"id": "dashboards/browse",
"text": "Dashboards",
"subTitle": "Create and manage dashboards to visualize your data",
"icon": "apps",
"url": "/dashboards",
"sortWeight": -3700,
"children": [
{
"id": "dashboards/playlists",
"text": "Playlists",
"subTitle": "Groups of dashboards that are displayed in a sequence",
"icon": "presentation-play",
"url": "/playlists"
},
{
"id": "dashboards/snapshots",
"text": "Snapshots",
"subTitle": "Interactive, publically available, parsing of data",
"icon": "camera",
"url": "/dashboard/snapshots"
},
{
"id": "dashboards/library-panels",
"text": "Library panels",
"subTitle": "Reusable panels that can be added to multiple dashboards",
"icon": "library-panel",
"url": "/library-panels"
},
{
"id": "dashboards/public",
"text": "Public dashboards",
"subTitle": "Manage public dashboards",
"url": "/public-dashboards"
},
{
"id": "dashboards/new",
"text": "New dashboard",
"icon": "plus",
"url": "/dashboard/new",
"hideFromTabs": true,
"isCreateAction": true
},
{
"id": "dashboards/folder/new",
"text": "New folder",
"icon": "plus",
"url": "/dashboards/folder/new",
"hideFromTabs": true,
"isCreateAction": true
},
{
"id": "dashboards/import",
"text": "Import dashboard",
"icon": "plus",
"url": "/dashboard/import",
"hideFromTabs": true,
"isCreateAction": true
}
]
},
{
"id": "explore",
"text": "Explore",
"subTitle": "Explore your data",
"icon": "compass",
"url": "/explore",
"sortWeight": -3600
}
]
}

View File

@@ -0,0 +1,7 @@
[
{
"orgId": 1,
"name": "Main Org.",
"role": "Admin"
}
]

View File

@@ -0,0 +1,166 @@
otel_exporter {
endpoint alloy:4317;
}
otel_service_name "proxy";
###
# Instance
###
upstream backend {
server host.docker.internal:3001;
}
upstream frontend {
server frontend-service:3000;
}
map "$request_method:$cookie_fs_unavailable" $reject_login {
default 0;
"POST:1" 1;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name _;
otel_trace on;
otel_trace_context propagate;
otel_span_name "$request_method Request";
# SSL settings for proxying to HTTPS backend
proxy_ssl_verify off;
proxy_ssl_server_name on;
location ~ ^/-/down/?$ {
add_header Set-Cookie "fs_unavailable=true; Max-Age=60; Path=/; HttpOnly" always;
return 302 $scheme://$http_host/;
}
location ~ ^/-/down/(?<age>\d+)/?$ {
add_header Set-Cookie "fs_unavailable=true; Max-Age=$age; Path=/; HttpOnly" always;
return 302 $scheme://$http_host/;
}
location ~ ^/-/up/?$ {
return 302 $scheme://$http_host/-/down/0;
}
# Mock bootdata endpoints (both /bootdata and /api/frontend/settings)
location = /bootdata {
default_type application/json;
add_header Access-Control-Allow-Origin "*" always;
alias /etc/nginx/mock-data/bootdata.json;
}
# Mock user organizations endpoint
location = /api/user/orgs {
default_type application/json;
add_header Access-Control-Allow-Origin "*" always;
alias /etc/nginx/mock-data/user-orgs.json;
}
# Specialcase POST /login to backend, GET to frontend
location = /login {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
if ($reject_login) {
add_header Content-Type application/json always;
return 503 '{"code":"Loading", "message": "Soon!"}';
}
if ($request_method = POST) {
proxy_pass https://backend;
break;
}
if ($request_method = GET) {
proxy_pass http://frontend;
break;
}
return 405;
}
# API calls go to the backend
# location ~ ^/(api|apis|avatar|bootdata|render|logout|public/plugins|public/openapi3|public/api-merged|goto|swagger|openapi/v3) {
# For test only allow logout, apis, and login
location ~ ^/(apis|logout|swagger|openapi/v3|login) {
# Add debug headers to the response
add_header Nginx-Trace-Id $otel_trace_id always;
add_header Nginx-Route "backend" always;
if ($cookie_fs_unavailable) {
add_header Content-Type application/json always;
return 503 '{"code":"Loading", "message": "Soon!"}';
}
proxy_pass https://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
# Everything else to the frontend
location / {
# Add debug headers to the response
add_header Nginx-Trace-Id $otel_trace_id always;
add_header Nginx-Route "frontend" always;
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
###
# CDN
###
map $request $loggable {
default 1;
"~^\x16\x03" 0;
}
server {
listen 81;
server_name localhost;
root /cdn;
# Enable directory listing
autoindex on;
autoindex_exact_size off;
autoindex_format html;
autoindex_localtime on;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
add_header Access-Control-Allow-Origin "*" always;
add_header Access-Control-Allow-Headers "X-Grafana-Device-Id" always;
# Suppress access log for malformed HTTPS requests (those starting with a TLS handshake)
access_log /var/log/nginx/access.log main if=$loggable;
# This serves paths like /grafana/12.1.0-88106/public/build/foo
location ~ ^/grafana[^/]*/[^/]+/(.*)$ {
alias /cdn/$1;
}
}