feat(node): Add tags

This commit is contained in:
web@ppanel 2025-01-13 21:37:56 +07:00
parent f26f1c24ae
commit f408fdffae
28 changed files with 132 additions and 14 deletions

View File

@ -91,6 +91,7 @@ const protocolConfigSchema = z.discriminatedUnion('protocol', [
const baseFormSchema = z.object({
name: z.string(),
tags: z.array(z.string()).nullish().default([]),
server_addr: z.string(),
speed_limit: z.number().nullish(),
traffic_ratio: z.number().default(1),

View File

@ -35,6 +35,7 @@ import { Tabs, TabsList, TabsTrigger } from '@workspace/ui/components/tabs';
import { Combobox } from '@workspace/ui/custom-components/combobox';
import { ArrayInput } from '@workspace/ui/custom-components/dynamic-Inputs';
import { EnhancedInput } from '@workspace/ui/custom-components/enhanced-input';
import TagInput from '@workspace/ui/custom-components/tag-input';
import { cn } from '@workspace/ui/lib/utils';
import { unitConversion } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
@ -63,6 +64,7 @@ export default function NodeForm<T extends { [x: string]: any }>({
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
tags: [],
traffic_ratio: 1,
protocol: 'shadowsocks',
...initialValues,
@ -158,6 +160,23 @@ export default function NodeForm<T extends { [x: string]: any }>({
)}
/>
</div>
<FormField
control={form.control}
name='tags'
render={({ field }) => (
<FormItem>
<FormLabel>{t('form.tags')}</FormLabel>
<FormControl>
<TagInput
placeholder={t('form.tagsPlaceholder')}
value={field.value || []}
onChange={(value) => form.setValue(field.name, value)}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className='grid grid-cols-3 gap-2'>
<FormField
control={form.control}

View File

@ -116,13 +116,6 @@ export default function NodeTable() {
);
},
},
{
accessorKey: 'status',
header: t('status'),
cell: ({ row }) => {
return <NodeStatusCell status={row.original?.status} />;
},
},
{
accessorKey: 'name',
header: t('name'),
@ -131,6 +124,13 @@ export default function NodeTable() {
accessorKey: 'server_addr',
header: t('serverAddr'),
},
{
accessorKey: 'status',
header: t('status'),
cell: ({ row }) => {
return <NodeStatusCell status={row.original?.status} />;
},
},
{
accessorKey: 'speed_limit',
header: t('speedLimit'),
@ -152,6 +152,24 @@ export default function NodeTable() {
return name ? <Badge variant='outline'>{name}</Badge> : '--';
},
},
{
accessorKey: 'tags',
header: t('tags'),
cell: ({ row }) => {
const tags = (row.getValue('tags') as string[]) || [];
return tags.length > 0 ? (
<div className='flex gap-1'>
{tags.map((tag) => (
<Badge key={tag} variant='outline'>
{tag}
</Badge>
))}
</div>
) : (
'--'
);
},
},
]}
params={[
{

View File

@ -100,6 +100,8 @@
"serverName": "Název služby",
"speedLimit": "Omezení rychlosti",
"speedLimitPlaceholder": "Bez omezení",
"tags": "Štítky",
"tagsPlaceholder": "Použijte Enter nebo čárku (,) pro zadání více štítků",
"trafficRatio": "Poměr provozu",
"transport": "Přenosový protokol",
"transportConfig": "Nastavení přenosového protokolu",
@ -120,6 +122,7 @@
"serverAddr": "Adresa serveru",
"speedLimit": "Omezení rychlosti",
"status": "Stav",
"tags": "Štítky",
"trafficRatio": "Poměr provozu",
"type": "Typ",
"updateSuccess": "Úspěšně aktualizováno",

View File

@ -100,6 +100,8 @@
"serverName": "Dienstname",
"speedLimit": "Geschwindigkeitsbegrenzung",
"speedLimitPlaceholder": "Keine Begrenzung",
"tags": "Tags",
"tagsPlaceholder": "Verwenden Sie die Eingabetaste oder das Komma (,), um mehrere Tags einzugeben",
"trafficRatio": "Verkehrsrate",
"transport": "Transportprotokoll",
"transportConfig": "Transportprotokollkonfiguration",
@ -120,6 +122,7 @@
"serverAddr": "Serveradresse",
"speedLimit": "Geschwindigkeitsbegrenzung",
"status": "Status",
"tags": "Stichwörter",
"trafficRatio": "Verkehrsrate",
"type": "Typ",
"updateSuccess": "Erfolgreich aktualisiert",

View File

@ -100,6 +100,8 @@
"serverName": "Service Name",
"speedLimit": "Speed Limit",
"speedLimitPlaceholder": "Unlimited",
"tags": "Tags",
"tagsPlaceholder": "Use Enter or comma (,) to enter multiple tags",
"trafficRatio": "Traffic Rate",
"transport": "Transport Protocol Configuration",
"transportConfig": "Transport Protocol Configuration",
@ -120,6 +122,7 @@
"serverAddr": "Server Address",
"speedLimit": "Speed Limit",
"status": "Status",
"tags": "Tags",
"trafficRatio": "Traffic Rate",
"type": "Type",
"updateSuccess": "Update successful",

View File

@ -100,6 +100,8 @@
"serverName": "Nombre del servicio",
"speedLimit": "Límite de velocidad",
"speedLimitPlaceholder": "Sin límite",
"tags": "Etiquetas",
"tagsPlaceholder": "Usa Enter o coma (,) para ingresar múltiples etiquetas",
"trafficRatio": "Tasa de tráfico",
"transport": "Protocolo de transporte",
"transportConfig": "Configuración del protocolo de transporte",
@ -120,6 +122,7 @@
"serverAddr": "Dirección del servidor",
"speedLimit": "Límite de velocidad",
"status": "Estado",
"tags": "Etiquetas",
"trafficRatio": "Tasa de tráfico",
"type": "Tipo",
"updateSuccess": "Actualización exitosa",

View File

@ -100,6 +100,8 @@
"serverName": "Nombre del servicio",
"speedLimit": "Límite de velocidad",
"speedLimitPlaceholder": "Sin límite",
"tags": "Etiquetas",
"tagsPlaceholder": "Usa Enter o coma (,) para ingresar múltiples etiquetas",
"trafficRatio": "Tasa de tráfico",
"transport": "Protocolo de transporte",
"transportConfig": "Configuración del protocolo de transporte",
@ -120,6 +122,7 @@
"serverAddr": "Dirección del servidor",
"speedLimit": "Límite de velocidad",
"status": "Estado",
"tags": "Etiquetas",
"trafficRatio": "Tasa de tráfico",
"type": "Tipo",
"updateSuccess": "Actualización exitosa",

View File

@ -100,6 +100,8 @@
"serverName": "نام سرویس",
"speedLimit": "محدودیت سرعت",
"speedLimitPlaceholder": "نامحدود",
"tags": "برچسب‌ها",
"tagsPlaceholder": "برای وارد کردن چندین برچسب از Enter یا ویرگول (,) استفاده کنید",
"trafficRatio": "نرخ ترافیک",
"transport": "پیکربندی پروتکل انتقال",
"transportConfig": "پیکربندی پروتکل انتقال",
@ -120,6 +122,7 @@
"serverAddr": "آدرس سرور",
"speedLimit": "محدودیت سرعت",
"status": "وضعیت",
"tags": "برچسب‌ها",
"trafficRatio": "نرخ ترافیک",
"type": "نوع",
"updateSuccess": "به‌روزرسانی موفقیت‌آمیز بود",

View File

@ -100,6 +100,8 @@
"serverName": "Palvelimen nimi",
"speedLimit": "Nopeusrajoitus",
"speedLimitPlaceholder": "Ei rajoitusta",
"tags": "Tunnisteet",
"tagsPlaceholder": "Käytä Enteriä tai pilkkua (,) syöttääksesi useita tunnisteita",
"trafficRatio": "Liikenteen nopeus",
"transport": "Siirtoprotokolla",
"transportConfig": "Siirtoprotokollan asetukset",
@ -120,6 +122,7 @@
"serverAddr": "Palvelimen osoite",
"speedLimit": "Nopeusrajoitus",
"status": "Tila",
"tags": "Tunnisteet",
"trafficRatio": "Liikenteen nopeus",
"type": "Tyyppi",
"updateSuccess": "Päivitys onnistui",

View File

@ -100,6 +100,8 @@
"serverName": "Nom du service",
"speedLimit": "Limite de vitesse",
"speedLimitPlaceholder": "Illimité",
"tags": "Étiquettes",
"tagsPlaceholder": "Utilisez Entrée ou une virgule (,) pour entrer plusieurs étiquettes",
"trafficRatio": "Taux de trafic",
"transport": "Protocole de transport",
"transportConfig": "Configuration du protocole de transport",
@ -120,6 +122,7 @@
"serverAddr": "Adresse du serveur",
"speedLimit": "Limite de vitesse",
"status": "Statut",
"tags": "Étiquettes",
"trafficRatio": "Taux de trafic",
"type": "Type",
"updateSuccess": "Mise à jour réussie",

View File

@ -100,6 +100,8 @@
"serverName": "सेवा नाम",
"speedLimit": "गति सीमा",
"speedLimitPlaceholder": "कोई सीमा नहीं",
"tags": "टैग्स",
"tagsPlaceholder": "कई टैग्स दर्ज करने के लिए एंटर या कॉमा (,) का उपयोग करें",
"trafficRatio": "ट्रैफिक दर",
"transport": "ट्रांसपोर्ट प्रोटोकॉल",
"transportConfig": "ट्रांसपोर्ट प्रोटोकॉल कॉन्फ़िगरेशन",
@ -120,6 +122,7 @@
"serverAddr": "सर्वर पता",
"speedLimit": "गति सीमा",
"status": "स्थिति",
"tags": "टैग्स",
"trafficRatio": "ट्रैफिक दर",
"type": "प्रकार",
"updateSuccess": "सफलतापूर्वक अपडेट किया गया",

View File

@ -100,6 +100,8 @@
"serverName": "Szolgáltatás neve",
"speedLimit": "Sebességkorlátozás",
"speedLimitPlaceholder": "Korlátlan",
"tags": "Címkék",
"tagsPlaceholder": "Használja az Entert vagy a vesszőt (,) több címke megadásához",
"trafficRatio": "Forgalmi arány",
"transport": "Szállítási protokoll",
"transportConfig": "Szállítási protokoll beállítások",
@ -120,6 +122,7 @@
"serverAddr": "Szerver cím",
"speedLimit": "Sebességkorlátozás",
"status": "Állapot",
"tags": "Címkék",
"trafficRatio": "Forgalmi arány",
"type": "Típus",
"updateSuccess": "Sikeres frissítés",

View File

@ -100,6 +100,8 @@
"serverName": "サービス名",
"speedLimit": "速度制限",
"speedLimitPlaceholder": "無制限",
"tags": "タグ",
"tagsPlaceholder": "Enterキーまたはカンマ,)を使用して複数のタグを入力",
"trafficRatio": "トラフィックレート",
"transport": "トランスポートプロトコル",
"transportConfig": "トランスポートプロトコル設定",
@ -120,6 +122,7 @@
"serverAddr": "サーバーアドレス",
"speedLimit": "速度制限",
"status": "ステータス",
"tags": "タグ",
"trafficRatio": "トラフィックレート",
"type": "タイプ",
"updateSuccess": "更新成功",

View File

@ -100,6 +100,8 @@
"serverName": "서비스 이름",
"speedLimit": "속도 제한",
"speedLimitPlaceholder": "제한 없음",
"tags": "태그",
"tagsPlaceholder": "여러 태그를 입력하려면 Enter 또는 쉼표(,)를 사용하세요",
"trafficRatio": "트래픽 비율",
"transport": "전송 프로토콜",
"transportConfig": "전송 프로토콜 설정",
@ -120,6 +122,7 @@
"serverAddr": "서버 주소",
"speedLimit": "속도 제한",
"status": "상태",
"tags": "태그",
"trafficRatio": "트래픽 비율",
"type": "유형",
"updateSuccess": "업데이트 성공",

View File

@ -100,6 +100,8 @@
"serverName": "Tjenestenavn",
"speedLimit": "Hastighetsbegrensning",
"speedLimitPlaceholder": "Ingen begrensning",
"tags": "Merker",
"tagsPlaceholder": "Bruk Enter eller komma (,) for å legge inn flere merker",
"trafficRatio": "Trafikkhastighet",
"transport": "Transportprotokoll",
"transportConfig": "Transportprotokollkonfigurasjon",
@ -120,6 +122,7 @@
"serverAddr": "Serveradresse",
"speedLimit": "Hastighetsbegrensning",
"status": "Status",
"tags": "Merker",
"trafficRatio": "Trafikkhastighet",
"type": "Type",
"updateSuccess": "Oppdatering vellykket",

View File

@ -100,6 +100,8 @@
"serverName": "Nazwa usługi",
"speedLimit": "Ograniczenie prędkości",
"speedLimitPlaceholder": "Bez ograniczeń",
"tags": "Tagi",
"tagsPlaceholder": "Użyj Enter lub przecinka (,) aby wprowadzić wiele tagów",
"trafficRatio": "Wskaźnik ruchu",
"transport": "Protokół transportowy",
"transportConfig": "Konfiguracja protokołu transportowego",
@ -120,6 +122,7 @@
"serverAddr": "Adres serwera",
"speedLimit": "Ograniczenie prędkości",
"status": "Status",
"tags": "Tagi",
"trafficRatio": "Wskaźnik ruchu",
"type": "Typ",
"updateSuccess": "Aktualizacja zakończona pomyślnie",

View File

@ -100,6 +100,8 @@
"serverName": "Nome do Serviço",
"speedLimit": "Limite de Velocidade",
"speedLimitPlaceholder": "Sem Limite",
"tags": "Etiquetas",
"tagsPlaceholder": "Use Enter ou vírgula (,) para inserir várias etiquetas",
"trafficRatio": "Taxa de Tráfego",
"transport": "Protocolo de Transporte",
"transportConfig": "Configuração do Protocolo de Transporte",
@ -120,6 +122,7 @@
"serverAddr": "Endereço do Servidor",
"speedLimit": "Limite de Velocidade",
"status": "Status",
"tags": "Etiquetas",
"trafficRatio": "Taxa de Tráfego",
"type": "Tipo",
"updateSuccess": "Atualizado com sucesso",

View File

@ -100,6 +100,8 @@
"serverName": "Nume serviciu",
"speedLimit": "Limită de viteză",
"speedLimitPlaceholder": "Nelimitat",
"tags": "Etichete",
"tagsPlaceholder": "Folosiți Enter sau virgulă (,) pentru a introduce mai multe etichete",
"trafficRatio": "Rata de trafic",
"transport": "Protocol de transport",
"transportConfig": "Configurație protocol de transport",
@ -120,6 +122,7 @@
"serverAddr": "Adresă server",
"speedLimit": "Limită de viteză",
"status": "Stare",
"tags": "Etichete",
"trafficRatio": "Rata de trafic",
"type": "Tip",
"updateSuccess": "Actualizat cu succes",

View File

@ -100,6 +100,8 @@
"serverName": "Имя сервера",
"speedLimit": "Ограничение скорости",
"speedLimitPlaceholder": "Без ограничений",
"tags": "Теги",
"tagsPlaceholder": "Используйте Enter или запятую (,), чтобы ввести несколько тегов",
"trafficRatio": "Коэффициент трафика",
"transport": "Транспортный протокол",
"transportConfig": "Настройки транспортного протокола",
@ -120,6 +122,7 @@
"serverAddr": "Адрес сервера",
"speedLimit": "Ограничение скорости",
"status": "Статус",
"tags": "Теги",
"trafficRatio": "Коэффициент трафика",
"type": "Тип",
"updateSuccess": "Успешно обновлено",

View File

@ -100,6 +100,8 @@
"serverName": "ชื่อบริการ",
"speedLimit": "จำกัดความเร็ว",
"speedLimitPlaceholder": "ไม่จำกัด",
"tags": "แท็ก",
"tagsPlaceholder": "ใช้ Enter หรือเครื่องหมายจุลภาค (,) เพื่อป้อนแท็กหลายรายการ",
"trafficRatio": "อัตราการจราจร",
"transport": "โปรโตคอลการส่ง",
"transportConfig": "การตั้งค่าโปรโตคอลการส่ง",
@ -120,6 +122,7 @@
"serverAddr": "ที่อยู่เซิร์ฟเวอร์",
"speedLimit": "จำกัดความเร็ว",
"status": "สถานะ",
"tags": "แท็ก",
"trafficRatio": "อัตราการจราจร",
"type": "ประเภท",
"updateSuccess": "อัปเดตสำเร็จ",

View File

@ -100,6 +100,8 @@
"serverName": "Sunucu Adı",
"speedLimit": "Hız Sınırı",
"speedLimitPlaceholder": "Sınırsız",
"tags": "Etiketler",
"tagsPlaceholder": "Birden fazla etiket girmek için Enter veya virgül (,) kullanın",
"trafficRatio": "Trafik Oranı",
"transport": "Taşıma Protokolü",
"transportConfig": "Taşıma Protokolü Ayarları",
@ -120,6 +122,7 @@
"serverAddr": "Sunucu Adresi",
"speedLimit": "Hız Sınırı",
"status": "Durum",
"tags": "Etiketler",
"trafficRatio": "Trafik Oranı",
"type": "Tür",
"updateSuccess": "Başarıyla güncellendi",

View File

@ -100,6 +100,8 @@
"serverName": "Назва сервісу",
"speedLimit": "Обмеження швидкості",
"speedLimitPlaceholder": "Без обмежень",
"tags": "Теги",
"tagsPlaceholder": "Використовуйте Enter або кому (,) для введення декількох тегів",
"trafficRatio": "Швидкість трафіку",
"transport": "Транспортний протокол",
"transportConfig": "Налаштування транспортного протоколу",
@ -120,6 +122,7 @@
"serverAddr": "Адреса сервера",
"speedLimit": "Обмеження швидкості",
"status": "Статус",
"tags": "Теги",
"trafficRatio": "Швидкість трафіку",
"type": "Тип",
"updateSuccess": "Успішно оновлено",

View File

@ -100,6 +100,8 @@
"serverName": "Tên dịch vụ",
"speedLimit": "Giới hạn tốc độ",
"speedLimitPlaceholder": "Không giới hạn",
"tags": "Thẻ",
"tagsPlaceholder": "Nhấn Enter hoặc dấu phẩy (,) để nhập nhiều thẻ",
"trafficRatio": "Tỷ lệ lưu lượng",
"transport": "Giao thức truyền tải",
"transportConfig": "Cấu hình giao thức truyền tải",
@ -120,6 +122,7 @@
"serverAddr": "Địa chỉ máy chủ",
"speedLimit": "Giới hạn tốc độ",
"status": "Trạng thái",
"tags": "Thẻ",
"trafficRatio": "Tỷ lệ lưu lượng",
"type": "Loại",
"updateSuccess": "Cập nhật thành công",

View File

@ -100,6 +100,8 @@
"serverName": "服务名称",
"speedLimit": "速度限制",
"speedLimitPlaceholder": "无限制",
"tags": "标签",
"tagsPlaceholder": "使用回车或逗号(,)输入多个标签",
"trafficRatio": "流量速率",
"transport": "传输协议",
"transportConfig": "传输协议配置",
@ -120,6 +122,7 @@
"serverAddr": "服务器地址",
"speedLimit": "速度限制",
"status": "状态",
"tags": "标签",
"trafficRatio": "流量速率",
"type": "类型",
"updateSuccess": "更新成功",

View File

@ -100,6 +100,8 @@
"serverName": "服務名稱",
"speedLimit": "速度限制",
"speedLimitPlaceholder": "無限制",
"tags": "標籤",
"tagsPlaceholder": "使用 Enter 或逗號 (,) 輸入多個標籤",
"trafficRatio": "流量速率",
"transport": "傳輸協議",
"transportConfig": "傳輸協議配置",
@ -120,6 +122,7 @@
"serverAddr": "伺服器地址",
"speedLimit": "速度限制",
"status": "狀態",
"tags": "標籤",
"trafficRatio": "流量速率",
"type": "類型",
"updateSuccess": "更新成功",

View File

@ -1,5 +1,5 @@
// @ts-ignore
// API 更新时间:
// API 唯一标识:
import * as announcement from './announcement';

View File

@ -7,9 +7,10 @@ interface TagInputProps {
value?: string[];
onChange?: (tags: string[]) => void;
placeholder?: string;
separator?: string;
}
export function TagInput({ value = [], onChange, placeholder }: TagInputProps) {
export function TagInput({ value = [], onChange, placeholder, separator = ',' }: TagInputProps) {
const [inputValue, setInputValue] = useState('');
const [tags, setTags] = useState<string[]>(value);
@ -17,17 +18,27 @@ export function TagInput({ value = [], onChange, placeholder }: TagInputProps) {
setTags(value.map((tag) => tag.trim()).filter((tag) => tag));
}, [value]);
function normalizeInput(input: string) {
// 将中文逗号替换为英文逗号
return input.replace(//g, ',');
}
function addTag() {
const newTag = inputValue.trim();
if (newTag && !tags.includes(newTag)) {
const newTags = [...tags, newTag];
updateTags(newTags);
const normalizedInput = normalizeInput(inputValue);
const newTags = normalizedInput
.split(separator)
.map((tag) => tag.trim())
.filter((tag) => tag && !tags.includes(tag));
if (newTags.length > 0) {
const updatedTags = [...tags, ...newTags];
updateTags(updatedTags);
}
setInputValue('');
}
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === 'Enter') {
if (event.key === 'Enter' || event.key === separator || event.key === '') {
event.preventDefault();
addTag();
} else if (event.key === 'Backspace' && inputValue === '') {