Use in-cluster registry for builds and deploys; improve logging and cluster ops.
Remove external registry Ingress (repo.3fase.ir) and route Kaniko push and app pulls through the internal ClusterIP registry. Add RegistryService, ensure StorageClass and pull secrets on deploy, make Elasticsearch install/repair more resilient, and add per-cluster Deploy Elastic controls in admin UI. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -120,28 +120,55 @@ function ResourcePanel({ clusterId }: { clusterId: string }) {
|
||||
);
|
||||
}
|
||||
|
||||
function CentralLoggingPanel() {
|
||||
function apiErrorMessage(err: unknown, fallback: string): string {
|
||||
const e = err as { response?: { data?: { message?: string | string[] } } };
|
||||
const msg = e?.response?.data?.message;
|
||||
if (Array.isArray(msg)) return msg.join(', ');
|
||||
if (typeof msg === 'string' && msg.trim()) return msg;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function CentralLoggingPanel({
|
||||
clusters,
|
||||
selectedClusterId,
|
||||
onSelectCluster,
|
||||
}: {
|
||||
clusters: Cluster[];
|
||||
selectedClusterId: string | null;
|
||||
onSelectCluster: (id: string) => void;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const clusterId = selectedClusterId || clusters.find((c) => c.isDefault)?.id || clusters[0]?.id;
|
||||
|
||||
const { data: status, isLoading } = useQuery({
|
||||
queryKey: ['admin-elasticsearch-status'],
|
||||
queryFn: () => api.get('/admin/elasticsearch/status').then((r) => r.data),
|
||||
queryKey: ['admin-elasticsearch-status', clusterId],
|
||||
queryFn: () =>
|
||||
api.get('/admin/elasticsearch/status', { params: clusterId ? { clusterId } : {} }).then((r) => r.data),
|
||||
enabled: !!clusterId,
|
||||
});
|
||||
|
||||
const deployMutation = useMutation({
|
||||
mutationFn: () => api.post('/admin/elasticsearch/deploy'),
|
||||
onSuccess: () => {
|
||||
mutationFn: (targetClusterId?: string) =>
|
||||
api.post('/admin/elasticsearch/deploy', null, {
|
||||
params: targetClusterId ? { clusterId: targetClusterId } : {},
|
||||
}),
|
||||
onSuccess: (res) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-elasticsearch-status'] });
|
||||
toast.success('Logging stack deployment started');
|
||||
toast.success(res.data?.message || 'Logging stack deployment started');
|
||||
},
|
||||
onError: () => toast.error('Failed to deploy logging stack'),
|
||||
onError: (err) => toast.error(apiErrorMessage(err, 'Failed to deploy logging stack')),
|
||||
});
|
||||
|
||||
const undeployMutation = useMutation({
|
||||
mutationFn: () => api.delete('/admin/elasticsearch/undeploy'),
|
||||
mutationFn: (targetClusterId?: string) =>
|
||||
api.delete('/admin/elasticsearch/undeploy', {
|
||||
params: targetClusterId ? { clusterId: targetClusterId } : {},
|
||||
}),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-elasticsearch-status'] });
|
||||
toast.success('Logging stack removed');
|
||||
},
|
||||
onError: (err) => toast.error(apiErrorMessage(err, 'Failed to remove logging stack')),
|
||||
});
|
||||
|
||||
const kibanaCmd = 'kubectl port-forward svc/kibana 5601:5601 -n logging';
|
||||
@@ -158,21 +185,34 @@ function CentralLoggingPanel() {
|
||||
End users never get Kibana access — staff use port-forward.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-wrap gap-2 items-center">
|
||||
{clusters.length > 1 && (
|
||||
<select
|
||||
className="input-field text-sm py-1.5 max-w-[200px]"
|
||||
value={clusterId || ''}
|
||||
onChange={(e) => onSelectCluster(e.target.value)}
|
||||
>
|
||||
{clusters.map((c) => (
|
||||
<option key={c.id} value={c.id}>
|
||||
{c.name}{c.isDefault ? ' (default)' : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
{!status?.deployed ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => deployMutation.mutate()}
|
||||
disabled={deployMutation.isPending}
|
||||
onClick={() => deployMutation.mutate(clusterId)}
|
||||
disabled={deployMutation.isPending || !clusterId}
|
||||
className="btn-primary text-sm"
|
||||
>
|
||||
{deployMutation.isPending ? 'Deploying…' : 'Deploy stack'}
|
||||
{deployMutation.isPending ? 'Deploying…' : 'Deploy Elastic'}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => undeployMutation.mutate()}
|
||||
disabled={undeployMutation.isPending}
|
||||
onClick={() => undeployMutation.mutate(clusterId)}
|
||||
disabled={undeployMutation.isPending || !clusterId}
|
||||
className="btn-secondary text-sm text-red-600"
|
||||
>
|
||||
Remove stack
|
||||
@@ -206,20 +246,65 @@ function CentralLoggingPanel() {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-amber-700">
|
||||
Not deployed on the default cluster. New clusters install this automatically; use Deploy for existing clusters.
|
||||
</p>
|
||||
<div className="text-sm text-amber-700 space-y-1">
|
||||
<p>
|
||||
Not ready on this cluster
|
||||
{status?.deployStatus ? ` (${status.deployStatus}${status.helmReleaseStatus ? ` · helm: ${status.helmReleaseStatus}` : ''})` : ''}.
|
||||
</p>
|
||||
<p className="text-gray-600">
|
||||
New clusters install automatically; if that failed (e.g. missing StorageClass), click Deploy Elastic to install or repair.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ClusterElasticButton({ clusterId, clusterName }: { clusterId: string; clusterName: string }) {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: status } = useQuery({
|
||||
queryKey: ['admin-elasticsearch-status', clusterId],
|
||||
queryFn: () => api.get('/admin/elasticsearch/status', { params: { clusterId } }).then((r) => r.data),
|
||||
});
|
||||
|
||||
const deployMutation = useMutation({
|
||||
mutationFn: () => api.post('/admin/elasticsearch/deploy', null, { params: { clusterId } }),
|
||||
onSuccess: (res) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-elasticsearch-status'] });
|
||||
toast.success(res.data?.message || `Elasticsearch deploy started on ${clusterName}`);
|
||||
},
|
||||
onError: (err) => toast.error(apiErrorMessage(err, `Failed to deploy Elasticsearch on ${clusterName}`)),
|
||||
});
|
||||
|
||||
if (status?.deployed) {
|
||||
return (
|
||||
<span className="text-xs text-green-700 font-medium flex items-center gap-1">
|
||||
<CheckCircle className="w-3 h-3" /> Elastic
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => deployMutation.mutate()}
|
||||
disabled={deployMutation.isPending}
|
||||
className="btn-ghost text-sm text-indigo-700"
|
||||
title="Install or repair central Elasticsearch on this cluster"
|
||||
>
|
||||
<ScrollText className="w-3 h-3 inline" />
|
||||
{deployMutation.isPending ? 'Deploying…' : 'Deploy Elastic'}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AdminClustersPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const confirm = useConfirm();
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [testingId, setTestingId] = useState<string | null>(null);
|
||||
const [expandedResources, setExpandedResources] = useState<Set<string>>(new Set());
|
||||
const [loggingClusterId, setLoggingClusterId] = useState<string | null>(null);
|
||||
const [form, setForm] = useState({
|
||||
name: '',
|
||||
description: '',
|
||||
@@ -308,7 +393,11 @@ export default function AdminClustersPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<CentralLoggingPanel />
|
||||
<CentralLoggingPanel
|
||||
clusters={clusters}
|
||||
selectedClusterId={loggingClusterId}
|
||||
onSelectCluster={setLoggingClusterId}
|
||||
/>
|
||||
|
||||
{showForm && (
|
||||
<div className="card space-y-4 animate-slide-up">
|
||||
@@ -477,6 +566,7 @@ export default function AdminClustersPage() {
|
||||
>
|
||||
<BarChart3 className="w-4 h-4 inline" /> Resources
|
||||
</button>
|
||||
<ClusterElasticButton clusterId={cluster.id} clusterName={cluster.name} />
|
||||
<button
|
||||
onClick={() => testMutation.mutate(cluster.id)}
|
||||
disabled={testingId === cluster.id}
|
||||
|
||||
Reference in New Issue
Block a user