Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/newjitsu' into newjitsu
Browse files Browse the repository at this point in the history
  • Loading branch information
absorbb committed Dec 21, 2023
2 parents 8e6075a + 0b3f380 commit 3d57a1b
Show file tree
Hide file tree
Showing 17 changed files with 222 additions and 357 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/close-stale-issues.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Close inactive issues
on:
schedule:
- cron: "30 1 * * *" #once a day at 1:30am

jobs:
close-issues:
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v5
with:
days-before-issue-stale: 15
days-before-issue-close: 15
stale-issue-label: "🕰️Stale"
exempt-issue-labels: "⏳Postpone"
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
days-before-pr-stale: -1
days-before-pr-close: -1
repo-token: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<p align="center">
👉<b>Looking for Jitsu Classic? Switch to
<a href="https://github.com/jitsucom/jitsu/tree/master">classic branch</a>, and read about <a href="https://docs.jitsu.com/jitsu-classic">Jitsu Classic and Jitsu Next differences</a></b>
<a href="https://github.com/jitsucom/jitsu/tree/master">classic branch</a>, and read about <a href="[https://docs.jitsu.com/jitsu-classic](https://jitsu.com/blog/jitsu-next">Jitsu Classic and Jitsu Next differences</a></b>
</p>
<p align="center">
<img src="https://github.com/jitsucom/jitsu/blob/newjitsu/.readme-assets/github-hero-light-mode.png?raw=true#gh-light-mode-only" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { LoadingAnimation } from "../GlobalLoader/GlobalLoader";
import React from "react";
import { ErrorCard } from "../GlobalError/GlobalError";
import { Input } from "antd";
import { useAppConfig } from "../../lib/context";
import { useAppConfig, useWorkspace } from "../../lib/context";

function groupByType(sources: SourceType[]): Record<string, SourceType[]> {
const groups: Record<string, SourceType[]> = {};
Expand Down Expand Up @@ -57,6 +57,7 @@ export function getServiceIcon(source: SourceType, icons: Record<string, string>
export const ServicesCatalog: React.FC<{ onClick: (packageType, packageId: string) => void }> = ({ onClick }) => {
const { data, isLoading, error } = useApi<{ sources: SourceType[] }>(`/api/sources?mode=meta`);
const sourcesIconsLoader = useApi<{ sources: SourceType[] }>(`/api/sources?mode=icons-only`);
const workspace = useWorkspace();
const [filter, setFilter] = React.useState("");
const appconfig = useAppConfig();
const sourcesIcons: Record<string, string> = sourcesIconsLoader.data
Expand Down Expand Up @@ -95,6 +96,7 @@ export const ServicesCatalog: React.FC<{ onClick: (packageType, packageId: strin
.filter(
source =>
!appconfig.mitCompliant ||
workspace.featuresEnabled.includes("ignore_sources_licenses") ||
source.meta.license?.toLowerCase() === "mit" ||
(source.meta.mitVersions && source.meta.mitVersions.length > 0)
);
Expand Down
3 changes: 3 additions & 0 deletions webapps/console/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ function parseIfNeeded(o: any): any {
export function getAuthBearerToken(req: NextApiRequest): string | undefined {
if (req.headers.authorization && req.headers.authorization.toLowerCase().indexOf("bearer ") === 0) {
return req.headers.authorization.substring("bearer ".length);
} else if (req.query?.__unsafe_token) {
//very unsafe, but some tools we use can't set headers, so we need to allow this
return req.query.__unsafe_token as string;
}
return undefined;
}
Expand Down
11 changes: 0 additions & 11 deletions webapps/console/lib/ee-client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { get } from "./useApi";
import { DomainStatus } from "./server/ee";
import * as auth from "firebase/auth";

export type ClassicProjectStatus = {
Expand All @@ -11,7 +10,6 @@ export type ClassicProjectStatus = {
};

export interface EeClient {
attachDomain(domain: string): Promise<DomainStatus>;
checkClassicProject(): Promise<ClassicProjectStatus>;
createCustomToken(): Promise<string>;
}
Expand Down Expand Up @@ -39,15 +37,6 @@ export function getEeClient(host: string, workspaceId: string): EeClient {
return cachedToken;
};
return {
attachDomain: async domain => {
cachedToken = await refreshTokenIfNeeded();
return await get(removeDoubleSlashes(`${host}/api/domain`), {
query: { domain },
headers: {
Authorization: `Bearer ${cachedToken.token}`,
},
});
},
checkClassicProject: async () => {
const fbToken = await auth.getAuth().currentUser?.getIdToken();
return await get(removeDoubleSlashes(`${host}/api/is-active`), {
Expand Down
1 change: 1 addition & 0 deletions webapps/console/lib/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export const AppConfig = z.object({
//iso date
readOnlyUntil: z.string().optional(),
disableSignup: z.boolean().optional(),
customDomainsEnabled: z.boolean().optional(),
ee: z.object({
available: z.boolean(),
host: z.string().optional(),
Expand Down
39 changes: 39 additions & 0 deletions webapps/console/lib/server/custom-domains.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { db } from "./db";
import { StreamConfig } from "../schema";
import dns from "dns";
import { getLog } from "juava";

type DomainAvailability = { available: true; usedInWorkspaces?: never } | { available: false; usedInWorkspace: string };

export const customDomainCnames = process.env.CUSTOM_DOMAIN_CNAMES?.split(",");

/**
* Tells if the given domain is used in other workspaces.
*/
Expand All @@ -26,3 +30,38 @@ export async function isDomainAvailable(domain: string, workspaceId: string): Pr
return { available: true };
}
}

function resolveCname(domain: string): Promise<string | undefined> {
return new Promise((resolve, reject) => {
dns.resolveCname(domain, (err, addresses) => {
if (err) {
reject(err);
} else {
if (addresses.length === 1) {
resolve(addresses[0]);
} else if (!addresses || addresses.length === 0) {
resolve(undefined);
} else {
getLog()
.atWarn()
.log(`Domain ${domain} has multiple CNAME records: ${addresses.join(", ")}. Using first one`);
resolve(addresses[0]);
}
}
});
});
}

export async function isCnameValid(domain: string): Promise<boolean> {
if (!customDomainCnames || customDomainCnames.length == 0) {
throw new Error(`CUSTOM_DOMAIN_CNAMES is not set. isCnameValid() should not be called`);
}
let cnameRecord: string | undefined;
try {
cnameRecord = await resolveCname(domain);
} catch (e) {
getLog().atError().withCause(e).log(`Domain ${domain} has no CNAME records`);
return false;
}
return !!(cnameRecord && customDomainCnames.includes(cnameRecord.toLowerCase()));
}
10 changes: 0 additions & 10 deletions webapps/console/lib/server/ee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,3 @@ export function createJwt(
const token = jwt.sign({ userId, email, workspaceId, exp: expiresSecondsTimestamp }, jwtSecret);
return { jwt: token, expiresAt: new Date(expiresSecondsTimestamp * 1000).toISOString() };
}

export type DomainStatus = { error?: string } & (
| { needsConfiguration: false }
| { needsConfiguration: true; configurationType: "cname"; cnameValue: string }
| {
needsConfiguration: true;
configurationType: "verification";
verification: { type: string; domain: string; value: string }[];
}
);
21 changes: 21 additions & 0 deletions webapps/console/lib/shared/domain-check-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from "zod";
import { Simplify } from "type-fest";

export const DomainCheckResponse = z.union([
z.object({
ok: z.literal(true),
reason: z.never().optional(),
}),
z.object({
ok: z.literal(false),
reason: z.union([z.literal("used_by_other_workspace"), z.literal("invalid_domain_name")]),
cnameValue: z.never().optional(),
}),
z.object({
ok: z.literal(false),
reason: z.literal("requires_cname_configuration"),
cnameValue: z.string().optional(),
}),
]);

export type DomainCheckResponse = Simplify<z.infer<typeof DomainCheckResponse>>;
6 changes: 5 additions & 1 deletion webapps/console/pages/[workspaceId]/services.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ const ServicesList: React.FC<{}> = () => {
`/api/sources/versions?type=${packageType}&package=${encodeURIComponent(packageId)}`
);
const versions = rawVersions.versions
.filter((v: any) => v.isRelease && (v.isMit || !appconfig.mitCompliant))
.filter(
(v: any) =>
v.isRelease &&
(v.isMit || !appconfig.mitCompliant || workspace.featuresEnabled.includes("ignore_sources_licenses"))
)
.map((v: any) => v.name);
const sourceType = await rpc(`/api/sources/${packageType}/${encodeURIComponent(packageId)}`);

Expand Down
65 changes: 34 additions & 31 deletions webapps/console/pages/[workspaceId]/streams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { getEeClient } from "../../lib/ee-client";
import { assertDefined, requireDefined } from "juava";
import { ReloadOutlined } from "@ant-design/icons";
import { confirmOp, feedbackError } from "../../lib/ui";
import type { DomainStatus } from "../../lib/server/ee";
import { getAntdModal, useAntdModal } from "../../lib/modal";
import { get } from "../../lib/useApi";
import { Activity, AlertTriangle, Check, Globe, Wrench, Zap } from "lucide-react";
Expand All @@ -27,6 +26,7 @@ import { useLinksQuery } from "../../lib/queries";
import { toURL } from "../../lib/shared/url";
import JSON5 from "json5";
import { EditorToolbar } from "../../components/EditorToolbar/EditorToolbar";
import { DomainCheckResponse } from "../../lib/shared/domain-check-response";

const Streams: React.FC<any> = () => {
return (
Expand Down Expand Up @@ -77,10 +77,10 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void>
);
const [reloadTrigger, setReloadTrigger] = useState(0);
const [deleting, setDeleting] = useState(false);
const { data, isLoading, error, refetch } = useQuery<DomainStatus>(
const { data, isLoading, error, refetch } = useQuery<DomainCheckResponse>(
["domain-status", domain.toLowerCase(), reloadTrigger],
async () => {
return await eeClient.attachDomain(domain);
return await get(`/api/${workspace.id}/domain-check?domain=${domain.toLowerCase()}`);
},
{ cacheTime: 0 }
);
Expand All @@ -94,9 +94,7 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void>
{/*</div>*/}
<div className={"text-blue-600 w-4 h-4 mr-1.5"}>
<Globe
className={`w-full h-full ${
error || data?.error ? "text-red-600" : data?.needsConfiguration ? "text-yellow-600" : "text-blue-600"
}`}
className={`w-full h-full ${error ? "text-red-600" : data?.ok ? "text-blue-600" : "text-yellow-600"}`}
/>
</div>
<div className="font-bold text-lg">{domain}</div>
Expand All @@ -113,14 +111,14 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void>
<FaExternalLinkAlt />
</Button>
</Tooltip>
{data?.needsConfiguration && (
{!data?.ok && (
<Tooltip title="See configuration instructions">
<Button
type="text"
danger
disabled={isLoading || deleting}
onClick={() => {
DomainConfigurationInstructions.show({ domain, status: data });
DomainConfigurationInstructions.show({ domain, status: data! });
}}
className="border-0"
>
Expand Down Expand Up @@ -174,29 +172,29 @@ const CustomDomain: React.FC<{ domain: string; deleteDomain: () => Promise<void>
</span>
</StatusBadge>
);
} else if (error || data?.error) {
} else if (error) {
return <StatusBadge status="error">ERROR</StatusBadge>;
} else if (data?.needsConfiguration) {
} else if (!data?.ok) {
return <StatusBadge status="warning">Configuration Required</StatusBadge>;
} else {
return <StatusBadge status="success">OK</StatusBadge>;
}
})()}
</div>
{(error || data?.error) && (
{error && (
<div className="flex items-start mt-1">
<div className={"mr-2"}>Description:</div>
<div className="">{`${data?.error || "Internal error"}`}</div>
<div className="">{`${"Internal error"}`}</div>
</div>
)}
{data?.needsConfiguration && (
{!data?.ok && (
<div className="flex items-start mt-1">
<div className={"mr-2"}>Description:</div>
<div className="">
See{" "}
<a
className={"cursor-pointer"}
onClick={() => DomainConfigurationInstructions.show({ domain, status: data })}
onClick={() => DomainConfigurationInstructions.show({ domain, status: data! })}
>
<u>configuration instructions</u>
</a>
Expand Down Expand Up @@ -234,25 +232,16 @@ export const DNSRecordTable: React.FC<DNSRecordTableProps> = ({ records }) => {
);
};

export type DomainInstructionsProps = { domain: string; status: DomainStatus };
export type DomainInstructionsProps = { domain: string; status: DomainCheckResponse };
const DomainConfigurationInstructions: React.FC<DomainInstructionsProps> & {
show: (p: DomainInstructionsProps) => void;
} = ({ domain, status }) => {
if (status.needsConfiguration && status.configurationType === "cname") {
if (status.reason === "requires_cname_configuration") {
return (
<div>
<h3>Set the following record on your DNS provider to continue</h3>
<p className="bg-bgLight py-2 my-4">
<DNSRecordTable records={[{ type: "CNAME", domain, value: status.cnameValue }]} />
</p>
</div>
);
} else if (status.needsConfiguration && status.configurationType == "verification") {
return (
<div>
<h3>Set the following record on your DNS provider to continue</h3>
<p className="bg-bgLight py-2 my-4">
<DNSRecordTable records={status.verification} />
<DNSRecordTable records={[{ type: "CNAME", domain, value: status.cnameValue! }]} />
</p>
</div>
);
Expand Down Expand Up @@ -282,10 +271,24 @@ const DomainsEditor: React.FC<CustomWidgetProps<string[]>> = props => {
const add = async () => {
setAddPending(true);
try {
const { available } = await get(`/api/${workspace.id}/domain-check?domain=${addValue}`);
if (!available) {
feedbackError(`Domain ${addValue} is not available. It is used by other workspace`);
return;
const available: DomainCheckResponse = await get(`/api/${workspace.id}/domain-check?domain=${addValue}`);
if (!available.ok) {
if (available.reason === "used_by_other_workspace") {
feedbackError(
<>
Domain <code>{addValue}</code> is not available. It is used by other workspace. Contact{" "}
<code>[email protected]</code> if you think this is a mistake
</>
);
return;
} else if (available.reason === "invalid_domain_name") {
feedbackError(
<>
Invalid domain name <code>{addValue}</code>
</>
);
return;
}
}
const newVal = [...domains, addValue as string];
setDomains(newVal);
Expand Down Expand Up @@ -544,7 +547,7 @@ const StreamsList: React.FC<{}> = () => {
},
domains: {
editor: DomainsEditor,
hidden: !appConfig.ee.available,
hidden: !appConfig.customDomainsEnabled,
displayName: "Custom Tracking Domains",
documentation: (
<>
Expand Down
Loading

1 comment on commit 3d57a1b

@vercel
Copy link

@vercel vercel bot commented on 3d57a1b Dec 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

new-jitsu – ./webapps/console

ag.ru
logu.au
ozon.ru
sse.ere
erxes.io
baidu.dom
ilmiya.io
sambla.se
bobsec.com
sambla.com
agro4u.life
bluetick.ai
myilmiya.io
protontv.eu
t.quenti.io
alicesec.com
d.askloan.tw
dev.aclis.io
docs.dh19.de
docs.dh19.eu
hunterbi.com
joseviso.com
mydomain.dom
t.thequack.ai
thinkr.com.br
use.jitsu.com
usepolygon.io
www.sambla.se
ajewellers.com
data.uselog.io
gpt.whatfa.com
sidetrekai.com
t.papermark.io
t.saasmonk.app
use2.jitsu.com
w.d2.jitsu.com
www.kellen.top
*.dataspecc.com
app.bluetick.ai
caddy.jitsu.com
data.askloan.tw
enterticket.com
events.mitzu.io
ildar.jitsu.com
jitsu.efeer.com
jitsu.ivve.tech
krestomatio.com
sevenbillion.co
w2.d2.jitsu.com
xrt.webxr.tools
app.jotverse.com
caddy2.jitsu.com
cname2.jitsu.com
data.mysitee.com
data.toptere.com
dev-t.democo.dev
events.quenti.io
utils.doogma.com
worthsystems.com
data.music2me.com
data.timeplus.com
event-gateway.com
test.bigfootproof.com
teste.fazcomex.com.br
analytics.dev.knekt.io
loraboutiquedental.com
notion.twelftree.co.uk
dev-portal.zoopsign.com
event.tradejobsnz.co.nz
investing-poc.jitsu.dev
savvy-replay.jitsu.tech
data.analytics-smart.com
data.handelsregister.app
event.clickncruise.co.uk
jt.fairhopeweb.github.io
savvy-replay2.jitsu.tech
savvy-replay3.jitsu.tech
savvy-replay4.jitsu.tech
track.alquimiaweb.com.br
track.pressance-group.jp
track.uniquecafes.com.br
colectha.agenciavoolu.com
kolectha.agenciavoolu.com
lp.loraboutiquedental.com
stage-portal.zoopsign.com
new-jitsu-jitsu.vercel.app
lodercom-colectha.voolu.shop
warehouse1.trendstyle.com.au
d0.livingdesignsfurniture.com
ingest-load-testing.jitsu.dev
jitsu.precisaosistemas.com.br
analytics.inspiresolutions.app
betteruptime-monitoring.jitsu.dev
canvas.livingdesignsfurniture.com
analytics.dev.inspiresolutions.app
cl9vt45z50001znkunc6v8fmm.d.jitsu.com
clm2jikrm00002v6r5l6niws3.d.jitsu.com
new-jitsu-git-newjitsu-jitsu.vercel.app
3000-rajaraodv-customerdemo-nmpsqwflswt.ws-us102.gitpod.io
new.jitsu.dev

Please sign in to comment.