mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 00:52:18 +03:00
fix(live-ws, dashboard): allow 0.0.0.0 dashboard origin and stop non-square SVG image warnings (#11269)
Validated on the combined 8-PR board: live-server-allowlist suite green within the board's 88/88, typecheck:core + dashboard-typecheck clean. 0.0.0.0 as a browser Origin is loopback-equivalent (it can never name a LAN host — same treatment as headroom/detect.ts and outboundUrlGuard.ts), so the local-only posture is unchanged and the FORBIDDEN_ORIGIN reconnect flood on the dev server's printed URL stops. Thank you @Minamaged18!
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
|
||||
import { useEffect, useRef, useState, useCallback } from "react";
|
||||
import { Card, Button, ModelSelectModal } from "@/shared/components";
|
||||
import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { copyToClipboard } from "@/shared/utils/clipboard";
|
||||
import { buildOpenCodeConfigDocument } from "@/shared/services/opencodeConfig";
|
||||
@@ -643,38 +642,32 @@ export default function DefaultToolCard({
|
||||
};
|
||||
|
||||
const renderIcon = () => {
|
||||
// Tool SVGs are non-square (e.g. opencode is 234×42, cursor is 467×532).
|
||||
// next/image's dev check warns whenever the rendered aspect-ratio size
|
||||
// differs from the square width/height attributes, so these render as a
|
||||
// plain <img> capped at 32px on both axes — true ratio, no dev noise.
|
||||
const renderImg = (src: string) => (
|
||||
// eslint-disable-next-line @next/next/no-img-element -- local static SVG asset
|
||||
<img
|
||||
src={src}
|
||||
alt={tool.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="size-8 object-contain rounded-lg"
|
||||
style={{ width: "auto", height: "auto", maxWidth: 32, maxHeight: 32 }}
|
||||
onError={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.display = "none";
|
||||
}}
|
||||
/>
|
||||
);
|
||||
if (tool.image) {
|
||||
return (
|
||||
<Image
|
||||
src={tool.image}
|
||||
alt={tool.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="size-8 object-contain rounded-lg"
|
||||
sizes="32px"
|
||||
onError={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.display = "none";
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return renderImg(tool.image);
|
||||
}
|
||||
if (tool.imageLight || tool.imageDark) {
|
||||
const themedSrc = isDark
|
||||
? tool.imageDark || tool.imageLight
|
||||
: tool.imageLight || tool.imageDark;
|
||||
return (
|
||||
<Image
|
||||
src={themedSrc}
|
||||
alt={tool.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="size-8 object-contain rounded-lg"
|
||||
sizes="32px"
|
||||
onError={(e) => {
|
||||
(e.currentTarget as HTMLElement).style.display = "none";
|
||||
}}
|
||||
/>
|
||||
);
|
||||
return renderImg(themedSrc);
|
||||
}
|
||||
if (tool.icon) {
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,11 @@ export const DEFAULT_ALLOWED_ORIGINS: readonly string[] = Object.freeze([
|
||||
"http://127.0.0.1:20128",
|
||||
"http://localhost:20128",
|
||||
"http://[::1]:20128",
|
||||
// 0.0.0.0 is the "unspecified" address but browsers treat it as loopback
|
||||
// when the user pastes it into the address bar; the dashboard is reachable
|
||||
// at http://0.0.0.0:20128 and its WS Origin is exactly that string. Same
|
||||
// local-only posture as the entries above — it never refers to a LAN host.
|
||||
"http://0.0.0.0:20128",
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -401,34 +401,56 @@ const ProviderIcon = memo(function ProviderIcon({
|
||||
className={className}
|
||||
style={{ display: "inline-flex", alignItems: "center", ...style }}
|
||||
>
|
||||
<Image
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- themed local SVG asset; see the Tier 2 comment for why these use a plain <img> */}
|
||||
<img
|
||||
src={themedSrc}
|
||||
alt={providerId}
|
||||
width={size}
|
||||
height={size}
|
||||
style={{ objectFit: "contain" }}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
flex: "none",
|
||||
width: "auto",
|
||||
height: "auto",
|
||||
maxWidth: size,
|
||||
maxHeight: size,
|
||||
}}
|
||||
onError={() => setFailedAssets((current) => ({ ...current, [themedKey]: true }))}
|
||||
unoptimized
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Tier 2: Local SVG — fastest, cached separately from the JS bundle
|
||||
// Tier 2: Local SVG — fastest, cached separately from the JS bundle.
|
||||
// Rendered as a plain <img> (not next/image): provider SVGs carry their own
|
||||
// intrinsic aspect ratio (e.g. opencode.svg is 234×42), and next/image's
|
||||
// dev-mode check warns whenever the layout size differs from the square
|
||||
// width/height attributes — a false positive for non-square logos rendered
|
||||
// at fixed icon sizes. We keep `width/height` attributes for layout reserve
|
||||
// but let the intrinsic ratio win on both axes (`width/height: "auto"`) so
|
||||
// wide logos like opencode render at their true aspect ratio instead of
|
||||
// being letterboxed into a 1:1 box.
|
||||
if (hasSvg && !svgFailed) {
|
||||
return (
|
||||
<span
|
||||
className={className}
|
||||
style={{ display: "inline-flex", alignItems: "center", ...style }}
|
||||
>
|
||||
<Image
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- local static SVG asset, see comment above */}
|
||||
<img
|
||||
src={`/providers/${localSvgId}.svg`}
|
||||
alt={providerId}
|
||||
width={size}
|
||||
height={size}
|
||||
style={{ objectFit: "contain" }}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
flex: "none",
|
||||
width: "auto",
|
||||
height: "auto",
|
||||
maxWidth: size,
|
||||
maxHeight: size,
|
||||
}}
|
||||
onError={() => setFailedAssets((current) => ({ ...current, [svgKey]: true }))}
|
||||
unoptimized
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { CliCatalogEntry } from "@/shared/schemas/cliCatalog";
|
||||
import type { ToolBatchStatus } from "@/shared/types/cliBatchStatus";
|
||||
@@ -38,12 +37,18 @@ export default function CliToolCard({
|
||||
<div className="flex items-center gap-2.5">
|
||||
{/* Icon / image */}
|
||||
{imageSrc ? (
|
||||
<Image
|
||||
// Plain <img> (not next/image): tool SVGs are non-square (opencode
|
||||
// 234×42, cursor 467×532) and next/image's dev check warns whenever the
|
||||
// rendered aspect-ratio size differs from the square width/height
|
||||
// attributes. object-contain + max caps keep the logo at its true ratio.
|
||||
// eslint-disable-next-line @next/next/no-img-element -- local static SVG asset
|
||||
<img
|
||||
src={imageSrc}
|
||||
alt={tool.name}
|
||||
width={32}
|
||||
height={32}
|
||||
className="rounded-md object-contain flex-shrink-0"
|
||||
style={{ width: "auto", height: "auto", maxWidth: 32, maxHeight: 32 }}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
|
||||
@@ -130,6 +130,9 @@ describe("isOriginAllowed", () => {
|
||||
assert.equal(isOriginAllowed("http://127.0.0.1:20128", EMPTY_ENV), true);
|
||||
assert.equal(isOriginAllowed("http://localhost:20128", EMPTY_ENV), true);
|
||||
assert.equal(isOriginAllowed("http://[::1]:20128", EMPTY_ENV), true);
|
||||
// 0.0.0.0 is loopback-equivalent in the browser; the dashboard is often
|
||||
// opened at http://0.0.0.0:20128, which sends exactly that Origin on WS.
|
||||
assert.equal(isOriginAllowed("http://0.0.0.0:20128", EMPTY_ENV), true);
|
||||
});
|
||||
|
||||
it("accepts an Origin matching LIVE_WS_ALLOWED_ORIGINS", () => {
|
||||
|
||||
Reference in New Issue
Block a user