feat: unify and enhance the lifecycle of an instance (#1440)

* feat: add lifecycle state management and color coding for instance statuses

* feat: enhance instance lifecycle management with state derivation and UI updates

* feat: improve resource handling in informer with enhanced error reporting

* feat: enhance instance lifecycle logging with detailed merge and delete events

* feat: implement ResourceKeyProvider interface for consistent key generation in informers

* refactor: refactor instance resource handling and key function resolution in informers

* refactor: remove unused deployState and registerState columns from instance table

* feat: enhance runtime instance retrieval with improved matching logic and fallback handling

* feat: improve runtime instance identification with enhanced error logging and filtering

* feat: add pod watch selector and RPC port identifiers for improved service configuration

* feat: add refresh button and localization support for improved user interaction

* fix: fix lint

* feat: enhance runtime instance retrieval with improved fallback handling and logging for ambiguous matches

* fix: enhance instance lifecycle and deployment state management with new types and improved data handling
diff --git a/api/mesh/v1alpha1/runtime_instance_helper.go b/api/mesh/v1alpha1/runtime_instance_helper.go
index c69c002..3bffb4c 100644
--- a/api/mesh/v1alpha1/runtime_instance_helper.go
+++ b/api/mesh/v1alpha1/runtime_instance_helper.go
@@ -23,4 +23,8 @@
 	StartupProbe   = "startup"
 )
 
-const InstanceTerminating = "Terminating"
+const (
+	InstanceStarting    = "Starting"
+	InstanceCrashing    = "Crashing"
+	InstanceTerminating = "Terminating"
+)
diff --git a/pkg/console/model/application.go b/pkg/console/model/application.go
index d11943f..ee70a03 100644
--- a/pkg/console/model/application.go
+++ b/pkg/console/model/application.go
@@ -131,17 +131,18 @@
 }
 
 type AppInstanceInfoResp struct {
-	AppName         string            `json:"appName"`
-	CreateTime      string            `json:"createTime"`
-	DeployState     string            `json:"deployState"`
-	DeployClusters  string            `json:"deployClusters"`
-	IP              string            `json:"ip"`
-	Labels          map[string]string `json:"labels"`
-	Name            string            `json:"name"`
-	RegisterCluster string            `json:"registerCluster"`
-	RegisterState   string            `json:"registerState"`
-	RegisterTime    string            `json:"registerTime"`
-	WorkloadName    string            `json:"workloadName"`
+	AppName         string                 `json:"appName"`
+	CreateTime      string                 `json:"createTime"`
+	LifecycleState  InstanceLifecycleState `json:"lifecycleState"`
+	DeployState     InstanceDeployState    `json:"deployState"`
+	DeployClusters  string                 `json:"deployClusters"`
+	IP              string                 `json:"ip"`
+	Labels          map[string]string      `json:"labels"`
+	Name            string                 `json:"name"`
+	RegisterCluster string                 `json:"registerCluster"`
+	RegisterState   InstanceRegisterState  `json:"registerState"`
+	RegisterTime    string                 `json:"registerTime"`
+	WorkloadName    string                 `json:"workloadName"`
 }
 
 type ApplicationServiceReq struct {
diff --git a/pkg/console/model/instance.go b/pkg/console/model/instance.go
index e933d77..907fea2 100644
--- a/pkg/console/model/instance.go
+++ b/pkg/console/model/instance.go
@@ -20,6 +20,7 @@
 import (
 	"github.com/duke-git/lancet/v2/strutil"
 
+	meshproto "github.com/apache/dubbo-admin/api/mesh/v1alpha1"
 	"github.com/apache/dubbo-admin/pkg/config/app"
 	meshresource "github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
 	coremodel "github.com/apache/dubbo-admin/pkg/core/resource/model"
@@ -54,17 +55,18 @@
 }
 
 type SearchInstanceResp struct {
-	Ip               string            `json:"ip"`
-	Name             string            `json:"name"`
-	WorkloadName     string            `json:"workloadName"`
-	AppName          string            `json:"appName"`
-	DeployState      string            `json:"deployState"`
-	DeployCluster    string            `json:"deployCluster"`
-	RegisterState    string            `json:"registerState"`
-	RegisterClusters []string          `json:"registerClusters"`
-	CreateTime       string            `json:"createTime"`
-	RegisterTime     string            `json:"registerTime"`
-	Labels           map[string]string `json:"labels"`
+	Ip               string                 `json:"ip"`
+	Name             string                 `json:"name"`
+	WorkloadName     string                 `json:"workloadName"`
+	AppName          string                 `json:"appName"`
+	LifecycleState   InstanceLifecycleState `json:"lifecycleState"`
+	DeployState      InstanceDeployState    `json:"deployState"`
+	DeployCluster    string                 `json:"deployCluster"`
+	RegisterState    InstanceRegisterState  `json:"registerState"`
+	RegisterClusters []string               `json:"registerClusters"`
+	CreateTime       string                 `json:"createTime"`
+	RegisterTime     string                 `json:"registerTime"`
+	Labels           map[string]string      `json:"labels"`
 }
 
 func NewSearchInstanceResp() *SearchInstanceResp {
@@ -85,13 +87,10 @@
 	if cfg.Engine != nil && cfg.Engine.ID == instance.SourceEngine {
 		r.DeployCluster = cfg.Engine.Name
 	}
-	if r.RegisterTime != "" {
-		r.RegisterState = "Registered"
-	} else {
-		r.RegisterState = "UnRegistered"
-	}
+	r.RegisterState = DeriveInstanceRegisterState(instance)
 	r.Labels = instance.Tags
-	r.DeployState = instance.DeployState
+	r.DeployState = DeriveInstanceDeployState(instance)
+	r.LifecycleState = DeriveInstanceLifecycleState(instance, r.DeployState, r.RegisterState)
 	r.WorkloadName = instance.WorkloadName
 	r.AppName = instance.AppName
 	return r
@@ -104,23 +103,74 @@
 	Value string `json:"value"`
 }
 
+// InstanceDeployState describes the runtime deployment state reported by the platform.
+type InstanceDeployState string
+
+const (
+	// InstanceDeployStateUnknown indicates the deployment state cannot be derived from runtime metadata.
+	InstanceDeployStateUnknown InstanceDeployState = "Unknown"
+	// InstanceDeployStatePending indicates the workload has been accepted but is not running yet.
+	InstanceDeployStatePending InstanceDeployState = "Pending"
+	// InstanceDeployStateStarting indicates the workload is running but not ready to serve.
+	InstanceDeployStateStarting InstanceDeployState = "Starting"
+	// InstanceDeployStateRunning indicates the workload is running and ready.
+	InstanceDeployStateRunning InstanceDeployState = "Running"
+	// InstanceDeployStateTerminating indicates the workload is shutting down.
+	InstanceDeployStateTerminating InstanceDeployState = "Terminating"
+	// InstanceDeployStateFailed indicates the workload has failed.
+	InstanceDeployStateFailed InstanceDeployState = "Failed"
+	// InstanceDeployStateSucceeded indicates the workload has completed successfully and exited.
+	InstanceDeployStateSucceeded InstanceDeployState = "Succeeded"
+	// InstanceDeployStateCrashing indicates the workload is repeatedly crashing or restarting.
+	InstanceDeployStateCrashing InstanceDeployState = "Crashing"
+)
+
+// InstanceRegisterState describes whether the instance is visible to the registry.
+type InstanceRegisterState string
+
+const (
+	// InstanceRegisterStateRegistered indicates the instance has been registered to the registry.
+	InstanceRegisterStateRegistered InstanceRegisterState = "Registered"
+	// InstanceRegisterStateUnregistered indicates the instance has not registered yet or has been removed.
+	InstanceRegisterStateUnregistered InstanceRegisterState = "UnRegistered"
+)
+
+// InstanceLifecycleState describes the user-facing lifecycle synthesized from deploy/register signals.
+type InstanceLifecycleState string
+
+const (
+	// InstanceLifecycleStateStarting indicates the instance is still warming up.
+	InstanceLifecycleStateStarting InstanceLifecycleState = "Starting"
+	// InstanceLifecycleStateServing indicates the instance is both running and registered.
+	InstanceLifecycleStateServing InstanceLifecycleState = "Serving"
+	// InstanceLifecycleStateDraining indicates the instance is running but has started unregistering.
+	InstanceLifecycleStateDraining InstanceLifecycleState = "Draining"
+	// InstanceLifecycleStateTerminating indicates the instance is shutting down.
+	InstanceLifecycleStateTerminating InstanceLifecycleState = "Terminating"
+	// InstanceLifecycleStateError indicates the instance is in an unexpected or failed state.
+	InstanceLifecycleStateError InstanceLifecycleState = "Error"
+	// InstanceLifecycleStateUnknown indicates the lifecycle cannot be inferred from current signals.
+	InstanceLifecycleStateUnknown InstanceLifecycleState = "Unknown"
+)
+
 type InstanceDetailResp struct {
-	RpcPort          int64             `json:"rpcPort"`
-	Ip               string            `json:"ip"`
-	AppName          string            `json:"appName"`
-	WorkloadName     string            `json:"workloadName"`
-	Labels           map[string]string `json:"labels"`
-	CreateTime       string            `json:"createTime"`
-	ReadyTime        string            `json:"readyTime"`
-	RegisterTime     string            `json:"registerTime"`
-	RegisterClusters []string          `json:"registerClusters"`
-	DeployCluster    string            `json:"deployCluster"`
-	DeployState      string            `json:"deployState"`
-	RegisterState    string            `json:"registerState"`
-	Node             string            `json:"node"`
-	Image            string            `json:"image"`
-	Probes           ProbeStruct       `json:"probes"`
-	Tags             map[string]string `json:"tags"`
+	RpcPort          int64                  `json:"rpcPort"`
+	Ip               string                 `json:"ip"`
+	AppName          string                 `json:"appName"`
+	WorkloadName     string                 `json:"workloadName"`
+	Labels           map[string]string      `json:"labels"`
+	CreateTime       string                 `json:"createTime"`
+	ReadyTime        string                 `json:"readyTime"`
+	RegisterTime     string                 `json:"registerTime"`
+	RegisterClusters []string               `json:"registerClusters"`
+	DeployCluster    string                 `json:"deployCluster"`
+	LifecycleState   InstanceLifecycleState `json:"lifecycleState"`
+	DeployState      InstanceDeployState    `json:"deployState"`
+	RegisterState    InstanceRegisterState  `json:"registerState"`
+	Node             string                 `json:"node"`
+	Image            string                 `json:"image"`
+	Probes           ProbeStruct            `json:"probes"`
+	Tags             map[string]string      `json:"tags"`
 }
 
 const (
@@ -158,16 +208,9 @@
 	if cfg.Engine.ID == res.Spec.SourceEngine {
 		r.DeployCluster = cfg.Engine.Name
 	}
-	if strutil.IsNotBlank(instance.DeployState) {
-		r.DeployState = instance.DeployState
-	} else {
-		r.DeployState = "Unknown"
-	}
-	if strutil.IsBlank(r.RegisterTime) {
-		r.RegisterState = "UnRegistered"
-	} else {
-		r.RegisterState = "Registered"
-	}
+	r.DeployState = DeriveInstanceDeployState(instance)
+	r.RegisterState = DeriveInstanceRegisterState(instance)
+	r.LifecycleState = DeriveInstanceLifecycleState(instance, r.DeployState, r.RegisterState)
 	r.Node = instance.Node
 	r.Image = instance.Image
 	r.Probes = ProbeStruct{}
@@ -196,3 +239,69 @@
 	}
 	return r
 }
+
+func DeriveInstanceDeployState(instance *meshproto.Instance) InstanceDeployState {
+	if instance == nil || strutil.IsBlank(instance.DeployState) {
+		return InstanceDeployStateUnknown
+	}
+	deployState := InstanceDeployState(instance.DeployState)
+	switch deployState {
+	case InstanceDeployStateRunning:
+		if !isPodReady(instance) {
+			return InstanceDeployStateStarting
+		}
+		return InstanceDeployStateRunning
+	default:
+		return deployState
+	}
+}
+
+func DeriveInstanceRegisterState(instance *meshproto.Instance) InstanceRegisterState {
+	if instance == nil || strutil.IsBlank(instance.RegisterTime) {
+		return InstanceRegisterStateUnregistered
+	}
+	return InstanceRegisterStateRegistered
+}
+
+func DeriveInstanceLifecycleState(
+	instance *meshproto.Instance,
+	deployState InstanceDeployState,
+	registerState InstanceRegisterState,
+) InstanceLifecycleState {
+	switch deployState {
+	case InstanceDeployStateCrashing, InstanceDeployStateFailed, InstanceDeployStateUnknown, InstanceDeployStateSucceeded:
+		return InstanceLifecycleStateError
+	case InstanceDeployStateTerminating:
+		return InstanceLifecycleStateTerminating
+	}
+
+	if registerState == InstanceRegisterStateRegistered {
+		if deployState == InstanceDeployStateRunning {
+			return InstanceLifecycleStateServing
+		}
+		return InstanceLifecycleStateError
+	}
+
+	if instance != nil && deployState == InstanceDeployStateRunning && strutil.IsNotBlank(instance.UnregisterTime) {
+		return InstanceLifecycleStateDraining
+	}
+
+	switch deployState {
+	case InstanceDeployStatePending, InstanceDeployStateStarting, InstanceDeployStateRunning:
+		return InstanceLifecycleStateStarting
+	default:
+		return InstanceLifecycleStateUnknown
+	}
+}
+
+func isPodReady(instance *meshproto.Instance) bool {
+	for _, condition := range instance.Conditions {
+		if condition == nil {
+			continue
+		}
+		if condition.Type == "Ready" {
+			return condition.Status == "True"
+		}
+	}
+	return false
+}
diff --git a/pkg/console/service/application.go b/pkg/console/service/application.go
index cd295e2..9a5ee89 100644
--- a/pkg/console/service/application.go
+++ b/pkg/console/service/application.go
@@ -95,7 +95,8 @@
 	resp.Name = instance.Name
 	resp.AppName = instance.AppName
 	resp.CreateTime = instance.CreateTime
-	resp.DeployState = instance.DeployState
+	resp.DeployState = model.DeriveInstanceDeployState(instance)
+	resp.LifecycleState = model.DeriveInstanceLifecycleState(instance, resp.DeployState, model.DeriveInstanceRegisterState(instance))
 	if cfg.Engine.ID == instance.SourceEngine {
 		resp.DeployClusters = cfg.Engine.Name
 	}
@@ -104,7 +105,7 @@
 	if d := cfg.FindDiscovery(instanceRes.Mesh); d != nil {
 		resp.RegisterCluster = d.Name
 	}
-	resp.RegisterState = "Registered"
+	resp.RegisterState = model.DeriveInstanceRegisterState(instance)
 	resp.RegisterTime = instance.RegisterTime
 	resp.WorkloadName = instance.WorkloadName
 	return resp
diff --git a/pkg/core/controller/informer.go b/pkg/core/controller/informer.go
index 92cdb69..cf0003e 100644
--- a/pkg/core/controller/informer.go
+++ b/pkg/core/controller/informer.go
@@ -229,17 +229,10 @@
 	}
 	// from oldest to newest
 	for _, d := range deltas {
-		var resource model.Resource
-		var object interface{}
-		if o, ok := d.Object.(cache.DeletedFinalStateUnknown); ok {
-			object = o.Obj
-		} else {
-			object = d.Object
-		}
-		resource, ok := object.(model.Resource)
-		if !ok {
-			logger.Errorf("object from ListWatcher is not conformed to Resource, obj: %v", obj)
-			return bizerror.NewAssertionError("Resource", reflect.TypeOf(obj).Name())
+		resource, err := s.toResource(d.Object)
+		if err != nil {
+			logger.Errorf("object from ListWatcher is not conformed to Resource, obj: %v, err: %v", obj, err)
+			return err
 		}
 		switch d.Type {
 		case cache.Sync, cache.Replaced, cache.Added, cache.Updated:
@@ -257,6 +250,8 @@
 				s.EmitEvent(cache.Added, nil, resource)
 			}
 		case cache.Deleted:
+			logger.Infof("informer processing delete delta, resource kind: %s, key: %s",
+				resource.ResourceKind().ToString(), resource.ResourceKey())
 			if err := s.indexer.Delete(resource); err != nil {
 				logger.Errorf("failed to delete resource from informer, cause %v, resource: %s,", err, resource.String())
 				return err
@@ -267,6 +262,31 @@
 	return nil
 }
 
+func (s *informer) toResource(obj interface{}) (model.Resource, error) {
+	object := obj
+	if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
+		logger.Debugf("informer resolved tombstone object during delete handling, key: %s", tombstone.Key)
+		object = tombstone.Obj
+	}
+	if resource, ok := object.(model.Resource); ok {
+		return resource, nil
+	}
+	if s.transform != nil {
+		transformed, err := s.transform(object)
+		if err != nil {
+			return nil, err
+		}
+		if resource, ok := transformed.(model.Resource); ok {
+			return resource, nil
+		}
+		object = transformed
+	}
+	if object == nil {
+		return nil, bizerror.NewAssertionError("Resource", "nil")
+	}
+	return nil, bizerror.NewAssertionError("Resource", reflect.TypeOf(object).Name())
+}
+
 // EmitEvent emits an event to the event bus.
 func (s *informer) EmitEvent(typ cache.DeltaType, oldObj model.Resource, newObj model.Resource) {
 	event := events.NewResourceChangedEvent(typ, oldObj, newObj)
diff --git a/pkg/core/controller/listwatcher.go b/pkg/core/controller/listwatcher.go
index e90580f..4978f01 100644
--- a/pkg/core/controller/listwatcher.go
+++ b/pkg/core/controller/listwatcher.go
@@ -31,3 +31,9 @@
 	// return nil if there is no need to transform, see cache.SharedInformer for detail
 	TransformFunc() cache.TransformFunc
 }
+
+// ResourceKeyProvider can be optionally implemented by a ResourceListerWatcher when
+// raw watch objects need a key that is consistent with the transformed resource key.
+type ResourceKeyProvider interface {
+	KeyFunc() cache.KeyFunc
+}
diff --git a/pkg/core/discovery/subscriber/rpc_instance.go b/pkg/core/discovery/subscriber/rpc_instance.go
index 0343e5c..4879d1b 100644
--- a/pkg/core/discovery/subscriber/rpc_instance.go
+++ b/pkg/core/discovery/subscriber/rpc_instance.go
@@ -20,6 +20,7 @@
 import (
 	"reflect"
 
+	"github.com/duke-git/lancet/v2/slice"
 	"github.com/duke-git/lancet/v2/strutil"
 	"k8s.io/client-go/tools/cache"
 
@@ -110,7 +111,12 @@
 	// We should merge the rpc info into it
 	if instanceRes != nil {
 		meshresource.MergeRPCInstanceIntoInstance(rpcInstanceRes, instanceRes)
-		return s.instanceStore.Update(instanceRes)
+		if err := s.instanceStore.Update(instanceRes); err != nil {
+			return err
+		}
+		logger.Infof("instance lifecycle merged rpc source, instance: %s, rpc: %s, registerState: registered, deployState: %s",
+			instanceRes.ResourceKey(), rpcInstanceRes.ResourceKey(), instanceRes.Spec.DeployState)
+		return nil
 	}
 	// Otherwise we can create a new instance resource by rpc instance
 	instanceRes = meshresource.FromRPCInstance(rpcInstanceRes)
@@ -119,6 +125,8 @@
 		logger.Errorf("add instance resource failed, instance: %s, err: %s", instanceRes.ResourceKey(), err.Error())
 		return err
 	}
+	logger.Infof("instance lifecycle created rpc-only instance, instance: %s, rpc: %s, registerState: registered",
+		instanceRes.ResourceKey(), rpcInstanceRes.ResourceKey())
 
 	instanceAddEvent := events.NewResourceChangedEvent(cache.Added, nil, instanceRes)
 	s.eventEmitter.Send(instanceAddEvent)
@@ -135,10 +143,26 @@
 		logger.Warnf("cannot find instance resource for rpc instance %s, skipped deleting instance", rpcInstanceRes.Name)
 		return nil
 	}
+	meshresource.ClearRPCInstanceFromInstance(instanceRes)
+	if meshresource.HasRuntimeInstanceSource(instanceRes) {
+		if err := s.instanceStore.Update(instanceRes); err != nil {
+			logger.Errorf("update instance resource failed after rpc delete, instance: %s, err: %s",
+				instanceRes.ResourceKey(), err.Error())
+			return err
+		}
+		logger.Infof("instance lifecycle rpc source removed, keep instance by runtime source, instance: %s, rpc: %s, deployState: %s",
+			instanceRes.ResourceKey(), rpcInstanceRes.ResourceKey(), instanceRes.Spec.DeployState)
+		instanceUpdateEvent := events.NewResourceChangedEvent(cache.Updated, instanceRes, instanceRes)
+		s.eventEmitter.Send(instanceUpdateEvent)
+		logger.Debugf("rpc instance delete trigger instance update event, event: %s", instanceUpdateEvent.String())
+		return nil
+	}
 	if err := s.instanceStore.Delete(instanceRes); err != nil {
 		logger.Errorf("delete instance resource failed, instance: %s, err: %s", instanceRes.ResourceKey(), err.Error())
 		return err
 	}
+	logger.Infof("instance lifecycle rpc source removed and no runtime source remains, deleted instance: %s, rpc: %s",
+		instanceRes.ResourceKey(), rpcInstanceRes.ResourceKey())
 	instanceDeleteEvent := events.NewResourceChangedEvent(cache.Deleted, instanceRes, nil)
 	s.eventEmitter.Send(instanceDeleteEvent)
 	logger.Debugf("rpc instance delete trigger instance delete event, event: %s", instanceDeleteEvent.String())
@@ -173,7 +197,7 @@
 func (s *RPCInstanceEventSubscriber) findRelatedRuntimeInstanceAndMerge(instanceRes *meshresource.InstanceResource) {
 	switch s.engineCfg.Type {
 	case enginecfg.Kubernetes:
-		rtInstance := s.getRuntimeInstanceByIp(instanceRes.Spec.Ip)
+		rtInstance := s.getRuntimeInstanceForInstance(instanceRes)
 		if rtInstance == nil {
 			logger.Warnf("cannot find runtime instance for instace %s, skipping merging", instanceRes.ResourceKey())
 			return
@@ -184,7 +208,8 @@
 	}
 }
 
-func (s *RPCInstanceEventSubscriber) getRuntimeInstanceByIp(ip string) *meshresource.RuntimeInstanceResource {
+func (s *RPCInstanceEventSubscriber) getRuntimeInstanceForInstance(instanceRes *meshresource.InstanceResource) *meshresource.RuntimeInstanceResource {
+	ip := instanceRes.Spec.Ip
 	resources, err := s.rtInstanceStore.ListByIndexes([]index.IndexCondition{
 		{IndexName: index.ByRuntimeInstanceIPIndex, Value: ip, Operator: index.Equals},
 	})
@@ -195,9 +220,31 @@
 	if len(resources) == 0 {
 		return nil
 	}
-	runtimeInstanceRes, ok := resources[0].(*meshresource.RuntimeInstanceResource)
-	if !ok {
-		return nil
+	candidates := make([]*meshresource.RuntimeInstanceResource, 0, len(resources))
+	for _, item := range resources {
+		res, ok := item.(*meshresource.RuntimeInstanceResource)
+		if !ok {
+			continue
+		}
+		candidates = append(candidates, res)
 	}
-	return runtimeInstanceRes
+	// Prefer exact runtime identity match by app + rpcPort + mesh.
+	for _, candidate := range candidates {
+		if candidate.Spec == nil {
+			continue
+		}
+		if candidate.Mesh == instanceRes.Mesh &&
+			candidate.Spec.AppName == instanceRes.Spec.AppName &&
+			candidate.Spec.RpcPort == instanceRes.Spec.RpcPort {
+			return candidate
+		}
+	}
+	if len(candidates) > 1 {
+		keys := slice.Map(candidates, func(_ int, item *meshresource.RuntimeInstanceResource) string {
+			return item.ResourceKey()
+		})
+		logger.Warnf("multiple runtime instances share same ip %s, fallback to first candidate, runtime keys: %v, target instance: %s",
+			ip, keys, instanceRes.ResourceKey())
+	}
+	return candidates[0]
 }
diff --git a/pkg/core/engine/component.go b/pkg/core/engine/component.go
index 88dc293..8c893a9 100644
--- a/pkg/core/engine/component.go
+++ b/pkg/core/engine/component.go
@@ -24,8 +24,6 @@
 	"reflect"
 	"sync/atomic"
 
-	"k8s.io/client-go/tools/cache"
-
 	"github.com/apache/dubbo-admin/pkg/common/bizerror"
 	enginecfg "github.com/apache/dubbo-admin/pkg/config/engine"
 	storecfg "github.com/apache/dubbo-admin/pkg/config/store"
@@ -37,6 +35,7 @@
 	meshresource "github.com/apache/dubbo-admin/pkg/core/resource/apis/mesh/v1alpha1"
 	"github.com/apache/dubbo-admin/pkg/core/runtime"
 	"github.com/apache/dubbo-admin/pkg/core/store"
+	"k8s.io/client-go/tools/cache"
 )
 
 func init() {
@@ -157,7 +156,7 @@
 		if err != nil {
 			return fmt.Errorf("can not find store for resource kind %s, %w", rk, err)
 		}
-		informer := controller.NewInformerWithOptions(lw, emitter, rs, cache.MetaNamespaceKeyFunc, controller.Options{ResyncPeriod: 0})
+		informer := controller.NewInformerWithOptions(lw, emitter, rs, resolveInformerKeyFunc(lw), controller.Options{ResyncPeriod: 0})
 		if lw.TransformFunc() != nil {
 			err = informer.SetTransform(lw.TransformFunc())
 			if err != nil {
@@ -170,6 +169,13 @@
 	return nil
 }
 
+func resolveInformerKeyFunc(lw controller.ResourceListerWatcher) cache.KeyFunc {
+	if provider, ok := lw.(controller.ResourceKeyProvider); ok {
+		return provider.KeyFunc()
+	}
+	return cache.MetaNamespaceKeyFunc
+}
+
 func (e *engineComponent) initSubscribers(eventbus events.EventBus) error {
 	rs, err := e.storeRouter.ResourceKindRoute(meshresource.InstanceKind)
 	if err != nil {
diff --git a/pkg/core/engine/subscriber/runtime_instance.go b/pkg/core/engine/subscriber/runtime_instance.go
index f7d3870..b39a098 100644
--- a/pkg/core/engine/subscriber/runtime_instance.go
+++ b/pkg/core/engine/subscriber/runtime_instance.go
@@ -92,14 +92,7 @@
 
 // processUpsert when runtime instance added or updated, we should add/update the corresponding instance resource
 func (s *RuntimeInstanceEventSubscriber) processUpsert(rtInstanceRes *meshresource.RuntimeInstanceResource) error {
-	var instanceResource *meshresource.InstanceResource
-	var err error
-	switch rtInstanceRes.Spec.SourceEngineType {
-	case string(enginecfg.Kubernetes):
-		instanceResource, err = s.getRelatedInstanceByIP(rtInstanceRes)
-	default:
-		instanceResource, err = s.getRelatedInstanceByName(rtInstanceRes)
-	}
+	instanceResource, err := s.getRelatedInstance(rtInstanceRes)
 	if err != nil {
 		return err
 	}
@@ -107,12 +100,19 @@
 	// so we should merge the runtime info into it
 	if instanceResource != nil {
 		meshresource.MergeRuntimeInstanceIntoInstance(rtInstanceRes, instanceResource)
-		return s.instanceStore.Update(instanceResource)
+		if err = s.instanceStore.Update(instanceResource); err != nil {
+			return err
+		}
+		logger.Infof("instance lifecycle merged runtime source, instance: %s, runtime: %s, deployState: %s, hasRPCSource: %t",
+			instanceResource.ResourceKey(), rtInstanceRes.ResourceKey(), instanceResource.Spec.DeployState,
+			meshresource.HasRPCInstanceSource(instanceResource))
+		return nil
 	}
 	// if instance resource does not exist, that is to say the rpc instance does not exist in remote registry.
 	// we need to check whether the runtime instance resource is enough to create a new instance resource
 	if !checkAttributesEnough(rtInstanceRes) {
-		logger.Warnf("cannot identify runtime instance %s to a dubbo instance, skipped creating new instance", rtInstanceRes.ResourceKey())
+		logger.Warnf("cannot identify runtime instance %s to a dubbo instance, skipped creating new instance, reason: %s",
+			rtInstanceRes.ResourceKey(), runtimeIdentityMissingReason(rtInstanceRes))
 		return nil
 	}
 	// if conditions met, we should create a new instance resource by runtime instance
@@ -121,6 +121,8 @@
 		logger.Errorf("add instance resource failed, instance: %s, err: %s", instanceRes.ResourceKey(), err.Error())
 		return err
 	}
+	logger.Infof("instance lifecycle created runtime-only instance, instance: %s, runtime: %s, deployState: %s",
+		instanceRes.ResourceKey(), rtInstanceRes.ResourceKey(), instanceRes.Spec.DeployState)
 	instanceAddEvent := events.NewResourceChangedEvent(cache.Added, nil, instanceRes)
 	s.eventEmitter.Send(instanceAddEvent)
 	logger.Debugf("runtime instance upsert trigger instance add event, event: %s", instanceAddEvent.String())
@@ -129,14 +131,7 @@
 
 // processDelete when runtime instance deleted, we should delete the corresponding instance resource
 func (s *RuntimeInstanceEventSubscriber) processDelete(rtInstanceRes *meshresource.RuntimeInstanceResource) error {
-	var instanceResource *meshresource.InstanceResource
-	var err error
-	switch rtInstanceRes.Spec.SourceEngineType {
-	case string(enginecfg.Kubernetes):
-		instanceResource, err = s.getRelatedInstanceByIP(rtInstanceRes)
-	default:
-		instanceResource, err = s.getRelatedInstanceByName(rtInstanceRes)
-	}
+	instanceResource, err := s.getRelatedInstance(rtInstanceRes)
 	if err != nil {
 		return err
 	}
@@ -144,16 +139,76 @@
 		logger.Warnf("cannot find instance resource by runtime instance %s, skipped deleting instance", rtInstanceRes.ResourceKey())
 		return nil
 	}
+	meshresource.ClearRuntimeInstanceFromInstance(instanceResource)
+	if meshresource.HasRPCInstanceSource(instanceResource) {
+		if err = s.instanceStore.Update(instanceResource); err != nil {
+			logger.Errorf("update instance resource failed after runtime delete, instance: %s, err: %s",
+				instanceResource.ResourceKey(), err.Error())
+			return err
+		}
+		logger.Infof("instance lifecycle runtime source removed, keep instance by rpc source, instance: %s, runtime: %s, registerState: registered",
+			instanceResource.ResourceKey(), rtInstanceRes.ResourceKey())
+		instanceUpdateEvent := events.NewResourceChangedEvent(cache.Updated, instanceResource, instanceResource)
+		s.eventEmitter.Send(instanceUpdateEvent)
+		logger.Debugf("runtime instance delete trigger instance update event, event: %s", instanceUpdateEvent.String())
+		return nil
+	}
 	if err = s.instanceStore.Delete(instanceResource); err != nil {
 		logger.Errorf("delete instance resource failed, instance: %s, err: %s", instanceResource.ResourceKey(), err.Error())
 		return err
 	}
+	logger.Infof("instance lifecycle runtime source removed and no rpc source remains, deleted instance: %s, runtime: %s",
+		instanceResource.ResourceKey(), rtInstanceRes.ResourceKey())
 	instanceDeleteEvent := events.NewResourceChangedEvent(cache.Deleted, instanceResource, nil)
 	s.eventEmitter.Send(instanceDeleteEvent)
 	logger.Debugf("runtime instance delete trigger instance delete event, event: %s", instanceDeleteEvent.String())
 	return nil
 }
 
+func (s *RuntimeInstanceEventSubscriber) getRelatedInstance(
+	rtInstanceRes *meshresource.RuntimeInstanceResource) (*meshresource.InstanceResource, error) {
+	if rtInstanceRes == nil || rtInstanceRes.Spec == nil {
+		return nil, nil
+	}
+	switch rtInstanceRes.Spec.SourceEngineType {
+	case string(enginecfg.Kubernetes):
+		return s.getRelatedKubernetesInstance(rtInstanceRes)
+	default:
+		return s.getRelatedInstanceByName(rtInstanceRes)
+	}
+}
+
+func (s *RuntimeInstanceEventSubscriber) getRelatedKubernetesInstance(
+	rtInstanceRes *meshresource.RuntimeInstanceResource) (*meshresource.InstanceResource, error) {
+	if rtInstanceRes == nil || rtInstanceRes.Spec == nil {
+		return nil, nil
+	}
+	if hasRuntimeIdentity(rtInstanceRes) {
+		instanceResName := meshresource.BuildInstanceResName(rtInstanceRes.Spec.AppName, rtInstanceRes.Spec.Ip, rtInstanceRes.Spec.RpcPort)
+		if strutil.IsNotBlank(rtInstanceRes.Mesh) && constants.DefaultMesh != rtInstanceRes.Mesh {
+			res, exists, err := s.instanceStore.GetByKey(coremodel.BuildResourceKey(rtInstanceRes.Mesh, instanceResName))
+			if err != nil {
+				return nil, err
+			}
+			if exists {
+				instanceRes, ok := res.(*meshresource.InstanceResource)
+				if !ok {
+					return nil, bizerror.NewAssertionError("InstanceResource", reflect.TypeOf(res).Name())
+				}
+				return instanceRes, nil
+			}
+		}
+		instanceRes, err := s.getRelatedInstanceByName(rtInstanceRes)
+		if err != nil {
+			return nil, err
+		}
+		if instanceRes != nil {
+			return instanceRes, nil
+		}
+	}
+	return s.getRelatedInstanceByIP(rtInstanceRes)
+}
+
 func (s *RuntimeInstanceEventSubscriber) getRelatedInstanceByName(
 	rtInstanceRes *meshresource.RuntimeInstanceResource) (*meshresource.InstanceResource, error) {
 	if rtInstanceRes.Spec == nil || strutil.IsBlank(rtInstanceRes.Spec.AppName) ||
@@ -171,21 +226,28 @@
 		return nil, nil
 	}
 	instanceResList := make([]*meshresource.InstanceResource, len(resources))
+	filtered := make([]*meshresource.InstanceResource, 0, len(resources))
 	for i, item := range resources {
 		res, ok := item.(*meshresource.InstanceResource)
 		if !ok {
 			return nil, bizerror.NewAssertionError("InstanceResource", reflect.TypeOf(item).Name())
 		}
 		instanceResList[i] = res
+		if strutil.IsBlank(rtInstanceRes.Mesh) || res.Mesh == rtInstanceRes.Mesh {
+			filtered = append(filtered, res)
+		}
 	}
-	if len(instanceResList) > 1 {
-		resKeys := slice.Map(instanceResList, func(index int, item *meshresource.InstanceResource) string {
+	if len(filtered) == 0 {
+		return nil, nil
+	}
+	if len(filtered) > 1 {
+		resKeys := slice.Map(filtered, func(index int, item *meshresource.InstanceResource) string {
 			return item.ResourceKey()
 		})
 		logger.Warnf("there are more than two instances which have the same name, instance keys: %s, name: %s", resKeys, instanceResName)
 		return nil, nil
 	}
-	return instanceResList[0], nil
+	return filtered[0], nil
 }
 
 func (s *RuntimeInstanceEventSubscriber) getRelatedInstanceByIP(
@@ -225,3 +287,38 @@
 	}
 	return true
 }
+
+func runtimeIdentityMissingReason(rtInstanceRes *meshresource.RuntimeInstanceResource) string {
+	if rtInstanceRes == nil {
+		return "runtime instance is nil"
+	}
+	if rtInstanceRes.Spec == nil {
+		return "runtime instance spec is nil"
+	}
+	if strutil.IsBlank(rtInstanceRes.Spec.AppName) {
+		return "appName is empty"
+	}
+	if strutil.IsBlank(rtInstanceRes.Spec.Ip) {
+		return "pod ip is empty"
+	}
+	if rtInstanceRes.Spec.RpcPort <= 0 {
+		return "rpcPort is not configured"
+	}
+	if strutil.IsBlank(rtInstanceRes.Mesh) {
+		return "mesh is empty"
+	}
+	if constants.DefaultMesh == rtInstanceRes.Mesh {
+		return "mesh is default, missing registry identifier"
+	}
+	return "unknown"
+}
+
+func hasRuntimeIdentity(rtInstanceRes *meshresource.RuntimeInstanceResource) bool {
+	if rtInstanceRes == nil || rtInstanceRes.Spec == nil {
+		return false
+	}
+	return strutil.IsNotBlank(rtInstanceRes.Spec.AppName) &&
+		strutil.IsNotBlank(rtInstanceRes.Spec.Ip) &&
+		rtInstanceRes.Spec.RpcPort > 0 &&
+		strutil.IsNotBlank(rtInstanceRes.Mesh)
+}
diff --git a/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go b/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go
index ffc72ba..f7b86c3 100644
--- a/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go
+++ b/pkg/core/resource/apis/mesh/v1alpha1/instance_helper.go
@@ -19,6 +19,9 @@
 
 import (
 	"fmt"
+	"time"
+
+	"github.com/apache/dubbo-admin/pkg/common/constants"
 )
 
 func BuildInstanceResName(appName string, ip string, rpcPort int64) string {
@@ -79,3 +82,55 @@
 	instanceRes.Spec.Conditions = rtInstanceRes.Spec.Conditions
 	instanceRes.Spec.SourceEngine = rtInstanceRes.Spec.SourceEngine
 }
+
+func ClearRPCInstanceFromInstance(instanceRes *InstanceResource) {
+	if instanceRes == nil || instanceRes.Spec == nil {
+		return
+	}
+	instanceRes.Spec.ReleaseVersion = ""
+	instanceRes.Spec.RegisterTime = ""
+	instanceRes.Spec.UnregisterTime = time.Now().Format(constants.TimeFormatStr)
+	instanceRes.Spec.Protocol = ""
+	instanceRes.Spec.Serialization = ""
+	instanceRes.Spec.PreferSerialization = ""
+	instanceRes.Spec.Tags = nil
+}
+
+func ClearRuntimeInstanceFromInstance(instanceRes *InstanceResource) {
+	if instanceRes == nil || instanceRes.Spec == nil {
+		return
+	}
+	instanceRes.Labels = nil
+	instanceRes.Spec.Image = ""
+	instanceRes.Spec.CreateTime = ""
+	instanceRes.Spec.StartTime = ""
+	instanceRes.Spec.ReadyTime = ""
+	instanceRes.Spec.DeployState = ""
+	instanceRes.Spec.WorkloadType = ""
+	instanceRes.Spec.WorkloadName = ""
+	instanceRes.Spec.Node = ""
+	instanceRes.Spec.Probes = nil
+	instanceRes.Spec.Conditions = nil
+	instanceRes.Spec.SourceEngine = ""
+}
+
+func HasRuntimeInstanceSource(instanceRes *InstanceResource) bool {
+	if instanceRes == nil || instanceRes.Spec == nil {
+		return false
+	}
+	return instanceRes.Spec.SourceEngine != "" ||
+		instanceRes.Spec.DeployState != "" ||
+		instanceRes.Spec.WorkloadName != "" ||
+		instanceRes.Spec.Node != "" ||
+		instanceRes.Spec.Image != "" ||
+		instanceRes.Spec.StartTime != "" ||
+		instanceRes.Spec.ReadyTime != "" ||
+		len(instanceRes.Spec.Conditions) > 0
+}
+
+func HasRPCInstanceSource(instanceRes *InstanceResource) bool {
+	if instanceRes == nil || instanceRes.Spec == nil {
+		return false
+	}
+	return instanceRes.Spec.RegisterTime != ""
+}
diff --git a/pkg/engine/kubernetes/listerwatcher/runtime_instance.go b/pkg/engine/kubernetes/listerwatcher/runtime_instance.go
index 725b921..bc35f3f 100644
--- a/pkg/engine/kubernetes/listerwatcher/runtime_instance.go
+++ b/pkg/engine/kubernetes/listerwatcher/runtime_instance.go
@@ -48,6 +48,7 @@
 }
 
 var _ controller.ResourceListerWatcher = &PodListerWatcher{}
+var _ controller.ResourceKeyProvider = &PodListerWatcher{}
 
 func NewPodListWatcher(clientset *kubernetes.Clientset, cfg *enginecfg.Config) (*PodListerWatcher, error) {
 	var selector fields.Selector
@@ -105,10 +106,7 @@
 				readyTime = c.LastTransitionTime.Format(constants.TimeFormatStr)
 			}
 		})
-		phase := string(pod.Status.Phase)
-		if pod.DeletionTimestamp != nil {
-			phase = meshproto.InstanceTerminating
-		}
+		phase := derivePodPhase(pod)
 		var workloadName string
 		var workloadType string
 		if len(pod.GetOwnerReferences()) > 0 {
@@ -173,6 +171,105 @@
 	}
 }
 
+func (p *PodListerWatcher) KeyFunc() cache.KeyFunc {
+	return p.resourceKeyFromObject
+}
+
+func (p *PodListerWatcher) resourceKeyFromObject(obj interface{}) (string, error) {
+	for {
+		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+		if !ok {
+			break
+		}
+		obj = tombstone.Obj
+	}
+	switch o := obj.(type) {
+	case *v1.Pod:
+		return p.resourceKeyFromPod(o), nil
+	case *meshresource.RuntimeInstanceResource:
+		return o.ResourceKey(), nil
+	default:
+		if obj == nil {
+			return "", bizerror.NewAssertionError("Pod", "nil")
+		}
+		return "", bizerror.NewAssertionError("Pod", reflect.TypeOf(obj).Name())
+	}
+}
+
+func (p *PodListerWatcher) resourceKeyFromPod(pod *v1.Pod) string {
+	return coremodel.BuildResourceKey(p.getDubboMesh(pod), pod.Name)
+}
+
+func derivePodPhase(pod *v1.Pod) string {
+	if pod == nil {
+		return "Unknown"
+	}
+	if pod.DeletionTimestamp != nil {
+		return meshproto.InstanceTerminating
+	}
+	if hasCrashingContainerStatus(pod.Status.InitContainerStatuses) || hasCrashingContainerStatus(pod.Status.ContainerStatuses) {
+		return meshproto.InstanceCrashing
+	}
+	switch pod.Status.Phase {
+	case v1.PodPending:
+		if hasStartingContainerStatus(pod.Status.InitContainerStatuses) || hasStartingContainerStatus(pod.Status.ContainerStatuses) {
+			return meshproto.InstanceStarting
+		}
+		return string(v1.PodPending)
+	case v1.PodRunning:
+		if !isPodReady(pod.Status.Conditions) {
+			return meshproto.InstanceStarting
+		}
+		return string(v1.PodRunning)
+	case v1.PodFailed:
+		return string(v1.PodFailed)
+	case v1.PodSucceeded:
+		return string(v1.PodSucceeded)
+	case v1.PodUnknown:
+		return string(v1.PodUnknown)
+	default:
+		return string(pod.Status.Phase)
+	}
+}
+
+func isPodReady(conditions []v1.PodCondition) bool {
+	for _, condition := range conditions {
+		if condition.Type == v1.PodReady {
+			return condition.Status == v1.ConditionTrue
+		}
+	}
+	return false
+}
+
+func hasStartingContainerStatus(statuses []v1.ContainerStatus) bool {
+	for _, status := range statuses {
+		if status.State.Waiting == nil {
+			continue
+		}
+		switch status.State.Waiting.Reason {
+		case "ContainerCreating", "PodInitializing":
+			return true
+		}
+	}
+	return false
+}
+
+func hasCrashingContainerStatus(statuses []v1.ContainerStatus) bool {
+	for _, status := range statuses {
+		if status.State.Waiting != nil {
+			switch status.State.Waiting.Reason {
+			case "CrashLoopBackOff", "ImagePullBackOff", "ErrImagePull", "RunContainerError",
+				"CreateContainerConfigError", "CreateContainerError", "StartError":
+				return true
+			}
+		}
+		if status.State.Terminated != nil && status.State.Terminated.ExitCode != 0 {
+			return true
+		}
+	}
+	return false
+}
+
 func (p *PodListerWatcher) getMainContainer(pod *v1.Pod) *v1.Container {
 	containers := pod.Spec.Containers
 	strategy := p.cfg.Properties.MainContainerChooseStrategy
diff --git a/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml b/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml
index 91a5961..1bcdfdc 100644
--- a/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml
+++ b/release/kubernetes/dubbo-samples-shop/dubbo-samples-shop-all.yaml
@@ -114,6 +114,8 @@
         app: shop-order

         orderVersion: v1

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20882"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-order

@@ -161,6 +163,8 @@
         app: shop-order

         orderVersion: v2

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20883"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-order

@@ -205,6 +209,8 @@
       labels:

         app: shop-user

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20884"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-user

@@ -250,6 +256,8 @@
         app: shop-detail

         detailVersion: v1

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20885"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-detail

@@ -297,6 +305,8 @@
         app: shop-detail

         detailVersion: v2

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20886"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-detail

@@ -344,6 +354,8 @@
         app: shop-comment

         commentVersion: v1

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20887"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-comment

@@ -391,6 +403,8 @@
         app: shop-comment

         commentVersion: v2

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20888"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-comment

@@ -434,6 +448,8 @@
       labels:

         app: shop-user

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20892"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-user-gray

@@ -480,6 +496,8 @@
         app: shop-order

         orderVersion: v1

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20891"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-order

@@ -525,6 +543,8 @@
         app: shop-detail

         detailVersion: v1

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20890"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-detail

@@ -570,6 +590,8 @@
         app: shop-comment

         commentVersion: v1

         app-type: dubbo

+        org.apache.dubbo/dubbo-rpc-port: "20889"

+        org.apache.dubbo/registry: nacos2.5

     spec:

       containers:

         - name: shop-comment

diff --git a/release/kubernetes/dubbo-system/dubbo-admin.yaml b/release/kubernetes/dubbo-system/dubbo-admin.yaml
index 18faf41..54216c4 100644
--- a/release/kubernetes/dubbo-system/dubbo-admin.yaml
+++ b/release/kubernetes/dubbo-system/dubbo-admin.yaml
@@ -113,6 +113,20 @@
       id: default

       name: default

       type: kubernetes

+      properties:

+        podWatchSelector: metadata.namespace=dubbo-samples-shop

+        dubboAppIdentifier:

+          type: ByLabel

+          labelKey: app

+        dubboRPCPortIdentifier:

+          type: ByLabel

+          labelKey: org.apache.dubbo/dubbo-rpc-port

+        dubboDiscoveryIdentifier:

+          type: ByLabel

+          labelKey: org.apache.dubbo/registry

+        mainContainerChooseStrategy:

+          type: ByIndex

+          index: 0

 ---

 apiVersion: v1

 kind: Service

diff --git a/ui-vue3/src/base/constants.ts b/ui-vue3/src/base/constants.ts
index 166772e..e1c865f 100644
--- a/ui-vue3/src/base/constants.ts
+++ b/ui-vue3/src/base/constants.ts
@@ -57,8 +57,8 @@
 export const PRIMARY_COLOR_R = computed(() => getTextColorByBackground(PRIMARY_COLOR.value))
 
 export const INSTANCE_REGISTER_COLOR: { [key: string]: string } = {
-  HEALTHY: 'green',
-  REGISTED: 'green'
+  REGISTERED: 'green',
+  UNREGISTERED: 'default'
 }
 
 export const TAB_HEADER_TITLE: Component = {
@@ -83,7 +83,19 @@
  */
 export const INSTANCE_DEPLOY_COLOR: { [key: string]: string } = {
   RUNNING: 'green',
+  STARTING: 'gold',
   PENDING: 'yellow',
   TERMINATING: 'red',
-  CRASHING: 'darkRed'
+  FAILED: 'red',
+  UNKNOWN: 'default',
+  CRASHING: 'red'
+}
+
+export const INSTANCE_LIFECYCLE_COLOR: { [key: string]: string } = {
+  STARTING: 'gold',
+  SERVING: 'green',
+  DRAINING: 'orange',
+  TERMINATING: 'red',
+  ERROR: 'red',
+  UNKNOWN: 'default'
 }
diff --git a/ui-vue3/src/base/i18n/en.ts b/ui-vue3/src/base/i18n/en.ts
index f81082e..777c7dc 100644
--- a/ui-vue3/src/base/i18n/en.ts
+++ b/ui-vue3/src/base/i18n/en.ts
@@ -172,6 +172,7 @@
     instanceName: 'InstanceName',
     ip: 'Ip',
     name: 'Name',
+    lifecycleState: 'Lifecycle State',
     deployState: 'Deploy State',
     deployCluster: 'Deploy Cluster',
     deployClusters: 'Deploy Clusters',
@@ -564,6 +565,7 @@
   dependentService: 'Dependent Service',
   submit: 'Submit',
   reset: 'Reset',
+  refresh: 'Refresh',
   router: {
     resource: {
       app: {
diff --git a/ui-vue3/src/base/i18n/zh.ts b/ui-vue3/src/base/i18n/zh.ts
index 3d1c476..c382c0e 100644
--- a/ui-vue3/src/base/i18n/zh.ts
+++ b/ui-vue3/src/base/i18n/zh.ts
@@ -194,6 +194,7 @@
     instanceIP: '实例IP',
     ip: 'IP',
     name: '实例名称',
+    lifecycleState: '生命周期状态',
     deployState: '部署状态',
     deployCluster: '部署集群',
     deployClusters: '部署集群',
@@ -554,6 +555,7 @@
   idx: '序号',
   submit: '提交',
   reset: '重置',
+  refresh: '刷新',
   router: {
     resource: {
       app: {
diff --git a/ui-vue3/src/components/SearchTable.vue b/ui-vue3/src/components/SearchTable.vue
index 5324e6a..e67424d 100644
--- a/ui-vue3/src/components/SearchTable.vue
+++ b/ui-vue3/src/components/SearchTable.vue
@@ -68,7 +68,7 @@
           </a-form>
         </a-col>
         <a-col :span="6">
-          <a-flex style="justify-content: flex-end">
+          <a-flex style="justify-content: flex-end; align-items: center; gap: 12px">
             <slot name="customOperation"></slot>
             <div class="common-tool" @click="commonTool.customColumns = !commonTool.customColumns">
               <div class="custom-column button">
@@ -221,22 +221,23 @@
 
   .common-tool {
     margin-top: 5px;
-    width: 100px;
     cursor: pointer;
     position: relative;
+    flex: none;
 
     .button {
-      vertical-align: center;
+      display: flex;
+      align-items: center;
+      justify-content: center;
       line-height: 24px;
       font-size: 24px;
-      float: right;
 
       &:hover {
         color: v-bind('PRIMARY_COLOR');
       }
 
       svg {
-        margin-left: 10px;
+        margin-left: 0;
       }
     }
 
diff --git a/ui-vue3/src/views/resources/applications/tabs/instance.vue b/ui-vue3/src/views/resources/applications/tabs/instance.vue
index ed59e4e..b75bad3 100644
--- a/ui-vue3/src/views/resources/applications/tabs/instance.vue
+++ b/ui-vue3/src/views/resources/applications/tabs/instance.vue
@@ -39,13 +39,24 @@
           </a-tooltip>
         </template>
         <template v-if="column.dataIndex === 'deployState'">
-          <a-tag :color="INSTANCE_DEPLOY_COLOR[text.toUpperCase()]">{{ text }}</a-tag>
+          <a-tag :color="INSTANCE_DEPLOY_COLOR[(text || 'UNKNOWN').toUpperCase()] || 'default'">
+            {{ text }}
+          </a-tag>
+        </template>
+        <template v-if="column.dataIndex === 'lifecycleState'">
+          <a-tag :color="INSTANCE_LIFECYCLE_COLOR[(text || 'UNKNOWN').toUpperCase()] || 'default'">
+            {{ text }}
+          </a-tag>
         </template>
         <template v-if="column.dataIndex === 'deployClusters'">
           <a-tag>{{ text }}</a-tag>
         </template>
         <template v-if="column.dataIndex === 'registerState'">
-          <a-tag :color="INSTANCE_REGISTER_COLOR[text.toUpperCase()]">{{ text }}</a-tag>
+          <a-tag
+            :color="INSTANCE_REGISTER_COLOR[(text || 'UNREGISTERED').toUpperCase()] || 'default'"
+          >
+            {{ text }}
+          </a-tag>
         </template>
         <template v-if="column.dataIndex === 'registerCluster'">
           <a-tag>{{ text }}</a-tag>
@@ -64,7 +75,12 @@
 
 <script setup lang="ts">
 import { onMounted, provide, reactive } from 'vue'
-import { INSTANCE_DEPLOY_COLOR, INSTANCE_REGISTER_COLOR, PRIMARY_COLOR } from '@/base/constants'
+import {
+  INSTANCE_DEPLOY_COLOR,
+  INSTANCE_LIFECYCLE_COLOR,
+  INSTANCE_REGISTER_COLOR,
+  PRIMARY_COLOR
+} from '@/base/constants'
 import { Icon } from '@iconify/vue'
 import SearchTable from '@/components/SearchTable.vue'
 import { SearchDomain } from '@/utils/SearchUtil'
@@ -127,6 +143,12 @@
     width: 150
   },
   {
+    title: 'instanceDomain.lifecycleState',
+    dataIndex: 'lifecycleState',
+    key: 'lifecycleState',
+    width: 150
+  },
+  {
     title: 'instanceDomain.deployState',
     dataIndex: 'deployState',
     key: 'deployState',
@@ -203,7 +225,7 @@
 * on (pod) group_left(pod_ip)
 kube_pod_info{pod_ip="${ip}"}`)
       instance.cpu = isNumber(cpu) ? cpu.toFixed(3) + 'u' : cpu
-      instance.memory = bytesToHuman(mem)
+      instance.memory = isNumber(mem) ? bytesToHuman(mem) : mem
     })
   })
 }
@@ -255,7 +277,7 @@
 })
 
 const viewDetail = (record: any) => {
-  router.push(`/resources/instances/detail/${record.name}/${record.ip}`)
+  router.push(`/resources/instances/detail/${record.name}/${record.ip}/${record.appName}`)
 }
 
 provide(PROVIDE_INJECT_KEY.SEARCH_DOMAIN, searchDomain)
diff --git a/ui-vue3/src/views/resources/instances/index.vue b/ui-vue3/src/views/resources/instances/index.vue
index ae88eb3..6055b08 100644
--- a/ui-vue3/src/views/resources/instances/index.vue
+++ b/ui-vue3/src/views/resources/instances/index.vue
@@ -17,6 +17,18 @@
 <template>
   <div class="instances-container">
     <search-table :search-domain="searchDomain">
+      <template #customOperation>
+        <a-button
+          class="refresh-button"
+          :loading="searchDomain.table.loading"
+          @click="searchDomain.onSearch()"
+        >
+          <template #icon>
+            <Icon icon="material-symbols:refresh-rounded"></Icon>
+          </template>
+          {{ $t('refresh') }}
+        </a-button>
+      </template>
       <template #bodyCell="{ text, record, index, column }">
         <template v-if="column.dataIndex === 'name'">
           <a-tooltip :title="text">
@@ -43,18 +55,14 @@
           <span>{{ text }}</span>
         </template>
 
-        <template v-if="column.dataIndex === 'deployState'">
-          <a-tag :color="INSTANCE_DEPLOY_COLOR[text.toUpperCase()]">{{ text }}</a-tag>
-        </template>
-
-        <template v-if="column.dataIndex === 'deployCluster'">
-          <a-tag color="grey">
+        <template v-if="column.dataIndex === 'lifecycleState'">
+          <a-tag :color="INSTANCE_LIFECYCLE_COLOR[(text || 'UNKNOWN').toUpperCase()] || 'default'">
             {{ text }}
           </a-tag>
         </template>
 
-        <template v-if="column.dataIndex === 'registerState'">
-          <a-tag :color="INSTANCE_REGISTER_COLOR[text.toUpperCase()]">
+        <template v-if="column.dataIndex === 'deployCluster'">
+          <a-tag color="grey">
             {{ text }}
           </a-tag>
         </template>
@@ -83,7 +91,7 @@
 import SearchTable from '@/components/SearchTable.vue'
 import { SearchDomain } from '@/utils/SearchUtil'
 import { PROVIDE_INJECT_KEY } from '@/base/enums/ProvideInject'
-import { INSTANCE_DEPLOY_COLOR, INSTANCE_REGISTER_COLOR, PRIMARY_COLOR } from '@/base/constants'
+import { INSTANCE_LIFECYCLE_COLOR, PRIMARY_COLOR } from '@/base/constants'
 import router from '@/router'
 import { Icon } from '@iconify/vue'
 import { queryMetrics } from '@/base/http/promQuery'
@@ -110,13 +118,11 @@
     width: 200
   },
   {
-    title: 'instanceDomain.deployState',
-    key: 'deployState',
-    dataIndex: 'deployState',
-    width: 120
-    // sorter: (a: any, b: any) => sortString(a.deployState, b.deployState)
+    title: 'instanceDomain.lifecycleState',
+    key: 'lifecycleState',
+    dataIndex: 'lifecycleState',
+    width: 130
   },
-
   {
     title: 'instanceDomain.deployCluster',
     key: 'deployCluster',
@@ -125,13 +131,6 @@
     width: 120
   },
   {
-    title: 'instanceDomain.registerState',
-    key: 'registerState',
-    dataIndex: 'registerState',
-    // sorter: (a: any, b: any) => sortString(a.registerState, b.registerState),
-    width: 120
-  },
-  {
     title: 'instanceDomain.registerCluster',
     key: 'registerClusters',
     dataIndex: 'registerClusters',
diff --git a/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue b/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue
index bcfaade..cf21a23 100644
--- a/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue
+++ b/ui-vue3/src/views/resources/instances/slots/InstanceTabHeaderSlot.vue
@@ -17,18 +17,53 @@
 <template>
   <!--      example like blow-->
   <div class="__container_AppTabHeaderSlot">
-    <a-row>
-      <a-col :span="12">
+    <a-row :gutter="12" align="middle">
+      <a-col flex="none">
         <span class="header-desc">{{ $t('instanceDomain.name') }}: {{ route.params?.name }}</span>
       </a-col>
+      <a-col flex="none">
+        <a-tag :color="lifecycleColor(instanceLifecycleState)">
+          {{ instanceLifecycleState }}
+        </a-tag>
+      </a-col>
     </a-row>
   </div>
 </template>
 
 <script lang="ts" setup>
+import { ref, watch } from 'vue'
 import { useRoute } from 'vue-router'
+import { getInstanceDetail } from '@/api/service/instance'
+import { INSTANCE_LIFECYCLE_COLOR } from '@/base/constants'
 
 const route = useRoute()
+const instanceLifecycleState = ref('Unknown')
+
+const lifecycleColor = (state?: string) => {
+  return INSTANCE_LIFECYCLE_COLOR[(state || 'UNKNOWN').toUpperCase()] || 'default'
+}
+
+const fetchInstanceLifecycleState = async () => {
+  const instanceName = route.params?.name
+  if (!instanceName) {
+    instanceLifecycleState.value = 'Unknown'
+    return
+  }
+
+  try {
+    const { data } = await getInstanceDetail({
+      instanceName,
+      instanceIP: route.params?.pathId
+    })
+    instanceLifecycleState.value = data?.lifecycleState || 'Unknown'
+  } catch {
+    instanceLifecycleState.value = 'Unknown'
+  }
+}
+
+watch(() => [route.params?.name, route.params?.pathId], fetchInstanceLifecycleState, {
+  immediate: true
+})
 </script>
 <style lang="less" scoped>
 .__container_AppTabHeaderSlot {
diff --git a/ui-vue3/src/views/resources/instances/tabs/detail.vue b/ui-vue3/src/views/resources/instances/tabs/detail.vue
index 134144e..495a359 100644
--- a/ui-vue3/src/views/resources/instances/tabs/detail.vue
+++ b/ui-vue3/src/views/resources/instances/tabs/detail.vue
@@ -51,21 +51,13 @@
           <a-col :span="12">
             <a-card class="_detail" style="height: 100%">
               <a-descriptions class="description-column" :column="1">
-                <!-- deployState -->
                 <a-descriptions-item
                   :label="$t('instanceDomain.deployState')"
                   :labelStyle="{ fontWeight: 'bold' }"
                 >
-                  <a-typography-paragraph
-                    type="success"
-                    style=""
-                    v-if="instanceDetail?.deployState === 'Running'"
-                  >
-                    Running
-                  </a-typography-paragraph>
-                  <a-typography-paragraph type="danger" v-else>
+                  <a-tag :color="deployColor(instanceDetail?.deployState)">
                     {{ instanceDetail?.deployState }}
-                  </a-typography-paragraph>
+                  </a-tag>
                 </a-descriptions-item>
 
                 <!-- Start time -->
@@ -249,7 +241,7 @@
 import { CopyOutlined } from '@ant-design/icons-vue'
 import useClipboard from 'vue-clipboard3'
 import { message } from 'ant-design-vue'
-import { PRIMARY_COLOR, PRIMARY_COLOR_T } from '@/base/constants'
+import { INSTANCE_DEPLOY_COLOR, PRIMARY_COLOR, PRIMARY_COLOR_T } from '@/base/constants'
 import { getInstanceDetail } from '@/api/service/instance'
 import { useRoute, useRouter } from 'vue-router'
 import { formattedDate } from '@/utils/DateUtil'
@@ -296,6 +288,10 @@
 const isProbeOpen = (status: boolean) => {
   return status ? '开启' : '关闭'
 }
+
+const deployColor = (state?: string) => {
+  return INSTANCE_DEPLOY_COLOR[(state || 'UNKNOWN').toUpperCase()] || 'default'
+}
 </script>
 
 <style lang="less" scoped>