1
// Copyright Istio Authors
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
7
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
26
"istio.io/istio/pkg/ctrlz/fw"
27
"istio.io/istio/pkg/ctrlz/topics/assets"
30
// ReadableCollection is a staticCollection collection of entries to be exposed via CtrlZ.
31
type ReadableCollection interface {
33
Keys() (keys []string, err error)
34
Get(id string) (any, error)
37
// collection topic is a Topic fw.implementation that exposes a set of collections through CtrlZ.
38
type collectionTopic struct {
41
collections []ReadableCollection
43
mainTmpl *template.Template
44
listTmpl *template.Template
45
itemTmpl *template.Template
48
var _ fw.Topic = &collectionTopic{}
50
// Title is implementation of Topic.Title.
51
func (c *collectionTopic) Title() string {
55
// Prefix is implementation of Topic.Prefix.
56
func (c *collectionTopic) Prefix() string {
60
// Activate is implementation of Topic.Activate.
61
func (c *collectionTopic) Activate(context fw.TopicContext) {
62
l := template.Must(context.Layout().Clone())
63
c.mainTmpl = assets.ParseTemplate(l, "templates/collection/main.html")
65
l = template.Must(context.Layout().Clone())
66
c.listTmpl = assets.ParseTemplate(l, "templates/collection/list.html")
68
l = template.Must(context.Layout().Clone())
69
c.itemTmpl = assets.ParseTemplate(l, "templates/collection/item.html")
71
_ = context.HTMLRouter().
76
HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
77
parts := strings.SplitN(req.URL.Path, "/", 4)
78
parts = parts[2:] // Skip the empty and title parts.
85
c.handleCollection(w, req, parts[0])
89
c.handleItem(w, req, parts[0], parts[1])
92
c.handleError(w, req, fmt.Sprintf("InvalidUrl %s", req.URL.Path))
97
// mainContext is passed to the template processor and carries information that is used by the main template.
98
type mainContext struct {
104
func (c *collectionTopic) handleMain(w http.ResponseWriter, _ *http.Request) {
105
context := mainContext{}
106
names := make([]string, 0, len(c.collections))
107
for _, n := range c.collections {
108
names = append(names, n.Name())
111
context.Collections = names
112
context.Title = c.title
113
fw.RenderHTML(w, c.mainTmpl, context)
116
// listContext is passed to the template processor and carries information that is used by the list template.
117
type listContext struct {
123
func (c *collectionTopic) handleCollection(w http.ResponseWriter, _ *http.Request, collection string) {
124
k, err := c.listCollection(collection)
125
context := listContext{}
127
context.Collection = collection
130
context.Error = err.Error()
132
fw.RenderHTML(w, c.listTmpl, context)
135
// itemContext is passed to the template processor and carries information that is used by the list template.
136
type itemContext struct {
143
func (c *collectionTopic) handleItem(w http.ResponseWriter, _ *http.Request, collection, key string) {
144
v, err := c.getItem(collection, key)
145
context := itemContext{}
152
if b, err = yaml.Marshal(v); err != nil {
153
context.Error = err.Error()
159
context.Collection = collection
163
context.Error = err.Error()
165
fw.RenderHTML(w, c.itemTmpl, context)
168
func (c *collectionTopic) handleError(w http.ResponseWriter, _ *http.Request, errorText string) {
169
fw.RenderHTML(w, c.mainTmpl, mainContext{Error: errorText})
172
func (c *collectionTopic) listCollection(name string) ([]string, error) {
173
for _, col := range c.collections {
174
if col.Name() == name {
179
return nil, fmt.Errorf("collection not found: %q", name)
182
func (c *collectionTopic) getItem(collection string, id string) (any, error) {
183
for _, col := range c.collections {
184
if col.Name() == collection {
189
return nil, fmt.Errorf("collection not found: %q", collection)
192
// NewCollectionTopic creates a new custom topic that exposes the provided collections.
193
func NewCollectionTopic(title string, prefix string, collections ...ReadableCollection) fw.Topic {
194
return &collectionTopic{
197
collections: collections,
201
// NewStaticCollection creates a static collection from the given set of items.
202
func NewStaticCollection(name string, items map[string]any) ReadableCollection {
203
return &staticCollection{
209
// staticCollection is a ReadableCollection implementation that operates on static data that is supplied
210
// during construction.
211
type staticCollection struct {
216
var _ ReadableCollection = &staticCollection{}
218
// Name is implementation of ReadableCollection.Name.
219
func (r *staticCollection) Name() string {
223
// Keys is implementation of ReadableCollection.Keys.
224
func (r *staticCollection) Keys() ([]string, error) {
225
keys := make([]string, 0, len(r.items))
226
for k := range r.items {
227
keys = append(keys, k)
234
// Get is implementation of ReadableCollection.Get.
235
func (r *staticCollection) Get(id string) (any, error) {
236
return r.items[id], nil