fix: iframe resize #224

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-11-10 20:29:02 +01:00
parent bbd30ca6e0
commit 931ae55a1c
8 changed files with 285 additions and 168 deletions

View File

@@ -9,6 +9,7 @@
"deploy": "npx wrangler deploy",
"cf-typegen": "wrangler types",
"build": "pnpm with-env vite build",
"build:embed": "vite build --config vite.embed.config.ts",
"serve": "vite preview",
"test": "vitest run",
"format": "biome format",
@@ -26,11 +27,13 @@
"@faker-js/faker": "^9.6.0",
"@hookform/resolvers": "^3.3.4",
"@hyperdx/node-opentelemetry": "^0.8.1",
"@iframe-resizer/child": "^5.0.0",
"@iframe-resizer/parent": "^5.0.0",
"@number-flow/react": "0.3.5",
"@openpanel/common": "workspace:^",
"@openpanel/constants": "workspace:^",
"@openpanel/integrations": "workspace:^",
"@openpanel/importer": "workspace:^",
"@openpanel/integrations": "workspace:^",
"@openpanel/json": "workspace:*",
"@openpanel/payments": "workspace:*",
"@openpanel/sdk-info": "workspace:^",

File diff suppressed because one or more lines are too long

View File

@@ -11,6 +11,7 @@
import { createFileRoute } from '@tanstack/react-router'
import { Route as rootRouteImport } from './routes/__root'
import { Route as IframeTestRouteImport } from './routes/iframe-test'
import { Route as StepsRouteImport } from './routes/_steps'
import { Route as PublicRouteImport } from './routes/_public'
import { Route as LoginRouteImport } from './routes/_login'
@@ -94,6 +95,11 @@ const AppOrganizationIdProjectIdProfilesProfileIdRouteImport = createFileRoute(
'/_app/$organizationId/$projectId_/profiles/$profileId',
)()
const IframeTestRoute = IframeTestRouteImport.update({
id: '/iframe-test',
path: '/iframe-test',
getParentRoute: () => rootRouteImport,
} as any)
const StepsRoute = StepsRouteImport.update({
id: '/_steps',
getParentRoute: () => rootRouteImport,
@@ -474,6 +480,7 @@ const AppOrganizationIdProjectIdProfilesProfileIdTabsEventsRoute =
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/iframe-test': typeof IframeTestRoute
'/$organizationId': typeof AppOrganizationIdRouteWithChildren
'/login': typeof LoginLoginRoute
'/reset-password': typeof LoginResetPasswordRoute
@@ -532,6 +539,7 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/iframe-test': typeof IframeTestRoute
'/login': typeof LoginLoginRoute
'/reset-password': typeof LoginResetPasswordRoute
'/onboarding': typeof PublicOnboardingRoute
@@ -587,6 +595,7 @@ export interface FileRoutesById {
'/_login': typeof LoginRouteWithChildren
'/_public': typeof PublicRouteWithChildren
'/_steps': typeof StepsRouteWithChildren
'/iframe-test': typeof IframeTestRoute
'/_app/$organizationId': typeof AppOrganizationIdRouteWithChildren
'/_login/login': typeof LoginLoginRoute
'/_login/reset-password': typeof LoginResetPasswordRoute
@@ -654,6 +663,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/iframe-test'
| '/$organizationId'
| '/login'
| '/reset-password'
@@ -712,6 +722,7 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/iframe-test'
| '/login'
| '/reset-password'
| '/onboarding'
@@ -766,6 +777,7 @@ export interface FileRouteTypes {
| '/_login'
| '/_public'
| '/_steps'
| '/iframe-test'
| '/_app/$organizationId'
| '/_login/login'
| '/_login/reset-password'
@@ -836,6 +848,7 @@ export interface RootRouteChildren {
LoginRoute: typeof LoginRouteWithChildren
PublicRoute: typeof PublicRouteWithChildren
StepsRoute: typeof StepsRouteWithChildren
IframeTestRoute: typeof IframeTestRoute
ApiConfigRoute: typeof ApiConfigRoute
ApiHealthcheckRoute: typeof ApiHealthcheckRoute
ShareOverviewShareIdRoute: typeof ShareOverviewShareIdRoute
@@ -843,6 +856,13 @@ export interface RootRouteChildren {
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/iframe-test': {
id: '/iframe-test'
path: '/iframe-test'
fullPath: '/iframe-test'
preLoaderRoute: typeof IframeTestRouteImport
parentRoute: typeof rootRouteImport
}
'/_steps': {
id: '/_steps'
path: ''
@@ -1694,6 +1714,7 @@ const rootRouteChildren: RootRouteChildren = {
LoginRoute: LoginRouteWithChildren,
PublicRoute: PublicRouteWithChildren,
StepsRoute: StepsRouteWithChildren,
IframeTestRoute: IframeTestRoute,
ApiConfigRoute: ApiConfigRoute,
ApiHealthcheckRoute: ApiHealthcheckRoute,
ShareOverviewShareIdRoute: ShareOverviewShareIdRoute,

View File

@@ -0,0 +1,28 @@
import { ScriptOnce, createFileRoute } from '@tanstack/react-router';
import { useEffect, useRef } from 'react';
export const Route = createFileRoute('/iframe-test')({
component: IframeTestLayout,
});
function IframeTestLayout() {
return (
<div className="w-full h-full center-center p-32">
<div className="border-8 border-border rounded-lg p-4 w-full max-w-5xl">
<iframe
data-openpanel-embed
src="http://localhost:3000/share/overview/zef2XC"
style={{
width: '100%',
height: '100%',
minHeight: '100vh',
}}
scrolling="no"
loading="lazy"
title="OpenPanel Dashboard"
/>
</div>
<script src="/openpanel-embed.js" />
</div>
);
}

View File

@@ -12,14 +12,37 @@ import OverviewTopPages from '@/components/overview/overview-top-pages';
import OverviewTopSources from '@/components/overview/overview-top-sources';
import { useTRPC } from '@/integrations/trpc/react';
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { createFileRoute, notFound, useSearch } from '@tanstack/react-router';
import {
ScriptOnce,
createFileRoute,
notFound,
useSearch,
} from '@tanstack/react-router';
import { EyeClosedIcon, FrownIcon } from 'lucide-react';
import { z } from 'zod';
import '@iframe-resizer/child';
const shareSearchSchema = z.object({
header: z.optional(z.number().or(z.string().or(z.boolean()))),
});
const iframeResizerScript = `
(function() {
if (typeof window !== 'undefined' && window.iFrameResizer) {
window.iFrameResizer.onMessage = function(message) {
if (message && message.type === 'load-custom-styles') {
var css = (message.opts && message.opts.styles) || '';
if (!css) return;
var style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}
};
}
})();
`;
export const Route = createFileRoute('/share/overview/$shareId')({
component: RouteComponent,
validateSearch: shareSearchSchema,
@@ -76,7 +99,8 @@ function RouteComponent() {
header !== '0' && header !== 0 && header !== 'false' && header !== false;
return (
<div>
<div style={{ minHeight: '100vh' }}>
<ScriptOnce>{iframeResizerScript}</ScriptOnce>
{isHeaderVisible && (
<div className="mx-auto max-w-7xl justify-between row gap-4 p-4 pb-0">
<div className="col gap-1">

View File

@@ -0,0 +1,32 @@
import iframeResize from '@iframe-resizer/parent';
(() => {
function initOpenPanelEmbeds() {
iframeResize(
{
license: 'GPLv3', // OpenPanel is AGPL-3.0, compatible with GPL-3.0
checkOrigin: true,
log: true, // Enable logging for testing
onReady(iframe) {
console.log('iframeResizer ready', iframe);
const styles = iframe.getAttribute('data-openpanel-styles');
if (styles) {
console.log('sending message to load custom styles');
console.log('styles', styles);
iframe.iFrameResizer.sendMessage({
type: 'load-custom-styles',
opts: { styles },
});
}
},
},
'iframe[data-openpanel-embed]',
);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initOpenPanelEmbeds);
} else {
initOpenPanelEmbeds();
}
})();

View File

@@ -0,0 +1,17 @@
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
input: './src/scripts/openpanel-embed.ts',
output: {
format: 'iife',
dir: 'public',
entryFileNames: 'openpanel-embed.js',
name: 'OpenPanelEmbed',
},
},
minify: true,
emptyOutDir: false,
},
});