);
}
@@ -457,22 +503,8 @@ function HighlightCode({
getLineProps,
getTokenProps,
}) => (
-
-
+
+
{tokens
.map((line, index) => {
if (index === tokens.length - 1 && line.length === 1 && line[0].content === "\n") {
@@ -495,7 +527,8 @@ function HighlightCode({
{...lineProps}
className={cn(
"flex w-full justify-start transition-opacity duration-500",
- lineProps.className
+ lineProps.className,
+ isWrapped && "flex-wrap"
)}
style={{
opacity: shouldDim ? dimAmount : undefined,
@@ -504,9 +537,10 @@ function HighlightCode({
>
{showLineNumbers && (
= compactThreshold;
+
return (
-
-
-
{title}
+
+
+
{title}
{accessory &&
{accessory}
}
{loading ? (
) : v !== undefined ? (
-
- {animate ?
: v}
+
+ {shouldCompact ? (
+
: formatNumberCompact(v)}
+ content={formatNumber(v)}
+ />
+ ) : animate ? (
+
+ ) : (
+ formatNumber(v)
+ )}
{suffix &&
{suffix}
}
) : (
diff --git a/apps/webapp/app/components/primitives/AnimatedNumber.tsx b/apps/webapp/app/components/primitives/AnimatedNumber.tsx
index a58567050..f2f309a52 100644
--- a/apps/webapp/app/components/primitives/AnimatedNumber.tsx
+++ b/apps/webapp/app/components/primitives/AnimatedNumber.tsx
@@ -1,4 +1,4 @@
-import { motion, useSpring, useTransform, useMotionValue, animate } from "framer-motion";
+import { animate, motion, useMotionValue, useTransform } from "framer-motion";
import { useEffect } from "react";
export function AnimatedNumber({ value }: { value: number }) {
diff --git a/apps/webapp/app/components/primitives/DateTime.tsx b/apps/webapp/app/components/primitives/DateTime.tsx
index 11a7ab990..7d75bc735 100644
--- a/apps/webapp/app/components/primitives/DateTime.tsx
+++ b/apps/webapp/app/components/primitives/DateTime.tsx
@@ -98,7 +98,45 @@ export function formatDateTime(
}
export function formatDateTimeISO(date: Date, timeZone: string): string {
- return new Date(date.toLocaleString("en-US", { timeZone })).toISOString();
+ // Special handling for UTC
+ if (timeZone === "UTC") {
+ return date.toISOString();
+ }
+
+ // Get the date parts in the target timezone
+ const dateFormatter = new Intl.DateTimeFormat("en-US", {
+ timeZone,
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ hour: "2-digit",
+ minute: "2-digit",
+ second: "2-digit",
+ hour12: false,
+ });
+
+ // Get the timezone offset for this specific date
+ const timeZoneFormatter = new Intl.DateTimeFormat("en-US", {
+ timeZone,
+ timeZoneName: "longOffset",
+ });
+
+ const dateParts = Object.fromEntries(
+ dateFormatter.formatToParts(date).map(({ type, value }) => [type, value])
+ );
+
+ const timeZoneParts = timeZoneFormatter.formatToParts(date);
+ const offset =
+ timeZoneParts.find((part) => part.type === "timeZoneName")?.value.replace("GMT", "") ||
+ "+00:00";
+
+ // Format: YYYY-MM-DDThh:mm:ss.sssยฑhh:mm
+ return (
+ `${dateParts.year}-${dateParts.month}-${dateParts.day}T` +
+ `${dateParts.hour}:${dateParts.minute}:${dateParts.second}.${String(
+ date.getMilliseconds()
+ ).padStart(3, "0")}${offset}`
+ );
}
// New component that only shows date when it changes
diff --git a/apps/webapp/app/components/primitives/LoadingBarDivider.tsx b/apps/webapp/app/components/primitives/LoadingBarDivider.tsx
index f227f9e73..d35067ea6 100644
--- a/apps/webapp/app/components/primitives/LoadingBarDivider.tsx
+++ b/apps/webapp/app/components/primitives/LoadingBarDivider.tsx
@@ -7,7 +7,7 @@ type LoadingBarDividerProps = {
export function LoadingBarDivider({ isLoading }: LoadingBarDividerProps) {
return (
-
+
);
@@ -21,11 +21,9 @@ export function AnimationDivider({ isLoading }: LoadingBarDividerProps) {
if (isPresent) {
const enterAnimation = async () => {
await animate(
- [
- [scope.current, { width: "30%", left: "35%" }, { duration: 1, ease: "easeIn" }],
- [scope.current, { width: 0, left: "100%" }, { duration: 1, ease: "easeOut" }],
- ],
- { repeat: Infinity, repeatType: "reverse" }
+ scope.current,
+ { left: ["-100%", "100%"], width: "100%" },
+ { duration: 2, ease: "easeOut", repeat: Infinity }
);
};
enterAnimation();
diff --git a/apps/webapp/app/components/runs/v3/PacketDisplay.tsx b/apps/webapp/app/components/runs/v3/PacketDisplay.tsx
index 430402c89..4da733f2c 100644
--- a/apps/webapp/app/components/runs/v3/PacketDisplay.tsx
+++ b/apps/webapp/app/components/runs/v3/PacketDisplay.tsx
@@ -44,6 +44,7 @@ export function PacketDisplay({
code={data}
maxLines={20}
showLineNumbers={false}
+ showTextWrapping
/>
);
}
diff --git a/apps/webapp/app/components/runs/v3/RunIcon.tsx b/apps/webapp/app/components/runs/v3/RunIcon.tsx
index c03b32731..8a1924b3e 100644
--- a/apps/webapp/app/components/runs/v3/RunIcon.tsx
+++ b/apps/webapp/app/components/runs/v3/RunIcon.tsx
@@ -97,6 +97,7 @@ export function RunIcon({ name, className, spanName }: TaskIconProps) {
case "task-hook-onResume":
case "task-hook-onComplete":
case "task-hook-cleanup":
+ case "task-hook-onCancel":
return
;
case "task-hook-onFailure":
case "task-hook-catchError":
diff --git a/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx b/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx
index 23948508b..0dfc5f10e 100644
--- a/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx
+++ b/apps/webapp/app/components/runs/v3/TaskTriggerSource.tsx
@@ -12,10 +12,12 @@ export function TaskTriggerSourceIcon({
}) {
switch (source) {
case "STANDARD": {
- return
;
+ return
;
}
case "SCHEDULED": {
- return
;
+ return (
+
+ );
}
}
}
diff --git a/apps/webapp/app/env.server.ts b/apps/webapp/app/env.server.ts
index f0931683b..589c9a0a3 100644
--- a/apps/webapp/app/env.server.ts
+++ b/apps/webapp/app/env.server.ts
@@ -460,6 +460,7 @@ const EnvironmentSchema = z.object({
RUN_ENGINE_REUSE_SNAPSHOT_COUNT: z.coerce.number().int().default(0),
RUN_ENGINE_MAXIMUM_ENV_COUNT: z.coerce.number().int().optional(),
RUN_ENGINE_WORKER_SHUTDOWN_TIMEOUT_MS: z.coerce.number().int().default(60_000),
+ RUN_ENGINE_RETRY_WARM_START_THRESHOLD_MS: z.coerce.number().int().default(30_000),
RUN_ENGINE_WORKER_REDIS_HOST: z
.string()
@@ -717,7 +718,7 @@ const EnvironmentSchema = z.object({
SLACK_BOT_TOKEN: z.string().optional(),
SLACK_SIGNUP_REASON_CHANNEL_ID: z.string().optional(),
-
+
// kapa.ai
KAPA_AI_WEBSITE_ID: z.string().optional(),
});
diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
index 00ec1336a..24446a7ee 100644
--- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
+++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.queues/route.tsx
@@ -69,6 +69,7 @@ import { docsPath, EnvironmentParamSchema, v3BillingPath } from "~/utils/pathBui
import { PauseEnvironmentService } from "~/v3/services/pauseEnvironment.server";
import { PauseQueueService } from "~/v3/services/pauseQueue.server";
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
+import { Header3 } from "~/components/primitives/Headers";
import { Input } from "~/components/primitives/Input";
import { useThrottle } from "~/hooks/useThrottle";
@@ -257,13 +258,30 @@ export default function Page() {
suffix={env.paused && environment.queued > 0 ? "paused" : undefined}
animate
accessory={
}
- valueClassName={env.paused ? "text-amber-500" : undefined}
+ valueClassName={env.paused ? "text-warning" : undefined}
+ compactThreshold={1000000}
+ />
+
-
Name
Queued
- Running
+ Running/limit
+
+
+
Environment
+
+ This queue is limited by your environment's concurrency limit of{" "}
+ {environment.concurrencyLimit}.
+
+
+
+
User
+
+ This queue is limited by a concurrency limit set in your code.
+
+
+
+ }
+ >
+ Limited by
+
Release on waitpoint
-
Concurrency limit
Pause/resume
@@ -342,80 +389,103 @@ export default function Page() {
{queues.length > 0 ? (
- queues.map((queue) => (
-
-
-
- {queue.type === "task" ? (
-
- }
- content={`This queue was automatically created from your "${queue.name}" task`}
- />
- ) : (
-
- }
- content={`This is a custom queue you added in your code.`}
- />
+ queues.map((queue) => {
+ const limit = queue.concurrencyLimit ?? environment.concurrencyLimit;
+ const isAtLimit = queue.running === limit;
+ return (
+
+
+
+ {queue.type === "task" ? (
+
+ }
+ content={`This queue was automatically created from your "${queue.name}" task`}
+ />
+ ) : (
+
+ }
+ content={`This is a custom queue you added in your code.`}
+ />
+ )}
+
+ {queue.name}
+
+ {queue.paused ? (
+
+ Paused
+
+ ) : null}
+ {isAtLimit ? (
+
+ At concurrency limit
+
+ ) : null}
+
+
+
+ {queue.queued}
+
+
- {queue.name}
+ >
+ {queue.running}/
+
+ {limit}
- {queue.paused ? (
-
- Paused
-
- ) : null}
-
-
-
- {queue.queued}
-
-
- {queue.running}
-
-
- {queue.releaseConcurrencyOnWaitpoint ? "Yes" : "No"}
-
-
- {queue.concurrencyLimit ?? (
-
- Max ({environment.concurrencyLimit})
-
- )}
-
- }
- hiddenButtons={!queue.paused && }
- />
-
- ))
+
+
+ {queue.concurrencyLimit ? "User" : "Environment"}
+
+
+ {queue.releaseConcurrencyOnWaitpoint ? "Yes" : "No"}
+
+
+ }
+ hiddenButtons={
+ !queue.paused &&
+ }
+ />
+
+ );
+ })
) : (
@@ -503,7 +573,7 @@ function EnvironmentPauseResumeButton({
type="button"
variant="secondary/small"
LeadingIcon={env.paused ? PlayIcon : PauseIcon}
- leadingIconClassName={env.paused ? "text-success" : "text-amber-500"}
+ leadingIconClassName={env.paused ? "text-success" : "text-warning"}
>
{env.paused ? "Resume..." : "Pause environment..."}
@@ -512,8 +582,8 @@ function EnvironmentPauseResumeButton({
{env.paused
- ? `Resume processing runs in ${environmentFullTitle(env)}.`
- : `Pause processing runs in ${environmentFullTitle(env)}.`}
+ ? `Resume processing runs in ${environmentFullTitle(env)}`
+ : `Pause processing runs in ${environmentFullTitle(env)}`}
@@ -540,7 +610,9 @@ function EnvironmentPauseResumeButton({
type="submit"
disabled={isLoading}
variant={env.paused ? "primary/medium" : "danger/medium"}
- LeadingIcon={isLoading ? : env.paused ? PlayIcon : PauseIcon}
+ LeadingIcon={
+ isLoading ? : env.paused ? PlayIcon : PauseIcon
+ }
shortcut={{ modifiers: ["mod"], key: "enter" }}
>
{env.paused ? "Resume environment" : "Pause environment"}
@@ -582,7 +654,7 @@ function QueuePauseResumeButton({
type="button"
variant="tertiary/small"
LeadingIcon={queue.paused ? PlayIcon : PauseIcon}
- leadingIconClassName={queue.paused ? "text-success" : "text-amber-500"}
+ leadingIconClassName={queue.paused ? "text-success" : "text-warning"}
>
{queue.paused ? "Resume..." : "Pause..."}
@@ -703,7 +775,7 @@ export function QueueFilters() {
const search = searchParams.get("query") ?? "";
return (
-
+
Max duration
- {run.maxDurationInSeconds ? `${run.maxDurationInSeconds}s` : "โ"}
+ {run.maxDurationInSeconds
+ ? `${run.maxDurationInSeconds}s (${formatDurationMilliseconds(
+ run.maxDurationInSeconds * 1000,
+ { style: "short" }
+ )})`
+ : "โ"}
diff --git a/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx b/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx
index 1ac03e1ac..1d96293d0 100644
--- a/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx
+++ b/apps/webapp/app/routes/resources.orgs.$organizationSlug.select-plan.tsx
@@ -678,7 +678,7 @@ export function TierPro({