rename mixan to OPENPANEL!

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-03-11 13:40:46 +01:00
parent 6d4f9010d4
commit e6c0bc2ec8
201 changed files with 1193 additions and 1047 deletions

View File

@@ -1,10 +1,6 @@
<p align="center"> # openpanel
<img src="images/mixan.svg">
</p>
# mixan Openpanel is a simple analytics tool for logging events on web and react-native. My goal is to make a minimal mixpanel copy with the most basic features (for now).
Mixan is a simple analytics tool for logging events on web and react-native. My goal is to make a minimal mixpanel copy with the most basic features (for now).
- Easy to use - Easy to use
- Fully responsive UI - Fully responsive UI
@@ -71,24 +67,24 @@ As of today (~~2023-12-12~~ 2024-01-16) I have more then ~~1.2~~ 2.8 million eve
- [x] Create web sdk - [x] Create web sdk
- [x] Screen view function should take in title, path and parse query string (especially utm tags) - [x] Screen view function should take in title, path and parse query string (especially utm tags)
## @mixan/sdk ## @openpanel/sdk
For pushing events For pushing events
### Install ### Install
- npm: `npm install @mixan/sdk` - npm: `npm install @openpanel/sdk`
- pnpm: `pnpm add @mixan/sdk` - pnpm: `pnpm add @openpanel/sdk`
- yarn: `yarn add @mixan/sdk` - yarn: `yarn add @openpanel/sdk`
### Usage ### Usage
```ts ```ts
import { MixanWeb } from '@mixan/web'; import { OpenpanelWeb } from '@openpanel/web';
// import { MixanNative } from '@mixan/sdk-native'; // import { OpenpanelNative } from '@openpanel/sdk-native';
const mixan = new MixanWeb({ const openpanel = new OpenpanelWeb({
clientId: 'uuid', clientId: 'uuid',
url: 'http://localhost:8080/api/sdk', url: 'http://localhost:8080/api/sdk',
batchInterval: 10000, batchInterval: 10000,
@@ -96,7 +92,7 @@ const mixan = new MixanWeb({
trackIp: true, trackIp: true,
}); });
// const mixan = new MixanNative({ // const openpanel = new OpenpanelNative({
// clientId: 'uuid', // clientId: 'uuid',
// clientSecret: 'uuid', // clientSecret: 'uuid',
// url: 'http://localhost:8080/api/sdk', // url: 'http://localhost:8080/api/sdk',
@@ -108,12 +104,12 @@ const mixan = new MixanWeb({
// Call this before you send any events // Call this before you send any events
// It will create a anonymous profile // It will create a anonymous profile
// This profile will be merged if you call `setUser` in a later stage // This profile will be merged if you call `setUser` in a later stage
mixan.init(); openpanel.init();
// tracks all outgoing links as a `link_out` event // tracks all outgoing links as a `link_out` event
mixan.trackOutgoingLinks(); openpanel.trackOutgoingLinks();
mixan.setUser({ openpanel.setUser({
id: 'id', id: 'id',
first_name: 'John', first_name: 'John',
last_name: 'Doe', last_name: 'Doe',
@@ -122,25 +118,25 @@ mixan.setUser({
}); });
// will upsert 'app_open' on user property and increment it // will upsert 'app_open' on user property and increment it
mixan.increment('app_open'); openpanel.increment('app_open');
// will upsert 'app_open' on user property and increment it by 10 // will upsert 'app_open' on user property and increment it by 10
mixan.increment('app_open', 10); openpanel.increment('app_open', 10);
// will upsert 'app_open' on user property and decrement it by 2 // will upsert 'app_open' on user property and decrement it by 2
mixan.decrement('app_open', 2); openpanel.decrement('app_open', 2);
// send a sign_in event // send a sign_in event
mixan.event('sign_in'); openpanel.event('sign_in');
// send a sign_in event with properties // send a sign_in event with properties
mixan.event('sign_in', { openpanel.event('sign_in', {
provider: 'gmail', provider: 'gmail',
}); });
// Screen view for web // Screen view for web
mixan.screenView(); openpanel.screenView();
// Screen view for native // Screen view for native
mixan.screenView('Article', { openpanel.screenView('Article', {
id: '3', id: '3',
title: 'Nice article here', title: 'Nice article here',
}); });
@@ -148,10 +144,10 @@ mixan.screenView('Article', {
// Call this when a user is logged out. // Call this when a user is logged out.
// This will just make sure you do not send // This will just make sure you do not send
// the associated profile id for the next events // the associated profile id for the next events
mixan.clear(); openpanel.clear();
``` ```
## @mixan/dashboard ## @openpanel/dashboard
A nextjs web app. Collects all events and your gui to analyze your data. A nextjs web app. Collects all events and your gui to analyze your data.

View File

@@ -1,5 +1,5 @@
{ {
"name": "@mixan/api", "name": "@openpanel/api",
"version": "0.0.1", "version": "0.0.1",
"scripts": { "scripts": {
"dev": "dotenv -e ../../.env -c -v WATCH=1 tsup", "dev": "dotenv -e ../../.env -c -v WATCH=1 tsup",
@@ -14,10 +14,10 @@
"@fastify/cors": "^9.0.0", "@fastify/cors": "^9.0.0",
"@fastify/websocket": "^8.3.1", "@fastify/websocket": "^8.3.1",
"@logtail/pino": "^0.4.19", "@logtail/pino": "^0.4.19",
"@mixan/common": "workspace:*", "@openpanel/common": "workspace:*",
"@mixan/db": "workspace:*", "@openpanel/db": "workspace:*",
"@mixan/queue": "workspace:*", "@openpanel/queue": "workspace:*",
"@mixan/redis": "workspace:*", "@openpanel/redis": "workspace:*",
"fastify": "^4.25.2", "fastify": "^4.25.2",
"ico-to-png": "^0.2.1", "ico-to-png": "^0.2.1",
"pino": "^8.17.2", "pino": "^8.17.2",
@@ -28,10 +28,10 @@
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@mixan/eslint-config": "workspace:*", "@openpanel/eslint-config": "workspace:*",
"@mixan/prettier-config": "workspace:*", "@openpanel/prettier-config": "workspace:*",
"@mixan/sdk": "workspace:*", "@openpanel/sdk": "workspace:*",
"@mixan/tsconfig": "workspace:*", "@openpanel/tsconfig": "workspace:*",
"@types/ramda": "^0.29.6", "@types/ramda": "^0.29.6",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
@@ -44,8 +44,8 @@
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,
"extends": [ "extends": [
"@mixan/eslint-config/base" "@openpanel/eslint-config/base"
] ]
}, },
"prettier": "@mixan/prettier-config" "prettier": "@openpanel/prettier-config"
} }

View File

@@ -1,44 +1,44 @@
import { omit, prop, uniqBy } from 'ramda'; import { omit, prop, uniqBy } from "ramda";
import { generateProfileId, getTime, toISOString } from '@mixan/common'; import { generateProfileId, getTime, toISOString } from "@openpanel/common";
import type { Event, IServiceCreateEventPayload } from '@mixan/db'; import type { Event, IServiceCreateEventPayload } from "@openpanel/db";
import { import {
createEvent as createClickhouseEvent, createEvent as createClickhouseEvent,
db, db,
formatClickhouseDate, formatClickhouseDate,
getSalts, getSalts,
} from '@mixan/db'; } from "@openpanel/db";
import { parseIp } from '../src/utils/parseIp'; import { parseIp } from "../src/utils/parseIp";
import { parseUserAgent } from '../src/utils/parseUserAgent'; import { parseUserAgent } from "../src/utils/parseUserAgent";
const clean = omit([ const clean = omit([
'ip', "ip",
'os', "os",
'ua', "ua",
'url', "url",
'hash', "hash",
'host', "host",
'path', "path",
'device', "device",
'screen', "screen",
'hostname', "hostname",
'language', "language",
'referrer', "referrer",
'timezone', "timezone",
]); ]);
async function main() { async function main() {
const events = await db.event.findMany({ const events = await db.event.findMany({
where: { where: {
project_id: '4e2798cb-e255-4e9d-960d-c9ad095aabd7', project_id: "4e2798cb-e255-4e9d-960d-c9ad095aabd7",
name: 'screen_view', name: "screen_view",
createdAt: { createdAt: {
gte: new Date('2024-01-01'), gte: new Date("2024-01-01"),
lt: new Date('2024-02-04'), lt: new Date("2024-02-04"),
}, },
}, },
orderBy: { orderBy: {
createdAt: 'asc', createdAt: "asc",
}, },
}); });
@@ -50,7 +50,7 @@ async function main() {
const properties = event.properties as Record<string, any>; const properties = event.properties as Record<string, any>;
if (properties.ua?.includes('bot')) { if (properties.ua?.includes("bot")) {
// console.log('IGNORE', event.id); // console.log('IGNORE', event.id);
continue; continue;
} }
@@ -67,20 +67,20 @@ async function main() {
} }
} }
console.log('Total users', Object.keys(grouped).length); console.log("Total users", Object.keys(grouped).length);
let uidx = -1; let uidx = -1;
for (const profile_id of Object.keys(grouped)) { for (const profile_id of Object.keys(grouped)) {
uidx++; uidx++;
console.log(`User index ${uidx}`); console.log(`User index ${uidx}`);
const events = uniqBy(prop('createdAt'), grouped[profile_id] || []); const events = uniqBy(prop("createdAt"), grouped[profile_id] || []);
if (events) { if (events) {
let lastSessionStart = null; let lastSessionStart = null;
let screenViews = 0; let screenViews = 0;
let totalDuration = 0; let totalDuration = 0;
console.log('new user...'); console.log("new user...");
let eidx = -1; let eidx = -1;
for (const event of events) { for (const event of events) {
eidx++; eidx++;
@@ -93,7 +93,7 @@ async function main() {
const projectId = event.project_id; const projectId = event.project_id;
const path = properties.path!; const path = properties.path!;
const ip = properties.ip!; const ip = properties.ip!;
const origin = 'https://mixan.kiddo.se'; const origin = "https://openpanel.kiddo.se";
const ua = properties.ua!; const ua = properties.ua!;
const uaInfo = parseUserAgent(ua); const uaInfo = parseUserAgent(ua);
const salts = await getSalts(); const salts = await getSalts();
@@ -133,8 +133,8 @@ async function main() {
? nextEvent.createdAt.getTime() - event.createdAt.getTime() ? nextEvent.createdAt.getTime() - event.createdAt.getTime()
: 0, : 0,
path, path,
referrer: properties?.referrer?.host ?? '', // TODO referrer: properties?.referrer?.host ?? "", // TODO
referrerName: properties?.referrer?.host ?? '', // TODO referrerName: properties?.referrer?.host ?? "", // TODO
}; };
if (!prevEventAt) { if (!prevEventAt) {
@@ -173,8 +173,8 @@ async function main() {
async function createEvent(event: IServiceCreateEventPayload) { async function createEvent(event: IServiceCreateEventPayload) {
console.log( console.log(
`Create ${event.name} - ${event.path} - ${formatClickhouseDate( `Create ${event.name} - ${event.path} - ${formatClickhouseDate(
event.createdAt event.createdAt,
)} - ${event.duration / 1000} sec` )} - ${event.duration / 1000} sec`,
); );
await createClickhouseEvent(event); await createClickhouseEvent(event);
} }
@@ -183,7 +183,7 @@ async function createSessionStart(event: IServiceCreateEventPayload) {
const session: IServiceCreateEventPayload = { const session: IServiceCreateEventPayload = {
...event, ...event,
duration: 0, duration: 0,
name: 'session_start', name: "session_start",
createdAt: toISOString(getTime(event.createdAt) - 100), createdAt: toISOString(getTime(event.createdAt) - 100),
}; };
@@ -197,7 +197,7 @@ async function createSessionEnd(
options: { options: {
screenViews: number; screenViews: number;
totalDuration: number; totalDuration: number;
} },
) { ) {
const properties: Record<string, unknown> = {}; const properties: Record<string, unknown> = {};
if (options.screenViews === 1) { if (options.screenViews === 1) {
@@ -213,7 +213,7 @@ async function createSessionEnd(
...sessionStart.properties, ...sessionStart.properties,
}, },
duration: options.totalDuration, duration: options.totalDuration,
name: 'session_end', name: "session_end",
createdAt: toISOString(prevEventAt.getTime() + 10), createdAt: toISOString(prevEventAt.getTime() + 10),
}; };

View File

@@ -1,19 +1,19 @@
import { logger, logInfo, noop } from '@/utils/logger'; import { logger, logInfo, noop } from "@/utils/logger";
import { getClientIp, parseIp } from '@/utils/parseIp'; import { getClientIp, parseIp } from "@/utils/parseIp";
import { getReferrerWithQuery, parseReferrer } from '@/utils/parseReferrer'; import { getReferrerWithQuery, parseReferrer } from "@/utils/parseReferrer";
import { isUserAgentSet, parseUserAgent } from '@/utils/parseUserAgent'; import { isUserAgentSet, parseUserAgent } from "@/utils/parseUserAgent";
import { isSameDomain, parsePath } from '@/utils/url'; import { isSameDomain, parsePath } from "@/utils/url";
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from "fastify";
import { omit } from 'ramda'; import { omit } from "ramda";
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from "uuid";
import { generateDeviceId, getTime, toISOString } from '@mixan/common'; import { generateDeviceId, getTime, toISOString } from "@openpanel/common";
import type { IServiceCreateEventPayload } from '@mixan/db'; import type { IServiceCreateEventPayload } from "@openpanel/db";
import { createEvent, getEvents, getSalts } from '@mixan/db'; import { createEvent, getEvents, getSalts } from "@openpanel/db";
import type { JobsOptions } from '@mixan/queue'; import type { JobsOptions } from "@openpanel/queue";
import { eventsQueue } from '@mixan/queue'; import { eventsQueue } from "@openpanel/queue";
import { findJobByPrefix } from '@mixan/queue/src/utils'; import { findJobByPrefix } from "@openpanel/queue/src/utils";
import type { PostEventPayload } from '@mixan/sdk'; import type { PostEventPayload } from "@openpanel/sdk";
const SESSION_TIMEOUT = 1000 * 60 * 30; const SESSION_TIMEOUT = 1000 * 60 * 30;
const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000; const SESSION_END_TIMEOUT = SESSION_TIMEOUT + 1000;
@@ -55,7 +55,7 @@ export async function postEvent(
request: FastifyRequest<{ request: FastifyRequest<{
Body: PostEventPayload; Body: PostEventPayload;
}>, }>,
reply: FastifyReply reply: FastifyReply,
) { ) {
const contextLogger = createContextLogger(request); const contextLogger = createContextLogger(request);
let deviceId: string | null = null; let deviceId: string | null = null;
@@ -65,23 +65,23 @@ export async function postEvent(
// replace thing is just for older sdks when we didn't have `__` // replace thing is just for older sdks when we didn't have `__`
// remove when kiddokitchen app (24.09.02) is not used anymore // remove when kiddokitchen app (24.09.02) is not used anymore
return ( return (
((properties[name] || properties[name.replace('__', '')]) as ((properties[name] || properties[name.replace("__", "")]) as
| string | string
| null | null
| undefined) ?? undefined | undefined) ?? undefined
); );
}; };
const profileId = body.profileId ?? ''; const profileId = body.profileId ?? "";
const createdAt = new Date(body.timestamp); const createdAt = new Date(body.timestamp);
const url = getProperty('__path'); const url = getProperty("__path");
const { path, hash, query } = parsePath(url); const { path, hash, query } = parsePath(url);
const referrer = isSameDomain(getProperty('__referrer'), url) const referrer = isSameDomain(getProperty("__referrer"), url)
? null ? null
: parseReferrer(getProperty('__referrer')); : parseReferrer(getProperty("__referrer"));
const utmReferrer = getReferrerWithQuery(query); const utmReferrer = getReferrerWithQuery(query);
const ip = getClientIp(request)!; const ip = getClientIp(request)!;
const origin = request.headers.origin!; const origin = request.headers.origin!;
const ua = request.headers['user-agent']!; const ua = request.headers["user-agent"]!;
const uaInfo = parseUserAgent(ua); const uaInfo = parseUserAgent(ua);
const salts = await getSalts(); const salts = await getSalts();
const currentDeviceId = generateDeviceId({ const currentDeviceId = generateDeviceId({
@@ -101,66 +101,66 @@ export async function postEvent(
if (isServerEvent) { if (isServerEvent) {
const [event] = await withTiming( const [event] = await withTiming(
'Get last event (server-event)', "Get last event (server-event)",
getEvents( getEvents(
`SELECT * FROM events WHERE name = 'screen_view' AND profile_id = '${profileId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1` `SELECT * FROM events WHERE name = 'screen_view' AND profile_id = '${profileId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`,
) ),
); );
eventsQueue.add('event', { eventsQueue.add("event", {
type: 'createEvent', type: "createEvent",
payload: { payload: {
name: body.name, name: body.name,
deviceId: event?.deviceId || '', deviceId: event?.deviceId || "",
sessionId: event?.sessionId || '', sessionId: event?.sessionId || "",
profileId, profileId,
projectId, projectId,
properties: Object.assign( properties: Object.assign(
{}, {},
omit(['__path', '__referrer'], properties), omit(["__path", "__referrer"], properties),
{ {
hash, hash,
query, query,
} },
), ),
createdAt, createdAt,
country: event?.country ?? '', country: event?.country ?? "",
city: event?.city ?? '', city: event?.city ?? "",
region: event?.region ?? '', region: event?.region ?? "",
continent: event?.continent ?? '', continent: event?.continent ?? "",
os: event?.os ?? '', os: event?.os ?? "",
osVersion: event?.osVersion ?? '', osVersion: event?.osVersion ?? "",
browser: event?.browser ?? '', browser: event?.browser ?? "",
browserVersion: event?.browserVersion ?? '', browserVersion: event?.browserVersion ?? "",
device: event?.device ?? '', device: event?.device ?? "",
brand: event?.brand ?? '', brand: event?.brand ?? "",
model: event?.model ?? '', model: event?.model ?? "",
duration: 0, duration: 0,
path: event?.path ?? '', path: event?.path ?? "",
referrer: event?.referrer ?? '', referrer: event?.referrer ?? "",
referrerName: event?.referrerName ?? '', referrerName: event?.referrerName ?? "",
referrerType: event?.referrerType ?? '', referrerType: event?.referrerType ?? "",
profile: undefined, profile: undefined,
meta: undefined, meta: undefined,
}, },
}); });
return reply.status(200).send(''); return reply.status(200).send("");
} }
const [geo, sessionEndJobCurrentDeviceId, sessionEndJobPreviousDeviceId] = const [geo, sessionEndJobCurrentDeviceId, sessionEndJobPreviousDeviceId] =
await withTiming( await withTiming(
'Get geo and jobs from queue', "Get geo and jobs from queue",
Promise.all([ Promise.all([
parseIp(ip), parseIp(ip),
findJobByPrefix( findJobByPrefix(
eventsQueue, eventsQueue,
`sessionEnd:${projectId}:${currentDeviceId}:` `sessionEnd:${projectId}:${currentDeviceId}:`,
), ),
findJobByPrefix( findJobByPrefix(
eventsQueue, eventsQueue,
`sessionEnd:${projectId}:${previousDeviceId}:` `sessionEnd:${projectId}:${previousDeviceId}:`,
), ),
]) ]),
); );
const createSessionStart = const createSessionStart =
@@ -178,9 +178,9 @@ export async function postEvent(
deviceId = currentDeviceId; deviceId = currentDeviceId;
// Queue session end // Queue session end
eventsQueue.add( eventsQueue.add(
'event', "event",
{ {
type: 'createSessionEnd', type: "createSessionEnd",
payload: { payload: {
deviceId, deviceId,
}, },
@@ -188,27 +188,27 @@ export async function postEvent(
{ {
delay: SESSION_END_TIMEOUT, delay: SESSION_END_TIMEOUT,
jobId: `sessionEnd:${projectId}:${deviceId}:${Date.now()}`, jobId: `sessionEnd:${projectId}:${deviceId}:${Date.now()}`,
} },
); );
} }
const [[sessionStartEvent], prevEventJob] = await withTiming( const [[sessionStartEvent], prevEventJob] = await withTiming(
'Get session start event', "Get session start event",
Promise.all([ Promise.all([
getEvents( getEvents(
`SELECT * FROM events WHERE name = 'session_start' AND device_id = '${deviceId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1` `SELECT * FROM events WHERE name = 'session_start' AND device_id = '${deviceId}' AND project_id = '${projectId}' ORDER BY created_at DESC LIMIT 1`,
), ),
findJobByPrefix(eventsQueue, `event:${projectId}:${deviceId}:`), findJobByPrefix(eventsQueue, `event:${projectId}:${deviceId}:`),
]) ]),
); );
const payload: Omit<IServiceCreateEventPayload, 'id'> = { const payload: Omit<IServiceCreateEventPayload, "id"> = {
name: body.name, name: body.name,
deviceId, deviceId,
profileId, profileId,
projectId, projectId,
sessionId: createSessionStart ? uuid() : sessionStartEvent?.sessionId ?? '', sessionId: createSessionStart ? uuid() : sessionStartEvent?.sessionId ?? "",
properties: Object.assign({}, omit(['__path', '__referrer'], properties), { properties: Object.assign({}, omit(["__path", "__referrer"], properties), {
hash, hash,
query, query,
}), }),
@@ -227,27 +227,27 @@ export async function postEvent(
duration: 0, duration: 0,
path: path, path: path,
referrer: referrer?.url, referrer: referrer?.url,
referrerName: referrer?.name ?? utmReferrer?.name ?? '', referrerName: referrer?.name ?? utmReferrer?.name ?? "",
referrerType: referrer?.type ?? utmReferrer?.type ?? '', referrerType: referrer?.type ?? utmReferrer?.type ?? "",
profile: undefined, profile: undefined,
meta: undefined, meta: undefined,
}; };
const isDelayed = prevEventJob ? await prevEventJob?.isDelayed() : false; const isDelayed = prevEventJob ? await prevEventJob?.isDelayed() : false;
if (isDelayed && prevEventJob && prevEventJob.data.type === 'createEvent') { if (isDelayed && prevEventJob && prevEventJob.data.type === "createEvent") {
const prevEvent = prevEventJob.data.payload; const prevEvent = prevEventJob.data.payload;
const duration = getTime(payload.createdAt) - getTime(prevEvent.createdAt); const duration = getTime(payload.createdAt) - getTime(prevEvent.createdAt);
contextLogger.add('prevEvent', prevEvent); contextLogger.add("prevEvent", prevEvent);
// Set path from prev screen_view event if current event is not a screen_view // Set path from prev screen_view event if current event is not a screen_view
if (payload.name != 'screen_view') { if (payload.name != "screen_view") {
payload.path = prevEvent.path; payload.path = prevEvent.path;
} }
if (payload.name === 'screen_view') { if (payload.name === "screen_view") {
if (duration < 0) { if (duration < 0) {
contextLogger.send('duration is wrong', { contextLogger.send("duration is wrong", {
payload, payload,
duration, duration,
}); });
@@ -255,21 +255,21 @@ export async function postEvent(
// Skip update duration if it's wrong // Skip update duration if it's wrong
// Seems like request is not in right order // Seems like request is not in right order
await withTiming( await withTiming(
'Update previous job with duration', "Update previous job with duration",
prevEventJob.updateData({ prevEventJob.updateData({
type: 'createEvent', type: "createEvent",
payload: { payload: {
...prevEvent, ...prevEvent,
duration, duration,
}, },
}) }),
); );
} }
await withTiming('Promote previous job', prevEventJob.promote()); await withTiming("Promote previous job", prevEventJob.promote());
} }
} else if (payload.name !== 'screen_view') { } else if (payload.name !== "screen_view") {
contextLogger.send('no previous job', { contextLogger.send("no previous job", {
prevEventJob, prevEventJob,
payload, payload,
}); });
@@ -278,23 +278,23 @@ export async function postEvent(
if (createSessionStart) { if (createSessionStart) {
// We do not need to queue session_start // We do not need to queue session_start
await withTiming( await withTiming(
'Create session start event', "Create session start event",
createEvent({ createEvent({
...payload, ...payload,
name: 'session_start', name: "session_start",
// @ts-expect-error // @ts-expect-error
createdAt: toISOString(getTime(payload.createdAt) - 100), createdAt: toISOString(getTime(payload.createdAt) - 100),
}) }),
); );
} }
const options: JobsOptions = {}; const options: JobsOptions = {};
if (payload.name === 'screen_view') { if (payload.name === "screen_view") {
options.delay = SESSION_TIMEOUT; options.delay = SESSION_TIMEOUT;
options.jobId = `event:${projectId}:${deviceId}:${Date.now()}`; options.jobId = `event:${projectId}:${deviceId}:${Date.now()}`;
} }
contextLogger.send('event is queued', { contextLogger.send("event is queued", {
ip, ip,
origin, origin,
ua, ua,
@@ -312,14 +312,14 @@ export async function postEvent(
// Queue current event // Queue current event
eventsQueue eventsQueue
.add( .add(
'event', "event",
{ {
type: 'createEvent', type: "createEvent",
payload, payload,
}, },
options options,
) )
.catch(noop('Failed to queue event')); .catch(noop("Failed to queue event"));
reply.status(202).send(deviceId); reply.status(202).send(deviceId);
} }

View File

@@ -1,13 +1,13 @@
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from "fastify";
import type * as WebSocket from 'ws'; import type * as WebSocket from "ws";
import { getSafeJson } from '@mixan/common'; import { getSafeJson } from "@openpanel/common";
import type { IServiceCreateEventPayload } from '@mixan/db'; import type { IServiceCreateEventPayload } from "@openpanel/db";
import { getEvents, getLiveVisitors } from '@mixan/db'; import { getEvents, getLiveVisitors } from "@openpanel/db";
import { redis, redisPub, redisSub } from '@mixan/redis'; import { redis, redisPub, redisSub } from "@openpanel/redis";
export function getLiveEventInfo(key: string) { export function getLiveEventInfo(key: string) {
return key.split(':').slice(2) as [string, string]; return key.split(":").slice(2) as [string, string];
} }
export async function test( export async function test(
@@ -16,20 +16,20 @@ export async function test(
projectId: string; projectId: string;
}; };
}>, }>,
reply: FastifyReply reply: FastifyReply,
) { ) {
const [event] = await getEvents( const [event] = await getEvents(
`SELECT * FROM events WHERE project_id = '${req.params.projectId}' AND name = 'screen_view' LIMIT 1` `SELECT * FROM events WHERE project_id = '${req.params.projectId}' AND name = 'screen_view' LIMIT 1`,
); );
if (!event) { if (!event) {
return reply.status(404).send('No event found'); return reply.status(404).send("No event found");
} }
redisPub.publish('event', JSON.stringify(event)); redisPub.publish("event", JSON.stringify(event));
redis.set( redis.set(
`live:event:${event.projectId}:${Math.random() * 1000}`, `live:event:${event.projectId}:${Math.random() * 1000}`,
'', "",
'EX', "EX",
10 10,
); );
reply.status(202).send(event); reply.status(202).send(event);
} }
@@ -42,15 +42,15 @@ export function wsVisitors(
Params: { Params: {
projectId: string; projectId: string;
}; };
}> }>,
) { ) {
const { params } = req; const { params } = req;
redisSub.subscribe('event'); redisSub.subscribe("event");
redisSub.psubscribe('__key*:expired'); redisSub.psubscribe("__key*:expired");
const message = (channel: string, message: string) => { const message = (channel: string, message: string) => {
if (channel === 'event') { if (channel === "event") {
const event = getSafeJson<IServiceCreateEventPayload>(message); const event = getSafeJson<IServiceCreateEventPayload>(message);
if (event?.projectId === params.projectId) { if (event?.projectId === params.projectId) {
getLiveVisitors(params.projectId).then((count) => { getLiveVisitors(params.projectId).then((count) => {
@@ -68,14 +68,14 @@ export function wsVisitors(
} }
}; };
redisSub.on('message', message); redisSub.on("message", message);
redisSub.on('pmessage', pmessage); redisSub.on("pmessage", pmessage);
connection.socket.on('close', () => { connection.socket.on("close", () => {
redisSub.unsubscribe('event'); redisSub.unsubscribe("event");
redisSub.punsubscribe('__key*:expired'); redisSub.punsubscribe("__key*:expired");
redisSub.off('message', message); redisSub.off("message", message);
redisSub.off('pmessage', pmessage); redisSub.off("pmessage", pmessage);
}); });
} }
@@ -87,11 +87,11 @@ export function wsEvents(
Params: { Params: {
projectId: string; projectId: string;
}; };
}> }>,
) { ) {
const { params } = req; const { params } = req;
redisSub.subscribe('event'); redisSub.subscribe("event");
const message = (channel: string, message: string) => { const message = (channel: string, message: string) => {
const event = getSafeJson<IServiceCreateEventPayload>(message); const event = getSafeJson<IServiceCreateEventPayload>(message);
@@ -100,10 +100,10 @@ export function wsEvents(
} }
}; };
redisSub.on('message', message); redisSub.on("message", message);
connection.socket.on('close', () => { connection.socket.on("close", () => {
redisSub.unsubscribe('event'); redisSub.unsubscribe("event");
redisSub.off('message', message); redisSub.off("message", message);
}); });
} }

View File

@@ -1,9 +1,9 @@
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from "fastify";
import icoToPng from 'ico-to-png'; import icoToPng from "ico-to-png";
import sharp from 'sharp'; import sharp from "sharp";
import { createHash } from '@mixan/common'; import { createHash } from "@openpanel/common";
import { redis } from '@mixan/redis'; import { redis } from "@openpanel/redis";
interface GetFaviconParams { interface GetFaviconParams {
url: string; url: string;
@@ -12,9 +12,9 @@ interface GetFaviconParams {
async function getImageBuffer(url: string) { async function getImageBuffer(url: string) {
try { try {
const res = await fetch(url); const res = await fetch(url);
const contentType = res.headers.get('content-type'); const contentType = res.headers.get("content-type");
if (!contentType?.includes('image')) { if (!contentType?.includes("image")) {
return null; return null;
} }
@@ -22,7 +22,7 @@ async function getImageBuffer(url: string) {
return null; return null;
} }
if (contentType === 'image/x-icon' || url.endsWith('.ico')) { if (contentType === "image/x-icon" || url.endsWith(".ico")) {
const arrayBuffer = await res.arrayBuffer(); const arrayBuffer = await res.arrayBuffer();
const buffer = Buffer.from(arrayBuffer); const buffer = Buffer.from(arrayBuffer);
return await icoToPng(buffer, 30); return await icoToPng(buffer, 30);
@@ -30,36 +30,36 @@ async function getImageBuffer(url: string) {
return await sharp(await res.arrayBuffer()) return await sharp(await res.arrayBuffer())
.resize(30, 30, { .resize(30, 30, {
fit: 'cover', fit: "cover",
}) })
.png() .png()
.toBuffer(); .toBuffer();
} catch (e) { } catch (e) {
console.log('Failed to get image from url', url); console.log("Failed to get image from url", url);
console.log(e); console.log(e);
} }
} }
const imageExtensions = ['svg', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'ico']; const imageExtensions = ["svg", "png", "jpg", "jpeg", "gif", "webp", "ico"];
export async function getFavicon( export async function getFavicon(
request: FastifyRequest<{ request: FastifyRequest<{
Querystring: GetFaviconParams; Querystring: GetFaviconParams;
}>, }>,
reply: FastifyReply reply: FastifyReply,
) { ) {
function sendBuffer(buffer: Buffer, cacheKey?: string) { function sendBuffer(buffer: Buffer, cacheKey?: string) {
if (cacheKey) { if (cacheKey) {
redis.set(`favicon:${cacheKey}`, buffer.toString('base64')); redis.set(`favicon:${cacheKey}`, buffer.toString("base64"));
} }
reply.type('image/png'); reply.type("image/png");
console.log('buffer', buffer.byteLength); console.log("buffer", buffer.byteLength);
return reply.send(buffer); return reply.send(buffer);
} }
if (!request.query.url) { if (!request.query.url) {
return reply.status(404).send('Not found'); return reply.status(404).send("Not found");
} }
const url = decodeURIComponent(request.query.url); const url = decodeURIComponent(request.query.url);
@@ -69,7 +69,7 @@ export async function getFavicon(
const cacheKey = createHash(url, 32); const cacheKey = createHash(url, 32);
const cache = await redis.get(`favicon:${cacheKey}`); const cache = await redis.get(`favicon:${cacheKey}`);
if (cache) { if (cache) {
return sendBuffer(Buffer.from(cache, 'base64')); return sendBuffer(Buffer.from(cache, "base64"));
} }
const buffer = await getImageBuffer(url); const buffer = await getImageBuffer(url);
if (buffer && buffer.byteLength > 0) { if (buffer && buffer.byteLength > 0) {
@@ -80,7 +80,7 @@ export async function getFavicon(
const { hostname, origin } = new URL(url); const { hostname, origin } = new URL(url);
const cache = await redis.get(`favicon:${hostname}`); const cache = await redis.get(`favicon:${hostname}`);
if (cache) { if (cache) {
return sendBuffer(Buffer.from(cache, 'base64')); return sendBuffer(Buffer.from(cache, "base64"));
} }
// TRY FAVICON.ICO // TRY FAVICON.ICO
@@ -94,7 +94,7 @@ export async function getFavicon(
function findFavicon(res: string) { function findFavicon(res: string) {
const match = res.match( const match = res.match(
/(\<link(.+?)image\/x-icon(.+?)\>|\<link(.+?)shortcut\sicon(.+?)\>)/ /(\<link(.+?)image\/x-icon(.+?)\>|\<link(.+?)shortcut\sicon(.+?)\>)/,
); );
if (!match) { if (!match) {
return null; return null;
@@ -112,16 +112,16 @@ export async function getFavicon(
} }
} }
return reply.status(404).send('Not found'); return reply.status(404).send("Not found");
} }
export async function clearFavicons( export async function clearFavicons(
request: FastifyRequest, request: FastifyRequest,
reply: FastifyReply reply: FastifyReply,
) { ) {
const keys = await redis.keys('favicon:*'); const keys = await redis.keys("favicon:*");
for (const key of keys) { for (const key of keys) {
await redis.del(key); await redis.del(key);
} }
return reply.status(404).send('OK'); return reply.status(404).send("OK");
} }

View File

@@ -1,21 +1,24 @@
import { getClientIp, parseIp } from '@/utils/parseIp'; import { getClientIp, parseIp } from "@/utils/parseIp";
import { isUserAgentSet, parseUserAgent } from '@/utils/parseUserAgent'; import { isUserAgentSet, parseUserAgent } from "@/utils/parseUserAgent";
import type { FastifyReply, FastifyRequest } from 'fastify'; import type { FastifyReply, FastifyRequest } from "fastify";
import { assocPath, pathOr } from 'ramda'; import { assocPath, pathOr } from "ramda";
import { getProfileById, upsertProfile } from '@mixan/db'; import { getProfileById, upsertProfile } from "@openpanel/db";
import type { IncrementProfilePayload, UpdateProfilePayload } from '@mixan/sdk'; import type {
IncrementProfilePayload,
UpdateProfilePayload,
} from "@openpanel/sdk";
export async function updateProfile( export async function updateProfile(
request: FastifyRequest<{ request: FastifyRequest<{
Body: UpdateProfilePayload; Body: UpdateProfilePayload;
}>, }>,
reply: FastifyReply reply: FastifyReply,
) { ) {
const { profileId, properties, ...rest } = request.body; const { profileId, properties, ...rest } = request.body;
const projectId = request.projectId; const projectId = request.projectId;
const ip = getClientIp(request)!; const ip = getClientIp(request)!;
const ua = request.headers['user-agent']!; const ua = request.headers["user-agent"]!;
const uaInfo = parseUserAgent(ua); const uaInfo = parseUserAgent(ua);
const geo = await parseIp(ip); const geo = await parseIp(ip);
@@ -37,29 +40,29 @@ export async function incrementProfileProperty(
request: FastifyRequest<{ request: FastifyRequest<{
Body: IncrementProfilePayload; Body: IncrementProfilePayload;
}>, }>,
reply: FastifyReply reply: FastifyReply,
) { ) {
const { profileId, property, value } = request.body; const { profileId, property, value } = request.body;
const projectId = request.projectId; const projectId = request.projectId;
const profile = await getProfileById(profileId); const profile = await getProfileById(profileId);
if (!profile) { if (!profile) {
return reply.status(404).send('Not found'); return reply.status(404).send("Not found");
} }
const parsed = parseInt( const parsed = parseInt(
pathOr<string>('0', property.split('.'), profile.properties), pathOr<string>("0", property.split("."), profile.properties),
10 10,
); );
if (isNaN(parsed)) { if (isNaN(parsed)) {
return reply.status(400).send('Not number'); return reply.status(400).send("Not number");
} }
profile.properties = assocPath( profile.properties = assocPath(
property.split('.'), property.split("."),
parsed + value, parsed + value,
profile.properties profile.properties,
); );
await upsertProfile({ await upsertProfile({
@@ -75,29 +78,29 @@ export async function decrementProfileProperty(
request: FastifyRequest<{ request: FastifyRequest<{
Body: IncrementProfilePayload; Body: IncrementProfilePayload;
}>, }>,
reply: FastifyReply reply: FastifyReply,
) { ) {
const { profileId, property, value } = request.body; const { profileId, property, value } = request.body;
const projectId = request.projectId; const projectId = request.projectId;
const profile = await getProfileById(profileId); const profile = await getProfileById(profileId);
if (!profile) { if (!profile) {
return reply.status(404).send('Not found'); return reply.status(404).send("Not found");
} }
const parsed = parseInt( const parsed = parseInt(
pathOr<string>('0', property.split('.'), profile.properties), pathOr<string>("0", property.split("."), profile.properties),
10 10,
); );
if (isNaN(parsed)) { if (isNaN(parsed)) {
return reply.status(400).send('Not number'); return reply.status(400).send("Not number");
} }
profile.properties = assocPath( profile.properties = assocPath(
property.split('.'), property.split("."),
parsed - value, parsed - value,
profile.properties profile.properties,
); );
await upsertProfile({ await upsertProfile({

View File

@@ -1,43 +1,43 @@
import cors from '@fastify/cors'; import cors from "@fastify/cors";
import Fastify from 'fastify'; import Fastify from "fastify";
import { redisPub } from '@mixan/redis'; import { redisPub } from "@openpanel/redis";
import eventRouter from './routes/event.router'; import eventRouter from "./routes/event.router";
import liveRouter from './routes/live.router'; import liveRouter from "./routes/live.router";
import miscRouter from './routes/misc.router'; import miscRouter from "./routes/misc.router";
import profileRouter from './routes/profile.router'; import profileRouter from "./routes/profile.router";
import { logger, logInfo } from './utils/logger'; import { logger, logInfo } from "./utils/logger";
declare module 'fastify' { declare module "fastify" {
interface FastifyRequest { interface FastifyRequest {
projectId: string; projectId: string;
} }
} }
const port = parseInt(process.env.API_PORT || '3000', 10); const port = parseInt(process.env.API_PORT || "3000", 10);
const startServer = async () => { const startServer = async () => {
logInfo('Starting server'); logInfo("Starting server");
try { try {
const fastify = Fastify({ const fastify = Fastify({
logger: logger, logger: logger,
}); });
fastify.register(cors, { fastify.register(cors, {
origin: '*', origin: "*",
}); });
fastify.decorateRequest('projectId', ''); fastify.decorateRequest("projectId", "");
fastify.register(eventRouter, { prefix: '/event' }); fastify.register(eventRouter, { prefix: "/event" });
fastify.register(profileRouter, { prefix: '/profile' }); fastify.register(profileRouter, { prefix: "/profile" });
fastify.register(liveRouter, { prefix: '/live' }); fastify.register(liveRouter, { prefix: "/live" });
fastify.register(miscRouter, { prefix: '/misc' }); fastify.register(miscRouter, { prefix: "/misc" });
fastify.setErrorHandler((error, request, reply) => { fastify.setErrorHandler((error, request, reply) => {
fastify.log.error(error); fastify.log.error(error);
}); });
fastify.get('/', (request, reply) => { fastify.get("/", (request, reply) => {
reply.send({ name: 'openpanel sdk api' }); reply.send({ name: "openpanel sdk api" });
}); });
// fastify.get('/health-check', async (request, reply) => { // fastify.get('/health-check', async (request, reply) => {
// try { // try {
@@ -47,8 +47,8 @@ const startServer = async () => {
// reply.status(500).send() // reply.status(500).send()
// } // }
// }) // })
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === "production") {
for (const signal of ['SIGINT', 'SIGTERM']) { for (const signal of ["SIGINT", "SIGTERM"]) {
process.on(signal, (err) => { process.on(signal, (err) => {
logger.fatal(err, `uncaught exception detected ${signal}`); logger.fatal(err, `uncaught exception detected ${signal}`);
fastify.close().then((err) => { fastify.close().then((err) => {
@@ -59,12 +59,12 @@ const startServer = async () => {
} }
await fastify.listen({ await fastify.listen({
host: process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost', host: process.env.NODE_ENV === "production" ? "0.0.0.0" : "localhost",
port, port,
}); });
// Notify when keys expires // Notify when keys expires
redisPub.config('SET', 'notify-keyspace-events', 'Ex'); redisPub.config("SET", "notify-keyspace-events", "Ex");
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@@ -1,56 +1,56 @@
import { isBot } from '@/bots'; import { isBot } from "@/bots";
import * as controller from '@/controllers/event.controller'; import * as controller from "@/controllers/event.controller";
import { validateSdkRequest } from '@/utils/auth'; import { validateSdkRequest } from "@/utils/auth";
import type { FastifyPluginCallback, FastifyRequest } from 'fastify'; import type { FastifyPluginCallback, FastifyRequest } from "fastify";
import { createBotEvent } from '@mixan/db'; import { createBotEvent } from "@openpanel/db";
import type { PostEventPayload } from '@mixan/sdk'; import type { PostEventPayload } from "@openpanel/sdk";
const eventRouter: FastifyPluginCallback = (fastify, opts, done) => { const eventRouter: FastifyPluginCallback = (fastify, opts, done) => {
fastify.addHook( fastify.addHook(
'preHandler', "preHandler",
async ( async (
req: FastifyRequest<{ req: FastifyRequest<{
Body: PostEventPayload; Body: PostEventPayload;
}>, }>,
reply reply,
) => { ) => {
try { try {
const projectId = await validateSdkRequest(req.headers); const projectId = await validateSdkRequest(req.headers);
req.projectId = projectId; req.projectId = projectId;
const bot = req.headers['user-agent'] const bot = req.headers["user-agent"]
? isBot(req.headers['user-agent']) ? isBot(req.headers["user-agent"])
: null; : null;
if (bot) { if (bot) {
const path = (req.body?.properties?.__path || const path = (req.body?.properties?.__path ||
req.body?.properties?.path) as string | undefined; req.body?.properties?.path) as string | undefined;
req.log.warn({ ...req.headers, bot }, 'Bot detected (event)'); req.log.warn({ ...req.headers, bot }, "Bot detected (event)");
await createBotEvent({ await createBotEvent({
...bot, ...bot,
projectId, projectId,
path: path ?? '', path: path ?? "",
createdAt: new Date(req.body?.timestamp), createdAt: new Date(req.body?.timestamp),
}); });
reply.status(202).send('OK'); reply.status(202).send("OK");
} }
} catch (e) { } catch (e) {
req.log.error(e, 'Failed to create bot event'); req.log.error(e, "Failed to create bot event");
reply.status(401).send(); reply.status(401).send();
return; return;
} }
} },
); );
fastify.route({ fastify.route({
method: 'POST', method: "POST",
url: '/', url: "/",
handler: controller.postEvent, handler: controller.postEvent,
}); });
fastify.route({ fastify.route({
method: 'GET', method: "GET",
url: '/', url: "/",
handler: controller.postEvent, handler: controller.postEvent,
}); });
done(); done();

View File

@@ -1,13 +1,13 @@
import type { RawRequestDefaultExpression } from 'fastify'; import type { RawRequestDefaultExpression } from 'fastify';
import { verifyPassword } from '@mixan/common'; import { verifyPassword } from '@openpanel/common';
import { db } from '@mixan/db'; import { db } from '@openpanel/db';
export async function validateSdkRequest( export async function validateSdkRequest(
headers: RawRequestDefaultExpression['headers'] headers: RawRequestDefaultExpression['headers']
): Promise<string> { ): Promise<string> {
const clientId = headers['mixan-client-id'] as string; const clientId = headers['openpanel-client-id'] as string;
const clientSecret = headers['mixan-client-secret'] as string; const clientSecret = headers['openpanel-client-secret'] as string;
const origin = headers.origin; const origin = headers.origin;
if (!clientId) { if (!clientId) {
throw new Error('Misisng client id'); throw new Error('Misisng client id');

View File

@@ -1,41 +1,41 @@
import { stripTrailingSlash } from '@mixan/common'; import { stripTrailingSlash } from "@openpanel/common";
import referrers from '../referrers'; import referrers from "../referrers";
function getHostname(url: string | undefined) { function getHostname(url: string | undefined) {
if (!url) { if (!url) {
return ''; return "";
} }
try { try {
return new URL(url).hostname; return new URL(url).hostname;
} catch (e) { } catch (e) {
return ''; return "";
} }
} }
export function parseReferrer(url: string | undefined) { export function parseReferrer(url: string | undefined) {
const hostname = getHostname(url); const hostname = getHostname(url);
const match = referrers[hostname] ?? referrers[hostname.replace('www.', '')]; const match = referrers[hostname] ?? referrers[hostname.replace("www.", "")];
return { return {
name: match?.name ?? '', name: match?.name ?? "",
type: match?.type ?? 'unknown', type: match?.type ?? "unknown",
url: stripTrailingSlash(url ?? ''), url: stripTrailingSlash(url ?? ""),
}; };
} }
export function getReferrerWithQuery( export function getReferrerWithQuery(
query: Record<string, string> | undefined query: Record<string, string> | undefined,
) { ) {
if (!query) { if (!query) {
return null; return null;
} }
const source = query.utm_source ?? query.ref ?? query.utm_referrer ?? ''; const source = query.utm_source ?? query.ref ?? query.utm_referrer ?? "";
const match = Object.values(referrers).find( const match = Object.values(referrers).find(
(referrer) => referrer.name.toLowerCase() === source?.toLowerCase() (referrer) => referrer.name.toLowerCase() === source?.toLowerCase(),
); );
if (!match) { if (!match) {
@@ -45,6 +45,6 @@ export function getReferrerWithQuery(
return { return {
name: match.name, name: match.name,
type: match.type, type: match.type,
url: '', url: "",
}; };
} }

View File

@@ -1,5 +1,5 @@
{ {
"extends": "@mixan/tsconfig/base.json", "extends": "@openpanel/tsconfig/base.json",
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {

View File

@@ -4,7 +4,7 @@ import type { Options } from 'tsup';
const options: Options = { const options: Options = {
clean: true, clean: true,
entry: ['src/index.ts'], entry: ['src/index.ts'],
noExternal: [/^@mixan\/.*$/u, /^@\/.*$/u], noExternal: [/^@openpanel\/.*$/u, /^@\/.*$/u],
sourcemap: true, sourcemap: true,
splitting: false, splitting: false,
}; };

View File

@@ -7,7 +7,7 @@ await import('./src/env.mjs');
/** @type {import("next").NextConfig} */ /** @type {import("next").NextConfig} */
const config = { const config = {
reactStrictMode: true, reactStrictMode: true,
transpilePackages: ['@mixan/queue'], transpilePackages: ['@openpanel/queue'],
eslint: { ignoreDuringBuilds: true }, eslint: { ignoreDuringBuilds: true },
typescript: { ignoreBuildErrors: true }, typescript: { ignoreBuildErrors: true },
experimental: { experimental: {

View File

@@ -1,5 +1,5 @@
{ {
"name": "@mixan/dashboard", "name": "@openpanel/dashboard",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
@@ -16,11 +16,11 @@
"@clerk/nextjs": "^4.29.7", "@clerk/nextjs": "^4.29.7",
"@clickhouse/client": "^0.2.9", "@clickhouse/client": "^0.2.9",
"@hookform/resolvers": "^3.3.4", "@hookform/resolvers": "^3.3.4",
"@mixan/common": "workspace:^", "@openpanel/common": "workspace:^",
"@mixan/constants": "workspace:^", "@openpanel/constants": "workspace:^",
"@mixan/db": "workspace:^", "@openpanel/db": "workspace:^",
"@mixan/queue": "workspace:^", "@openpanel/queue": "workspace:^",
"@mixan/validation": "workspace:^", "@openpanel/validation": "workspace:^",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-aspect-ratio": "^1.0.3", "@radix-ui/react-aspect-ratio": "^1.0.3",
@@ -93,9 +93,9 @@
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@mixan/eslint-config": "workspace:*", "@openpanel/eslint-config": "workspace:*",
"@mixan/prettier-config": "workspace:*", "@openpanel/prettier-config": "workspace:*",
"@mixan/tsconfig": "workspace:*", "@openpanel/tsconfig": "workspace:*",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/lodash.debounce": "^4.0.9", "@types/lodash.debounce": "^4.0.9",
"@types/lodash.throttle": "^4.1.9", "@types/lodash.throttle": "^4.1.9",
@@ -121,10 +121,10 @@
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,
"extends": [ "extends": [
"@mixan/eslint-config/base", "@openpanel/eslint-config/base",
"@mixan/eslint-config/react", "@openpanel/eslint-config/react",
"@mixan/eslint-config/nextjs" "@openpanel/eslint-config/nextjs"
] ]
}, },
"prettier": "@mixan/prettier-config" "prettier": "@openpanel/prettier-config"
} }

View File

@@ -13,15 +13,14 @@ import {
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { import {
getDefaultIntervalByDates, getDefaultIntervalByDates,
getDefaultIntervalByRange, getDefaultIntervalByRange,
} from '@mixan/constants'; } from '@openpanel/constants';
import type { getReportsByDashboardId } from '@mixan/db'; import type { getReportsByDashboardId } from '@openpanel/db';
import { ChevronRight, MoreHorizontal, PlusIcon, Trash } from 'lucide-react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { OverviewReportRange } from '../../overview-sticky-header'; import { OverviewReportRange } from '../../overview-sticky-header';

View File

@@ -1,9 +1,8 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getDashboardById, getReportsByDashboardId } from '@openpanel/db';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getDashboardById, getReportsByDashboardId } from '@mixan/db';
import { ListReports } from './list-reports'; import { ListReports } from './list-reports';
interface PageProps { interface PageProps {

View File

@@ -7,13 +7,12 @@ import { Button } from '@/components/ui/button';
import { ToastAction } from '@/components/ui/toast'; import { ToastAction } from '@/components/ui/toast';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals'; import { pushModal } from '@/modals';
import type { IServiceDashboards } from '@openpanel/db';
import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react'; import { LayoutPanelTopIcon, Pencil, PlusIcon, Trash } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { IServiceDashboards } from '@mixan/db';
interface ListDashboardsProps { interface ListDashboardsProps {
dashboards: IServiceDashboards; dashboards: IServiceDashboards;
} }

View File

@@ -1,7 +1,6 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getDashboardsByProjectId } from '@openpanel/db';
import { getDashboardsByProjectId } from '@mixan/db';
import { HeaderDashboards } from './header-dashboards'; import { HeaderDashboards } from './header-dashboards';
import { ListDashboards } from './list-dashboards'; import { ListDashboards } from './list-dashboards';

View File

@@ -1,6 +1,5 @@
import { ChartSwitchShortcut } from '@/components/report/chart'; import { ChartSwitchShortcut } from '@/components/report/chart';
import type { IChartEvent } from '@openpanel/validation';
import type { IChartEvent } from '@mixan/validation';
interface Props { interface Props {
projectId: string; projectId: string;

View File

@@ -13,10 +13,9 @@ import {
useEventQueryFilters, useEventQueryFilters,
useEventQueryNamesFilter, useEventQueryNamesFilter,
} from '@/hooks/useEventQueryFilters'; } from '@/hooks/useEventQueryFilters';
import type { IServiceCreateEventPayload } from '@openpanel/db';
import { round } from 'mathjs'; import { round } from 'mathjs';
import type { IServiceCreateEventPayload } from '@mixan/db';
interface Props { interface Props {
event: IServiceCreateEventPayload; event: IServiceCreateEventPayload;
open: boolean; open: boolean;

View File

@@ -12,11 +12,10 @@ import {
SheetTitle, SheetTitle,
} from '@/components/ui/sheet'; } from '@/components/ui/sheet';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { IServiceCreateEventPayload } from '@openpanel/db';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { IServiceCreateEventPayload } from '@mixan/db';
import { import {
EventIconColors, EventIconColors,
EventIconMapper, EventIconMapper,

View File

@@ -12,6 +12,7 @@ import {
SheetTrigger, SheetTrigger,
} from '@/components/ui/sheet'; } from '@/components/ui/sheet';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { EventMeta } from '@openpanel/db';
import type { VariantProps } from 'class-variance-authority'; import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority'; import { cva } from 'class-variance-authority';
import type { LucideIcon } from 'lucide-react'; import type { LucideIcon } from 'lucide-react';
@@ -19,8 +20,6 @@ import * as Icons from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { EventMeta } from '@mixan/db';
const variants = cva('flex items-center justify-center shrink-0 rounded-full', { const variants = cva('flex items-center justify-center shrink-0 rounded-full', {
variants: { variants: {
size: { size: {

View File

@@ -9,8 +9,7 @@ import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import { useNumber } from '@/hooks/useNumerFormatter'; import { useNumber } from '@/hooks/useNumerFormatter';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { getProfileName } from '@/utils/getters'; import { getProfileName } from '@/utils/getters';
import type { IServiceCreateEventPayload } from '@openpanel/db';
import type { IServiceCreateEventPayload } from '@mixan/db';
import { EventDetails } from './event-details'; import { EventDetails } from './event-details';
import { EventEdit } from './event-edit'; import { EventEdit } from './event-edit';

View File

@@ -8,11 +8,10 @@ import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useCursor } from '@/hooks/useCursor'; import { useCursor } from '@/hooks/useCursor';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import type { IServiceCreateEventPayload } from '@openpanel/db';
import { isSameDay } from 'date-fns'; import { isSameDay } from 'date-fns';
import { GanttChartIcon } from 'lucide-react'; import { GanttChartIcon } from 'lucide-react';
import type { IServiceCreateEventPayload } from '@mixan/db';
import { EventListItem } from './event-list-item'; import { EventListItem } from './event-list-item';
import EventListener from './event-listener'; import EventListener from './event-listener';

View File

@@ -8,14 +8,13 @@ import {
} from '@/components/ui/tooltip'; } from '@/components/ui/tooltip';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { IServiceCreateEventPayload } from '@openpanel/db';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import useWebSocket from 'react-use-websocket'; import useWebSocket from 'react-use-websocket';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { IServiceCreateEventPayload } from '@mixan/db';
const AnimatedNumbers = dynamic(() => import('react-animated-numbers'), { const AnimatedNumbers = dynamic(() => import('react-animated-numbers'), {
ssr: false, ssr: false,
loading: () => <div>0</div>, loading: () => <div>0</div>,

View File

@@ -6,10 +6,9 @@ import {
eventQueryNamesFilter, eventQueryNamesFilter,
} from '@/hooks/useEventQueryFilters'; } from '@/hooks/useEventQueryFilters';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getEventList, getEventsCount } from '@openpanel/db';
import { parseAsInteger } from 'nuqs'; import { parseAsInteger } from 'nuqs';
import { getEventList, getEventsCount } from '@mixan/db';
import { StickyBelowHeader } from '../layout-sticky-below-header'; import { StickyBelowHeader } from '../layout-sticky-below-header';
import { EventChart } from './event-chart'; import { EventChart } from './event-chart';
import { EventList } from './event-list'; import { EventList } from './event-list';

View File

@@ -4,6 +4,7 @@ import { useEffect } from 'react';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { useUser } from '@clerk/nextjs'; import { useUser } from '@clerk/nextjs';
import type { IServiceDashboards } from '@openpanel/db';
import { import {
BuildingIcon, BuildingIcon,
CogIcon, CogIcon,
@@ -20,8 +21,6 @@ import type { LucideProps } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import type { IServiceDashboards } from '@mixan/db';
function LinkWithIcon({ function LinkWithIcon({
href, href,
icon: Icon, icon: Icon,

View File

@@ -2,11 +2,10 @@
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import type { IServiceOrganization } from '@openpanel/db';
import { Building } from 'lucide-react'; import { Building } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import type { IServiceOrganization } from '@mixan/db';
interface LayoutOrganizationSelectorProps { interface LayoutOrganizationSelectorProps {
organizations: IServiceOrganization[]; organizations: IServiceOrganization[];
} }

View File

@@ -2,10 +2,9 @@
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
import { usePathname, useRouter } from 'next/navigation'; import { usePathname, useRouter } from 'next/navigation';
import type { getProjectsByOrganizationSlug } from '@mixan/db';
interface LayoutProjectSelectorProps { interface LayoutProjectSelectorProps {
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>; projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
} }

View File

@@ -4,13 +4,12 @@ import { useEffect, useState } from 'react';
import { Logo } from '@/components/Logo'; import { Logo } from '@/components/Logo';
import { buttonVariants } from '@/components/ui/button'; import { buttonVariants } from '@/components/ui/button';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { IServiceDashboards, IServiceOrganization } from '@openpanel/db';
import { Rotate as Hamburger } from 'hamburger-react'; import { Rotate as Hamburger } from 'hamburger-react';
import { PlusIcon } from 'lucide-react'; import { PlusIcon } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import type { IServiceDashboards, IServiceOrganization } from '@mixan/db';
import LayoutMenu from './layout-menu'; import LayoutMenu from './layout-menu';
import LayoutOrganizationSelector from './layout-organization-selector'; import LayoutOrganizationSelector from './layout-organization-selector';

View File

@@ -2,7 +2,7 @@ import {
getCurrentOrganizations, getCurrentOrganizations,
getDashboardsByOrganization, getDashboardsByOrganization,
getDashboardsByProjectId, getDashboardsByProjectId,
} from '@mixan/db'; } from '@openpanel/db';
import { LayoutSidebar } from './layout-sidebar'; import { LayoutSidebar } from './layout-sidebar';

View File

@@ -1,4 +1,4 @@
import { getProjectsByOrganizationSlug } from '@mixan/db'; import { getProjectsByOrganizationSlug } from '@openpanel/db';
import LayoutProjectSelector from './layout-project-selector'; import LayoutProjectSelector from './layout-project-selector';

View File

@@ -10,8 +10,7 @@ import OverviewTopGeo from '@/components/overview/overview-top-geo';
import OverviewTopPages from '@/components/overview/overview-top-pages'; import OverviewTopPages from '@/components/overview/overview-top-pages';
import OverviewTopSources from '@/components/overview/overview-top-sources'; import OverviewTopSources from '@/components/overview/overview-top-sources';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { db } from '@openpanel/db';
import { db } from '@mixan/db';
import OverviewMetrics from '../../../../components/overview/overview-metrics'; import OverviewMetrics from '../../../../components/overview/overview-metrics';
import { CreateClient } from './create-client'; import { CreateClient } from './create-client';

View File

@@ -12,17 +12,16 @@ import {
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { getProfileName } from '@/utils/getters'; import { getProfileName } from '@/utils/getters';
import { notFound } from 'next/navigation'; import type { GetEventListOptions } from '@openpanel/db';
import { parseAsInteger, parseAsString } from 'nuqs';
import type { GetEventListOptions } from '@mixan/db';
import { import {
getConversionEventNames, getConversionEventNames,
getEventList, getEventList,
getEventsCount, getEventsCount,
getProfileById, getProfileById,
} from '@mixan/db'; } from '@openpanel/db';
import type { IChartEvent, IChartInput } from '@mixan/validation'; import type { IChartEvent, IChartInput } from '@openpanel/validation';
import { notFound } from 'next/navigation';
import { parseAsInteger, parseAsString } from 'nuqs';
import { EventList } from '../../events/event-list'; import { EventList } from '../../events/event-list';
import { StickyBelowHeader } from '../../layout-sticky-below-header'; import { StickyBelowHeader } from '../../layout-sticky-below-header';

View File

@@ -3,10 +3,9 @@ import { OverviewFiltersButtons } from '@/components/overview/filters/overview-f
import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer'; import { OverviewFiltersDrawer } from '@/components/overview/filters/overview-filters-drawer';
import { eventQueryFiltersParser } from '@/hooks/useEventQueryFilters'; import { eventQueryFiltersParser } from '@/hooks/useEventQueryFilters';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getProfileList, getProfileListCount } from '@openpanel/db';
import { parseAsInteger } from 'nuqs'; import { parseAsInteger } from 'nuqs';
import { getProfileList, getProfileListCount } from '@mixan/db';
import { StickyBelowHeader } from '../layout-sticky-below-header'; import { StickyBelowHeader } from '../layout-sticky-below-header';
import { ProfileList } from './profile-list'; import { ProfileList } from './profile-list';

View File

@@ -6,8 +6,7 @@ import { KeyValue, KeyValueSubtle } from '@/components/ui/key-value';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import { getProfileName } from '@/utils/getters'; import { getProfileName } from '@/utils/getters';
import type { IServiceProfile } from '@openpanel/db';
import type { IServiceProfile } from '@mixan/db';
type ProfileListItemProps = IServiceProfile; type ProfileListItemProps = IServiceProfile;

View File

@@ -6,10 +6,9 @@ import { Pagination } from '@/components/Pagination';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useCursor } from '@/hooks/useCursor'; import { useCursor } from '@/hooks/useCursor';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import type { IServiceProfile } from '@openpanel/db';
import { UsersIcon } from 'lucide-react'; import { UsersIcon } from 'lucide-react';
import type { IServiceProfile } from '@mixan/db';
import { ProfileListItem } from './profile-list-item'; import { ProfileListItem } from './profile-list-item';
interface ProfileListProps { interface ProfileListProps {

View File

@@ -1,10 +1,9 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getOrganizationBySlug, getReportById } from '@openpanel/db';
import { Pencil } from 'lucide-react'; import { Pencil } from 'lucide-react';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getOrganizationBySlug, getReportById } from '@mixan/db';
import ReportEditor from '../report-editor'; import ReportEditor from '../report-editor';
interface PageProps { interface PageProps {

View File

@@ -1,10 +1,9 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getOrganizationBySlug } from '@openpanel/db';
import { Pencil } from 'lucide-react'; import { Pencil } from 'lucide-react';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getOrganizationBySlug } from '@mixan/db';
import ReportEditor from './report-editor'; import ReportEditor from './report-editor';
interface PageProps { interface PageProps {

View File

@@ -22,11 +22,10 @@ import { Button } from '@/components/ui/button';
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import type { IServiceReport } from '@openpanel/db';
import { endOfDay, startOfDay } from 'date-fns'; import { endOfDay, startOfDay } from 'date-fns';
import { GanttChartSquareIcon } from 'lucide-react'; import { GanttChartSquareIcon } from 'lucide-react';
import type { IServiceReport } from '@mixan/db';
interface ReportEditorProps { interface ReportEditorProps {
report: IServiceReport | null; report: IServiceReport | null;
} }

View File

@@ -6,10 +6,9 @@ import { DataTable } from '@/components/DataTable';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals'; import { pushModal } from '@/modals';
import type { getClientsByOrganizationId } from '@openpanel/db';
import { PlusIcon } from 'lucide-react'; import { PlusIcon } from 'lucide-react';
import type { getClientsByOrganizationId } from '@mixan/db';
interface ListClientsProps { interface ListClientsProps {
clients: Awaited<ReturnType<typeof getClientsByOrganizationId>>; clients: Awaited<ReturnType<typeof getClientsByOrganizationId>>;
} }

View File

@@ -1,7 +1,6 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getClientsByOrganizationId } from '@openpanel/db';
import { getClientsByOrganizationId } from '@mixan/db';
import ListClients from './list-clients'; import ListClients from './list-clients';

View File

@@ -4,13 +4,12 @@ import { api, handleError } from '@/app/_trpc/client';
import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
import type { getOrganizationBySlug } from '@openpanel/db';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import type { getOrganizationBySlug } from '@mixan/db';
const validator = z.object({ const validator = z.object({
id: z.string().min(2), id: z.string().min(2),
name: z.string().min(2), name: z.string().min(2),

View File

@@ -3,14 +3,13 @@ import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { zInviteUser } from '@openpanel/validation';
import { SendIcon } from 'lucide-react'; import { SendIcon } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { z } from 'zod'; import type { z } from 'zod';
import { zInviteUser } from '@mixan/validation';
type IForm = z.infer<typeof zInviteUser>; type IForm = z.infer<typeof zInviteUser>;
export function InviteUser() { export function InviteUser() {

View File

@@ -9,8 +9,7 @@ import {
TableRow, TableRow,
} from '@/components/ui/table'; } from '@/components/ui/table';
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
import type { IServiceInvites } from '@openpanel/db';
import type { IServiceInvites } from '@mixan/db';
import { InviteUser } from './invite-user'; import { InviteUser } from './invite-user';

View File

@@ -1,9 +1,8 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { clerkClient } from '@clerk/nextjs'; import { clerkClient } from '@clerk/nextjs';
import { getInvites, getOrganizationBySlug } from '@openpanel/db';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getInvites, getOrganizationBySlug } from '@mixan/db';
import EditOrganization from './edit-organization'; import EditOrganization from './edit-organization';
import InvitedUsers from './invited-users'; import InvitedUsers from './invited-users';

View File

@@ -5,13 +5,12 @@ import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Widget, WidgetBody, WidgetHead } from '@/components/Widget'; import { Widget, WidgetBody, WidgetHead } from '@/components/Widget';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import type { getUserById } from '@openpanel/db';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import type { getUserById } from '@mixan/db';
const validator = z.object({ const validator = z.object({
firstName: z.string().min(2), firstName: z.string().min(2),
lastName: z.string().min(2), lastName: z.string().min(2),

View File

@@ -1,8 +1,7 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { auth } from '@clerk/nextjs'; import { auth } from '@clerk/nextjs';
import { getUserById } from '@openpanel/db';
import { getUserById } from '@mixan/db';
import EditProfile from './edit-profile'; import EditProfile from './edit-profile';
import { Logout } from './logout'; import { Logout } from './logout';

View File

@@ -6,10 +6,9 @@ import { columns } from '@/components/projects/table';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { pushModal } from '@/modals'; import { pushModal } from '@/modals';
import type { getProjectsByOrganizationSlug } from '@openpanel/db';
import { PlusIcon } from 'lucide-react'; import { PlusIcon } from 'lucide-react';
import type { getProjectsByOrganizationSlug } from '@mixan/db';
interface ListProjectsProps { interface ListProjectsProps {
projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>; projects: Awaited<ReturnType<typeof getProjectsByOrganizationSlug>>;
} }

View File

@@ -1,7 +1,6 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getProjectsByOrganizationSlug } from '@openpanel/db';
import { getProjectsByOrganizationSlug } from '@mixan/db';
import ListProjects from './list-projects'; import ListProjects from './list-projects';

View File

@@ -5,10 +5,9 @@ import { DataTable } from '@/components/DataTable';
import { columns } from '@/components/references/table'; import { columns } from '@/components/references/table';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { pushModal } from '@/modals'; import { pushModal } from '@/modals';
import type { IServiceReference } from '@openpanel/db';
import { PlusIcon } from 'lucide-react'; import { PlusIcon } from 'lucide-react';
import type { IServiceReference } from '@mixan/db';
interface ListProjectsProps { interface ListProjectsProps {
data: IServiceReference[]; data: IServiceReference[];
} }

View File

@@ -1,7 +1,6 @@
import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout'; import PageLayout from '@/app/(app)/[organizationId]/[projectId]/page-layout';
import { getExists } from '@/server/pageExists'; import { getExists } from '@/server/pageExists';
import { getReferences } from '@openpanel/db';
import { getReferences } from '@mixan/db';
import ListReferences from './list-references'; import ListReferences from './list-references';

View File

@@ -1,11 +1,10 @@
import { LogoSquare } from '@/components/Logo'; import { LogoSquare } from '@/components/Logo';
import { ProjectCard } from '@/components/projects/project-card'; import { ProjectCard } from '@/components/projects/project-card';
import { notFound, redirect } from 'next/navigation';
import { import {
getOrganizationBySlug, getOrganizationBySlug,
getProjectsByOrganizationSlug, getProjectsByOrganizationSlug,
} from '@mixan/db'; } from '@openpanel/db';
import { notFound, redirect } from 'next/navigation';
import { CreateProject } from './create-project'; import { CreateProject } from './create-project';

View File

@@ -1,10 +1,9 @@
// import { CreateOrganization } from '@clerk/nextjs'; // import { CreateOrganization } from '@clerk/nextjs';
import { LogoSquare } from '@/components/Logo'; import { LogoSquare } from '@/components/Logo';
import { getCurrentOrganizations } from '@openpanel/db';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import { getCurrentOrganizations } from '@mixan/db';
import { CreateOrganization } from './create-organization'; import { CreateOrganization } from './create-organization';
export default async function Page() { export default async function Page() {

View File

@@ -11,10 +11,9 @@ import OverviewTopEvents from '@/components/overview/overview-top-events';
import OverviewTopGeo from '@/components/overview/overview-top-geo'; import OverviewTopGeo from '@/components/overview/overview-top-geo';
import OverviewTopPages from '@/components/overview/overview-top-pages'; import OverviewTopPages from '@/components/overview/overview-top-pages';
import OverviewTopSources from '@/components/overview/overview-top-sources'; import OverviewTopSources from '@/components/overview/overview-top-sources';
import { getOrganizationBySlug, getShareOverviewById } from '@openpanel/db';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
import { getOrganizationBySlug, getShareOverviewById } from '@mixan/db';
interface PageProps { interface PageProps {
params: { params: {
id: string; id: string;

View File

@@ -3,12 +3,11 @@
import { api } from '@/app/_trpc/client'; import { api } from '@/app/_trpc/client';
import { pushModal, showConfirm } from '@/modals'; import { pushModal, showConfirm } from '@/modals';
import { clipboard } from '@/utils/clipboard'; import { clipboard } from '@/utils/clipboard';
import type { IServiceClientWithProject } from '@openpanel/db';
import { MoreHorizontal } from 'lucide-react'; import { MoreHorizontal } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { IServiceClientWithProject } from '@mixan/db';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { import {
DropdownMenu, DropdownMenu,

View File

@@ -1,8 +1,7 @@
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import type { IServiceClientWithProject } from '@openpanel/db';
import type { ColumnDef } from '@tanstack/react-table'; import type { ColumnDef } from '@tanstack/react-table';
import type { IServiceClientWithProject } from '@mixan/db';
import { ClientActions } from './ClientActions'; import { ClientActions } from './ClientActions';
export const columns: ColumnDef<IServiceClientWithProject>[] = [ export const columns: ColumnDef<IServiceClientWithProject>[] = [

View File

@@ -1,4 +1,4 @@
import { toDots } from '@mixan/common'; import { toDots } from '@openpanel/common';
import { Table, TableBody, TableCell, TableRow } from '../ui/table'; import { Table, TableBody, TableCell, TableRow } from '../ui/table';

View File

@@ -11,14 +11,13 @@ import {
import { useEventValues } from '@/hooks/useEventValues'; import { useEventValues } from '@/hooks/useEventValues';
import { useProfileProperties } from '@/hooks/useProfileProperties'; import { useProfileProperties } from '@/hooks/useProfileProperties';
import { useProfileValues } from '@/hooks/useProfileValues'; import { useProfileValues } from '@/hooks/useProfileValues';
import { XIcon } from 'lucide-react';
import type { Options as NuqsOptions } from 'nuqs';
import type { import type {
IChartEventFilter, IChartEventFilter,
IChartEventFilterOperator, IChartEventFilterOperator,
IChartEventFilterValue, IChartEventFilterValue,
} from '@mixan/validation'; } from '@openpanel/validation';
import { XIcon } from 'lucide-react';
import type { Options as NuqsOptions } from 'nuqs';
export interface OverviewFiltersDrawerContentProps { export interface OverviewFiltersDrawerContentProps {
projectId: string; projectId: string;

View File

@@ -1,4 +1,4 @@
import { getLiveVisitors } from '@mixan/db'; import { getLiveVisitors } from '@openpanel/db';
import type { LiveCounterProps } from './live-counter'; import type { LiveCounterProps } from './live-counter';
import LiveCounter from './live-counter'; import LiveCounter from './live-counter';

View File

@@ -1,4 +1,4 @@
import { getConversionEventNames } from '@mixan/db'; import { getConversionEventNames } from '@openpanel/db';
import type { OverviewLatestEventsProps } from './overview-latest-events'; import type { OverviewLatestEventsProps } from './overview-latest-events';
import OverviewLatestEvents from './overview-latest-events'; import OverviewLatestEvents from './overview-latest-events';

View File

@@ -3,11 +3,10 @@
import { Fragment } from 'react'; import { Fragment } from 'react';
import { api } from '@/app/_trpc/client'; import { api } from '@/app/_trpc/client';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { IChartInput } from '@openpanel/validation';
import { ChevronsUpDownIcon } from 'lucide-react'; import { ChevronsUpDownIcon } from 'lucide-react';
import AnimateHeight from 'react-animate-height'; import AnimateHeight from 'react-animate-height';
import type { IChartInput } from '@mixan/validation';
import { redisSub } from '../../../../../packages/redis'; import { redisSub } from '../../../../../packages/redis';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import { useOverviewOptions } from './useOverviewOptions'; import { useOverviewOptions } from './useOverviewOptions';

View File

@@ -6,8 +6,7 @@ import { ChartSwitch } from '@/components/report/chart';
import { Widget, WidgetBody } from '@/components/Widget'; import { Widget, WidgetBody } from '@/components/Widget';
import { useEventQueryFilters } from '@/hooks/useEventQueryFilters'; import { useEventQueryFilters } from '@/hooks/useEventQueryFilters';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { IChartInput } from '@openpanel/validation';
import type { IChartInput } from '@mixan/validation';
interface OverviewMetricsProps { interface OverviewMetricsProps {
projectId: string; projectId: string;

View File

@@ -2,12 +2,11 @@
import { api } from '@/app/_trpc/client'; import { api } from '@/app/_trpc/client';
import { pushModal } from '@/modals'; import { pushModal } from '@/modals';
import type { ShareOverview } from '@openpanel/db';
import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react'; import { EyeIcon, Globe2Icon, LockIcon } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import type { ShareOverview } from '@mixan/db';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { import {
DropdownMenu, DropdownMenu,

View File

@@ -1,4 +1,4 @@
import { getConversionEventNames } from '@mixan/db'; import { getConversionEventNames } from '@openpanel/db';
import type { OverviewTopEventsProps } from './overview-top-events'; import type { OverviewTopEventsProps } from './overview-top-events';
import OverviewTopEvents from './overview-top-events'; import OverviewTopEvents from './overview-top-events';

View File

@@ -1,3 +1,9 @@
import {
getDefaultIntervalByDates,
getDefaultIntervalByRange,
timeRanges,
} from '@openpanel/constants';
import { mapKeys } from '@openpanel/validation';
import { import {
parseAsBoolean, parseAsBoolean,
parseAsInteger, parseAsInteger,
@@ -6,13 +12,6 @@ import {
useQueryState, useQueryState,
} from 'nuqs'; } from 'nuqs';
import {
getDefaultIntervalByDates,
getDefaultIntervalByRange,
timeRanges,
} from '@mixan/constants';
import { mapKeys } from '@mixan/validation';
const nuqsOptions = { history: 'push' } as const; const nuqsOptions = { history: 'push' } as const;
export function useOverviewOptions() { export function useOverviewOptions() {

View File

@@ -1,8 +1,7 @@
import { mapKeys } from '@openpanel/validation';
import type { IChartInput } from '@openpanel/validation';
import { parseAsStringEnum, useQueryState } from 'nuqs'; import { parseAsStringEnum, useQueryState } from 'nuqs';
import { mapKeys } from '@mixan/validation';
import type { IChartInput } from '@mixan/validation';
export function useOverviewWidget<T extends string>( export function useOverviewWidget<T extends string>(
key: string, key: string,
widgets: Record< widgets: Record<

View File

@@ -1,12 +1,11 @@
'use client'; 'use client';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { IServiceProfile } from '@openpanel/db';
import { AvatarImage } from '@radix-ui/react-avatar'; import { AvatarImage } from '@radix-ui/react-avatar';
import type { VariantProps } from 'class-variance-authority'; import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority'; import { cva } from 'class-variance-authority';
import type { IServiceProfile } from '@mixan/db';
import { Avatar, AvatarFallback } from '../ui/avatar'; import { Avatar, AvatarFallback } from '../ui/avatar';
interface ProfileAvatarProps interface ProfileAvatarProps

View File

@@ -3,12 +3,11 @@
import { api } from '@/app/_trpc/client'; import { api } from '@/app/_trpc/client';
import { pushModal, showConfirm } from '@/modals'; import { pushModal, showConfirm } from '@/modals';
import { clipboard } from '@/utils/clipboard'; import { clipboard } from '@/utils/clipboard';
import type { IServiceProject } from '@openpanel/db';
import { MoreHorizontal } from 'lucide-react'; import { MoreHorizontal } from 'lucide-react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { IServiceProject } from '@mixan/db';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { import {
DropdownMenu, DropdownMenu,

View File

@@ -1,8 +1,8 @@
import { shortNumber } from '@/hooks/useNumerFormatter'; import { shortNumber } from '@/hooks/useNumerFormatter';
import Link from 'next/link'; import Link from 'next/link';
import type { IServiceProject } from '@mixan/db'; import type { IServiceProject } from '@openpanel/db';
import { chQuery } from '@mixan/db'; import { chQuery } from '@openpanel/db';
import { ChartSSR } from '../chart-ssr'; import { ChartSSR } from '../chart-ssr';

View File

@@ -1,9 +1,8 @@
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { IServiceProject } from '@openpanel/db';
import type { Project as IProject } from '@openpanel/db';
import type { ColumnDef } from '@tanstack/react-table'; import type { ColumnDef } from '@tanstack/react-table';
import { IServiceProject } from '@mixan/db';
import type { Project as IProject } from '@mixan/db';
import { ProjectActions } from './ProjectActions'; import { ProjectActions } from './ProjectActions';
export type Project = IProject; export type Project = IProject;

View File

@@ -1,8 +1,7 @@
import { formatDate, formatDateTime } from '@/utils/date'; import { formatDate, formatDateTime } from '@/utils/date';
import type { IServiceReference } from '@openpanel/db';
import type { ColumnDef } from '@tanstack/react-table'; import type { ColumnDef } from '@tanstack/react-table';
import type { IServiceReference } from '@mixan/db';
export const columns: ColumnDef<IServiceReference>[] = [ export const columns: ColumnDef<IServiceReference>[] = [
{ {
accessorKey: 'title', accessorKey: 'title',

View File

@@ -1,9 +1,8 @@
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import { chartTypes } from '@openpanel/constants';
import { objectToZodEnums } from '@openpanel/validation';
import { LineChartIcon } from 'lucide-react'; import { LineChartIcon } from 'lucide-react';
import { chartTypes } from '@mixan/constants';
import { objectToZodEnums } from '@mixan/validation';
import { Combobox } from '../ui/combobox'; import { Combobox } from '../ui/combobox';
import { changeChartType } from './reportSlice'; import { changeChartType } from './reportSlice';

View File

@@ -1,11 +1,10 @@
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import { ClockIcon } from 'lucide-react';
import { import {
isHourIntervalEnabledByRange, isHourIntervalEnabledByRange,
isMinuteIntervalEnabledByRange, isMinuteIntervalEnabledByRange,
} from '@mixan/constants'; } from '@openpanel/constants';
import type { IInterval } from '@mixan/validation'; import type { IInterval } from '@openpanel/validation';
import { ClockIcon } from 'lucide-react';
import { Combobox } from '../ui/combobox'; import { Combobox } from '../ui/combobox';
import { changeInterval } from './reportSlice'; import { changeInterval } from './reportSlice';

View File

@@ -1,9 +1,8 @@
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import { lineTypes } from '@openpanel/constants';
import { objectToZodEnums } from '@openpanel/validation';
import { Tv2Icon } from 'lucide-react'; import { Tv2Icon } from 'lucide-react';
import { lineTypes } from '@mixan/constants';
import { objectToZodEnums } from '@mixan/validation';
import { Combobox } from '../ui/combobox'; import { Combobox } from '../ui/combobox';
import { changeLineType } from './reportSlice'; import { changeLineType } from './reportSlice';

View File

@@ -9,13 +9,12 @@ import {
import { useBreakpoint } from '@/hooks/useBreakpoint'; import { useBreakpoint } from '@/hooks/useBreakpoint';
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { timeRanges } from '@openpanel/constants';
import type { IChartRange } from '@openpanel/validation';
import { endOfDay, format, startOfDay } from 'date-fns'; import { endOfDay, format, startOfDay } from 'date-fns';
import { CalendarIcon, ChevronsUpDownIcon } from 'lucide-react'; import { CalendarIcon, ChevronsUpDownIcon } from 'lucide-react';
import type { SelectRangeEventHandler } from 'react-day-picker'; import type { SelectRangeEventHandler } from 'react-day-picker';
import { timeRanges } from '@mixan/constants';
import type { IChartRange } from '@mixan/validation';
import type { ExtendedComboboxProps } from '../ui/combobox'; import type { ExtendedComboboxProps } from '../ui/combobox';
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group'; import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group';
import { changeDates, changeEndDate, changeStartDate } from './reportSlice'; import { changeDates, changeEndDate, changeStartDate } from './reportSlice';

View File

@@ -1,8 +1,7 @@
'use client'; 'use client';
import { api } from '@/app/_trpc/client'; import { api } from '@/app/_trpc/client';
import type { IChartInput } from '@openpanel/validation';
import type { IChartInput } from '@mixan/validation';
import { ChartEmpty } from './ChartEmpty'; import { ChartEmpty } from './ChartEmpty';
import { ReportAreaChart } from './ReportAreaChart'; import { ReportAreaChart } from './ReportAreaChart';

View File

@@ -10,8 +10,7 @@ import {
useState, useState,
} from 'react'; } from 'react';
import type { IChartSerie } from '@/server/api/routers/chart'; import type { IChartSerie } from '@/server/api/routers/chart';
import type { IChartInput } from '@openpanel/validation';
import type { IChartInput } from '@mixan/validation';
import { ChartLoading } from './ChartLoading'; import { ChartLoading } from './ChartLoading';
import { MetricCardLoading } from './MetricCard'; import { MetricCardLoading } from './MetricCard';

View File

@@ -4,11 +4,10 @@ import type { IChartData } from '@/app/_trpc/client';
import { ColorSquare } from '@/components/ColorSquare'; import { ColorSquare } from '@/components/ColorSquare';
import { fancyMinutes, useNumber } from '@/hooks/useNumerFormatter'; import { fancyMinutes, useNumber } from '@/hooks/useNumerFormatter';
import { theme } from '@/utils/theme'; import { theme } from '@/utils/theme';
import type { IChartMetric } from '@openpanel/validation';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { Area, AreaChart } from 'recharts'; import { Area, AreaChart } from 'recharts';
import type { IChartMetric } from '@mixan/validation';
import { import {
getDiffIndicator, getDiffIndicator,
PreviousDiffIndicatorText, PreviousDiffIndicatorText,

View File

@@ -5,6 +5,7 @@ import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import { getChartColor } from '@/utils/theme'; import { getChartColor } from '@/utils/theme';
import type { IChartLineType, IInterval } from '@openpanel/validation';
import { import {
Area, Area,
AreaChart, AreaChart,
@@ -14,8 +15,6 @@ import {
YAxis, YAxis,
} from 'recharts'; } from 'recharts';
import type { IChartLineType, IInterval } from '@mixan/validation';
import { getYAxisWidth } from './chart-utils'; import { getYAxisWidth } from './chart-utils';
import { useChartContext } from './ChartProvider'; import { useChartContext } from './ChartProvider';
import { ReportChartTooltip } from './ReportChartTooltip'; import { ReportChartTooltip } from './ReportChartTooltip';

View File

@@ -6,8 +6,7 @@ import { Progress } from '@/components/ui/progress';
import { useNumber } from '@/hooks/useNumerFormatter'; import { useNumber } from '@/hooks/useNumerFormatter';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import { getChartColor } from '@/utils/theme'; import { getChartColor } from '@/utils/theme';
import { NOT_SET_VALUE } from '@openpanel/constants';
import { NOT_SET_VALUE } from '@mixan/constants';
import { PreviousDiffIndicatorText } from '../PreviousDiffIndicator'; import { PreviousDiffIndicatorText } from '../PreviousDiffIndicator';
import { useChartContext } from './ChartProvider'; import { useChartContext } from './ChartProvider';

View File

@@ -5,10 +5,9 @@ import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import { getChartColor, theme } from '@/utils/theme'; import { getChartColor, theme } from '@/utils/theme';
import type { IInterval } from '@openpanel/validation';
import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts'; import { Bar, BarChart, CartesianGrid, Tooltip, XAxis, YAxis } from 'recharts';
import type { IInterval } from '@mixan/validation';
import { getYAxisWidth } from './chart-utils'; import { getYAxisWidth } from './chart-utils';
import { useChartContext } from './ChartProvider'; import { useChartContext } from './ChartProvider';
import { ReportChartTooltip } from './ReportChartTooltip'; import { ReportChartTooltip } from './ReportChartTooltip';

View File

@@ -7,6 +7,8 @@ import { useNumber } from '@/hooks/useNumerFormatter';
import { useRechartDataModel } from '@/hooks/useRechartDataModel'; import { useRechartDataModel } from '@/hooks/useRechartDataModel';
import { useVisibleSeries } from '@/hooks/useVisibleSeries'; import { useVisibleSeries } from '@/hooks/useVisibleSeries';
import { getChartColor } from '@/utils/theme'; import { getChartColor } from '@/utils/theme';
import type { IServiceReference } from '@openpanel/db';
import type { IChartLineType, IInterval } from '@openpanel/validation';
import { import {
CartesianGrid, CartesianGrid,
Line, Line,
@@ -17,9 +19,6 @@ import {
YAxis, YAxis,
} from 'recharts'; } from 'recharts';
import type { IServiceReference } from '@mixan/db';
import type { IChartLineType, IInterval } from '@mixan/validation';
import { getYAxisWidth } from './chart-utils'; import { getYAxisWidth } from './chart-utils';
import { useChartContext } from './ChartProvider'; import { useChartContext } from './ChartProvider';
import { ReportChartTooltip } from './ReportChartTooltip'; import { ReportChartTooltip } from './ReportChartTooltip';

View File

@@ -1,4 +1,5 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { NOT_SET_VALUE } from '@openpanel/constants';
import type { LucideIcon, LucideProps } from 'lucide-react'; import type { LucideIcon, LucideProps } from 'lucide-react';
import { import {
ActivityIcon, ActivityIcon,
@@ -14,8 +15,6 @@ import {
TabletIcon, TabletIcon,
} from 'lucide-react'; } from 'lucide-react';
import { NOT_SET_VALUE } from '@mixan/constants';
interface SerieIconProps extends LucideProps { interface SerieIconProps extends LucideProps {
name: string; name: string;
} }

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import type { IChartInput } from '@mixan/validation'; import type { IChartInput } from '@openpanel/validation';
import { Funnel } from '../funnel'; import { Funnel } from '../funnel';
import { Chart } from './Chart'; import { Chart } from './Chart';

View File

@@ -2,8 +2,7 @@
import type { RouterOutputs } from '@/app/_trpc/client'; import type { RouterOutputs } from '@/app/_trpc/client';
import { api } from '@/app/_trpc/client'; import { api } from '@/app/_trpc/client';
import type { IChartInput } from '@openpanel/validation';
import type { IChartInput } from '@mixan/validation';
import { ChartEmpty } from '../chart/ChartEmpty'; import { ChartEmpty } from '../chart/ChartEmpty';
import { withChartProivder } from '../chart/ChartProvider'; import { withChartProivder } from '../chart/ChartProvider';

View File

@@ -1,15 +1,11 @@
import { start } from 'repl'; import { start } from 'repl';
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import { isSameDay, isSameMonth } from 'date-fns';
import { import {
alphabetIds, alphabetIds,
getDefaultIntervalByDates, getDefaultIntervalByDates,
getDefaultIntervalByRange, getDefaultIntervalByRange,
isHourIntervalEnabledByRange, isHourIntervalEnabledByRange,
isMinuteIntervalEnabledByRange, isMinuteIntervalEnabledByRange,
} from '@mixan/constants'; } from '@openpanel/constants';
import type { import type {
IChartBreakdown, IChartBreakdown,
IChartEvent, IChartEvent,
@@ -18,7 +14,10 @@ import type {
IChartRange, IChartRange,
IChartType, IChartType,
IInterval, IInterval,
} from '@mixan/validation'; } from '@openpanel/validation';
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
import { isSameDay, isSameMonth } from 'date-fns';
type InitialState = IChartInput & { type InitialState = IChartInput & {
dirty: boolean; dirty: boolean;

View File

@@ -3,10 +3,9 @@ import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux'; import { useDispatch } from '@/redux';
import { cn } from '@/utils/cn'; import { cn } from '@/utils/cn';
import type { IChartEvent } from '@openpanel/validation';
import { DatabaseIcon } from 'lucide-react'; import { DatabaseIcon } from 'lucide-react';
import type { IChartEvent } from '@mixan/validation';
import { changeEvent } from '../reportSlice'; import { changeEvent } from '../reportSlice';
interface EventPropertiesComboboxProps { interface EventPropertiesComboboxProps {

View File

@@ -5,10 +5,9 @@ import { ColorSquare } from '@/components/ColorSquare';
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import type { IChartBreakdown } from '@openpanel/validation';
import { SplitIcon } from 'lucide-react'; import { SplitIcon } from 'lucide-react';
import type { IChartBreakdown } from '@mixan/validation';
import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice'; import { addBreakdown, changeBreakdown, removeBreakdown } from '../reportSlice';
import { ReportBreakdownMore } from './ReportBreakdownMore'; import { ReportBreakdownMore } from './ReportBreakdownMore';
import type { ReportEventMoreProps } from './ReportEventMore'; import type { ReportEventMoreProps } from './ReportEventMore';

View File

@@ -9,10 +9,9 @@ import { useAppParams } from '@/hooks/useAppParams';
import { useDebounceFn } from '@/hooks/useDebounceFn'; import { useDebounceFn } from '@/hooks/useDebounceFn';
import { useEventNames } from '@/hooks/useEventNames'; import { useEventNames } from '@/hooks/useEventNames';
import { useDispatch, useSelector } from '@/redux'; import { useDispatch, useSelector } from '@/redux';
import type { IChartEvent } from '@openpanel/validation';
import { GanttChart, GanttChartIcon, Users } from 'lucide-react'; import { GanttChart, GanttChartIcon, Users } from 'lucide-react';
import type { IChartEvent } from '@mixan/validation';
import { import {
addEvent, addEvent,
changeEvent, changeEvent,

View File

@@ -7,15 +7,14 @@ import { RenderDots } from '@/components/ui/RenderDots';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useMappings } from '@/hooks/useMappings'; import { useMappings } from '@/hooks/useMappings';
import { useDispatch } from '@/redux'; import { useDispatch } from '@/redux';
import { SlidersHorizontal, Trash } from 'lucide-react'; import { operators } from '@openpanel/constants';
import { operators } from '@mixan/constants';
import type { import type {
IChartEvent, IChartEvent,
IChartEventFilterOperator, IChartEventFilterOperator,
IChartEventFilterValue, IChartEventFilterValue,
} from '@mixan/validation'; } from '@openpanel/validation';
import { mapKeys } from '@mixan/validation'; import { mapKeys } from '@openpanel/validation';
import { SlidersHorizontal, Trash } from 'lucide-react';
import { changeEvent } from '../../reportSlice'; import { changeEvent } from '../../reportSlice';

View File

@@ -2,10 +2,9 @@ import { api } from '@/app/_trpc/client';
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useDispatch } from '@/redux'; import { useDispatch } from '@/redux';
import type { IChartEvent } from '@openpanel/validation';
import { FilterIcon } from 'lucide-react'; import { FilterIcon } from 'lucide-react';
import type { IChartEvent } from '@mixan/validation';
import { changeEvent } from '../../reportSlice'; import { changeEvent } from '../../reportSlice';
interface FiltersComboboxProps { interface FiltersComboboxProps {

View File

@@ -1,4 +1,4 @@
import type { IChartEvent } from '@mixan/validation'; import type { IChartEvent } from '@openpanel/validation';
import { FilterItem } from './FilterItem'; import { FilterItem } from './FilterItem';

View File

@@ -1,4 +1,4 @@
import type { IInterval } from '@mixan/validation'; import type { IInterval } from '@openpanel/validation';
export function formatDateInterval(interval: IInterval, date: Date): string { export function formatDateInterval(interval: IInterval, date: Date): string {
if (interval === 'hour' || interval === 'minute') { if (interval === 'hour' || interval === 'minute') {

View File

@@ -7,13 +7,12 @@ import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar'; import { Calendar } from '@/components/ui/calendar';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { zCreateReference } from '@openpanel/validation';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import type { z } from 'zod'; import type { z } from 'zod';
import { zCreateReference } from '@mixan/validation';
import { popModal } from '.'; import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container'; import { ModalContent, ModalHeader } from './Modal/Container';

View File

@@ -5,13 +5,12 @@ import { ButtonContainer } from '@/components/ButtonContainer';
import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import type { IServiceClient } from '@openpanel/db';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import type { IServiceClient } from '@mixan/db';
import { popModal } from '.'; import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container'; import { ModalContent, ModalHeader } from './Modal/Container';

View File

@@ -5,13 +5,12 @@ import { ButtonContainer } from '@/components/ButtonContainer';
import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import type { IServiceDashboard } from '@openpanel/db';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import type { IServiceDashboard } from '@mixan/db';
import { popModal } from '.'; import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container'; import { ModalContent, ModalHeader } from './Modal/Container';

View File

@@ -5,13 +5,12 @@ import { ButtonContainer } from '@/components/ButtonContainer';
import { InputWithLabel } from '@/components/forms/InputWithLabel'; import { InputWithLabel } from '@/components/forms/InputWithLabel';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import type { IServiceProject } from '@openpanel/db';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import type { IServiceProject } from '@mixan/db';
import { popModal } from '.'; import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container'; import { ModalContent, ModalHeader } from './Modal/Container';

View File

@@ -8,13 +8,12 @@ import { Combobox } from '@/components/ui/combobox';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import type { IChartInput } from '@openpanel/validation';
import { useRouter, useSearchParams } from 'next/navigation'; import { useRouter, useSearchParams } from 'next/navigation';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
import type { IChartInput } from '@mixan/validation';
import { popModal } from '.'; import { popModal } from '.';
import { ModalContent, ModalHeader } from './Modal/Container'; import { ModalContent, ModalHeader } from './Modal/Container';

Some files were not shown because too many files have changed in this diff Show More