+ {application.labels.map((label, index) => (
+
+ {label}
+
+ ))}
+
+ )}
diff --git a/view/app/self-host/components/application-details/labels.tsx b/view/app/self-host/components/application-details/labels.tsx
new file mode 100644
index 000000000..605f1c894
--- /dev/null
+++ b/view/app/self-host/components/application-details/labels.tsx
@@ -0,0 +1,95 @@
+'use client';
+import React, { useEffect, useRef, useState } from 'react';
+import { Input } from '@/components/ui/input';
+import { Plus } from 'lucide-react';
+import { Badge } from '@/components/ui/badge';
+import { useUpdateApplicationLabelsMutation } from '@/redux/services/deploy/applicationsApi';
+
+interface LabelsProps {
+ applicationId: string;
+ labels?: string[];
+ onLabelsChange?: (labels: string[]) => void;
+ isEditable?: boolean;
+}
+
+export default function Labels({ applicationId, labels = [], onLabelsChange, isEditable = true }: LabelsProps) {
+ const [localLabels, setLocalLabels] = useState
(labels);
+ const [isAdding, setIsAdding] = useState(false);
+ const [newLabel, setNewLabel] = useState('');
+ const inputRef = useRef(null);
+ const [updateLabels, { isLoading }] = useUpdateApplicationLabelsMutation();
+
+ useEffect(() => {
+ setLocalLabels(labels);
+ }, [labels]);
+
+ useEffect(() => {
+ if (isAdding && inputRef.current) inputRef.current.focus();
+ }, [isAdding]);
+
+ const saveLabels = async (updated: string[]) => {
+ await updateLabels({ id: applicationId, labels: updated }).unwrap();
+ setLocalLabels(updated);
+ onLabelsChange?.(updated);
+ };
+
+ const handleAdd = async () => {
+ const value = newLabel.trim();
+ if (!value) {
+ setIsAdding(false);
+ setNewLabel('');
+ return;
+ }
+ const updated = [...localLabels, value];
+ await saveLabels(updated);
+ setIsAdding(false);
+ setNewLabel('');
+ };
+
+ const handleKeyDown = async (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ await handleAdd();
+ } else if (e.key === 'Escape') {
+ setIsAdding(false);
+ setNewLabel('');
+ }
+ };
+
+ return (
+
+ {isEditable ? (
+ <>
+ {isAdding ? (
+
setNewLabel(e.target.value)}
+ onKeyDown={handleKeyDown}
+ onBlur={handleAdd}
+ className="h-6 w-28 text-xs"
+ placeholder="New label"
+ disabled={isLoading}
+ />
+ ) : (
+
+ )}
+ >
+ ) : (
+ <>
+ {localLabels.map((label, index) => (
+
+ {label}
+
+ ))}
+ >
+ )}
+
+ );
+}
diff --git a/view/app/self-host/components/application.tsx b/view/app/self-host/components/application.tsx
index ab32d57bd..da31542e4 100644
--- a/view/app/self-host/components/application.tsx
+++ b/view/app/self-host/components/application.tsx
@@ -13,6 +13,8 @@ import { useRouter } from 'next/navigation';
import { Application } from '@/redux/types/applications';
import { Environment } from '@/redux/types/deploy-form';
import { Skeleton } from '@/components/ui/skeleton';
+import Labels from './application-details/labels';
+
function AppItem({
name,
@@ -22,7 +24,8 @@ function AppItem({
build_pack,
port,
id,
- status
+ status,
+ labels
}: Application) {
const router = useRouter();
const formattedDate = updated_at
@@ -34,7 +37,7 @@ function AppItem({
minute: '2-digit'
})
: 'N/A';
-
+
const formattedBuildPack = build_pack
.replace(/([A-Z])/g, ' $1')
.trim()
@@ -89,6 +92,13 @@ function AppItem({
{name}
+ {labels && labels.length > 0 && (
+