Compare commits
2 Commits
module-l4-
...
module-app
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b7a1d9925 | ||
|
|
870cd164f9 |
@@ -229,7 +229,7 @@ func (r *AppEnvController) triggerApplyEnv(ctx context.Context, appEnv *sysv1alp
|
||||
OpID: opID,
|
||||
State: appv1alpha1.ApplyingEnv.String(),
|
||||
RawAppName: am.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: am.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(am.Spec.Config),
|
||||
})
|
||||
|
||||
|
||||
@@ -250,8 +250,9 @@ func (r *EntranceStatusManagerController) updateEntranceStatus(ctx context.Conte
|
||||
State: am.Status.State.String(),
|
||||
EntranceStatuses: appCopy.Status.EntranceStatuses,
|
||||
RawAppName: appCopy.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: am.Spec.Type.String(),
|
||||
Title: app.AppTitle(am.Spec.Config),
|
||||
SharedEntrances: appCopy.Spec.SharedEntrances,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ func (r *PodAbnormalSuspendAppController) trySuspendApp(ctx context.Context, own
|
||||
State: appv1alpha1.Stopping.String(),
|
||||
Progress: message,
|
||||
RawAppName: am.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: am.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(am.Spec.Config),
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
|
||||
@@ -13,6 +13,8 @@ const (
|
||||
AppMarketSourceKey = constants.AppMarketSourceKey
|
||||
AppInstallSourceKey = "bytetrade.io/install-source"
|
||||
AppUninstallAllKey = "bytetrade.io/uninstall-all"
|
||||
AppStopAllKey = "bytetrade.io/stop-all"
|
||||
AppResumeAllKey = "bytetrade.io/resume-all"
|
||||
AppImagesKey = "bytetrade.io/images"
|
||||
)
|
||||
|
||||
@@ -144,6 +146,11 @@ type UninstallRequest struct {
|
||||
All bool `json:"all"`
|
||||
}
|
||||
|
||||
// StopRequest represents a request to stop an application.
|
||||
type StopRequest struct {
|
||||
All bool `json:"all"`
|
||||
}
|
||||
|
||||
type ManifestRenderRequest struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
@@ -50,6 +50,11 @@ func (h *Handler) appApplyEnv(req *restful.Request, resp *restful.Response) {
|
||||
|
||||
appCopy := appMgr.DeepCopy()
|
||||
appCopy.Spec.OpType = appv1alpha1.ApplyEnvOp
|
||||
if appCopy.Annotations == nil {
|
||||
klog.Errorf("not support operation %s,name:%s", appv1alpha1.ApplyEnvOp, appCopy.Spec.AppName)
|
||||
api.HandleError(resp, req, fmt.Errorf("not support operation %s", appv1alpha1.ApplyEnvOp))
|
||||
return
|
||||
}
|
||||
appCopy.Annotations[api.AppTokenKey] = token
|
||||
|
||||
err = h.ctrlClient.Patch(req.Request.Context(), appCopy, client.MergeFrom(&appMgr))
|
||||
@@ -81,7 +86,7 @@ func (h *Handler) appApplyEnv(req *restful.Request, resp *restful.Response) {
|
||||
OpID: opID,
|
||||
State: appv1alpha1.ApplyingEnv.String(),
|
||||
RawAppName: am.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: am.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(am.Spec.Config),
|
||||
})
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ func (h *Handler) cancel(req *restful.Request, resp *restful.Response) {
|
||||
OpID: opID,
|
||||
State: cancelState.String(),
|
||||
RawAppName: a.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: a.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(a.Spec.Config),
|
||||
})
|
||||
|
||||
|
||||
@@ -623,7 +623,7 @@ func (h *installHandlerHelper) applyApplicationManager(marketSource string) (opI
|
||||
OpID: opID,
|
||||
State: v1alpha1.Pending.String(),
|
||||
RawAppName: a.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: a.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(a.Spec.Config),
|
||||
})
|
||||
return
|
||||
|
||||
@@ -103,7 +103,7 @@ func (h *Handler) uninstall(req *restful.Request, resp *restful.Response) {
|
||||
OpID: opID,
|
||||
State: v1alpha1.Uninstalling.String(),
|
||||
RawAppName: a.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: a.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(a.Spec.Config),
|
||||
})
|
||||
|
||||
|
||||
@@ -348,7 +348,9 @@ func (h *Handler) appUpgrade(req *restful.Request, resp *restful.Response) {
|
||||
appCopy.Spec.Config = config
|
||||
appCopy.Spec.OpType = appv1alpha1.UpgradeOp
|
||||
if appCopy.Annotations == nil {
|
||||
appCopy.Annotations = make(map[string]string)
|
||||
klog.Errorf("not support operation %s,name: %s", appv1alpha1.UpgradeOp, appCopy.Spec.AppName)
|
||||
api.HandleError(resp, req, fmt.Errorf("not support operation %s", appv1alpha1.UpgradeOp))
|
||||
return
|
||||
}
|
||||
appCopy.Annotations[api.AppRepoURLKey] = request.RepoURL
|
||||
appCopy.Annotations[api.AppVersionKey] = request.Version
|
||||
@@ -384,7 +386,7 @@ func (h *Handler) appUpgrade(req *restful.Request, resp *restful.Response) {
|
||||
OpID: opID,
|
||||
State: appv1alpha1.Upgrading.String(),
|
||||
RawAppName: am.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: am.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(am.Spec.Config),
|
||||
})
|
||||
|
||||
|
||||
@@ -276,7 +276,7 @@ func (h *Handler) setupAppEntranceDomain(req *restful.Request, resp *restful.Res
|
||||
OpID: opID,
|
||||
State: v1alpha1.Upgrading.String(),
|
||||
RawAppName: am.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: am.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(am.Spec.Config),
|
||||
Message: fmt.Sprintf("app %s was upgrade via setup domain by user %s", am.Spec.AppName, am.Spec.AppOwner),
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"bytetrade.io/web3os/app-service/pkg/apiserver/api"
|
||||
"bytetrade.io/web3os/app-service/pkg/appstate"
|
||||
"bytetrade.io/web3os/app-service/pkg/constants"
|
||||
"bytetrade.io/web3os/app-service/pkg/kubesphere"
|
||||
"bytetrade.io/web3os/app-service/pkg/users/userspace"
|
||||
"bytetrade.io/web3os/app-service/pkg/utils"
|
||||
apputils "bytetrade.io/web3os/app-service/pkg/utils/app"
|
||||
@@ -22,6 +23,15 @@ import (
|
||||
func (h *Handler) suspend(req *restful.Request, resp *restful.Response) {
|
||||
app := req.PathParameter(ParamAppName)
|
||||
owner := req.Attribute(constants.UserContextAttribute).(string)
|
||||
|
||||
// read optional body to support all=true
|
||||
request := &api.StopRequest{}
|
||||
if req.Request.ContentLength > 0 {
|
||||
if err := req.ReadEntity(request); err != nil {
|
||||
api.HandleBadRequest(resp, req, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if userspace.IsSysApp(app) {
|
||||
api.HandleBadRequest(resp, req, errors.New("sys app can not be suspend"))
|
||||
return
|
||||
@@ -42,6 +52,8 @@ func (h *Handler) suspend(req *restful.Request, resp *restful.Response) {
|
||||
return
|
||||
}
|
||||
am.Spec.OpType = v1alpha1.StopOp
|
||||
am.Annotations[api.AppStopAllKey] = fmt.Sprintf("%t", request.All)
|
||||
|
||||
err = h.ctrlClient.Update(req.Request.Context(), &am)
|
||||
if err != nil {
|
||||
api.HandleError(resp, req, err)
|
||||
@@ -72,7 +84,7 @@ func (h *Handler) suspend(req *restful.Request, resp *restful.Response) {
|
||||
OpID: opID,
|
||||
State: v1alpha1.Stopping.String(),
|
||||
RawAppName: a.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: a.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(a.Spec.Config),
|
||||
Reason: constants.AppStopByUser,
|
||||
Message: fmt.Sprintf("app %s was stop by user %s", a.Spec.AppName, a.Spec.AppOwner),
|
||||
@@ -106,6 +118,16 @@ func (h *Handler) resume(req *restful.Request, resp *restful.Response) {
|
||||
}
|
||||
|
||||
am.Spec.OpType = v1alpha1.ResumeOp
|
||||
// if current user is admin, also resume server side
|
||||
isAdmin, err := kubesphere.IsAdmin(req.Request.Context(), h.kubeConfig, owner)
|
||||
if err != nil {
|
||||
api.HandleError(resp, req, err)
|
||||
return
|
||||
}
|
||||
am.Annotations[api.AppResumeAllKey] = fmt.Sprintf("%t", false)
|
||||
if isAdmin {
|
||||
am.Annotations[api.AppResumeAllKey] = fmt.Sprintf("%t", true)
|
||||
}
|
||||
err = h.ctrlClient.Update(req.Request.Context(), &am)
|
||||
if err != nil {
|
||||
api.HandleError(resp, req, err)
|
||||
@@ -134,7 +156,7 @@ func (h *Handler) resume(req *restful.Request, resp *restful.Response) {
|
||||
OpID: opID,
|
||||
State: v1alpha1.Resuming.String(),
|
||||
RawAppName: a.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: a.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(a.Spec.Config),
|
||||
Message: fmt.Sprintf("app %s was resume by user %s", a.Spec.AppName, a.Spec.AppOwner),
|
||||
})
|
||||
|
||||
@@ -1144,7 +1144,7 @@ func (h *Handler) applicationManagerMutate(req *restful.Request, resp *restful.R
|
||||
OpID: pam.Status.OpID,
|
||||
State: pam.Status.State.String(),
|
||||
RawAppName: pam.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: pam.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(pam.Spec.Config),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,6 +86,10 @@ func (h *HelmOps) upgrade() error {
|
||||
}
|
||||
}
|
||||
|
||||
if h.app.Type == appv1alpha1.Middleware.String() {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = h.UpdatePolicy()
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to update policy err=%v", err)
|
||||
@@ -97,9 +101,7 @@ func (h *HelmOps) upgrade() error {
|
||||
klog.Errorf("Failed to register app provider err=%v", err)
|
||||
return err
|
||||
}
|
||||
if h.app.Type == appv1alpha1.Middleware.String() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ok, err := h.WaitForStartUp()
|
||||
if !ok {
|
||||
// canceled
|
||||
|
||||
@@ -75,6 +75,10 @@ func (h *HelmOpsV2) Upgrade() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if h.App().Type == appv1alpha1.Middleware.String() {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = h.UpdatePolicy()
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to update policy for app %s err=%v", h.App().AppName, err)
|
||||
@@ -86,10 +90,6 @@ func (h *HelmOpsV2) Upgrade() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if h.App().Type == appv1alpha1.Middleware.String() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ok, err := h.WaitForStartUp()
|
||||
if err != nil && errors.Is(err, errcode.ErrPodPending) {
|
||||
return err
|
||||
|
||||
@@ -104,7 +104,7 @@ func (p *PendingApp) Exec(ctx context.Context) (StatefulInProgressApp, error) {
|
||||
OpID: p.manager.Status.OpID,
|
||||
State: appsv1.Downloading.String(),
|
||||
RawAppName: p.manager.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: p.manager.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(p.manager.Spec.Config),
|
||||
})
|
||||
|
||||
|
||||
@@ -2,10 +2,13 @@ package appstate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
appsv1 "bytetrade.io/web3os/app-service/api/app.bytetrade.io/v1alpha1"
|
||||
"bytetrade.io/web3os/app-service/pkg/apiserver/api"
|
||||
"bytetrade.io/web3os/app-service/pkg/appcfg"
|
||||
"bytetrade.io/web3os/app-service/pkg/constants"
|
||||
"bytetrade.io/web3os/app-service/pkg/kubeblocks"
|
||||
"bytetrade.io/web3os/app-service/pkg/users/userspace"
|
||||
@@ -62,6 +65,34 @@ func (p *ResumingApp) exec(ctx context.Context) error {
|
||||
klog.Errorf("resume %s %s failed %v", p.manager.Spec.Type, p.manager.Spec.AppName, err)
|
||||
return fmt.Errorf("resume app %s failed %w", p.manager.Spec.AppName, err)
|
||||
}
|
||||
|
||||
// If resume-all is requested, also resume v2 server-side shared charts by scaling them up
|
||||
if p.manager.Annotations[api.AppResumeAllKey] == "true" {
|
||||
var appCfg *appcfg.ApplicationConfig
|
||||
if err := json.Unmarshal([]byte(p.manager.Spec.Config), &appCfg); err != nil {
|
||||
klog.Errorf("unmarshal to appConfig failed %v", err)
|
||||
return err
|
||||
}
|
||||
if appCfg != nil && appCfg.IsV2() && appCfg.HasClusterSharedCharts() {
|
||||
for _, chart := range appCfg.SubCharts {
|
||||
if !chart.Shared {
|
||||
continue
|
||||
}
|
||||
ns := chart.Namespace(appCfg.OwnerName)
|
||||
// create a shallow copy with target namespace/name for scaling logic
|
||||
amCopy := p.manager.DeepCopy()
|
||||
amCopy.Spec.AppNamespace = ns
|
||||
amCopy.Spec.AppName = chart.Name
|
||||
klog.Infof("resume-amCopy.Spec.AppNamespace: %s", ns)
|
||||
klog.Infof("resume-amCopy.Spec.AppName: %s", chart.Name)
|
||||
if err := suspendOrResumeApp(ctx, p.client, amCopy, int32(1)); err != nil {
|
||||
klog.Errorf("failed to resume shared chart %s in namespace %s: %v", chart.Name, ns, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.manager.Spec.Type == "middleware" && userspace.IsKbMiddlewares(p.manager.Spec.AppName) {
|
||||
err = p.execMiddleware(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,10 +2,13 @@ package appstate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
appsv1 "bytetrade.io/web3os/app-service/api/app.bytetrade.io/v1alpha1"
|
||||
"bytetrade.io/web3os/app-service/pkg/apiserver/api"
|
||||
"bytetrade.io/web3os/app-service/pkg/appcfg"
|
||||
"bytetrade.io/web3os/app-service/pkg/constants"
|
||||
"bytetrade.io/web3os/app-service/pkg/kubeblocks"
|
||||
"bytetrade.io/web3os/app-service/pkg/users/userspace"
|
||||
@@ -75,13 +78,42 @@ func (p *SuspendingApp) Exec(ctx context.Context) (StatefulInProgressApp, error)
|
||||
}
|
||||
|
||||
func (p *SuspendingApp) exec(ctx context.Context) error {
|
||||
err := suspendOrResumeApp(ctx, p.client, p.manager, int32(0))
|
||||
if err != nil {
|
||||
klog.Errorf("suspend %s %s failed %v", p.manager.Spec.Type, p.manager.Spec.AppName, err)
|
||||
return fmt.Errorf("suspend app %s failed %w", p.manager.Spec.AppName, err)
|
||||
// If stop-all is requested, also stop v2 server-side shared charts by scaling them down
|
||||
if p.manager.Annotations[api.AppStopAllKey] == "true" {
|
||||
var appCfg *appcfg.ApplicationConfig
|
||||
if err := json.Unmarshal([]byte(p.manager.Spec.Config), &appCfg); err != nil {
|
||||
klog.Errorf("unmarshal to appConfig failed %v", err)
|
||||
return err
|
||||
}
|
||||
if appCfg != nil && appCfg.IsV2() && appCfg.HasClusterSharedCharts() {
|
||||
for _, chart := range appCfg.SubCharts {
|
||||
if !chart.Shared {
|
||||
continue
|
||||
}
|
||||
ns := chart.Namespace(appCfg.OwnerName)
|
||||
// create a shallow copy with target namespace/name for scaling logic
|
||||
amCopy := p.manager.DeepCopy()
|
||||
amCopy.Spec.AppNamespace = ns
|
||||
amCopy.Spec.AppName = chart.Name
|
||||
klog.Infof("amCopy.Spec.AppNamespace: %s", ns)
|
||||
klog.Infof("amCopy.Spec.AppName: %s", chart.Name)
|
||||
|
||||
if err := suspendOrResumeApp(ctx, p.client, amCopy, int32(0)); err != nil {
|
||||
klog.Errorf("failed to stop shared chart %s in namespace %s: %v", chart.Name, ns, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err := suspendOrResumeApp(ctx, p.client, p.manager, int32(0))
|
||||
if err != nil {
|
||||
klog.Errorf("suspend %s %s failed %v", p.manager.Spec.Type, p.manager.Spec.AppName, err)
|
||||
return fmt.Errorf("suspend app %s failed %w", p.manager.Spec.AppName, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.manager.Spec.Type == appsv1.Middleware && userspace.IsKbMiddlewares(p.manager.Spec.AppName) {
|
||||
err = p.execMiddleware(ctx)
|
||||
err := p.execMiddleware(ctx)
|
||||
if err != nil {
|
||||
klog.Errorf("suspend middleware %s failed %v", p.manager.Spec.AppName, err)
|
||||
return err
|
||||
|
||||
@@ -84,7 +84,7 @@ func (b *baseStatefulApp) updateStatus(ctx context.Context, am *appsv1.Applicati
|
||||
OpID: b.manager.Status.OpID,
|
||||
State: state.String(),
|
||||
RawAppName: b.manager.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: b.manager.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(b.manager.Spec.Config),
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
|
||||
@@ -87,7 +87,7 @@ func PublishAppEventToQueue(p utils.EventParams) {
|
||||
EventID: fmt.Sprintf("%s-%s-%d", p.Owner, p.Name, now.UnixMilli()),
|
||||
CreateTime: now,
|
||||
Name: p.Name,
|
||||
Type: "app",
|
||||
Type: p.Type,
|
||||
OpType: p.OpType,
|
||||
OpID: p.OpID,
|
||||
State: p.State,
|
||||
@@ -99,9 +99,10 @@ func PublishAppEventToQueue(p utils.EventParams) {
|
||||
}
|
||||
return p.RawAppName
|
||||
}(),
|
||||
Title: p.Title,
|
||||
Reason: p.Reason,
|
||||
Message: p.Message,
|
||||
Title: p.Title,
|
||||
Reason: p.Reason,
|
||||
Message: p.Message,
|
||||
SharedEntrances: p.SharedEntrances,
|
||||
}
|
||||
if len(p.EntranceStatuses) > 0 {
|
||||
data.EntranceStatuses = p.EntranceStatuses
|
||||
|
||||
@@ -198,7 +198,7 @@ func (imc *ImageManagerClient) PollDownloadProgress(ctx context.Context, am *app
|
||||
}
|
||||
|
||||
}
|
||||
err = imc.updateProgress(ctx, am, &lastProgress, ret*100)
|
||||
err = imc.updateProgress(ctx, am, &lastProgress, ret*100, am.Spec.OpType == appv1alpha1.UpgradeOp)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -209,7 +209,7 @@ func (imc *ImageManagerClient) PollDownloadProgress(ctx context.Context, am *app
|
||||
}
|
||||
}
|
||||
|
||||
func (imc *ImageManagerClient) updateProgress(ctx context.Context, am *appv1alpha1.ApplicationManager, lastProgress *float64, progress float64) error {
|
||||
func (imc *ImageManagerClient) updateProgress(ctx context.Context, am *appv1alpha1.ApplicationManager, lastProgress *float64, progress float64, isUpgrade bool) error {
|
||||
if *lastProgress > progress {
|
||||
return errors.New("no need to update progress")
|
||||
}
|
||||
@@ -219,14 +219,19 @@ func (imc *ImageManagerClient) updateProgress(ctx context.Context, am *appv1alph
|
||||
*lastProgress = progress
|
||||
|
||||
appevent.PublishAppEventToQueue(utils.EventParams{
|
||||
Owner: am.Spec.AppOwner,
|
||||
Name: am.Spec.AppName,
|
||||
OpType: string(am.Status.OpType),
|
||||
OpID: am.Status.OpID,
|
||||
State: appv1alpha1.Downloading.String(),
|
||||
Owner: am.Spec.AppOwner,
|
||||
Name: am.Spec.AppName,
|
||||
OpType: string(am.Status.OpType),
|
||||
OpID: am.Status.OpID,
|
||||
State: func() string {
|
||||
if isUpgrade {
|
||||
return appv1alpha1.Upgrading.String()
|
||||
}
|
||||
return appv1alpha1.Downloading.String()
|
||||
}(),
|
||||
Progress: progressStr,
|
||||
RawAppName: am.Spec.RawAppName,
|
||||
Type: "app",
|
||||
Type: am.Spec.Type.String(),
|
||||
Title: apputils.AppTitle(am.Spec.Config),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ type Event struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
SharedEntrances []v1alpha1.Entrance `json:"sharedEntrances,omitempty"`
|
||||
}
|
||||
|
||||
// EventParams defines parameters to publish an app-related event
|
||||
@@ -43,6 +44,7 @@ type EventParams struct {
|
||||
Title string
|
||||
Reason string
|
||||
Message string
|
||||
SharedEntrances []v1alpha1.Entrance
|
||||
}
|
||||
|
||||
type UserEvent struct {
|
||||
|
||||
Reference in New Issue
Block a user