import { BlockWrapper } from 'app/components/block-wrapper' import { BlockByDataInt as BlockByData } from 'app/components/block' import { NeoButton, Modal } from 'app/design/controls' import { fetcher } from 'app/lib/fetcher'; import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import Html from 'app/ui/atoms/html' import { Row, View } from 'app/design/view'; import { Text } from 'app/design/typography'; import Confirm from 'app/ui/molecules/dialogs/confirm'; import Badge from 'app/ui/molecules/profile/badge'; import emitter, { EVENTS } from 'app/context/emitter'; import Link from 'app/ui/atoms/link'; import { looksLikeHtml } from 'app/lib/util'; import { isFormResponseComplete } from 'app/lib/form/form-helpers'; import { useFocusEffect } from 'app/lib/hooks/router'; import { useTranslation } from 'react-i18next'; function getFormNamesFromBlock(block) { const content = Array.isArray(block?.content) ? block.content : []; return content .filter((item) => item?.type === 'form') .map((item) => item?.data?.params?.display || item?.name) .filter((name) => name && !name.includes('_delete')); } function shouldCloseAfterFormResponse(responseData) { return isFormResponseComplete(responseData); } function getHostname(url) { if (!url || typeof url !== 'string') return ''; try { return new URL(url).hostname.replace(/^www\./, ''); } catch { return ''; } } function getMainDomain(containers) { if (!Array.isArray(containers) || !containers.length) return ''; const neo = containers.find((container) => /neo/i.test(container?.title || '')); const neoHost = getHostname(neo?.url); if (neoHost) return neoHost; const hosts = containers .map((container) => getHostname(container?.url)) .filter(Boolean) .sort((a, b) => a.length - b.length); return hosts[0] || ''; } function getStatusBadgeData(text) { if (!text || typeof text !== 'string') return null; const normalized = text.trim().toLowerCase(); let color = 'neutral'; if (/(run|active|ready|online|live|ok|success|healthy)/.test(normalized)) { color = 'green'; } else if (/(fail|error|down|stop|destroy|crash|unhealthy)/.test(normalized)) { color = 'red'; } else if (/(deploy|start|pend|wait|progress|build|creat|provision|queue)/.test(normalized)) { color = 'amber'; } return { text: text.trim(), color }; } function isDestructiveButton(item) { return /(tear|delete|destroy|remove|down|deploy)/i.test(item?.title || item?.name || ''); } export default function ElementDeploy({ data, blockWrapperProps, url }) { const { t } = useTranslation(); const [blockData, setBlockData] = useState(null); const [formBlock, setFormBlock] = useState(null); const [showConfirm, setShowConfirm] = useState(false); const dataUrlRef = useRef(data?.data_url); const refreshBlockData = useCallback(async () => { const dataUrl = dataUrlRef.current; if (!dataUrl) return; const sResponse = await fetcher(`/api.php?r=${dataUrl}`); setBlockData(sResponse?.data[0]?.data); }, []); useEffect(() => { refreshBlockData(); }, [refreshBlockData]); useFocusEffect( useCallback(() => { refreshBlockData(); }, [refreshBlockData]) ); useEffect(() => { dataUrlRef.current = blockData?.data_url ?? data?.data_url ?? dataUrlRef.current; }, [blockData?.data_url, data?.data_url]); const handleOpenDeployForm = async (item) => { if (item.form_url) { const sResponse = await fetcher(`/api.php?r=${item.form_url}`); setFormBlock({ content: sResponse.data, designbox_id: 0, title: t('Deployment settings') }); } if (item.request_url) { setShowConfirm(`/api.php?r=${item.request_url}`) } }; const handleCloseDeployForm = useCallback(() => { setFormBlock(null); refreshBlockData(); }, [refreshBlockData]); useEffect(() => { if (!formBlock) return; const formNames = getFormNamesFromBlock(formBlock); if (!formNames.length) return; const subscriptions = formNames.map((formName) => emitter.addListener(EVENTS.form(formName), (event) => { if (event.action !== 'received') return; if (!shouldCloseAfterFormResponse(event.data)) return; handleCloseDeployForm(); }) ); return () => subscriptions.forEach((subscription) => subscription.remove()); }, [formBlock, handleCloseDeployForm]); useEffect(() => { if (!blockData?.text || blockData?.buttons.length > 0 || !blockData.data_url) return; const dataUrl = blockData.data_url; const poll = async () => { const sResponse = await fetcher(`/api.php?r=${dataUrl}`); setBlockData(sResponse?.data[0]?.data); }; poll(); const intervalId = setInterval(poll, 5000); return () => clearInterval(intervalId); }, [blockData?.text, blockData?.data_url]); const mainDomain = useMemo( () => getMainDomain(blockData?.containers), [blockData?.containers] ); const statusBadge = useMemo( () => getStatusBadgeData(blockData?.text), [blockData?.text] ); const buttons = Array.isArray(blockData?.buttons) ? blockData.buttons : []; const containers = Array.isArray(blockData?.containers) ? blockData.containers : []; return ( {(!!statusBadge || buttons.length > 0) && ( {(!!statusBadge && looksLikeHtml(statusBadge.text)) && ( )} {(!!statusBadge && !looksLikeHtml(statusBadge.text)) && ( {t('Status:')} )} {buttons.length > 0 && ( {buttons.map((item, index) => { const destructive = isDestructiveButton(item); const buttonStyle = destructive || item?.primary ? 'borderedProminent' : 'bordered'; const buttonLabel = String(item?.title || item?.name || '').trim(); if (!buttonLabel) return null; return ( 1 ? "small" : "xlarge"} role={destructive ? 'destructive' : undefined} accessibilityLabel={buttonLabel} onPress={() => handleOpenDeployForm(item)} > {buttonLabel} ); })} )} )} {containers.length > 0 && ( {containers.map((container, index) => ( {!!container?.title && ( {container.title} )} {!!container?.url && ( {container.url} )} {!!container?.info && ( {container.info} )} ))} )} {!!formBlock && ( )} setShowConfirm(false)} handleOk={async () => { await fetcher(showConfirm); setShowConfirm(false); handleCloseDeployForm() }} /> ); }