mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-19 04:26:39 +00:00
Feat: Add a new CLI command: vela top which can display system information and resource status in UI form (#4556)
* Feat: UI sketch init Signed-off-by: HanMengnan <1448189829@qq.com> * Feat: Resource data load finish Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: optimize code struct Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: add test case and fix some bugs 1. add test case to package model 2. fix a bug of ListClusters 3. optimize code structure Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: fix go.mod package version Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: fix some bugs and optimize code structure Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: fix irregular code and add comments Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: supplement test cases and fix bugs Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: move the `vela status --ui` command out and add it as a new command `vela top` Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: fix package import sequence and go.mod Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: add copyright header Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: update description and example of command `vela top` Signed-off-by: HanMengnan <1448189829@qq.com> * Fix: convert color variables to const variables Signed-off-by: HanMengnan <1448189829@qq.com>
This commit is contained in:
@@ -124,6 +124,11 @@ require (
|
||||
sigs.k8s.io/gateway-api v0.4.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell/v2 v2.5.2
|
||||
github.com/rivo/tview v0.0.0-20220709181631-73bf2902b59a
|
||||
)
|
||||
|
||||
require github.com/rogpeppe/go-internal v1.8.1
|
||||
|
||||
require (
|
||||
@@ -186,6 +191,7 @@ require (
|
||||
github.com/fluxcd/pkg/apis/meta v0.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-logr/zapr v1.2.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
@@ -228,10 +234,11 @@ require (
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/lib/pq v1.10.3 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
@@ -260,6 +267,7 @@ require (
|
||||
github.com/prometheus/common v0.28.0 // indirect
|
||||
github.com/prometheus/procfs v0.6.0 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rubenv/sql-migrate v0.0.0-20210614095031-55d5740dbbcc // indirect
|
||||
github.com/russross/blackfriday v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
|
||||
@@ -718,6 +718,11 @@ github.com/fvbommel/sortorder v1.0.1/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui72
|
||||
github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko=
|
||||
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
|
||||
github.com/gdamore/tcell/v2 v2.5.2 h1:tKzG29kO9p2V++3oBY2W9zUjYu7IK1MENFeY/BzJSVY=
|
||||
github.com/gdamore/tcell/v2 v2.5.2/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||
github.com/gertd/go-pluralize v0.1.7 h1:RgvJTJ5W7olOoAks97BOwOlekBFsLEyh00W48Z6ZEZY=
|
||||
github.com/gertd/go-pluralize v0.1.7/go.mod h1:O4eNeeIf91MHh1GJ2I47DNtaesm66NYvjYgAahcqSDQ=
|
||||
github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg=
|
||||
@@ -1409,6 +1414,8 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U
|
||||
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lovoo/gcloud-opentracing v0.3.0/go.mod h1:ZFqk2y38kMDDikZPAK7ynTTGuyt17nSPdS3K5e+ZTBY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -1460,8 +1467,9 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
@@ -1844,6 +1852,10 @@ github.com/rancher/wrangler v0.8.3/go.mod h1:dKEaHNB4izxmPUtpq1Hvr3z3Oh+9k5pCZyF
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||
github.com/retailnext/hllpp v1.0.1-0.20180308014038-101a6d2f8b52/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc=
|
||||
github.com/rivo/tview v0.0.0-20220709181631-73bf2902b59a h1:ZjJ1XcvsZkNVO+Rq/vQTOXtN3cmuAgpCp8m4fKG5CkY=
|
||||
github.com/rivo/tview v0.0.0-20220709181631-73bf2902b59a/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
|
||||
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
@@ -2566,6 +2578,7 @@ golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -2603,6 +2616,7 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -2615,6 +2629,7 @@ golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJ
|
||||
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
|
||||
@@ -171,7 +171,7 @@ func (c *AppCollector) ListApplicationResources(app *v1beta1.Application, queryT
|
||||
klog.Errorf("fetch object for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
|
||||
continue
|
||||
}
|
||||
rootStatus, err := checkResourceStatus(*rootObject)
|
||||
rootStatus, err := CheckResourceStatus(*rootObject)
|
||||
if err != nil {
|
||||
klog.Errorf("check status for resource apiVersion=%s kind=%s namespace=%s name=%s failure %s, skip this resource", root.APIVersion, root.Kind, root.Namespace, root.Name, err.Error())
|
||||
continue
|
||||
|
||||
@@ -525,7 +525,8 @@ var checkServiceStatus = func(obj unstructured.Unstructured) (*types.HealthStatu
|
||||
return &health, nil
|
||||
}
|
||||
|
||||
func checkResourceStatus(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
||||
// CheckResourceStatus return object status data
|
||||
func CheckResourceStatus(obj unstructured.Unstructured) (*types.HealthStatus, error) {
|
||||
group := obj.GroupVersionKind().Group
|
||||
kind := obj.GroupVersionKind().Kind
|
||||
var checkFunc healthyCheckFunc
|
||||
@@ -844,7 +845,7 @@ func iterateListSubResources(ctx context.Context, cluster string, k8sClient clie
|
||||
if !filter(rtn) && len(rtn.LeafNodes) == 0 {
|
||||
continue
|
||||
}
|
||||
healthStatus, err := checkResourceStatus(item)
|
||||
healthStatus, err := CheckResourceStatus(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ func TestHelmResourceStatus(t *testing.T) {
|
||||
for _, s := range testCases {
|
||||
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(s.hr.DeepCopy())
|
||||
assert.NoError(t, err)
|
||||
res, err := checkResourceStatus(unstructured.Unstructured{Object: obj})
|
||||
res, err := CheckResourceStatus(unstructured.Unstructured{Object: obj})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res, s.res)
|
||||
}
|
||||
@@ -430,7 +430,7 @@ func TestHelmRepoResourceStatus(t *testing.T) {
|
||||
for _, s := range testCases {
|
||||
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(s.hr.DeepCopy())
|
||||
assert.NoError(t, err)
|
||||
res, err := checkResourceStatus(unstructured.Unstructured{Object: obj})
|
||||
res, err := CheckResourceStatus(unstructured.Unstructured{Object: obj})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, res, s.res)
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ func NewCommandWithIOStreams(ioStream util.IOStreams) *cobra.Command {
|
||||
NewCapabilityShowCommand(commandArgs, ioStream),
|
||||
|
||||
// Manage Apps
|
||||
NewTopCommand(commandArgs, "11", ioStream),
|
||||
NewListCommand(commandArgs, "10", ioStream),
|
||||
NewAppStatusCommand(commandArgs, "9", ioStream),
|
||||
NewDeleteCommand(commandArgs, "7", ioStream),
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/view"
|
||||
)
|
||||
|
||||
// NewTopCommand will create command `top` for displaying the platform overview
|
||||
func NewTopCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "top",
|
||||
Short: "Launch UI to display the platform overview.",
|
||||
Long: "Launch UI to display platform overview information and diagnose the status for any specific application.",
|
||||
Example: ` # Launch UI to display platform overview information and diagnose the status for any specific application
|
||||
vela top`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return launchUI(c, cmd)
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
types.TagCommandOrder: order,
|
||||
types.TagCommandType: types.TypeApp,
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func launchUI(c common.Args, _ *cobra.Command) error {
|
||||
k8sClient, err := c.GetClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get k8s client: %w", err)
|
||||
}
|
||||
restConfig, err := c.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app := view.NewApp(k8sClient, restConfig)
|
||||
app.Init()
|
||||
|
||||
return app.Run()
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// App represent the ui of application
|
||||
type App struct {
|
||||
*tview.Application
|
||||
actions model.KeyActions
|
||||
Components map[string]tview.Primitive
|
||||
Main *Pages
|
||||
}
|
||||
|
||||
// NewApp return the ui of application
|
||||
func NewApp() *App {
|
||||
a := &App{
|
||||
Application: tview.NewApplication(),
|
||||
actions: make(model.KeyActions),
|
||||
Main: NewPages(),
|
||||
}
|
||||
a.Components = map[string]tview.Primitive{
|
||||
"info": NewInfo(),
|
||||
"menu": NewMenu(),
|
||||
"logo": NewLogo(),
|
||||
"crumbs": NewCrumbs(),
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Init the ui of application init
|
||||
func (a *App) Init() {
|
||||
a.SetRoot(a.Main, true)
|
||||
}
|
||||
|
||||
// HasAction judge whether the key has the corresponding action
|
||||
func (a *App) HasAction(key tcell.Key) (model.KeyAction, bool) {
|
||||
action, ok := a.actions[key]
|
||||
return action, ok
|
||||
}
|
||||
|
||||
// AddAction add a new keyAction
|
||||
func (a *App) AddAction(actions model.KeyActions) {
|
||||
a.actions.Add(actions)
|
||||
}
|
||||
|
||||
// DelAction delete a keyAction
|
||||
func (a *App) DelAction(keys []tcell.Key) {
|
||||
a.actions.Delete(keys)
|
||||
}
|
||||
|
||||
// QueueUpdateDraw queues up a ui action and redraw the ui.
|
||||
func (a *App) QueueUpdateDraw(f func()) {
|
||||
if a.Application == nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
a.Application.QueueUpdateDraw(f)
|
||||
}()
|
||||
}
|
||||
|
||||
// InfoBoard return system info component
|
||||
func (a *App) InfoBoard() *InfoBoard {
|
||||
return a.Components["info"].(*InfoBoard)
|
||||
}
|
||||
|
||||
// Logo return logo component
|
||||
func (a *App) Logo() *Logo {
|
||||
return a.Components["logo"].(*Logo)
|
||||
}
|
||||
|
||||
// Menu return key action menu component
|
||||
func (a *App) Menu() *Menu {
|
||||
return a.Components["menu"].(*Menu)
|
||||
}
|
||||
|
||||
// Crumbs return the crumbs component
|
||||
func (a *App) Crumbs() *Crumbs {
|
||||
return a.Components["crumbs"].(*Crumbs)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
func TestApp(t *testing.T) {
|
||||
app := NewApp()
|
||||
assert.Equal(t, len(app.actions), 0)
|
||||
assert.Equal(t, len(app.Components), 4)
|
||||
t.Run("app init", func(t *testing.T) {
|
||||
app.Init()
|
||||
app.QueueUpdateDraw(func() {})
|
||||
})
|
||||
t.Run("add action", func(t *testing.T) {
|
||||
app.AddAction(model.KeyActions{
|
||||
tcell.KeyEnter: model.KeyAction{
|
||||
Description: "",
|
||||
Action: nil,
|
||||
Visible: false,
|
||||
Shared: false,
|
||||
},
|
||||
})
|
||||
assert.Equal(t, len(app.actions), 1)
|
||||
})
|
||||
t.Run("delete action", func(t *testing.T) {
|
||||
app.DelAction([]tcell.Key{tcell.KeyEnter})
|
||||
assert.Equal(t, len(app.actions), 0)
|
||||
})
|
||||
t.Run("menu", func(t *testing.T) {
|
||||
assert.NotEmpty(t, app.Menu())
|
||||
})
|
||||
t.Run("crumbs", func(t *testing.T) {
|
||||
assert.NotEmpty(t, app.Crumbs())
|
||||
})
|
||||
t.Run("logo", func(t *testing.T) {
|
||||
assert.NotEmpty(t, app.Logo())
|
||||
})
|
||||
t.Run("info board", func(t *testing.T) {
|
||||
assert.NotEmpty(t, app.InfoBoard())
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// Crumbs component lay on footer of app and indicate resource level
|
||||
type Crumbs struct {
|
||||
*tview.Flex
|
||||
}
|
||||
|
||||
// NewCrumbs return a new crumbs component
|
||||
func NewCrumbs() *Crumbs {
|
||||
c := &Crumbs{
|
||||
Flex: tview.NewFlex(),
|
||||
}
|
||||
c.init()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Crumbs) init() {
|
||||
}
|
||||
|
||||
// StackPop change itself when accept "pop" notify from app's main view
|
||||
func (c *Crumbs) StackPop(_, _ model.Component) {
|
||||
num := c.GetItemCount()
|
||||
if num >= 2 {
|
||||
c.RemoveItem(c.GetItem(num - 1))
|
||||
c.RemoveItem(c.GetItem(num - 2))
|
||||
}
|
||||
}
|
||||
|
||||
// StackPush change itself when accept "push" notify from app's main view
|
||||
func (c *Crumbs) StackPush(component model.Component) {
|
||||
name := component.Name()
|
||||
t := tview.NewTextView()
|
||||
t.SetBackgroundColor(config.CrumbsBackgroundColor)
|
||||
t.SetTextAlign(tview.AlignCenter)
|
||||
|
||||
t.SetText(name)
|
||||
c.AddItem(t, len(name)+2, 0, false)
|
||||
c.AddItem(tview.NewTextView(), 1, 0, false)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCrumbs(t *testing.T) {
|
||||
crumbs := NewCrumbs()
|
||||
assert.Equal(t, crumbs.GetItemCount(), 0)
|
||||
t.Run("stack push", func(t *testing.T) {
|
||||
p := NewPages()
|
||||
crumbs.StackPush(p)
|
||||
assert.Equal(t, crumbs.GetItemCount(), 2)
|
||||
})
|
||||
t.Run("stack pop", func(t *testing.T) {
|
||||
crumbs.StackPop(nil, nil)
|
||||
assert.Equal(t, crumbs.GetItemCount(), 0)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"github.com/rivo/tview"
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// InfoBoard a component which display system info
|
||||
type InfoBoard struct {
|
||||
*tview.Table
|
||||
}
|
||||
|
||||
// NewInfo return an info component instance
|
||||
func NewInfo() *InfoBoard {
|
||||
c := &InfoBoard{
|
||||
Table: tview.NewTable(),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Init info component init
|
||||
func (board *InfoBoard) Init(restConf *rest.Config) {
|
||||
var row int
|
||||
info := model.NewInfo()
|
||||
board.SetCell(row, 0, board.sectionCell("Context").SetTextColor(config.InfoSectionColor))
|
||||
board.SetCell(row, 1, infoCell(info.CurrentContext()).SetTextColor(config.InfoTextColor))
|
||||
row++
|
||||
|
||||
board.SetCell(row, 0, board.sectionCell("Cluster Num").SetTextColor(config.InfoSectionColor))
|
||||
board.SetCell(row, 1, infoCell(info.ClusterNum()).SetTextColor(config.InfoTextColor))
|
||||
row++
|
||||
|
||||
k8s := model.K8SVersion(restConf)
|
||||
board.SetCell(row, 0, board.sectionCell("K8S Version").SetTextColor(config.InfoSectionColor))
|
||||
board.SetCell(row, 1, infoCell(k8s).SetTextColor(config.InfoTextColor))
|
||||
row++
|
||||
|
||||
velaCLI := model.VelaCLIVersion()
|
||||
board.SetCell(row, 0, board.sectionCell("VelaCLI Version").SetTextColor(config.InfoSectionColor))
|
||||
board.SetCell(row, 1, infoCell(velaCLI).SetTextColor(config.InfoTextColor))
|
||||
row++
|
||||
|
||||
velaCore := model.VelaCoreVersion()
|
||||
board.SetCell(row, 0, board.sectionCell("VelaCore Version").SetTextColor(config.InfoSectionColor))
|
||||
board.SetCell(row, 1, infoCell(velaCore).SetTextColor(config.InfoTextColor))
|
||||
row++
|
||||
|
||||
goVersion := model.GOLangVersion()
|
||||
board.SetCell(row, 0, board.sectionCell("Golang Version").SetTextColor(config.InfoSectionColor))
|
||||
board.SetCell(row, 1, infoCell(goVersion).SetTextColor(config.InfoTextColor))
|
||||
}
|
||||
|
||||
func (board *InfoBoard) sectionCell(t string) *tview.TableCell {
|
||||
c := tview.NewTableCell(t + ":")
|
||||
c.SetAlign(tview.AlignLeft)
|
||||
return c
|
||||
}
|
||||
|
||||
func infoCell(t string) *tview.TableCell {
|
||||
c := tview.NewTableCell(t)
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
)
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
testEnv := &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
cfg, err := testEnv.Start()
|
||||
assert.NoError(t, err)
|
||||
info := NewInfo()
|
||||
info.Init(cfg)
|
||||
assert.Equal(t, info.GetColumnCount(), 2)
|
||||
assert.Equal(t, info.GetRowCount(), 6)
|
||||
assert.Equal(t, info.GetCell(0, 0).Text, "Context:")
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import "github.com/gdamore/tcell/v2"
|
||||
|
||||
const (
|
||||
// KeyHelp corresponding value of keyboard key "?"
|
||||
KeyHelp = 63
|
||||
// KeySlash corresponding value of keyboard key "/"
|
||||
KeySlash = 47
|
||||
// KeyColon corresponding value of keyboard key ":"
|
||||
KeyColon = 58
|
||||
// KeySpace corresponding value of keyboard key "SPACE"
|
||||
KeySpace = 32
|
||||
)
|
||||
|
||||
func init() {
|
||||
tcell.KeyNames[tcell.Key(KeyHelp)] = "?"
|
||||
tcell.KeyNames[tcell.Key(KeySlash)] = "/"
|
||||
tcell.KeyNames[tcell.Key(KeySpace)] = "space"
|
||||
}
|
||||
|
||||
// StandardizeKey standardized combined key event and return corresponding key
|
||||
func StandardizeKey(event *tcell.EventKey) tcell.Key {
|
||||
if event.Key() == 256 {
|
||||
return tcell.Key(event.Rune())
|
||||
}
|
||||
return event.Key()
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
)
|
||||
|
||||
var velaLogo = []string{
|
||||
` _ __ _ __ __ _ `,
|
||||
`| |/ /_ _ | |__ ___\ \ / /___ | | __ _ `,
|
||||
`| ' /| | | || '_ \ / _ \\ \ / // _ \| | / _\ |`,
|
||||
`| . \| |_| || |_) || __/ \ V /| __/| || (_| |`,
|
||||
`|_|\_\\__,_||_.__/ \___| \_/ \___||_| \__,_|`,
|
||||
}
|
||||
|
||||
// Logo logo component in header
|
||||
type Logo struct {
|
||||
*tview.TextView
|
||||
}
|
||||
|
||||
// NewLogo return logo ui component
|
||||
func NewLogo() *Logo {
|
||||
l := &Logo{
|
||||
TextView: tview.NewTextView(),
|
||||
}
|
||||
l.init()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Logo) init() {
|
||||
l.SetWrap(false)
|
||||
l.SetWordWrap(false)
|
||||
l.SetTextAlign(tview.AlignCenter)
|
||||
l.SetTextColor(config.LogoTextColor)
|
||||
l.SetDynamicColors(true)
|
||||
for _, line := range velaLogo {
|
||||
fmt.Fprintf(l, "%s\n", line)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLogo(t *testing.T) {
|
||||
logo := NewLogo()
|
||||
assert.NotEmpty(t, logo)
|
||||
assert.Equal(t, logo.GetText(false),
|
||||
` _ __ _ __ __ _
|
||||
| |/ /_ _ | |__ ___\ \ / /___ | | __ _
|
||||
| ' /| | | || '_ \ / _ \\ \ / // _ \| | / _\ |
|
||||
| . \| |_| || |_) || __/ \ V /| __/| || (_| |
|
||||
|_|\_\\__,_||_.__/ \___| \_/ \___||_| \__,_|
|
||||
|
||||
`)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// Menu is menu component which display key actions of app's main view
|
||||
type Menu struct {
|
||||
*tview.Table
|
||||
}
|
||||
|
||||
// NewMenu return a new menu instance
|
||||
func NewMenu() *Menu {
|
||||
m := &Menu{
|
||||
Table: tview.NewTable(),
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// StackPop change itself when accept "pop" notify from app's main view
|
||||
func (m *Menu) StackPop(old, new model.Component) {
|
||||
m.UpdateMenu(new.Hint())
|
||||
}
|
||||
|
||||
// StackPush change itself when accept "push" notify from app's main view
|
||||
func (m *Menu) StackPush(component model.Component) {
|
||||
m.UpdateMenu(component.Hint())
|
||||
}
|
||||
|
||||
// UpdateMenu update menu component
|
||||
func (m *Menu) UpdateMenu(hints []model.MenuHint) {
|
||||
m.Clear()
|
||||
// convert one-dimensional hints to two-dimensional hints
|
||||
tableCellStrHint := make([][]model.MenuHint, config.MenuRowNum)
|
||||
columCount := int(math.Ceil(float64(len(hints)) / config.MenuRowNum))
|
||||
for row := 0; row < config.MenuRowNum; row++ {
|
||||
tableCellStrHint[row] = make([]model.MenuHint, columCount)
|
||||
}
|
||||
|
||||
// convert two-dimensional hints to two-dimensional string
|
||||
tableCellStr := m.buildMenuTable(hints, tableCellStrHint)
|
||||
for row := 0; row < len(tableCellStr); row++ {
|
||||
for col := 0; col < len(tableCellStr[row]); col++ {
|
||||
c := tview.NewTableCell(tableCellStr[row][col])
|
||||
if len(tableCellStr[row][col]) == 0 {
|
||||
c = tview.NewTableCell("")
|
||||
}
|
||||
m.SetCell(row, col, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Menu) buildMenuTable(hints []model.MenuHint, table [][]model.MenuHint) [][]string {
|
||||
var row, col int
|
||||
for _, h := range hints {
|
||||
table[row][col] = h
|
||||
row++
|
||||
if row == config.MenuRowNum {
|
||||
row, col = 0, col+1
|
||||
}
|
||||
}
|
||||
out := make([][]string, len(table))
|
||||
for r := range out {
|
||||
out[r] = make([]string, len(table[r]))
|
||||
}
|
||||
for r := range table {
|
||||
for c := range table[r] {
|
||||
out[r][c] = formatPlainMenu(table[r][c])
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func formatPlainMenu(h model.MenuHint) string {
|
||||
menuFmt := " [blue:-:b]%-" + strconv.Itoa(10) + "s [:-:b]%s "
|
||||
return fmt.Sprintf(menuFmt, menuFormat(h), h.Description)
|
||||
}
|
||||
|
||||
func menuFormat(hint model.MenuHint) string {
|
||||
if len(hint.Key) == 0 && len(hint.Description) == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("<%s>", hint.Key)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
func TestMenu(t *testing.T) {
|
||||
menu := NewMenu()
|
||||
table := NewTable()
|
||||
|
||||
t.Run("stack push", func(t *testing.T) {
|
||||
table.actions.Add(model.KeyActions{
|
||||
tcell.KeyEnter: model.KeyAction{
|
||||
Description: "",
|
||||
Action: nil,
|
||||
Visible: false,
|
||||
Shared: false,
|
||||
},
|
||||
})
|
||||
menu.StackPush(table)
|
||||
assert.Equal(t, menu.GetCell(0, 0).Text, " [blue:-:b]<Enter> [:-:b] ")
|
||||
})
|
||||
t.Run("stack pop", func(t *testing.T) {
|
||||
table.actions.Add(model.KeyActions{
|
||||
tcell.KeyEsc: model.KeyAction{
|
||||
Description: "",
|
||||
Action: nil,
|
||||
Visible: false,
|
||||
Shared: false,
|
||||
},
|
||||
})
|
||||
menu.StackPop(nil, table)
|
||||
assert.Equal(t, menu.GetCell(1, 0).Text, " [blue:-:b]<Esc> [:-:b] ")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// Pages is the app's main content view component
|
||||
type Pages struct {
|
||||
*tview.Pages
|
||||
*model.Stack
|
||||
}
|
||||
|
||||
// NewPages return a page component
|
||||
func NewPages() *Pages {
|
||||
p := &Pages{
|
||||
Pages: tview.NewPages(),
|
||||
Stack: model.NewStack(),
|
||||
}
|
||||
p.Stack.AddListener(p)
|
||||
return p
|
||||
}
|
||||
|
||||
// Init table component
|
||||
func (p *Pages) Init() {}
|
||||
|
||||
// Name return pages' name
|
||||
func (p *Pages) Name() string {
|
||||
return "Pages"
|
||||
}
|
||||
|
||||
// Start table component
|
||||
func (p *Pages) Start() {}
|
||||
|
||||
// Stop table component
|
||||
func (p *Pages) Stop() {}
|
||||
|
||||
// Hint return key action menu hints of the component
|
||||
func (p *Pages) Hint() []model.MenuHint {
|
||||
return []model.MenuHint{}
|
||||
}
|
||||
|
||||
// StackPop change itself when accept "pop" notify from app's main view
|
||||
func (p *Pages) StackPop(old, _ model.Component) {
|
||||
p.delete(old)
|
||||
}
|
||||
|
||||
// StackPush change itself when accept "push" notify from app's main view
|
||||
func (p *Pages) StackPush(component model.Component) {
|
||||
p.addAndShow(component)
|
||||
}
|
||||
|
||||
// AddAndShow adds a new page and bring it to front.
|
||||
func (p *Pages) addAndShow(c model.Component) {
|
||||
p.add(c)
|
||||
p.show(c)
|
||||
}
|
||||
|
||||
func (p *Pages) add(c model.Component) {
|
||||
p.AddPage(componentID(c), c, true, true)
|
||||
}
|
||||
|
||||
func (p *Pages) delete(c model.Component) {
|
||||
p.RemovePage(componentID(c))
|
||||
}
|
||||
|
||||
func (p *Pages) show(c model.Component) {
|
||||
p.SwitchToPage(componentID(c))
|
||||
}
|
||||
|
||||
func componentID(c model.Component) string {
|
||||
if c.Name() == "" {
|
||||
panic("Component has no name")
|
||||
}
|
||||
return fmt.Sprintf("%s-%p", c.Name(), c)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPages(t *testing.T) {
|
||||
pages := NewPages()
|
||||
table := NewTable()
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
pages.Init()
|
||||
pages.Start()
|
||||
pages.Stop()
|
||||
assert.Equal(t, pages.Stack.Empty(), true)
|
||||
})
|
||||
t.Run("name", func(t *testing.T) {
|
||||
assert.Equal(t, pages.Name(), "Pages")
|
||||
})
|
||||
t.Run("hint", func(t *testing.T) {
|
||||
assert.Equal(t, len(pages.Hint()), 0)
|
||||
})
|
||||
|
||||
t.Run("component id", func(t *testing.T) {
|
||||
assert.Contains(t, componentID(table), "table")
|
||||
})
|
||||
t.Run("stack push", func(t *testing.T) {
|
||||
pages.StackPush(table)
|
||||
assert.Equal(t, pages.HasPage(componentID(table)), true)
|
||||
})
|
||||
t.Run("stack pop", func(t *testing.T) {
|
||||
pages.StackPop(table, nil)
|
||||
assert.Equal(t, pages.HasPage(componentID(table)), false)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// Table is a base table component which can be reused by other component
|
||||
type Table struct {
|
||||
*tview.Table
|
||||
actions model.KeyActions
|
||||
}
|
||||
|
||||
// NewTable return a new table component
|
||||
func NewTable() *Table {
|
||||
return &Table{
|
||||
Table: tview.NewTable(),
|
||||
actions: make(model.KeyActions),
|
||||
}
|
||||
}
|
||||
|
||||
// Init table component
|
||||
func (t *Table) Init() {
|
||||
t.SetBorder(true)
|
||||
t.SetBorderAttributes(tcell.AttrItalic)
|
||||
t.SetBorderPadding(1, 1, 1, 1)
|
||||
}
|
||||
|
||||
// Name return table's name
|
||||
func (t *Table) Name() string {
|
||||
return "table"
|
||||
}
|
||||
|
||||
// Start table component
|
||||
func (t *Table) Start() {
|
||||
}
|
||||
|
||||
// Stop table component
|
||||
func (t *Table) Stop() {
|
||||
}
|
||||
|
||||
// Hint return key action menu hints of the component
|
||||
func (t *Table) Hint() []model.MenuHint {
|
||||
return t.actions.Hint()
|
||||
}
|
||||
|
||||
// Actions return actions
|
||||
func (t *Table) Actions() model.KeyActions {
|
||||
return t.actions
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package component
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
func TestTable(t *testing.T) {
|
||||
table := NewTable()
|
||||
t.Run("init", func(t *testing.T) {
|
||||
table.Init()
|
||||
table.Start()
|
||||
table.Stop()
|
||||
})
|
||||
t.Run("name", func(t *testing.T) {
|
||||
assert.Equal(t, table.Name(), "table")
|
||||
})
|
||||
t.Run("action", func(t *testing.T) {
|
||||
assert.Equal(t, len(table.Actions()), 0)
|
||||
})
|
||||
t.Run("action and hint", func(t *testing.T) {
|
||||
table.actions.Add(model.KeyActions{
|
||||
tcell.KeyEnter: model.KeyAction{
|
||||
Description: "",
|
||||
Action: nil,
|
||||
Visible: false,
|
||||
Shared: false,
|
||||
},
|
||||
})
|
||||
assert.Equal(t, len(table.Actions()), 1)
|
||||
assert.Equal(t, len(table.Hint()), 1)
|
||||
assert.Equal(t, table.Hint()[0], model.MenuHint{
|
||||
Key: "Enter",
|
||||
Description: "",
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// InfoSectionColor system info component section text color
|
||||
InfoSectionColor = tcell.ColorRoyalBlue
|
||||
// InfoTextColor system info component text color
|
||||
InfoTextColor = tcell.ColorLightGray
|
||||
// LogoTextColor logo text color
|
||||
LogoTextColor = tcell.ColorRoyalBlue
|
||||
// CrumbsBackgroundColor crumbs background color
|
||||
CrumbsBackgroundColor = tcell.ColorRoyalBlue
|
||||
// ResourceTableTitleColor resource component title color
|
||||
ResourceTableTitleColor = tcell.ColorBlue
|
||||
// ResourceTableHeaderColor resource table header text color
|
||||
ResourceTableHeaderColor = tcell.ColorLightGray
|
||||
// ResourceTableBodyColor resource table body text color
|
||||
ResourceTableBodyColor = tcell.ColorBlue
|
||||
// ApplicationStartingAndRenderingPhaseColor application Starting and Rendering phase text color
|
||||
ApplicationStartingAndRenderingPhaseColor = "[blue::]"
|
||||
// ApplicationWorkflowSuspendingPhaseColor application WorkflowSuspending phase text color
|
||||
ApplicationWorkflowSuspendingPhaseColor = "[yellow::]"
|
||||
// ApplicationWorkflowTerminatedPhaseColor application WorkflowTerminated phase text color
|
||||
ApplicationWorkflowTerminatedPhaseColor = "[red::]"
|
||||
// ApplicationRunningPhaseColor application Running phase text color
|
||||
ApplicationRunningPhaseColor = "[green::]"
|
||||
// ObjectHealthyStatusColor is object Healthy status text color
|
||||
ObjectHealthyStatusColor = "[green::]"
|
||||
// ObjectUnhealthyStatusColor is object Unhealthy status text color
|
||||
ObjectUnhealthyStatusColor = "[red::]"
|
||||
// ObjectProgressingStatusColor is object Progressing status text color
|
||||
ObjectProgressingStatusColor = "[blue::]"
|
||||
// ObjectUnKnownStatusColor is object UnKnown status text color
|
||||
ObjectUnKnownStatusColor = "[gray::]"
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import "k8s.io/client-go/rest"
|
||||
|
||||
// Config application configs
|
||||
type Config struct {
|
||||
RestConfig *rest.Config
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
const (
|
||||
// HeaderRowNum ui header max row num
|
||||
HeaderRowNum = 8
|
||||
// FooterRowNum ui footer max row num
|
||||
FooterRowNum = 1
|
||||
// MenuRowNum menu component max row num
|
||||
MenuRowNum = 6
|
||||
// InfoColumnNum info component max column num
|
||||
InfoColumnNum = 30
|
||||
// LogoColumnNum logo component max column num
|
||||
LogoColumnNum = 50
|
||||
)
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
// Application is the application resource object
|
||||
type Application struct {
|
||||
name string
|
||||
namespace string
|
||||
phase string
|
||||
createTime string
|
||||
}
|
||||
|
||||
// ApplicationList is application resource list
|
||||
type ApplicationList struct {
|
||||
title []string
|
||||
data []Application
|
||||
}
|
||||
|
||||
// Header generate header of table in application view
|
||||
func (l *ApplicationList) Header() []string {
|
||||
return l.title
|
||||
}
|
||||
|
||||
// Body generate body of table in application view
|
||||
func (l *ApplicationList) Body() [][]string {
|
||||
data := make([][]string, 0)
|
||||
for _, app := range l.data {
|
||||
data = append(data, []string{app.name, app.namespace, app.phase, app.createTime})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// ListApplications list all apps in all namespaces
|
||||
func ListApplications(ctx context.Context, c client.Reader) *ApplicationList {
|
||||
list := &ApplicationList{title: []string{"Name", "Namespace", "Phase", "CreateTime"}}
|
||||
apps := v1beta1.ApplicationList{}
|
||||
namespace := ctx.Value(&CtxKeyNamespace).(string)
|
||||
|
||||
if err := c.List(ctx, &apps, client.InNamespace(namespace)); err != nil {
|
||||
return list
|
||||
}
|
||||
for _, app := range apps.Items {
|
||||
list.data = append(list.data, Application{app.Name, app.Namespace, string(app.Status.Phase), app.CreationTimestamp.String()})
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// LoadApplication load the corresponding application according to name and namespace
|
||||
func LoadApplication(c client.Client, name, ns string) (*v1beta1.Application, error) {
|
||||
app := new(v1beta1.Application)
|
||||
err := c.Get(context.Background(), client.ObjectKey{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
}, app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
func TestApplicationList_Header(t *testing.T) {
|
||||
appList := &ApplicationList{title: []string{"Name", "Namespace", "Phase", "CreateTime"}}
|
||||
assert.Equal(t, appList.Header(), []string{"Name", "Namespace", "Phase", "CreateTime"})
|
||||
}
|
||||
|
||||
func TestApplicationList_Body(t *testing.T) {
|
||||
appList := &ApplicationList{data: []Application{{"name", "namespace", "phase", "createTime"}}}
|
||||
assert.Equal(t, len(appList.data), 1)
|
||||
assert.Equal(t, appList.Body()[0], []string{"name", "namespace", "phase", "createTime"})
|
||||
}
|
||||
|
||||
var _ = Describe("test Application", func() {
|
||||
var err error
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, &CtxKeyNamespace, "")
|
||||
|
||||
It("list applications", func() {
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
applicationsList := ListApplications(ctx, k8sClient)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(applicationsList.Header()).To(Equal([]string{"Name", "Namespace", "Phase", "CreateTime"}))
|
||||
Expect(len(applicationsList.Body())).To(Equal(1))
|
||||
})
|
||||
It("load application info", func() {
|
||||
application, err := LoadApplication(k8sClient, "first-vela-app", "default")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(application.Name).To(Equal("first-vela-app"))
|
||||
Expect(application.Namespace).To(Equal("default"))
|
||||
Expect(application.ClusterName).To(Equal(""))
|
||||
Expect(len(application.Spec.Components)).To(Equal(1))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
prismclusterv1alpha1 "github.com/kubevela/prism/pkg/apis/cluster/v1alpha1"
|
||||
"github.com/oam-dev/cluster-gateway/pkg/config"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
)
|
||||
|
||||
// Cluster is cluster resource struct
|
||||
type Cluster struct {
|
||||
name string
|
||||
alias string
|
||||
clusterType string
|
||||
endpoint string
|
||||
labels string
|
||||
}
|
||||
|
||||
// ClusterList is cluster resource list
|
||||
type ClusterList struct {
|
||||
title []string
|
||||
data []Cluster
|
||||
}
|
||||
|
||||
// Header generate header of table in application view
|
||||
func (l *ClusterList) Header() []string {
|
||||
return l.title
|
||||
}
|
||||
|
||||
// Body generate body of table in application view
|
||||
func (l *ClusterList) Body() [][]string {
|
||||
data := make([][]string, 0)
|
||||
for _, cluster := range l.data {
|
||||
data = append(data, []string{cluster.name, cluster.alias, cluster.clusterType, cluster.endpoint, cluster.labels})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// ListClusters list clusters where application deploys resource
|
||||
func ListClusters(ctx context.Context, c client.Client) *ClusterList {
|
||||
list := &ClusterList{
|
||||
title: []string{"Name", "Alias", "Type", "EndPoint", "Labels"},
|
||||
}
|
||||
name := ctx.Value(&CtxKeyAppName).(string)
|
||||
ns := ctx.Value(&CtxKeyNamespace).(string)
|
||||
app, err := LoadApplication(c, name, ns)
|
||||
if err != nil {
|
||||
return list
|
||||
}
|
||||
clusterSet := make(map[string]interface{})
|
||||
|
||||
for _, svc := range app.Status.AppliedResources {
|
||||
if svc.Cluster == "" {
|
||||
clusterSet[multicluster.ClusterLocalName] = struct{}{}
|
||||
} else {
|
||||
clusterSet[svc.Cluster] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
clusters, _ := prismclusterv1alpha1.NewClusterClient(c).List(context.Background())
|
||||
|
||||
for _, cluster := range clusters.Items {
|
||||
if _, ok := clusterSet[cluster.Name]; ok {
|
||||
clusterInfo := Cluster{
|
||||
name: cluster.Name,
|
||||
alias: cluster.Spec.Alias,
|
||||
clusterType: string(cluster.Spec.CredentialType),
|
||||
endpoint: cluster.Spec.Endpoint,
|
||||
}
|
||||
var labels []string
|
||||
for k, v := range cluster.Labels {
|
||||
if !strings.HasPrefix(k, config.MetaApiGroupName) {
|
||||
labels = append(labels, "[blue::]"+k+"="+"[green::]"+v)
|
||||
}
|
||||
}
|
||||
clusterInfo.labels = strings.Join(labels, ",")
|
||||
list.data = append(list.data, clusterInfo)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestClusterList_Header(t *testing.T) {
|
||||
clusterList := &ClusterList{title: []string{"Name", "Alias", "Type", "EndPoint", "Labels"}}
|
||||
assert.Equal(t, len(clusterList.Header()), 5)
|
||||
assert.Equal(t, clusterList.Header(), []string{"Name", "Alias", "Type", "EndPoint", "Labels"})
|
||||
}
|
||||
|
||||
func TestClusterList_Body(t *testing.T) {
|
||||
clusterList := &ClusterList{data: []Cluster{{"local", "", "", "", ""}}}
|
||||
assert.Equal(t, len(clusterList.Body()), 1)
|
||||
assert.Equal(t, clusterList.Body()[0], []string{"local", "", "", "", ""})
|
||||
}
|
||||
|
||||
var _ = Describe("test cluster", func() {
|
||||
ctx := context.WithValue(context.Background(), &CtxKeyAppName, "first-vela-app")
|
||||
ctx = context.WithValue(ctx, &CtxKeyNamespace, "default")
|
||||
|
||||
It("list clusters", func() {
|
||||
clusterList := ListClusters(ctx, k8sClient)
|
||||
Expect(len(clusterList.Header())).To(Equal(5))
|
||||
Expect(clusterList.Header()).To(Equal([]string{"Name", "Alias", "Type", "EndPoint", "Labels"}))
|
||||
Expect(len(clusterList.Body())).To(Equal(1))
|
||||
Expect(clusterList.Body()[0]).To(Equal([]string{"local", "", "Internal", "-", ""}))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/helm"
|
||||
"github.com/oam-dev/kubevela/version"
|
||||
)
|
||||
|
||||
// Info is system info struct
|
||||
type Info struct {
|
||||
config *api.Config
|
||||
}
|
||||
|
||||
const (
|
||||
// Unknown info
|
||||
Unknown = "UNKNOWN"
|
||||
)
|
||||
|
||||
// NewInfo return a new info struct
|
||||
func NewInfo() *Info {
|
||||
info := &Info{}
|
||||
k := genericclioptions.NewConfigFlags(true)
|
||||
rawConfig, err := k.ToRawKubeConfigLoader().RawConfig()
|
||||
if err == nil {
|
||||
info.config = &rawConfig
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// CurrentContext return current context info
|
||||
func (i *Info) CurrentContext() string {
|
||||
if i.config != nil {
|
||||
return i.config.CurrentContext
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
|
||||
// ClusterNum return cluster number
|
||||
func (i *Info) ClusterNum() string {
|
||||
if i.config != nil {
|
||||
return strconv.Itoa(len(i.config.Clusters))
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// K8SVersion return k8s version info
|
||||
func K8SVersion(config *rest.Config) string {
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return Unknown
|
||||
}
|
||||
serverVersion, err := client.ServerVersion()
|
||||
if err != nil {
|
||||
return Unknown
|
||||
}
|
||||
vStr := fmt.Sprintf("%s.%s", serverVersion.Major, strings.Replace(serverVersion.Minor, "+", "", 1))
|
||||
return vStr
|
||||
}
|
||||
|
||||
// VelaCLIVersion return vela cli version info
|
||||
func VelaCLIVersion() string {
|
||||
return version.VelaVersion
|
||||
}
|
||||
|
||||
// VelaCoreVersion return vela core version info
|
||||
func VelaCoreVersion() string {
|
||||
results, err := helm.GetHelmRelease(types.DefaultKubeVelaNS)
|
||||
if err != nil {
|
||||
return Unknown
|
||||
}
|
||||
for _, result := range results {
|
||||
if result.Chart.ChartFullPath() == types.DefaultKubeVelaChartName {
|
||||
return result.Chart.AppVersion()
|
||||
}
|
||||
}
|
||||
return Unknown
|
||||
}
|
||||
|
||||
// GOLangVersion return golang version info
|
||||
func GOLangVersion() string {
|
||||
return runtime.Version()
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInfo_CurrentContext(t *testing.T) {
|
||||
info := NewInfo()
|
||||
assert.NotEqual(t, info.CurrentContext(), "UNKNOWN")
|
||||
}
|
||||
|
||||
func TestInfo_ClusterNum(t *testing.T) {
|
||||
info := NewInfo()
|
||||
assert.NotEqual(t, info.ClusterNum(), "0")
|
||||
}
|
||||
|
||||
func TestInfo_VelaCLIVersion(t *testing.T) {
|
||||
assert.NotEmpty(t, VelaCLIVersion())
|
||||
}
|
||||
|
||||
func TestInfo_VelaCoreVersion(t *testing.T) {
|
||||
assert.NotEmpty(t, VelaCoreVersion())
|
||||
}
|
||||
|
||||
func TestInfo_GOLangVersion(t *testing.T) {
|
||||
assert.Contains(t, GOLangVersion(), "go")
|
||||
}
|
||||
|
||||
var _ = Describe("test info", func() {
|
||||
It("test kubernetes version", func() {
|
||||
Expect(K8SVersion(cfg) != "UNKNOWN").To(BeTrue())
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/multicluster"
|
||||
"github.com/oam-dev/kubevela/pkg/velaql/providers/query"
|
||||
)
|
||||
|
||||
// K8SObject is k8s resource struct
|
||||
type K8SObject struct {
|
||||
name string
|
||||
namespace string
|
||||
kind string
|
||||
apiVersion string
|
||||
cluster string
|
||||
status string
|
||||
}
|
||||
|
||||
// K8SObjectList is k8s struct resource list
|
||||
type K8SObjectList struct {
|
||||
title []string
|
||||
data []K8SObject
|
||||
}
|
||||
|
||||
// Header generate header of table in k8s object view
|
||||
func (l *K8SObjectList) Header() []string {
|
||||
return l.title
|
||||
}
|
||||
|
||||
// Body generate header of table in k8s object view
|
||||
func (l *K8SObjectList) Body() [][]string {
|
||||
data := make([][]string, 0)
|
||||
for _, app := range l.data {
|
||||
data = append(data, []string{app.name, app.namespace, app.kind, app.apiVersion, app.cluster, app.status})
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// ListObjects return k8s object resource list
|
||||
func ListObjects(ctx context.Context, c client.Client) *K8SObjectList {
|
||||
list := &K8SObjectList{
|
||||
title: []string{"Name", "Namespace", "Kind", "APIVersion", "Cluster", "Status"},
|
||||
}
|
||||
name := ctx.Value(&CtxKeyAppName).(string)
|
||||
namespace := ctx.Value(&CtxKeyNamespace).(string)
|
||||
cluster := ctx.Value(&CtxKeyCluster).(string)
|
||||
|
||||
opt := query.Option{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
}
|
||||
if cluster != multicluster.ClusterLocalName {
|
||||
opt.Filter = query.FilterOption{Cluster: cluster}
|
||||
}
|
||||
collector := query.NewAppCollector(c, opt)
|
||||
appResList, err := collector.CollectResourceFromApp()
|
||||
|
||||
if err != nil {
|
||||
return list
|
||||
}
|
||||
|
||||
for _, resource := range appResList {
|
||||
list.data = append(list.data, LoadObjectDetail(resource))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
// LoadObjectDetail return the aim k8s object detail info
|
||||
func LoadObjectDetail(resource query.Resource) K8SObject {
|
||||
object := K8SObject{
|
||||
name: resource.Object.GetName(),
|
||||
namespace: resource.Object.GetNamespace(),
|
||||
kind: resource.Object.GetKind(),
|
||||
apiVersion: resource.Object.GetAPIVersion(),
|
||||
cluster: resource.Cluster,
|
||||
}
|
||||
status, err := query.CheckResourceStatus(*resource.Object)
|
||||
if err == nil {
|
||||
object.status = string(status.Status)
|
||||
}
|
||||
return object
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestK8SObjectList_Header(t *testing.T) {
|
||||
list := K8SObjectList{title: []string{"name", "namespace", "kind", "APIVersion", "cluster", "status"}}
|
||||
assert.Equal(t, len(list.Header()), 6)
|
||||
assert.Equal(t, list.Header(), []string{"name", "namespace", "kind", "APIVersion", "cluster", "status"})
|
||||
}
|
||||
|
||||
func TestK8SObjectList_Body(t *testing.T) {
|
||||
list := K8SObjectList{data: []K8SObject{{"", "", "", "", "", ""}}}
|
||||
assert.Equal(t, len(list.Body()), 1)
|
||||
assert.Equal(t, list.Body(), [][]string{{"", "", "", "", "", ""}})
|
||||
}
|
||||
|
||||
var _ = Describe("test k8s object", func() {
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, &CtxKeyAppName, "first-vela-app")
|
||||
ctx = context.WithValue(ctx, &CtxKeyNamespace, "default")
|
||||
ctx = context.WithValue(ctx, &CtxKeyCluster, "local")
|
||||
|
||||
It("list k8s object", func() {
|
||||
list := ListObjects(ctx, k8sClient)
|
||||
Expect(len(list.Header())).To(Equal(6))
|
||||
Expect(len(list.Body())).To(Equal(2))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
)
|
||||
|
||||
// KeyAction is key action struct
|
||||
type KeyAction struct {
|
||||
Description string
|
||||
Action func(*tcell.EventKey) *tcell.EventKey
|
||||
Visible bool
|
||||
Shared bool
|
||||
}
|
||||
|
||||
// KeyActions is a map from key to action
|
||||
type KeyActions map[tcell.Key]KeyAction
|
||||
|
||||
// Hint convert key action map to menu hints
|
||||
func (ka KeyActions) Hint() []MenuHint {
|
||||
tmp := make([]int, 0)
|
||||
for k := range ka {
|
||||
tmp = append(tmp, int(k))
|
||||
}
|
||||
sort.Ints(tmp)
|
||||
hints := make([]MenuHint, 0)
|
||||
|
||||
for _, key := range tmp {
|
||||
if name, ok := tcell.KeyNames[tcell.Key(key)]; ok {
|
||||
hints = append(hints,
|
||||
MenuHint{
|
||||
Key: name,
|
||||
Description: ka[tcell.Key(key)].Description,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
return hints
|
||||
}
|
||||
|
||||
// Add a key action to key action map
|
||||
func (ka KeyActions) Add(actions KeyActions) {
|
||||
for k, v := range actions {
|
||||
ka[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Set a key action to key action map
|
||||
func (ka KeyActions) Set(actions KeyActions) {
|
||||
for k, v := range actions {
|
||||
ka[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Delete aim key from key action map
|
||||
func (ka KeyActions) Delete(kk []tcell.Key) {
|
||||
for _, k := range kk {
|
||||
delete(ka, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear key action map clear up
|
||||
func (ka KeyActions) Clear() {
|
||||
for k := range ka {
|
||||
delete(ka, k)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestKeyActions_Add(t *testing.T) {
|
||||
actions := KeyActions{tcell.KeyEnter: KeyAction{}}
|
||||
actions.Add(KeyActions{tcell.KeyTAB: KeyAction{}})
|
||||
assert.Equal(t, len(actions), 2)
|
||||
}
|
||||
|
||||
func TestKeyActions_Clear(t *testing.T) {
|
||||
actions := KeyActions{tcell.KeyEnter: KeyAction{}}
|
||||
actions.Clear()
|
||||
assert.Equal(t, len(actions), 0)
|
||||
}
|
||||
|
||||
func TestKeyActions_Delete(t *testing.T) {
|
||||
actions := KeyActions{tcell.KeyEnter: KeyAction{}}
|
||||
actions.Delete([]tcell.Key{tcell.KeyEnter})
|
||||
assert.Equal(t, len(actions), 0)
|
||||
}
|
||||
|
||||
func TestKeyActions_Hint(t *testing.T) {
|
||||
actions := KeyActions{tcell.KeyEnter: KeyAction{}}
|
||||
hints := actions.Hint()
|
||||
assert.Equal(t, len(hints), 1)
|
||||
assert.Equal(t, hints[0], MenuHint{
|
||||
Key: "Enter",
|
||||
Description: "",
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeyActions_Set(t *testing.T) {
|
||||
actions := KeyActions{tcell.KeyEnter: KeyAction{}}
|
||||
actions.Set(KeyActions{tcell.KeyHelp: KeyAction{
|
||||
Description: "",
|
||||
Action: nil,
|
||||
Visible: false,
|
||||
Shared: false,
|
||||
}})
|
||||
assert.Equal(t, len(actions), 2)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
// MenuHint key action hints display by menu component
|
||||
type MenuHint struct {
|
||||
Key string
|
||||
Description string
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
// ResourceList an abstract kinds of resource list which can convert it to data of view in the form of table
|
||||
type ResourceList interface {
|
||||
// Header generate header of table in resource view
|
||||
Header() []string
|
||||
// Body generate body of table in resource view
|
||||
Body() [][]string
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import "sync"
|
||||
|
||||
const (
|
||||
// stackPush is the "push" notify type
|
||||
stackPush = iota
|
||||
// StackPop is the "pop" notify type
|
||||
StackPop
|
||||
)
|
||||
|
||||
// ResourceListener listen notify from the main view of app and render itself again
|
||||
type ResourceListener interface {
|
||||
// StackPop pop old component and render component
|
||||
StackPop(Component, Component)
|
||||
// StackPush push a new component
|
||||
StackPush(Component)
|
||||
}
|
||||
|
||||
// Stack is a stack to store components and notify listeners of main view of app
|
||||
type Stack struct {
|
||||
components []Component
|
||||
listeners []ResourceListener
|
||||
mx sync.RWMutex
|
||||
}
|
||||
|
||||
// NewStack return a new stack instance
|
||||
func NewStack() *Stack {
|
||||
return &Stack{
|
||||
components: make([]Component, 0),
|
||||
listeners: make([]ResourceListener, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// AddListener add a new resource listener
|
||||
func (s *Stack) AddListener(listener ResourceListener) {
|
||||
s.listeners = append(s.listeners, listener)
|
||||
}
|
||||
|
||||
// RemoveListener remove the aim resource listener
|
||||
func (s *Stack) RemoveListener(listener ResourceListener) {
|
||||
aimIndex := -1
|
||||
for index, item := range s.listeners {
|
||||
if item == listener {
|
||||
aimIndex = index
|
||||
}
|
||||
}
|
||||
if aimIndex == -1 {
|
||||
return
|
||||
}
|
||||
s.listeners = append(s.listeners[:aimIndex], s.listeners[aimIndex+1:]...)
|
||||
}
|
||||
|
||||
// TopComponent return top component of stack
|
||||
func (s *Stack) TopComponent() Component {
|
||||
if s.Empty() {
|
||||
return nil
|
||||
}
|
||||
return s.components[len(s.components)-1]
|
||||
}
|
||||
|
||||
// IsLastComponent check whether stack only have one component now
|
||||
func (s *Stack) IsLastComponent() bool {
|
||||
return len(s.components) == 1
|
||||
}
|
||||
|
||||
// PopComponent pop a component from stack
|
||||
func (s *Stack) PopComponent() {
|
||||
if s.Empty() {
|
||||
return
|
||||
}
|
||||
|
||||
s.mx.Lock()
|
||||
removeComponent := s.components[len(s.components)-1]
|
||||
s.components = s.components[:len(s.components)-1]
|
||||
s.mx.Unlock()
|
||||
|
||||
s.notifyListener(StackPop, removeComponent)
|
||||
}
|
||||
|
||||
// PushComponent add a new component to stack
|
||||
func (s *Stack) PushComponent(component Component) {
|
||||
if top := s.TopComponent(); top != nil {
|
||||
top.Stop()
|
||||
}
|
||||
|
||||
s.mx.Lock()
|
||||
s.components = append(s.components, component)
|
||||
s.mx.Unlock()
|
||||
|
||||
s.notifyListener(stackPush, component)
|
||||
}
|
||||
|
||||
// Empty return whether stack is empty
|
||||
func (s *Stack) Empty() bool {
|
||||
return len(s.components) == 0
|
||||
}
|
||||
|
||||
func (s *Stack) notifyListener(action int, component Component) {
|
||||
for _, listener := range s.listeners {
|
||||
switch action {
|
||||
case stackPush:
|
||||
listener.StackPush(component)
|
||||
case StackPop:
|
||||
listener.StackPop(component, s.TopComponent())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockListener struct{}
|
||||
|
||||
func (l *mockListener) StackPop(_ Component, _ Component) {}
|
||||
func (l *mockListener) StackPush(_ Component) {}
|
||||
|
||||
type mockComponent struct {
|
||||
tview.Primitive
|
||||
}
|
||||
|
||||
func (c *mockComponent) Init() {}
|
||||
func (c *mockComponent) Name() string {
|
||||
return ""
|
||||
}
|
||||
func (c *mockComponent) Start() {}
|
||||
func (c *mockComponent) Stop() {}
|
||||
func (c *mockComponent) Hint() []MenuHint {
|
||||
return []MenuHint{}
|
||||
}
|
||||
|
||||
func TestStack(t *testing.T) {
|
||||
stack := NewStack()
|
||||
assert.Equal(t, stack.Empty(), true)
|
||||
l := &mockListener{}
|
||||
stack.AddListener(l)
|
||||
c := &mockComponent{}
|
||||
stack.PushComponent(c)
|
||||
assert.Equal(t, stack.IsLastComponent(), true)
|
||||
assert.Equal(t, stack.TopComponent(), c)
|
||||
stack.PopComponent()
|
||||
assert.Equal(t, stack.Empty(), true)
|
||||
stack.RemoveListener(l)
|
||||
assert.Equal(t, len(stack.listeners), 0)
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
common2 "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
helmapi "github.com/oam-dev/kubevela/pkg/appfile/helm/flux2apis"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
|
||||
var createObject = func(name string, ns string, value string, kind string) *unstructured.Unstructured {
|
||||
o := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"name": name,
|
||||
"namespace": ns,
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"key": value,
|
||||
},
|
||||
},
|
||||
}
|
||||
o.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind(kind))
|
||||
return o
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func(done Done) {
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
CRDDirectoryPaths: []string{
|
||||
"../../../../charts/vela-core/crds",
|
||||
},
|
||||
}
|
||||
|
||||
By("start kube test env")
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
|
||||
By("new kube client")
|
||||
cfg.Timeout = time.Minute * 2
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
name, namespace := "first-vela-app", "default"
|
||||
testApp := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common2.ApplicationComponent{
|
||||
{
|
||||
Name: "webservice-test",
|
||||
Type: "webservice",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = k8sClient.Create(context.TODO(), testApp)
|
||||
Expect(err).Should(BeNil())
|
||||
testApp.Status = common2.AppStatus{
|
||||
AppliedResources: []common2.ClusterObjectReference{
|
||||
{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Ingress",
|
||||
Namespace: "default",
|
||||
Name: "ingress-http",
|
||||
APIVersion: "networking.k8s.io/v1beta1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Ingress",
|
||||
Namespace: "default",
|
||||
Name: "ingress-https",
|
||||
APIVersion: "networking.k8s.io/v1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Ingress",
|
||||
Namespace: "default",
|
||||
Name: "ingress-paths",
|
||||
APIVersion: "networking.k8s.io/v1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Service",
|
||||
Namespace: "default",
|
||||
Name: "nodeport",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: "Service",
|
||||
Namespace: "default",
|
||||
Name: "loadbalancer",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
Kind: helmapi.HelmReleaseGVK.Kind,
|
||||
Namespace: "default",
|
||||
Name: "helmRelease",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err = k8sClient.Status().Update(context.TODO(), testApp)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
svc := createObject("service1", namespace, "x", "Service")
|
||||
svcRaw, err := json.Marshal(svc)
|
||||
Expect(err).Should(Succeed())
|
||||
dply := createObject("deploy1", namespace, "y", "Deployment")
|
||||
dplyRaw, err := json.Marshal(dply)
|
||||
Expect(err).Should(Succeed())
|
||||
|
||||
rt := &v1beta1.ResourceTracker{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("%s-v1-%s", name, namespace),
|
||||
Labels: map[string]string{
|
||||
oam.LabelAppName: name,
|
||||
oam.LabelAppNamespace: namespace,
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
oam.AnnotationPublishVersion: "v1",
|
||||
},
|
||||
},
|
||||
Spec: v1beta1.ResourceTrackerSpec{
|
||||
ManagedResources: []v1beta1.ManagedResource{
|
||||
{
|
||||
ClusterObjectReference: common2.ClusterObjectReference{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
Namespace: namespace,
|
||||
Name: "web",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common2.OAMObjectReference{
|
||||
Component: "web",
|
||||
},
|
||||
Data: &runtime.RawExtension{Raw: svcRaw},
|
||||
},
|
||||
{
|
||||
ClusterObjectReference: common2.ClusterObjectReference{
|
||||
Cluster: "",
|
||||
ObjectReference: corev1.ObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Namespace: namespace,
|
||||
Name: "web",
|
||||
},
|
||||
},
|
||||
OAMObjectReference: common2.OAMObjectReference{
|
||||
Component: "web",
|
||||
},
|
||||
Data: &runtime.RawExtension{Raw: dplyRaw},
|
||||
},
|
||||
},
|
||||
Type: v1beta1.ResourceTrackerTypeVersioned,
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(context.TODO(), rt)).Should(BeNil())
|
||||
|
||||
close(done)
|
||||
}, 240)
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
func TestModel(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Model Suite")
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package model
|
||||
|
||||
import "github.com/rivo/tview"
|
||||
|
||||
type (
|
||||
// Component is an abstract of component of app
|
||||
Component interface {
|
||||
Primitive
|
||||
Initer
|
||||
Hinter
|
||||
}
|
||||
// Primitive is an abstract of tview ui component
|
||||
Primitive interface {
|
||||
tview.Primitive
|
||||
// Name return name of the component
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Initer is an abstract of components whose need to init
|
||||
Initer interface {
|
||||
// Start the component
|
||||
Start()
|
||||
// Stop the component
|
||||
Stop()
|
||||
// Init the component
|
||||
Init()
|
||||
}
|
||||
|
||||
// Hinter is an abstract of components which can provide menu hints to menu component
|
||||
Hinter interface {
|
||||
// Hint return key action menu hints of the component
|
||||
Hint() []MenuHint
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// CtxKeyAppName request context key of application name
|
||||
CtxKeyAppName = "appName"
|
||||
// CtxKeyCluster request context key of cluster name
|
||||
CtxKeyCluster = "cluster"
|
||||
// CtxKeyNamespace request context key of namespace name
|
||||
CtxKeyNamespace = "appNs"
|
||||
)
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/component"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// App application object
|
||||
type App struct {
|
||||
*component.App
|
||||
client client.Client
|
||||
config config.Config
|
||||
command *Command
|
||||
content *PageStack
|
||||
}
|
||||
|
||||
// NewApp return a new app object
|
||||
func NewApp(c client.Client, restConfig *rest.Config) *App {
|
||||
a := &App{
|
||||
App: component.NewApp(),
|
||||
client: c,
|
||||
config: config.Config{
|
||||
RestConfig: restConfig,
|
||||
},
|
||||
}
|
||||
a.command = NewCommand(a)
|
||||
a.content = NewPageStack(a)
|
||||
return a
|
||||
}
|
||||
|
||||
// Init the app
|
||||
func (a *App) Init() {
|
||||
a.command.Init()
|
||||
a.content.Init()
|
||||
a.content.AddListener(a.Menu())
|
||||
a.content.AddListener(a.Crumbs())
|
||||
|
||||
a.App.Init()
|
||||
a.layout()
|
||||
|
||||
a.bindKeys()
|
||||
a.SetInputCapture(a.keyboard)
|
||||
|
||||
a.defaultView(nil)
|
||||
}
|
||||
|
||||
func (a *App) layout() {
|
||||
main := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
main.SetBorder(true)
|
||||
main.SetBorderPadding(0, 0, 1, 1)
|
||||
main.AddItem(a.buildHeader(), config.HeaderRowNum, 1, false)
|
||||
main.AddItem(a.content, 0, 3, true)
|
||||
main.AddItem(a.Crumbs(), config.FooterRowNum, 1, false)
|
||||
a.Main.AddPage("main", main, true, false)
|
||||
}
|
||||
|
||||
func (a *App) buildHeader() tview.Primitive {
|
||||
header := tview.NewFlex()
|
||||
header.SetDirection(tview.FlexColumn)
|
||||
info := a.InfoBoard()
|
||||
info.Init(a.config.RestConfig)
|
||||
header.AddItem(info, config.InfoColumnNum, 1, false)
|
||||
header.AddItem(a.Menu(), 0, 2, false)
|
||||
header.AddItem(a.Logo(), config.LogoColumnNum, 1, false)
|
||||
return header
|
||||
}
|
||||
|
||||
// Run is the application running entrance
|
||||
func (a *App) Run() error {
|
||||
go func() {
|
||||
a.QueueUpdateDraw(func() {
|
||||
a.Main.SwitchToPage("main")
|
||||
})
|
||||
}()
|
||||
err := a.Application.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) bindKeys() {
|
||||
a.AddAction(model.KeyActions{
|
||||
tcell.KeyESC: model.KeyAction{Description: "Back", Action: a.Back, Visible: true, Shared: true},
|
||||
component.KeyHelp: model.KeyAction{Description: "Help", Action: a.helpView, Visible: true, Shared: true},
|
||||
})
|
||||
}
|
||||
|
||||
func (a *App) keyboard(event *tcell.EventKey) *tcell.EventKey {
|
||||
if action, ok := a.HasAction(component.StandardizeKey(event)); ok {
|
||||
return action.Action(event)
|
||||
}
|
||||
return event
|
||||
}
|
||||
|
||||
// inject add a new component to the app's main view to refresh the content of the main view
|
||||
func (a *App) inject(c model.Component) {
|
||||
c.Init()
|
||||
a.content.PushComponent(c)
|
||||
}
|
||||
|
||||
// defaultView is the first view of running application
|
||||
func (a *App) defaultView(event *tcell.EventKey) *tcell.EventKey {
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, &model.CtxKeyNamespace, "")
|
||||
a.command.run(ctx, "app")
|
||||
return event
|
||||
}
|
||||
|
||||
// helpView to display the view after pressing the Help key(?)
|
||||
func (a *App) helpView(_ *tcell.EventKey) *tcell.EventKey {
|
||||
top := a.content.TopComponent()
|
||||
if top != nil && top.Name() == "Help" {
|
||||
a.content.PopComponent()
|
||||
return nil
|
||||
}
|
||||
helpView := NewHelpView(a)
|
||||
a.inject(helpView)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Back to return before view corresponding to the ESC key
|
||||
func (a *App) Back(_ *tcell.EventKey) *tcell.EventKey {
|
||||
if !a.content.IsLastComponent() {
|
||||
a.content.PopComponent()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
func TestApp(t *testing.T) {
|
||||
testEnv := &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
cfg, err := testEnv.Start()
|
||||
assert.NoError(t, err)
|
||||
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
assert.NoError(t, err)
|
||||
app := NewApp(testClient, cfg)
|
||||
assert.Equal(t, len(app.Components), 4)
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
app.Init()
|
||||
assert.Equal(t, app.Main.HasPage("main"), true)
|
||||
_, ok := app.HasAction(tcell.KeyESC)
|
||||
assert.Equal(t, ok, true)
|
||||
app.content.Stack.RemoveListener(app.Crumbs())
|
||||
assert.NotEmpty(t, app.content.Stack.TopComponent())
|
||||
assert.Equal(t, app.content.Stack.Empty(), false)
|
||||
assert.Equal(t, app.content.Stack.IsLastComponent(), true)
|
||||
})
|
||||
t.Run("keyboard", func(t *testing.T) {
|
||||
evt1 := tcell.NewEventKey(tcell.KeyEsc, '/', 0)
|
||||
assert.Empty(t, app.keyboard(evt1))
|
||||
evt2 := tcell.NewEventKey(tcell.KeyTAB, '/', 0)
|
||||
assert.NotEmpty(t, app.keyboard(evt2))
|
||||
assert.Equal(t, app.keyboard(evt2), evt2)
|
||||
})
|
||||
t.Run("help view", func(t *testing.T) {
|
||||
assert.Empty(t, app.helpView(nil))
|
||||
assert.Equal(t, app.content.IsLastComponent(), false)
|
||||
assert.Empty(t, app.helpView(nil))
|
||||
assert.Equal(t, app.content.IsLastComponent(), true)
|
||||
})
|
||||
t.Run("back", func(t *testing.T) {
|
||||
assert.Empty(t, app.helpView(nil))
|
||||
app.Back(nil)
|
||||
assert.Equal(t, app.content.IsLastComponent(), true)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/component"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// ApplicationView is the application view, this view display info of application of KubeVela
|
||||
type ApplicationView struct {
|
||||
*ResourceView
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewApplicationView return a new application view
|
||||
func NewApplicationView(ctx context.Context, app *App) model.Component {
|
||||
v := &ApplicationView{
|
||||
ResourceView: NewResourceView(app),
|
||||
ctx: ctx,
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Init the application view
|
||||
func (v *ApplicationView) Init() {
|
||||
// set title of view
|
||||
title := fmt.Sprintf("[ %s ]", v.Name())
|
||||
v.SetTitle(title).SetTitleColor(config.ResourceTableTitleColor)
|
||||
// init view
|
||||
resourceList := v.ListApplications()
|
||||
v.ResourceView.Init(resourceList)
|
||||
|
||||
v.ColorizeStatusText(len(resourceList.Body()))
|
||||
v.bindKeys()
|
||||
}
|
||||
|
||||
// ListApplications list all applications
|
||||
func (v *ApplicationView) ListApplications() model.ResourceList {
|
||||
return model.ListApplications(v.ctx, v.app.client)
|
||||
}
|
||||
|
||||
// ColorizeStatusText colorize the status column text
|
||||
func (v *ApplicationView) ColorizeStatusText(rowNum int) {
|
||||
for i := 1; i < rowNum+1; i++ {
|
||||
status := v.Table.GetCell(i, 2).Text
|
||||
switch common.ApplicationPhase(status) {
|
||||
case common.ApplicationRendering, common.ApplicationStarting:
|
||||
status = config.ApplicationStartingAndRenderingPhaseColor + status
|
||||
case common.ApplicationWorkflowSuspending:
|
||||
status = config.ApplicationWorkflowSuspendingPhaseColor + status
|
||||
case common.ApplicationWorkflowTerminated:
|
||||
status = config.ApplicationWorkflowTerminatedPhaseColor + status
|
||||
case common.ApplicationRunning:
|
||||
status = config.ApplicationRunningPhaseColor + status
|
||||
default:
|
||||
}
|
||||
v.Table.GetCell(i, 2).SetText(status)
|
||||
}
|
||||
}
|
||||
|
||||
// Name return application view name
|
||||
func (v *ApplicationView) Name() string {
|
||||
return "Application"
|
||||
}
|
||||
|
||||
// Hint return key action menu hints of the application view
|
||||
func (v *ApplicationView) Hint() []model.MenuHint {
|
||||
return v.Actions().Hint()
|
||||
}
|
||||
|
||||
func (v *ApplicationView) bindKeys() {
|
||||
v.Actions().Delete([]tcell.Key{tcell.KeyEnter})
|
||||
v.Actions().Add(model.KeyActions{
|
||||
tcell.KeyEnter: model.KeyAction{Description: "Goto", Action: v.clusterView, Visible: true, Shared: true},
|
||||
tcell.KeyESC: model.KeyAction{Description: "Back", Action: v.app.Back, Visible: true, Shared: true},
|
||||
component.KeyHelp: model.KeyAction{Description: "Help", Action: v.app.helpView, Visible: true, Shared: true},
|
||||
})
|
||||
}
|
||||
|
||||
// clusterView switch app main view to the cluster view
|
||||
func (v *ApplicationView) clusterView(event *tcell.EventKey) *tcell.EventKey {
|
||||
row, _ := v.GetSelection()
|
||||
if row == 0 {
|
||||
return event
|
||||
}
|
||||
name, namespace := v.GetCell(row, 0).Text, v.GetCell(row, 1).Text
|
||||
|
||||
v.ctx = context.WithValue(v.ctx, &model.CtxKeyAppName, name)
|
||||
v.ctx = context.WithValue(v.ctx, &model.CtxKeyNamespace, namespace)
|
||||
|
||||
v.app.command.run(v.ctx, "cluster")
|
||||
return event
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
func TestApplicationView(t *testing.T) {
|
||||
testEnv := &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
cfg, err := testEnv.Start()
|
||||
assert.NoError(t, err)
|
||||
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
assert.NoError(t, err)
|
||||
app := NewApp(testClient, cfg)
|
||||
assert.Equal(t, len(app.Components), 4)
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, &model.CtxKeyNamespace, "")
|
||||
view := NewApplicationView(ctx, app)
|
||||
appView, ok := (view).(*ApplicationView)
|
||||
assert.Equal(t, ok, true)
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
appView.Init()
|
||||
assert.Equal(t, appView.Table.GetTitle(), "[ Application ]")
|
||||
assert.Equal(t, appView.GetCell(0, 0).Text, "Name")
|
||||
})
|
||||
|
||||
t.Run("colorize text", func(t *testing.T) {
|
||||
testData := [][]string{{"app", "ns", "running", ""}, {"app", "ns", "workflowSuspending", ""}, {"app", "ns", "workflowTerminated", ""}, {"app", "ns", "rendering", ""}}
|
||||
for i := 0; i < len(testData); i++ {
|
||||
for j := 0; j < 4; j++ {
|
||||
appView.Table.SetCell(1+i, j, tview.NewTableCell(testData[i][j]))
|
||||
}
|
||||
}
|
||||
appView.ColorizeStatusText(4)
|
||||
assert.Equal(t, appView.GetCell(1, 2).Text, "[green::]running")
|
||||
assert.Equal(t, appView.GetCell(2, 2).Text, "[yellow::]workflowSuspending")
|
||||
assert.Equal(t, appView.GetCell(3, 2).Text, "[red::]workflowTerminated")
|
||||
assert.Equal(t, appView.GetCell(4, 2).Text, "[blue::]rendering")
|
||||
})
|
||||
|
||||
t.Run("hint", func(t *testing.T) {
|
||||
assert.Equal(t, len(appView.Hint()), 3)
|
||||
})
|
||||
|
||||
t.Run("cluster view", func(t *testing.T) {
|
||||
appView.Table.Table.Table = appView.Table.Select(1, 1)
|
||||
assert.Empty(t, appView.clusterView(nil))
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/component"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// ClusterView is the cluster view, this view display info of cluster where selected application deployed
|
||||
type ClusterView struct {
|
||||
*ResourceView
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewClusterView return a new cluster view
|
||||
func NewClusterView(ctx context.Context, app *App) model.Component {
|
||||
v := &ClusterView{
|
||||
ResourceView: NewResourceView(app),
|
||||
ctx: ctx,
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Init cluster view init
|
||||
func (v *ClusterView) Init() {
|
||||
// set title of view
|
||||
title := fmt.Sprintf("[ %s ]", v.Name())
|
||||
v.SetTitle(title).SetTitleColor(config.ResourceTableTitleColor)
|
||||
|
||||
resourceList := v.ListClusters()
|
||||
v.ResourceView.Init(resourceList)
|
||||
v.bindKeys()
|
||||
}
|
||||
|
||||
// ListClusters list clusters where application deployed
|
||||
func (v *ClusterView) ListClusters() model.ResourceList {
|
||||
return model.ListClusters(v.ctx, v.app.client)
|
||||
}
|
||||
|
||||
// Name return cluster view name
|
||||
func (v *ClusterView) Name() string {
|
||||
return "Cluster"
|
||||
}
|
||||
|
||||
// Hint return key action menu hints of the cluster view
|
||||
func (v *ClusterView) Hint() []model.MenuHint {
|
||||
return v.Actions().Hint()
|
||||
}
|
||||
|
||||
func (v *ClusterView) bindKeys() {
|
||||
v.Actions().Delete([]tcell.Key{tcell.KeyEnter})
|
||||
v.Actions().Add(model.KeyActions{
|
||||
tcell.KeyEnter: model.KeyAction{Description: "Goto", Action: v.k8sObjectView, Visible: true, Shared: true},
|
||||
tcell.KeyESC: model.KeyAction{Description: "Back", Action: v.app.Back, Visible: true, Shared: true},
|
||||
component.KeyHelp: model.KeyAction{Description: "Help", Action: v.app.helpView, Visible: true, Shared: true},
|
||||
})
|
||||
}
|
||||
|
||||
// k8sObjectView switch app main view to k8s object view
|
||||
func (v *ClusterView) k8sObjectView(event *tcell.EventKey) *tcell.EventKey {
|
||||
row, _ := v.GetSelection()
|
||||
if row == 0 {
|
||||
return event
|
||||
}
|
||||
clusterName := v.GetCell(row, 0).Text
|
||||
v.ctx = context.WithValue(v.ctx, &model.CtxKeyCluster, clusterName)
|
||||
v.app.command.run(v.ctx, "k8s")
|
||||
return event
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
func TestClusterView(t *testing.T) {
|
||||
testEnv := &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
cfg, err := testEnv.Start()
|
||||
assert.NoError(t, err)
|
||||
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
assert.NoError(t, err)
|
||||
app := NewApp(testClient, cfg)
|
||||
assert.Equal(t, len(app.Components), 4)
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, &model.CtxKeyAppName, "")
|
||||
ctx = context.WithValue(ctx, &model.CtxKeyNamespace, "")
|
||||
view := NewClusterView(ctx, app)
|
||||
clusterView, ok := (view).(*ClusterView)
|
||||
assert.Equal(t, ok, true)
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
clusterView.Init()
|
||||
assert.Equal(t, clusterView.Table.GetTitle(), "[ Cluster ]")
|
||||
assert.Equal(t, clusterView.GetCell(0, 0).Text, "Name")
|
||||
})
|
||||
|
||||
t.Run("hint", func(t *testing.T) {
|
||||
assert.Equal(t, len(clusterView.Hint()), 3)
|
||||
})
|
||||
|
||||
t.Run("k8sObjectView", func(t *testing.T) {
|
||||
testData := []string{"local", "", "", "", ""}
|
||||
for j := 0; j < 5; j++ {
|
||||
clusterView.Table.SetCell(1, j, tview.NewTableCell(testData[j]))
|
||||
}
|
||||
assert.Equal(t, clusterView.GetCell(1, 0).Text, "local")
|
||||
clusterView.Table.Table.Table = clusterView.Table.Select(1, 1)
|
||||
assert.Empty(t, clusterView.k8sObjectView(nil))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// Command is a kind of abstract of user UI operation
|
||||
type Command struct {
|
||||
app *App
|
||||
}
|
||||
|
||||
// NewCommand return app's command instance
|
||||
func NewCommand(app *App) *Command {
|
||||
command := &Command{
|
||||
app: app,
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// Init command instance
|
||||
func (c *Command) Init() {}
|
||||
|
||||
func (c *Command) exec(_ string, component model.Component) {
|
||||
c.app.inject(component)
|
||||
}
|
||||
|
||||
func (c *Command) run(ctx context.Context, cmd string) {
|
||||
cmd = strings.ToLower(cmd)
|
||||
var component model.Component
|
||||
switch {
|
||||
case cmd == "?" || cmd == "h" || cmd == "help":
|
||||
component = NewHelpView(c.app)
|
||||
default:
|
||||
if resource, ok := ResourceMap[cmd]; ok {
|
||||
component = resource.viewFunc(ctx, c.app)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
c.exec(cmd, component)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/component"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// HelpView is the view which display help tips about how to use app
|
||||
type HelpView struct {
|
||||
*Table
|
||||
app *App
|
||||
}
|
||||
|
||||
// NewHelpView return a new help view
|
||||
func NewHelpView(app *App) *HelpView {
|
||||
v := &HelpView{
|
||||
Table: NewTable(),
|
||||
app: app,
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Init help view init
|
||||
func (v *HelpView) Init() {
|
||||
v.Table.Init()
|
||||
title := fmt.Sprintf("[ %s ]", v.Name())
|
||||
v.SetTitle(title).SetTitleColor(config.ResourceTableTitleColor)
|
||||
v.bindKeys()
|
||||
}
|
||||
|
||||
func (v *HelpView) bindKeys() {
|
||||
v.Actions().Add(model.KeyActions{
|
||||
tcell.KeyESC: model.KeyAction{Description: "Back", Action: v.app.Back, Visible: true, Shared: true},
|
||||
component.KeyHelp: model.KeyAction{Description: "Back", Action: v.app.Back, Visible: true, Shared: true},
|
||||
})
|
||||
}
|
||||
|
||||
// Name return help view name
|
||||
func (v *HelpView) Name() string {
|
||||
return "Help"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
func TestHelpView(t *testing.T) {
|
||||
testEnv := &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
cfg, err := testEnv.Start()
|
||||
assert.NoError(t, err)
|
||||
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
assert.NoError(t, err)
|
||||
app := NewApp(testClient, cfg)
|
||||
assert.Equal(t, len(app.Components), 4)
|
||||
helpView := NewHelpView(app)
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
helpView.Init()
|
||||
assert.Equal(t, helpView.GetTitle(), "[ Help ]")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/component"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// K8SView is a view which displays info of kubernetes objects which are generated when application deployed
|
||||
type K8SView struct {
|
||||
*ResourceView
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewK8SView return a new k8s view
|
||||
func NewK8SView(ctx context.Context, app *App) model.Component {
|
||||
v := &K8SView{
|
||||
ResourceView: NewResourceView(app),
|
||||
ctx: ctx,
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Init k8s view
|
||||
func (v *K8SView) Init() {
|
||||
// set title of view
|
||||
title := fmt.Sprintf("[ %s ]", v.Name())
|
||||
v.SetTitle(title).SetTitleColor(config.ResourceTableTitleColor)
|
||||
|
||||
resourceList := v.ListK8SObjects()
|
||||
v.ResourceView.Init(resourceList)
|
||||
|
||||
v.ColorizeStatusText(len(resourceList.Body()))
|
||||
v.bindKeys()
|
||||
}
|
||||
|
||||
// ListK8SObjects return kubernetes objects of the aimed application
|
||||
func (v *K8SView) ListK8SObjects() model.ResourceList {
|
||||
return model.ListObjects(v.ctx, v.app.client)
|
||||
}
|
||||
|
||||
// Name return k8s view name
|
||||
func (v *K8SView) Name() string {
|
||||
return "K8S-Object"
|
||||
}
|
||||
|
||||
// Hint return key action menu hints of the k8s view
|
||||
func (v *K8SView) Hint() []model.MenuHint {
|
||||
return v.Actions().Hint()
|
||||
}
|
||||
|
||||
// ColorizeStatusText colorize the status column text
|
||||
func (v *K8SView) ColorizeStatusText(rowNum int) {
|
||||
for i := 1; i < rowNum+1; i++ {
|
||||
status := v.Table.GetCell(i, 5).Text
|
||||
switch querytypes.HealthStatusCode(status) {
|
||||
case querytypes.HealthStatusHealthy:
|
||||
status = config.ObjectHealthyStatusColor + status
|
||||
case querytypes.HealthStatusUnHealthy:
|
||||
status = config.ObjectUnhealthyStatusColor + status
|
||||
case querytypes.HealthStatusProgressing:
|
||||
status = config.ObjectProgressingStatusColor + status
|
||||
case querytypes.HealthStatusUnKnown:
|
||||
status = config.ObjectUnKnownStatusColor + status
|
||||
default:
|
||||
}
|
||||
v.Table.GetCell(i, 5).SetText(status)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *K8SView) bindKeys() {
|
||||
v.Actions().Delete([]tcell.Key{tcell.KeyEnter})
|
||||
v.Actions().Add(model.KeyActions{
|
||||
tcell.KeyESC: model.KeyAction{Description: "Back", Action: v.app.Back, Visible: true, Shared: true},
|
||||
component.KeyHelp: model.KeyAction{Description: "Help", Action: v.app.helpView, Visible: true, Shared: true},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
func TestK8SView(t *testing.T) {
|
||||
testEnv := &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
cfg, err := testEnv.Start()
|
||||
assert.NoError(t, err)
|
||||
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
assert.NoError(t, err)
|
||||
app := NewApp(testClient, cfg)
|
||||
assert.Equal(t, len(app.Components), 4)
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, &model.CtxKeyAppName, "")
|
||||
ctx = context.WithValue(ctx, &model.CtxKeyNamespace, "")
|
||||
ctx = context.WithValue(ctx, &model.CtxKeyCluster, "")
|
||||
|
||||
view := NewK8SView(ctx, app)
|
||||
k8sView, ok := (view).(*K8SView)
|
||||
assert.Equal(t, ok, true)
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
k8sView.Init()
|
||||
assert.Equal(t, k8sView.Table.GetTitle(), "[ K8S-Object ]")
|
||||
assert.Equal(t, k8sView.GetCell(0, 0).Text, "Name")
|
||||
})
|
||||
|
||||
t.Run("colorize text", func(t *testing.T) {
|
||||
testData := [][]string{
|
||||
{"app", "ns", "", "", "", "Healthy"},
|
||||
{"app", "ns", "", "", "", "UnHealthy"},
|
||||
{"app", "ns", "", "", "", "Progressing"},
|
||||
{"app", "ns", "", "", "", "UnKnown"}}
|
||||
for i := 0; i < len(testData); i++ {
|
||||
for j := 0; j < len(testData[i]); j++ {
|
||||
k8sView.Table.SetCell(1+i, j, tview.NewTableCell(testData[i][j]))
|
||||
}
|
||||
}
|
||||
k8sView.ColorizeStatusText(4)
|
||||
assert.Equal(t, k8sView.GetCell(1, 5).Text, "[green::]Healthy")
|
||||
assert.Equal(t, k8sView.GetCell(2, 5).Text, "[red::]UnHealthy")
|
||||
assert.Equal(t, k8sView.GetCell(3, 5).Text, "[blue::]Progressing")
|
||||
assert.Equal(t, k8sView.GetCell(4, 5).Text, "[gray::]UnKnown")
|
||||
})
|
||||
|
||||
t.Run("hint", func(t *testing.T) {
|
||||
t.Run("hint", func(t *testing.T) {
|
||||
assert.Equal(t, len(k8sView.Hint()), 2)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/oam-dev/kubevela/references/cli/top/component"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// PageStack store views of app's main view, and it's a high level packing of "component.Pages"
|
||||
type PageStack struct {
|
||||
*component.Pages
|
||||
app *App
|
||||
}
|
||||
|
||||
// NewPageStack returns a new page stack.
|
||||
func NewPageStack(app *App) *PageStack {
|
||||
ps := &PageStack{
|
||||
Pages: component.NewPages(),
|
||||
app: app,
|
||||
}
|
||||
ps.Init()
|
||||
return ps
|
||||
}
|
||||
|
||||
// Init the pageStack
|
||||
func (ps *PageStack) Init() {
|
||||
ps.Stack.AddListener(ps)
|
||||
}
|
||||
|
||||
// StackPop change itself when accept "pop" notify from app's main view
|
||||
func (ps *PageStack) StackPop(_, _ model.Component) {}
|
||||
|
||||
// StackPush change itself when accept "pop" notify from app's main view
|
||||
func (ps *PageStack) StackPush(component model.Component) {
|
||||
ps.app.SetFocus(component)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/utils/common"
|
||||
)
|
||||
|
||||
func TestPageStack(t *testing.T) {
|
||||
testEnv := &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute * 3,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
UseExistingCluster: pointer.BoolPtr(false),
|
||||
}
|
||||
cfg, err := testEnv.Start()
|
||||
assert.NoError(t, err)
|
||||
testClient, err := client.New(cfg, client.Options{Scheme: common.Scheme})
|
||||
assert.NoError(t, err)
|
||||
app := NewApp(testClient, cfg)
|
||||
assert.Equal(t, len(app.Components), 4)
|
||||
|
||||
stack := NewPageStack(app)
|
||||
stack.Init()
|
||||
|
||||
t.Run("stack push", func(t *testing.T) {
|
||||
helpView := NewHelpView(app)
|
||||
stack.StackPush(helpView)
|
||||
})
|
||||
t.Run("stack pop", func(t *testing.T) {
|
||||
stack.StackPop(nil, nil)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rivo/tview"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/config"
|
||||
"github.com/oam-dev/kubevela/references/cli/top/model"
|
||||
)
|
||||
|
||||
// ResourceView is an abstract of resource view
|
||||
type ResourceView struct {
|
||||
*Table
|
||||
app *App
|
||||
}
|
||||
|
||||
// ResourceViewer is resource's renderer
|
||||
type ResourceViewer struct {
|
||||
viewFunc func(context.Context, *App) model.Component
|
||||
}
|
||||
|
||||
// ResourceMap is a map from resource name to resource's renderer
|
||||
var ResourceMap = map[string]ResourceViewer{
|
||||
"app": {
|
||||
viewFunc: NewApplicationView,
|
||||
},
|
||||
"cluster": {
|
||||
viewFunc: NewClusterView,
|
||||
},
|
||||
"k8s": {
|
||||
viewFunc: NewK8SView,
|
||||
},
|
||||
}
|
||||
|
||||
// NewResourceView return a new resource view
|
||||
func NewResourceView(app *App) *ResourceView {
|
||||
v := &ResourceView{
|
||||
Table: NewTable(),
|
||||
app: app,
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Init the resource view
|
||||
func (v *ResourceView) Init(list model.ResourceList) {
|
||||
v.SetSelectable(true, false)
|
||||
v.buildTable(list)
|
||||
}
|
||||
|
||||
func (v *ResourceView) buildTable(list model.ResourceList) {
|
||||
v.Table.Init()
|
||||
v.buildTableHeader(list.Header())
|
||||
v.buildTableBody(list.Body())
|
||||
}
|
||||
|
||||
// buildTableHeader render the resource table header
|
||||
func (v *ResourceView) buildTableHeader(header []string) {
|
||||
for i := 0; i < len(header); i++ {
|
||||
c := tview.NewTableCell(header[i]).SetTextColor(config.ResourceTableHeaderColor)
|
||||
c.SetExpansion(3)
|
||||
v.SetCell(0, i, c)
|
||||
}
|
||||
}
|
||||
|
||||
// buildTableBody render the resource table body
|
||||
func (v *ResourceView) buildTableBody(body [][]string) {
|
||||
for i := 0; i < len(body); i++ {
|
||||
for j := 0; j < len(body[i]); j++ {
|
||||
c := tview.NewTableCell(body[i][j])
|
||||
c.SetTextColor(config.ResourceTableBodyColor)
|
||||
c.SetExpansion(3)
|
||||
v.SetCell(i+1, j, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Name return view name
|
||||
func (v *ResourceView) Name() string {
|
||||
return "Resource"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestResourceView(t *testing.T) {
|
||||
view := NewResourceView(nil)
|
||||
assert.Equal(t, view.Name(), "Resource")
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/gdamore/tcell/v2"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/cli/top/component"
|
||||
)
|
||||
|
||||
// Table is a base table view, help view and resource view all base on it
|
||||
type Table struct {
|
||||
*component.Table
|
||||
}
|
||||
|
||||
// NewTable return a new table view
|
||||
func NewTable() *Table {
|
||||
t := &Table{
|
||||
Table: component.NewTable(),
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Init the table view
|
||||
func (t *Table) Init() {
|
||||
t.Table.Init()
|
||||
t.SetInputCapture(t.keyboard)
|
||||
}
|
||||
|
||||
func (t *Table) keyboard(event *tcell.EventKey) *tcell.EventKey {
|
||||
key := event.Key()
|
||||
if key == tcell.KeyUp || key == tcell.KeyDown {
|
||||
return event
|
||||
}
|
||||
if a, ok := t.Actions()[tcell.Key(event.Rune())]; ok {
|
||||
return a.Action(event)
|
||||
}
|
||||
return event
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTable(t *testing.T) {
|
||||
table := NewTable()
|
||||
|
||||
t.Run("init", func(t *testing.T) {
|
||||
table.Init()
|
||||
assert.Equal(t, table.Table.GetBorderAttributes(), tcell.AttrItalic)
|
||||
})
|
||||
|
||||
t.Run("keyboard", func(t *testing.T) {
|
||||
evt1 := tcell.NewEventKey(tcell.KeyUp, '/', 0)
|
||||
assert.Equal(t, table.keyboard(evt1), evt1)
|
||||
evt2 := tcell.NewEventKey(tcell.KeyEsc, '/', 0)
|
||||
assert.Equal(t, table.keyboard(evt2), evt2)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user