feat: add app preview via NodePort

- Backend: getPreviewInfo() in KubernetesService auto-patches ClusterIP
  service to NodePort for direct external access
- Backend: GET /applications/:id/preview endpoint returns access URL
  with nodePort, host IP (extracted from kubeconfig), and ingress URL
- Frontend: '🌐 Preview' button on app detail page (visible when running)
  opens the deployed app in a new browser tab via NodePort URL
- Tested: service patched to NodePort 30107 successfully
This commit is contained in:
keyhan
2026-04-05 16:33:48 +03:30
parent 51e56c6996
commit a7ef4649e5
3 changed files with 108 additions and 0 deletions
@@ -176,6 +176,16 @@ export default function AppDetailPage() {
onError: () => toast.error('Failed to update resources'),
});
const previewMutation = useMutation({
mutationFn: () => api.get(`/applications/${appId}/preview`).then((r) => r.data),
onSuccess: (data: { url: string; nodePort: number; host: string; ingressUrl?: string }) => {
// Open the preview URL in a new tab
window.open(data.url, '_blank');
toast.success(`Preview opened on port ${data.nodePort}`);
},
onError: () => toast.error('Failed to get preview URL. Make sure the app is deployed.'),
});
const uploadMutation = useMutation({
mutationFn: (file: File) => {
const formData = new FormData();
@@ -314,6 +324,18 @@ export default function AppDetailPage() {
{redeployMutation.isPending ? '⏳ Rebuilding...' : '🔄 Redeploy'}
</button>
)}
{/* Preview: open the running app in a new tab */}
{isRunning && (
<button
onClick={() => previewMutation.mutate()}
disabled={previewMutation.isPending}
className="px-4 py-2 rounded-lg text-sm font-medium bg-emerald-50 text-emerald-700 hover:bg-emerald-100 border border-emerald-200 disabled:opacity-50 transition-colors"
title="Open the running application in a new browser tab"
>
{previewMutation.isPending ? '⏳ Loading...' : '🌐 Preview'}
</button>
)}
</>
)}