feat: enhance channel configuration UI and validation

- Updated ChannelInstructionsPanel to include a button for viewing documentation, improving user guidance.
- Enhanced ChannelTokenField to support showing/hiding secret values with appropriate labels and icons.
- Refined ChannelTypeSelector to display connection type icons and improved layout for better user experience.
- Added new messages for documentation links, validation feedback, and secret management in i18n.
- Extended ChannelMeta to include optional documentation URLs for better context on configuration fields.
- Implemented credential validation logic in ChannelsPage to ensure user inputs are validated before saving.
- Introduced ChannelLogo component to display channel icons in the UI.
- Added tests for channel credential validation to ensure proper error handling and feedback.
This commit is contained in:
duanshuwen
2026-04-19 16:43:07 +08:00
parent d2e48b21d8
commit 18f12d6ce3
22 changed files with 1131 additions and 301 deletions

View File

@@ -1,47 +1,68 @@
import { BookOpen, ExternalLink } from 'lucide-react';
import type { ChannelMeta } from '../../lib/channel-meta';
type ChannelInstructionsPanelProps = {
meta: ChannelMeta;
title: string;
docsLabel: string;
diagnosticsNote: string;
viewDocsLabel: string;
};
export default function ChannelInstructionsPanel({
meta,
title,
docsLabel,
diagnosticsNote,
viewDocsLabel,
}: ChannelInstructionsPanelProps) {
return (
<section className="rounded-[16px] border border-[#E5E8EE] bg-[#FAFBFC] p-4 dark:border-[#2a2a2d] dark:bg-[#17171a]">
<div className="text-[13px] font-medium text-[#171717] dark:text-gray-100">{title}</div>
<div className="mt-1 text-[12px] leading-[18px] text-[#99A0AE] dark:text-gray-500">
{docsLabel}
</div>
const canOpenDocs = Boolean(meta.docsUrl);
<div className="mt-3 space-y-2">
<div className="rounded-[12px] border border-dashed border-[#DCE5F1] bg-white px-3 py-2 text-[12px] leading-[18px] text-[#525866] dark:border-[#2a2a2d] dark:bg-[#101013] dark:text-gray-300">
{meta.description}
function openDocs(): void {
if (!meta.docsUrl) return;
try {
if (window.electron?.openExternal) {
window.electron.openExternal(meta.docsUrl);
return;
}
} catch {
// Fall back to window.open below.
}
window.open(meta.docsUrl, '_blank', 'noopener,noreferrer');
}
return (
<section className="rounded-[28px] border border-black/10 bg-[#f7f3eb] px-8 py-7 shadow-[0_10px_30px_rgba(15,23,42,0.06)] dark:border-white/10 dark:bg-[#232327]">
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0">
<h3 className="text-[18px] font-semibold text-[#171717] dark:text-[#f3f4f6]">
{title}
</h3>
<p className="mt-3 max-w-3xl text-[15px] leading-7 text-[#667085] dark:text-gray-300">
{meta.description}
</p>
</div>
{meta.instructions.length > 0 ? (
<ol className="space-y-2">
{meta.instructions.map((instruction) => (
<li
key={instruction}
className="rounded-[12px] border border-[#E5E8EE] bg-white px-3 py-2 text-[12px] leading-[18px] text-[#525866] dark:border-[#2a2a2d] dark:bg-[#101013] dark:text-gray-300"
>
{instruction}
</li>
))}
</ol>
{canOpenDocs ? (
<button
type="button"
className="inline-flex h-12 shrink-0 items-center gap-2 rounded-full border border-black/10 bg-[#fbf8f1] px-5 text-[15px] font-semibold text-[#1f2937] transition-colors hover:bg-black/5 dark:border-white/10 dark:bg-[#2a2a2f] dark:text-gray-100 dark:hover:bg-white/10"
onClick={openDocs}
>
<BookOpen className="h-4 w-4" />
<span>{viewDocsLabel}</span>
<ExternalLink className="h-4 w-4" />
</button>
) : null}
</div>
<div className="mt-3 rounded-[12px] bg-[#EFF6FF] px-3 py-2 text-[12px] leading-[18px] text-[#2B4E8C] dark:bg-[#1d2633] dark:text-[#93c5fd]">
{diagnosticsNote}
</div>
{meta.instructions.length > 0 ? (
<ol className="mt-7 space-y-3 pl-7 text-[15px] leading-8 text-[#667085] dark:text-gray-300">
{meta.instructions.map((instruction) => (
<li key={instruction} className="list-decimal marker:font-semibold marker:text-[#4b5563] dark:marker:text-gray-300">
{instruction}
</li>
))}
</ol>
) : null}
</section>
);
}