Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function forwarderHost(): string {
// don't need to change their imports.
export { TONES, FlowNode, FlowEdge } from '@/shared/components/ui/flow-diagram'

function FlowDiagram({ source, port }: { source: string; port: string }) {
function FlowDiagram({ source, port, isMaster }: { source: string; port: string; isMaster: boolean }) {
const { t } = useTranslation()
return (
<div className="mt-3 flex items-stretch justify-center gap-1 sm:gap-2">
Expand All @@ -48,13 +48,17 @@ function FlowDiagram({ source, port }: { source: string; port: string }) {
tone="neutral"
/>
<FlowEdge label={t(`${SHARED}.diagram.flow1`)} />
<FlowNode
icon={<Forward size={20} />}
title={t(`${SHARED}.diagram.forwarder`)}
sub={t(`${SHARED}.diagram.forwarderSub`, { port })}
tone="accent"
/>
<FlowEdge label={t(`${SHARED}.diagram.flow2`)} />
{!isMaster && (
<>
<FlowNode
icon={<Forward size={20} />}
title={t(`${SHARED}.diagram.forwarder`)}
sub={t(`${SHARED}.diagram.forwarderSub`, { port })}
tone="accent"
/>
<FlowEdge label={t(`${SHARED}.diagram.flow2`)} />
</>
)}
<FlowNode
icon={<ShieldCheck size={20} />}
title={t(`${SHARED}.diagram.utmstack`)}
Expand All @@ -69,16 +73,53 @@ function FlowDiagram({ source, port }: { source: string; port: string }) {

function MasterCommandSection({ selection }: { selection: RemoteEnableSelection }) {
const { t } = useTranslation()
const [tab, setTab] = useState<'simple' | 'batch'>('simple')
if (!selection.apiKey) return null
const host = forwarderHost()
// ponytail: secret is generated once server-side; user must paste it themselves
const cmd = `POST ${selection.proto}://${host}:8080/v1/logs
Authorization: Bearer <YOUR_API_KEY_SECRET>
Content-Type: application/json`
const host = typeof window === 'undefined' ? 'utmstack-host' : window.location.host
const simpleCmd = `curl -k -X POST https://${host}/ingest \\
-H "Content-Type: application/json" \\
-H "Utm-Api-Key: <YOUR_API_KEY>" \\
-d '{
"dataType": "<data-type>",
"dataSource": "<data-source>",
"timestamp": "",
"raw": "<raw-log>"
}'`
const batchCmd = `curl -X POST https://${host}/ingest \\
--cacert /path/to/ca.crt \\
-H "Content-Type: application/json" \\
-H "Utm-Api-Key: <YOUR_API_KEY>" \\
-d '{
"logs": [
{
"dataType": "",
"dataSource": "",
"timestamp": "",
"raw": ""
},
{
"dataType": "",
"dataSource": "",
"timestamp": "",
"raw": ""
}
]
}'`
return (
<Section title={t(`${SHARED}.masterHeader.title`)} step={2}>
<p className="text-sm text-foreground/90">{t(`${SHARED}.masterHeader.body`, { name: selection.apiKey.name })}</p>
<CodeBlock code={cmd} />
<div className="flex gap-0 border-b border-border mb-3">
{(['simple', 'batch'] as const).map((v) => (
<button
key={v}
onClick={() => setTab(v)}
className={cn('px-4 py-1.5 text-sm capitalize transition-colors', tab === v ? 'border-b-2 border-primary font-medium' : 'text-muted-foreground hover:text-foreground')}
>
{v === 'simple' ? t(`${SHARED}.masterHeader.tabSimple`) : t(`${SHARED}.masterHeader.tabBatch`)}
</button>
))}
</div>
<CodeBlock code={tab === 'simple' ? simpleCmd : batchCmd} />
</Section>
)
}
Expand Down Expand Up @@ -197,7 +238,7 @@ export function ForwarderGuide({ source, port, sourceType, defaultProto, childre
<div className="space-y-4">
<Section title={t(`${SHARED}.intro.title`)}>
<p className="text-sm text-foreground/90">{t(`${SHARED}.intro.body`, { source })}</p>
<FlowDiagram source={source} port={port} />
<FlowDiagram source={source} port={port} isMaster={selection.isMaster} />
<p className="mt-3 rounded-md bg-muted/40 px-3 py-2 text-[11px] text-muted-foreground">
{t(`${SHARED}.noInstall`, { source })}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ export function RemoteEnablePanel({
const initialProto = defaultProto ?? availableProtos[0]
const [collectorId, setCollectorId] = useState<number | null>(null)
const [apiKeyId, setApiKeyId] = useState<number | null>(null)
const [masterProto, setMasterProto] = useState<'http' | 'https'>('https')
const [proto, setProto] = useState<Proto>(initialProto)
const [port, setPort] = useState(() => defaultPortFor(dataType, initialProto))
const httpDefaults = httpDefaultsFor(dataType)
Expand Down Expand Up @@ -209,12 +208,12 @@ export function RemoteEnablePanel({
useEffect(() => {
const resolvedKey = apiKeys.data?.data.find((k) => k.id === apiKeyId) ?? null
onSelectionChange?.({
proto: isMaster ? masterProto : proto,
proto: isMaster ? 'https' : proto,
port,
isMaster,
apiKey: resolvedKey ? { id: resolvedKey.id, name: resolvedKey.name } : null,
})
}, [proto, masterProto, port, isMaster, apiKeyId, apiKeys.data, onSelectionChange])
}, [proto, port, isMaster, apiKeyId, apiKeys.data, onSelectionChange])

const isHttp = proto === 'http' || proto === 'https'
const needsCerts = proto === 'tls' || proto === 'https'
Expand Down Expand Up @@ -390,36 +389,21 @@ export function RemoteEnablePanel({
</div>

{isMaster && (
<div className="grid gap-3 sm:grid-cols-2">
<label className="block">
<span className="mb-1 block text-[11px] font-medium text-muted-foreground">
{t(`${ROOT}.apiKeyLabel`)}
</span>
<ApiKeyPicker
keys={apiKeys.data?.data ?? []}
value={apiKeyId}
onChange={setApiKeyId}
onAddNew={() => navigate('/settings/api-keys')}
addLabel={t(`${ROOT}.apiKeyAddNew`)}
placeholder={t(`${ROOT}.apiKeyPlaceholder`)}
emptyLabel={t(`${ROOT}.apiKeyNone`)}
disabled={apiKeys.isLoading}
/>
</label>
<label className="block">
<span className="mb-1 block text-[11px] font-medium text-muted-foreground">
{t(`${ROOT}.protoLabel`)}
</span>
<select
value={masterProto}
onChange={(e) => setMasterProto(e.target.value as 'http' | 'https')}
className="h-9 w-full rounded-md border border-border bg-background px-3 text-sm text-foreground"
>
<option value="https">HTTPS</option>
<option value="http">HTTP</option>
</select>
</label>
</div>
<label className="block">
<span className="mb-1 block text-[11px] font-medium text-muted-foreground">
{t(`${ROOT}.apiKeyLabel`)}
</span>
<ApiKeyPicker
keys={apiKeys.data?.data ?? []}
value={apiKeyId}
onChange={setApiKeyId}
onAddNew={() => navigate('/settings/api-keys')}
addLabel={t(`${ROOT}.apiKeyAddNew`)}
placeholder={t(`${ROOT}.apiKeyPlaceholder`)}
emptyLabel={t(`${ROOT}.apiKeyNone`)}
disabled={apiKeys.isLoading}
/>
</label>
)}

{!isMaster && (<>
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/shared/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -2739,7 +2739,9 @@
},
"masterHeader": {
"title": "Autorisierungs-Header",
"body": "Fügen Sie diesen Authorization-Header jeder Anfrage hinzu, die Sie an den Master senden. Das Geheimnis für \"{{name}}\" wird nur einmal bei der Erstellung des API-Schlüssels angezeigt — kopieren Sie es dann und fügen Sie es anstelle des Platzhalters unten ein."
"body": "Fügen Sie diesen Authorization-Header jeder Anfrage hinzu, die Sie an den Master senden. Das Geheimnis für \"{{name}}\" wird nur einmal bei der Erstellung des API-Schlüssels angezeigt — kopieren Sie es dann und fügen Sie es anstelle des Platzhalters unten ein.",
"tabSimple": "Einfach",
"tabBatch": "Stapel"
}
},
"aix": {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/shared/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2898,7 +2898,9 @@
},
"masterHeader": {
"title": "Authorization header",
"body": "Include this Authorization header on every request you send to Master. The secret for \"{{name}}\" is only revealed once when the API key is generated — copy it then and paste it in place of the placeholder below."
"body": "Include this Authorization header on every request you send to Master. The secret for \"{{name}}\" is only revealed once when the API key is generated — copy it then and paste it in place of the placeholder below.",
"tabSimple": "Simple",
"tabBatch": "Batch"
}
},
"aix": {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/shared/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,9 @@
},
"masterHeader": {
"title": "Cabecera de autorización",
"body": "Incluye esta cabecera Authorization en cada solicitud que envíes al Master. El secreto de \"{{name}}\" solo se muestra una vez al generar la clave API — cópialo entonces y pégalo en lugar del marcador de posición de abajo."
"body": "Incluye esta cabecera Authorization en cada solicitud que envíes al Master. El secreto de \"{{name}}\" solo se muestra una vez al generar la clave API — cópialo entonces y pégalo en lugar del marcador de posición de abajo.",
"tabSimple": "Simple",
"tabBatch": "Lote"
}
},
"cisco": {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/shared/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -2739,7 +2739,9 @@
},
"masterHeader": {
"title": "En-tête d'autorisation",
"body": "Incluez cet en-tête Authorization dans chaque requête envoyée au Master. Le secret de \"{{name}}\" n'est révélé qu'une seule fois à la génération de la clé API — copiez-le à ce moment et collez-le à la place du placeholder ci-dessous."
"body": "Incluez cet en-tête Authorization dans chaque requête envoyée au Master. Le secret de \"{{name}}\" n'est révélé qu'une seule fois à la génération de la clé API — copiez-le à ce moment et collez-le à la place du placeholder ci-dessous.",
"tabSimple": "Simple",
"tabBatch": "Lot"
}
},
"aix": {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/shared/i18n/locales/it.json
Original file line number Diff line number Diff line change
Expand Up @@ -2739,7 +2739,9 @@
},
"masterHeader": {
"title": "Intestazione di autorizzazione",
"body": "Includi questa intestazione Authorization in ogni richiesta inviata al Master. Il segreto di \"{{name}}\" viene rivelato una sola volta al momento della generazione della chiave API — copialo allora e incollalo al posto del segnaposto qui sotto."
"body": "Includi questa intestazione Authorization in ogni richiesta inviata al Master. Il segreto di \"{{name}}\" viene rivelato una sola volta al momento della generazione della chiave API — copialo allora e incollalo al posto del segnaposto qui sotto.",
"tabSimple": "Semplice",
"tabBatch": "Batch"
}
},
"aix": {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/shared/i18n/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,9 @@
},
"masterHeader": {
"title": "Cabeçalho de autorização",
"body": "Inclua este cabeçalho Authorization em cada requisição enviada ao Master. O segredo de \"{{name}}\" é revelado apenas uma vez ao gerar a chave de API — copie-o naquele momento e cole no lugar do marcador abaixo."
"body": "Inclua este cabeçalho Authorization em cada requisição enviada ao Master. O segredo de \"{{name}}\" é revelado apenas uma vez ao gerar a chave de API — copie-o naquele momento e cole no lugar do marcador abaixo.",
"tabSimple": "Simples",
"tabBatch": "Lote"
}
},
"cisco": {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/shared/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -2621,7 +2621,9 @@
},
"masterHeader": {
"title": "Заголовок авторизации",
"body": "Включайте этот заголовок Authorization в каждый запрос, отправляемый на Master. Секрет ключа «{{name}}» отображается только один раз при его создании — скопируйте его тогда и вставьте вместо заполнителя ниже."
"body": "Включайте этот заголовок Authorization в каждый запрос, отправляемый на Master. Секрет ключа «{{name}}» отображается только один раз при его создании — скопируйте его тогда и вставьте вместо заполнителя ниже.",
"tabSimple": "Одиночный",
"tabBatch": "Пакетный"
}
},
"aix": {
Expand Down
Loading