fix(dashboard): remove popup from slack integrations
This commit is contained in:
62
apps/api/src/controllers/error.html
Normal file
62
apps/api/src/controllers/error.html
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Error - OpenPanel</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
text-align: left;
|
||||||
|
padding: 2rem;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 80px;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="error-container">
|
||||||
|
<img src="https://openpanel.dev/logo.svg" alt="OpenPanel Logo" class="logo">
|
||||||
|
<h1>Oops! Something went wrong</h1>
|
||||||
|
<p>We encountered an error while processing your request. Please try again later or contact support if the problem
|
||||||
|
persists.</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -224,39 +224,3 @@ export async function wsProjectNotifications(
|
|||||||
getRedisSub().off('message', message as any);
|
getRedisSub().off('message', message as any);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function wsIntegrationsSlack(
|
|
||||||
connection: {
|
|
||||||
socket: WebSocket;
|
|
||||||
},
|
|
||||||
req: FastifyRequest<{
|
|
||||||
Querystring: {
|
|
||||||
organizationId?: string;
|
|
||||||
};
|
|
||||||
}>,
|
|
||||||
) {
|
|
||||||
const { organizationId } = req.query;
|
|
||||||
|
|
||||||
if (!organizationId) {
|
|
||||||
connection.socket.send('No organizationId provided');
|
|
||||||
connection.socket.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subscribeToEvent = 'integrations:slack';
|
|
||||||
|
|
||||||
getRedisSub().subscribe(subscribeToEvent);
|
|
||||||
const onMessage = (channel: string, message: string) => {
|
|
||||||
if (channel === subscribeToEvent) {
|
|
||||||
const parsed = getSuperJson<{ organizationId: string }>(message);
|
|
||||||
if (parsed && parsed.organizationId === organizationId) {
|
|
||||||
connection.socket.send(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getRedisSub().on('message', onMessage);
|
|
||||||
connection.socket.on('close', () => {
|
|
||||||
getRedisSub().unsubscribe(subscribeToEvent);
|
|
||||||
getRedisSub().off('message', onMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
import type { WebhookEvent } from '@clerk/fastify';
|
import type { WebhookEvent } from '@clerk/fastify';
|
||||||
import { setSuperJson } from '@openpanel/common';
|
|
||||||
import { AccessLevel, db } from '@openpanel/db';
|
import { AccessLevel, db } from '@openpanel/db';
|
||||||
import {
|
import {
|
||||||
sendSlackNotification,
|
sendSlackNotification,
|
||||||
@@ -167,6 +168,7 @@ const paramsSchema = z.object({
|
|||||||
|
|
||||||
const metadataSchema = z.object({
|
const metadataSchema = z.object({
|
||||||
organizationId: z.string(),
|
organizationId: z.string(),
|
||||||
|
projectId: z.string(),
|
||||||
integrationId: z.string(),
|
integrationId: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -179,7 +181,7 @@ export async function slackWebhook(
|
|||||||
const parsedParams = paramsSchema.safeParse(request.query);
|
const parsedParams = paramsSchema.safeParse(request.query);
|
||||||
|
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
request.log.error('Invalid params', parsedParams);
|
request.log.error(parsedParams.error, 'Invalid params');
|
||||||
return reply.status(400).send({ error: 'Invalid params' });
|
return reply.status(400).send({ error: 'Invalid params' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +194,7 @@ export async function slackWebhook(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!parsedMetadata.success) {
|
if (!parsedMetadata.success) {
|
||||||
request.log.error('Invalid metadata', parsedMetadata.error.errors);
|
request.log.error(parsedMetadata.error, 'Invalid metadata');
|
||||||
return reply.status(400).send({ error: 'Invalid metadata' });
|
return reply.status(400).send({ error: 'Invalid metadata' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,10 +219,8 @@ export async function slackWebhook(
|
|||||||
},
|
},
|
||||||
'Failed to parse slack auth response',
|
'Failed to parse slack auth response',
|
||||||
);
|
);
|
||||||
return reply
|
const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8');
|
||||||
.status(400)
|
return reply.status(500).header('Content-Type', 'text/html').send(html);
|
||||||
.header('Content-Type', 'text/html')
|
|
||||||
.send('<h1>Failed to exchange code for token</h1>');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a notification first to confirm the connection
|
// Send a notification first to confirm the connection
|
||||||
@@ -230,10 +230,12 @@ export async function slackWebhook(
|
|||||||
'👋 Hello. You have successfully connected OpenPanel.dev to your Slack workspace.',
|
'👋 Hello. You have successfully connected OpenPanel.dev to your Slack workspace.',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { projectId, organizationId, integrationId } = parsedMetadata.data;
|
||||||
|
|
||||||
await db.integration.update({
|
await db.integration.update({
|
||||||
where: {
|
where: {
|
||||||
id: parsedMetadata.data.integrationId,
|
id: integrationId,
|
||||||
organizationId: parsedMetadata.data.organizationId,
|
organizationId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
config: {
|
config: {
|
||||||
@@ -243,22 +245,12 @@ export async function slackWebhook(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
getRedisPub().publish(
|
return reply.redirect(
|
||||||
'integrations:slack',
|
`${process.env.NEXT_PUBLIC_DASHBOARD_URL}/${organizationId}/${projectId}/settings/integrations?tab=installed`,
|
||||||
setSuperJson({
|
|
||||||
organizationId: parsedMetadata.data.organizationId,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return reply
|
|
||||||
.status(200)
|
|
||||||
.header('Content-Type', 'text/html')
|
|
||||||
.send('<h1>Slack integration added. You can close this window now.</h1>');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
request.log.error(err);
|
request.log.error(err);
|
||||||
return reply
|
const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8');
|
||||||
.status(500)
|
return reply.status(500).header('Content-Type', 'text/html').send(html);
|
||||||
.header('Content-Type', 'text/html')
|
|
||||||
.send('<h1>Failed to exchange code for token</h1>');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,11 +32,6 @@ const liveRouter: FastifyPluginCallback = (fastify, opts, done) => {
|
|||||||
{ websocket: true },
|
{ websocket: true },
|
||||||
controller.wsProjectNotifications,
|
controller.wsProjectNotifications,
|
||||||
);
|
);
|
||||||
fastify.get(
|
|
||||||
'/integrations/slack',
|
|
||||||
{ websocket: true },
|
|
||||||
controller.wsIntegrationsSlack,
|
|
||||||
);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,49 +19,20 @@ export function SlackIntegrationForm({
|
|||||||
defaultValues?: RouterOutputs['integration']['get'];
|
defaultValues?: RouterOutputs['integration']['get'];
|
||||||
onSuccess: () => void;
|
onSuccess: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { organizationId } = useAppParams();
|
const { organizationId, projectId } = useAppParams();
|
||||||
useWS(`/live/integrations/slack?organizationId=${organizationId}`, (res) => {
|
|
||||||
// @ts-expect-error
|
|
||||||
console.log('3. slack integration done', window.slackPopup);
|
|
||||||
// @ts-expect-error
|
|
||||||
if (window.slackPopup && typeof window.slackPopup.close === 'function') {
|
|
||||||
console.log('4. close popup');
|
|
||||||
// @ts-expect-error
|
|
||||||
window.slackPopup.close();
|
|
||||||
}
|
|
||||||
onSuccess();
|
|
||||||
});
|
|
||||||
const form = useForm<IForm>({
|
const form = useForm<IForm>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: defaultValues?.id,
|
id: defaultValues?.id,
|
||||||
organizationId,
|
organizationId,
|
||||||
|
projectId,
|
||||||
name: defaultValues?.name ?? '',
|
name: defaultValues?.name ?? '',
|
||||||
},
|
},
|
||||||
resolver: zodResolver(zCreateSlackIntegration),
|
resolver: zodResolver(zCreateSlackIntegration),
|
||||||
});
|
});
|
||||||
const mutation = api.integration.createOrUpdateSlack.useMutation({
|
const mutation = api.integration.createOrUpdateSlack.useMutation({
|
||||||
async onSuccess(res) {
|
async onSuccess(res) {
|
||||||
console.log('1. onSuccess', res);
|
window.location.href = res.slackInstallUrl;
|
||||||
|
|
||||||
const url = res.slackInstallUrl;
|
|
||||||
const width = 600;
|
|
||||||
const height = 800;
|
|
||||||
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
||||||
const top = window.screenY + (window.outerHeight - height) / 2.5;
|
|
||||||
console.log('2. open popup');
|
|
||||||
// @ts-expect-error
|
|
||||||
window.slackPopup = window.open(
|
|
||||||
url,
|
|
||||||
'',
|
|
||||||
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${top}, left=${left}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// The popup might have been blocked, so we redirect the user to the URL instead
|
|
||||||
//
|
|
||||||
// @ts-expect-error
|
|
||||||
if (!window.slackPopup) {
|
|
||||||
window.location.href = url;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onError() {
|
onError() {
|
||||||
toast.error('Failed to create integration');
|
toast.error('Failed to create integration');
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ export const slackInstaller = new InstallProvider({
|
|||||||
export const getSlackInstallUrl = ({
|
export const getSlackInstallUrl = ({
|
||||||
integrationId,
|
integrationId,
|
||||||
organizationId,
|
organizationId,
|
||||||
}: { integrationId: string; organizationId: string }) => {
|
projectId,
|
||||||
|
}: { integrationId: string; organizationId: string; projectId: string }) => {
|
||||||
return slackInstaller.generateInstallUrl({
|
return slackInstaller.generateInstallUrl({
|
||||||
scopes: [
|
scopes: [
|
||||||
'incoming-webhook',
|
'incoming-webhook',
|
||||||
@@ -27,7 +28,7 @@ export const getSlackInstallUrl = ({
|
|||||||
'team:read',
|
'team:read',
|
||||||
],
|
],
|
||||||
redirectUri: SLACK_OAUTH_REDIRECT_URL,
|
redirectUri: SLACK_OAUTH_REDIRECT_URL,
|
||||||
metadata: JSON.stringify({ integrationId, organizationId }),
|
metadata: JSON.stringify({ integrationId, organizationId, projectId }),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
slackInstallUrl: await getSlackInstallUrl({
|
slackInstallUrl: await getSlackInstallUrl({
|
||||||
integrationId: res.id,
|
integrationId: res.id,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
|
projectId: input.projectId,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -84,6 +85,7 @@ export const integrationRouter = createTRPCRouter({
|
|||||||
slackInstallUrl: await getSlackInstallUrl({
|
slackInstallUrl: await getSlackInstallUrl({
|
||||||
integrationId: res.id,
|
integrationId: res.id,
|
||||||
organizationId: input.organizationId,
|
organizationId: input.organizationId,
|
||||||
|
projectId: input.projectId,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -214,6 +214,7 @@ const zCreateIntegration = z.object({
|
|||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().min(1),
|
name: z.string().min(1),
|
||||||
organizationId: z.string().min(1),
|
organizationId: z.string().min(1),
|
||||||
|
projectId: z.string().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const zCreateSlackIntegration = zCreateIntegration;
|
export const zCreateSlackIntegration = zCreateIntegration;
|
||||||
|
|||||||
Reference in New Issue
Block a user