fix(realtime): fix charts display
This commit is contained in:
@@ -33,7 +33,7 @@ export default async function DashboardPage() {
|
||||
<TabsTrigger value="Links">Links</TabsTrigger>
|
||||
<TabsTrigger value="Realtime">Realtime</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="Links">
|
||||
<TabsContent className="space-y-3" value="Links">
|
||||
<UserUrlsList
|
||||
user={{
|
||||
id: user.id,
|
||||
|
||||
@@ -2,6 +2,18 @@
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import {
|
||||
addHours,
|
||||
addMinutes,
|
||||
differenceInDays,
|
||||
differenceInHours,
|
||||
differenceInMinutes,
|
||||
format,
|
||||
startOfDay,
|
||||
startOfHour,
|
||||
startOfMinute,
|
||||
} from "date-fns";
|
||||
import { create } from "lodash";
|
||||
|
||||
import { DAILY_DIMENSION_ENUMS } from "@/lib/enums";
|
||||
import {
|
||||
@@ -25,6 +37,7 @@ export interface Location {
|
||||
city?: string;
|
||||
country?: string;
|
||||
lastUpdate?: Date;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
device?: string;
|
||||
browser?: string;
|
||||
@@ -43,6 +56,7 @@ interface DatabaseLocation {
|
||||
country: string;
|
||||
lastUpdate: Date;
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
device?: string;
|
||||
browser?: string;
|
||||
userUrl?: {
|
||||
@@ -89,60 +103,128 @@ export default function Realtime({ isAdmin = false }: { isAdmin?: boolean }) {
|
||||
};
|
||||
|
||||
const processChartData = (locations: Location[]): ChartData[] => {
|
||||
const countBySegment: {
|
||||
[key: string]: { count: number; timestamp: number };
|
||||
} = {};
|
||||
const timestamps = locations
|
||||
.filter((loc) => loc.updatedAt)
|
||||
.map((loc) => new Date(loc.updatedAt || "").getTime());
|
||||
// 过滤有效数据
|
||||
const validLocations = locations.filter((loc) => loc.createdAt);
|
||||
if (validLocations.length === 0) return [];
|
||||
|
||||
if (timestamps.length === 0) return [];
|
||||
// 获取时间范围
|
||||
const dates = validLocations.map((loc) => new Date(loc.createdAt!));
|
||||
const minDate = new Date(Math.min(...dates.map((d) => d.getTime())));
|
||||
const maxDate = new Date(Math.max(...dates.map((d) => d.getTime())));
|
||||
|
||||
const minTime = Math.min(...timestamps);
|
||||
const maxTime = Math.max(...timestamps);
|
||||
const timeRangeMinutes = (maxTime - minTime) / (1000 * 60);
|
||||
console.log("[sss]", timeRangeMinutes);
|
||||
// 根据时间跨度选择分组策略
|
||||
const totalMinutes = differenceInMinutes(maxDate, minDate);
|
||||
const totalHours = differenceInHours(maxDate, minDate);
|
||||
const totalDays = differenceInDays(maxDate, minDate);
|
||||
|
||||
let segmentSizeMinutes: number;
|
||||
if (timeRangeMinutes <= 30) segmentSizeMinutes = 0.5;
|
||||
else if (timeRangeMinutes <= 60) segmentSizeMinutes = 2;
|
||||
else if (timeRangeMinutes <= 360) segmentSizeMinutes = 12;
|
||||
else if (timeRangeMinutes <= 720) segmentSizeMinutes = 24;
|
||||
else if (timeRangeMinutes <= 1440) segmentSizeMinutes = 36;
|
||||
else {
|
||||
segmentSizeMinutes = Math.ceil(timeRangeMinutes / 30);
|
||||
segmentSizeMinutes = Math.ceil(segmentSizeMinutes / 60) * 60;
|
||||
let groupByFn: (date: Date) => Date;
|
||||
let formatFn: (date: Date) => string;
|
||||
let intervalFn: (date: Date, interval: number) => Date;
|
||||
let interval: number;
|
||||
|
||||
// 30分钟内:按1分钟分组
|
||||
if (totalMinutes <= 30) {
|
||||
groupByFn = startOfMinute;
|
||||
formatFn = (date) => format(date, "MM-dd HH:mm");
|
||||
intervalFn = addMinutes;
|
||||
interval = 1;
|
||||
} else if (totalMinutes <= 60) {
|
||||
// 1小时内:按2分钟分组
|
||||
groupByFn = (date) => {
|
||||
const minutes = Math.floor(date.getMinutes() / 2) * 2;
|
||||
const grouped = startOfMinute(date);
|
||||
grouped.setMinutes(minutes);
|
||||
return grouped;
|
||||
};
|
||||
formatFn = (date) => format(date, "MM-dd HH:mm");
|
||||
intervalFn = addMinutes;
|
||||
interval = 2;
|
||||
} else if (totalHours <= 2) {
|
||||
// 2小时内:按4分钟分组
|
||||
groupByFn = (date) => {
|
||||
const minutes = Math.floor(date.getMinutes() / 4) * 4;
|
||||
const grouped = startOfMinute(date);
|
||||
grouped.setMinutes(minutes);
|
||||
return grouped;
|
||||
};
|
||||
formatFn = (date) => format(date, "MM-dd HH:mm");
|
||||
intervalFn = addMinutes;
|
||||
interval = 4;
|
||||
} else if (totalHours <= 6) {
|
||||
// 6小时内:按12分钟分组
|
||||
groupByFn = (date) => {
|
||||
const minutes = Math.floor(date.getMinutes() / 12) * 12;
|
||||
const grouped = startOfMinute(date);
|
||||
grouped.setMinutes(minutes);
|
||||
return grouped;
|
||||
};
|
||||
formatFn = (date) => format(date, "MM-dd HH:mm");
|
||||
intervalFn = addMinutes;
|
||||
interval = 12;
|
||||
} else if (totalHours <= 12) {
|
||||
// 12小时内:按24分钟分组
|
||||
groupByFn = (date) => {
|
||||
const minutes = Math.floor(date.getMinutes() / 24) * 24;
|
||||
const grouped = startOfMinute(date);
|
||||
grouped.setMinutes(minutes);
|
||||
return grouped;
|
||||
};
|
||||
formatFn = (date) => format(date, "MM-dd HH:mm");
|
||||
intervalFn = addMinutes;
|
||||
interval = 24;
|
||||
} else if (totalHours <= 24) {
|
||||
// 24小时内:按48分钟分组
|
||||
groupByFn = (date) => {
|
||||
const minutes = Math.floor(date.getMinutes() / 48) * 48;
|
||||
const grouped = startOfMinute(date);
|
||||
grouped.setMinutes(minutes);
|
||||
return grouped;
|
||||
};
|
||||
formatFn = (date) => format(date, "MM-dd HH:mm");
|
||||
intervalFn = addMinutes;
|
||||
interval = 48;
|
||||
} else if (totalDays <= 7) {
|
||||
// 7天内:按天分组
|
||||
groupByFn = startOfDay;
|
||||
formatFn = (date) => format(date, "MM-dd");
|
||||
intervalFn = addHours;
|
||||
interval = 24;
|
||||
} else {
|
||||
// 更长时间:按天分组
|
||||
groupByFn = startOfDay;
|
||||
formatFn = (date) => format(date, "MM-dd");
|
||||
intervalFn = addHours;
|
||||
interval = 24;
|
||||
}
|
||||
|
||||
locations.forEach((loc) => {
|
||||
if (!loc.updatedAt) return;
|
||||
const date = new Date(loc.updatedAt);
|
||||
const minutesSinceStart = Math.floor(
|
||||
(date.getTime() - minTime) / (1000 * 60),
|
||||
);
|
||||
const segmentIndex = Math.floor(minutesSinceStart / segmentSizeMinutes);
|
||||
const segmentStartMinutes = segmentIndex * segmentSizeMinutes;
|
||||
const segmentDate = new Date(minTime + segmentStartMinutes * 60 * 1000);
|
||||
const timeKey = `${segmentDate.getHours().toString().padStart(2, "0")}:${segmentDate
|
||||
.getMinutes()
|
||||
.toString()
|
||||
.padStart(2, "0")}`;
|
||||
// 分组聚合数据
|
||||
const groupedData = new Map<string, number>();
|
||||
|
||||
if (!countBySegment[timeKey]) {
|
||||
countBySegment[timeKey] = {
|
||||
count: 0,
|
||||
timestamp: segmentDate.getTime(),
|
||||
};
|
||||
}
|
||||
countBySegment[timeKey].count += loc.count;
|
||||
validLocations.forEach((loc) => {
|
||||
const date = new Date(loc.createdAt!);
|
||||
const groupedDate = groupByFn(date);
|
||||
const key = groupedDate.getTime().toString();
|
||||
|
||||
groupedData.set(key, (groupedData.get(key) || 0) + loc.count);
|
||||
});
|
||||
|
||||
return Object.keys(countBySegment)
|
||||
.sort((a, b) => countBySegment[a].timestamp - countBySegment[b].timestamp)
|
||||
.map((time) => ({
|
||||
time,
|
||||
count: countBySegment[time].count,
|
||||
}));
|
||||
// 填充时间间隔,确保连续性
|
||||
const result: ChartData[] = [];
|
||||
const startGroup = groupByFn(minDate);
|
||||
const endGroup = groupByFn(maxDate);
|
||||
|
||||
let current = startGroup;
|
||||
// 过滤掉count为0 的数据
|
||||
while (current <= endGroup) {
|
||||
const key = current.getTime().toString();
|
||||
result.push({
|
||||
time: formatFn(current),
|
||||
count: groupedData.get(key) || 0,
|
||||
});
|
||||
current = intervalFn(current, interval);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const appendLocationData = (
|
||||
@@ -176,6 +258,7 @@ export default function Realtime({ isAdmin = false }: { isAdmin?: boolean }) {
|
||||
browser: item.browser,
|
||||
userUrl: item.userUrl,
|
||||
updatedAt: item.updatedAt,
|
||||
createdAt: item.createdAt,
|
||||
});
|
||||
}
|
||||
totalNewClicks += clickCount;
|
||||
@@ -216,7 +299,7 @@ export default function Realtime({ isAdmin = false }: { isAdmin?: boolean }) {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.error) {
|
||||
console.error("API Error:", result.error);
|
||||
// console.error("API Error:", result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,72 +29,75 @@ export const RealtimeChart = ({
|
||||
chartData,
|
||||
totalClicks,
|
||||
}: RealtimeChartProps) => {
|
||||
const maxCount = Math.max(...chartData.map((d) => d.count), 1);
|
||||
// const tickInterval =
|
||||
// chartData.length <= 10 ? 0 : Math.ceil(chartData.length / 10);
|
||||
const getTickInterval = (dataLength: number) => {
|
||||
if (dataLength <= 6) return 0; // 显示所有刻度
|
||||
if (dataLength <= 12) return 1; // 每隔1个显示
|
||||
if (dataLength <= 24) return Math.ceil(dataLength / 8); // 大约8个刻度
|
||||
return Math.ceil(dataLength / 6); // 大约6个刻度
|
||||
if (dataLength <= 6) return 0;
|
||||
if (dataLength <= 12) return 1;
|
||||
if (dataLength <= 24) return Math.ceil(dataLength / 8);
|
||||
return Math.ceil(dataLength / 6);
|
||||
};
|
||||
const tickInterval = getTickInterval(chartData.length);
|
||||
|
||||
// console.log("chartData", chartData);
|
||||
// 过滤掉为count=0的数据,但是最后一个数据为0时不要剔除
|
||||
const filteredChartData = chartData.filter((item, index) => {
|
||||
return item.count !== 0 || index === chartData.length - 1;
|
||||
});
|
||||
const tickInterval = getTickInterval(filteredChartData.length);
|
||||
|
||||
return (
|
||||
<div className={cn(`rounded-lg border p-3 backdrop-blur-xl`, className)}>
|
||||
<div className={cn(`rounded-lg border p-3 backdrop-blur-2xl`, className)}>
|
||||
<div className="mb-1 flex items-center text-base font-semibold">
|
||||
<StatusDot status={1} />
|
||||
<h3 className="ml-2">Realtime Visits</h3>
|
||||
<Icons.mousePointerClick className="ml-auto size-4 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="mb-2 text-lg font-semibold">{totalClicks}</p>
|
||||
<ResponsiveContainer width={300} height={200}>
|
||||
<BarChart
|
||||
data={chartData}
|
||||
margin={{ top: 10, right: 0, left: -20, bottom: 0 }}
|
||||
barCategoryGap={1}
|
||||
>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
tick={{ fontSize: 12 }}
|
||||
interval={tickInterval}
|
||||
tickCount={Math.min(chartData.length, 10)}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
type="category"
|
||||
scale="point"
|
||||
padding={{ left: 14, right: 20 }}
|
||||
tickFormatter={(value) => value}
|
||||
/>
|
||||
<YAxis
|
||||
// domain={[0, maxCount]}
|
||||
domain={["dataMin", "dataMax"]}
|
||||
tickCount={5}
|
||||
tick={{ fontSize: 12 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={({ active, payload, label }) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className="rounded-md bg-primary-foreground/90 p-2 text-sm backdrop-blur-lg">
|
||||
<p className="label">{`${label}`}</p>
|
||||
<p className="label">{`Visits: ${payload[0].value}`}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="count"
|
||||
fill="#36d399"
|
||||
radius={[1, 1, 0, 0]}
|
||||
maxBarSize={20}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
{/* <ResponsiveContainer ></ResponsiveContainer> */}
|
||||
<BarChart
|
||||
width={300}
|
||||
height={200}
|
||||
data={filteredChartData}
|
||||
margin={{ top: 10, right: 0, left: -20, bottom: 0 }}
|
||||
barCategoryGap={1}
|
||||
>
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
tick={{ fontSize: 12 }}
|
||||
interval={tickInterval}
|
||||
tickCount={Math.min(filteredChartData.length, 10)}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
type="category"
|
||||
scale="point"
|
||||
padding={{ left: 14, right: 20 }}
|
||||
tickFormatter={(value) => value.split(" ")[1]}
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, "dataMax"]}
|
||||
tickCount={5}
|
||||
tick={{ fontSize: 12 }}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
<Tooltip
|
||||
content={({ active, payload, label }) => {
|
||||
if (active && payload && payload.length) {
|
||||
return (
|
||||
<div className="rounded-md border bg-primary-foreground py-2 text-primary backdrop-blur">
|
||||
<p className="label px-2 text-base font-medium">{`${label}`}</p>
|
||||
<p className="label px-2 text-sm">{`Visits: ${payload[0].value}`}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="count"
|
||||
fill="#36d399"
|
||||
radius={[1, 1, 0, 0]}
|
||||
maxBarSize={20}
|
||||
/>
|
||||
</BarChart>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,13 +40,8 @@ export default function RealtimeGlobe({
|
||||
const [countries, setCountries] = useState<any>({});
|
||||
const [currentLocation, setCurrentLocation] = useState<any>({});
|
||||
const [hexAltitude, setHexAltitude] = useState(0.001);
|
||||
const {
|
||||
ref: wrapperRef,
|
||||
width: wrapperWidth,
|
||||
height: wrapperHeight,
|
||||
} = useElementSize();
|
||||
const { ref: wrapperRef, width: wrapperWidth } = useElementSize();
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const highest =
|
||||
locations.reduce((acc, curr) => Math.max(acc, curr.count), 0) || 1;
|
||||
@@ -62,8 +57,7 @@ export default function RealtimeGlobe({
|
||||
const { MeshPhongMaterial } = await import("three");
|
||||
return { Globe, MeshPhongMaterial };
|
||||
} catch (err) {
|
||||
console.error("Failed to load Globe.gl:", err);
|
||||
setError("Failed to load Globe.gl library");
|
||||
// console.error("Failed to load Globe.gl:", err);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
@@ -128,7 +122,8 @@ export default function RealtimeGlobe({
|
||||
|
||||
globe = new Globe(container)
|
||||
.width(wrapperWidth)
|
||||
.height(wrapperWidth > 728 ? wrapperWidth * 0.8 : wrapperWidth)
|
||||
.height(wrapperWidth)
|
||||
.globeOffset([0, -100])
|
||||
.atmosphereColor("rgba(170, 170, 200, 0.8)")
|
||||
.backgroundColor("rgba(0,0,0,0)")
|
||||
.globeMaterial(
|
||||
@@ -188,7 +183,6 @@ export default function RealtimeGlobe({
|
||||
}
|
||||
|
||||
setIsLoaded(true);
|
||||
setError(null);
|
||||
});
|
||||
|
||||
if (globe.controls()) {
|
||||
@@ -214,14 +208,7 @@ export default function RealtimeGlobe({
|
||||
}
|
||||
|
||||
globeInstanceRef.current = globe;
|
||||
|
||||
// console.log("Globe initialization complete");
|
||||
} catch (err) {
|
||||
// console.error("Error initializing globe:", err);
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed to initialize globe",
|
||||
);
|
||||
}
|
||||
} catch (err) {}
|
||||
}, [
|
||||
countries,
|
||||
locations,
|
||||
@@ -308,16 +295,11 @@ export default function RealtimeGlobe({
|
||||
}, [cleanup]);
|
||||
|
||||
return (
|
||||
<div ref={wrapperRef} className="relative -mt-10">
|
||||
<div ref={wrapperRef} className="relative">
|
||||
<div
|
||||
ref={globeRef}
|
||||
className="flex justify-center"
|
||||
style={{
|
||||
// width: `${wrapperWidth}px`,
|
||||
// height:
|
||||
// wrapperWidth > 728
|
||||
// ? `${wrapperWidth * 0.8}px`
|
||||
// : `${wrapperWidth}px`,
|
||||
maxWidth: `${wrapperWidth}px`, // 比较疑惑
|
||||
minHeight: "100px",
|
||||
}}
|
||||
|
||||
@@ -34,7 +34,7 @@ export default async function DashboardPage() {
|
||||
<TabsTrigger value="Realtime">Realtime</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="Links">
|
||||
<TabsContent className="space-y-3" value="Links">
|
||||
<UserUrlsList
|
||||
user={{
|
||||
id: user.id,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { create } from "lodash";
|
||||
|
||||
import { prisma } from "@/lib/db";
|
||||
import { checkUserStatus } from "@/lib/dto/user";
|
||||
@@ -41,7 +42,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const whereClause: any = {
|
||||
...(isAdmin === "true" ? {} : { userUrl: { userId: user.id } }),
|
||||
updatedAt: {
|
||||
createdAt: {
|
||||
gte: startDate,
|
||||
lte: endDate,
|
||||
},
|
||||
@@ -67,6 +68,7 @@ export async function GET(request: NextRequest) {
|
||||
country: true,
|
||||
device: true,
|
||||
browser: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
userUrl: {
|
||||
select: {
|
||||
@@ -92,6 +94,7 @@ export async function GET(request: NextRequest) {
|
||||
country: string;
|
||||
lastUpdate: Date;
|
||||
updatedAt: Date;
|
||||
createdAt: Date;
|
||||
device: string;
|
||||
browser: string;
|
||||
userUrl: {
|
||||
@@ -125,6 +128,7 @@ export async function GET(request: NextRequest) {
|
||||
country: item.country || "",
|
||||
lastUpdate: item.updatedAt,
|
||||
updatedAt: item.updatedAt,
|
||||
createdAt: item.createdAt,
|
||||
device: item.device || "",
|
||||
browser: item.browser || "",
|
||||
userUrl: item.userUrl,
|
||||
@@ -211,7 +215,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const whereClause: any = {
|
||||
...(isAdmin ? {} : { userUrl: { userId: user.id } }),
|
||||
updatedAt: {
|
||||
createdAt: {
|
||||
gt: sinceDate,
|
||||
},
|
||||
latitude: {
|
||||
@@ -232,6 +236,7 @@ export async function POST(request: NextRequest) {
|
||||
country: true,
|
||||
device: true,
|
||||
browser: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
userUrl: {
|
||||
select: {
|
||||
|
||||
+36
-36
@@ -1,39 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
|
||||
<url><loc>https://wr.do/robots.txt</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/manifest.json</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/pricing</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/privacy</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/terms</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/authentification</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare-email-worker</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/components</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/config-files</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/database</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/email</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/installation</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/markdown-files</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/quick-start</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/dns-records</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/emails</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/cloudflare</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/other</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/vercel</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/zeabur</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/icon</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/markdown</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/meta-info</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/qrcode</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/screenshot</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/text</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/plan</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/quick-start</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/short-urls</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/wroom</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/chat</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/password-prompt</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/opengraph-image.jpg</loc><lastmod>2025-05-23T15:05:05.510Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/robots.txt</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/authentification</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/cloudflare-email-worker</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/components</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/config-files</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/database</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/email</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/installation</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/markdown-files</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/developer/quick-start</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/dns-records</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/emails</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/cloudflare</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/other</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/vercel</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/examples/zeabur</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/icon</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/markdown</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/meta-info</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/qrcode</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/screenshot</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/open-api/text</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/plan</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/quick-start</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/short-urls</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/docs/wroom</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/pricing</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/privacy</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/terms</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/chat</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/manifest.json</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/password-prompt</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
<url><loc>https://wr.do/opengraph-image.jpg</loc><lastmod>2025-05-24T09:27:12.895Z</lastmod><changefreq>daily</changefreq><priority>0.7</priority></url>
|
||||
</urlset>
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user