提交 39a1d7f7 编写于 作者: M Marcin Maciaszczyk 提交者: GitHub

Introduce global search (#1895)

* Initial search implementation

* Support cluster resources

* Support config resources

* Fix filter icon placement

* Support discovery resources

* Sort results aphabetically

* Make breadcrumb dynamic

* Make search work with filtering

* Move search logic to the frontend

* Remove !important

* Remove !importants

* Fix lint errors

* Update client usage

* Fix serve:prod

* Fix lint error

* Add frontend tests
上级 26329c63
......@@ -162,6 +162,7 @@ function compileES6(translation) {
path.join(conf.paths.externs, 'uirouter.js'),
path.join(conf.paths.externs, 'dataselect.js'),
path.join(conf.paths.externs, 'dirPagination.js'),
path.join(conf.paths.externs, 'searchapi.js'),
];
let closureCompilerConfig = {
......
......@@ -39,6 +39,7 @@
<translation id="7569244915796303702" key="MSG_BREADCRUMBS_RC_LABEL" desc="Label 'Replication Controllers' that appears as a breadcrumbs on the action bar.">Replication Controllers</translation>
<translation id="837518421741773666" key="MSG_BREADCRUMBS_REPLICA_SETS_LABEL" desc="Label 'Replica Sets' that appears as a breadcrumbs on the action bar.">Replica Sets</translation>
<translation id="5794147661362074597" key="MSG_BREADCRUMBS_ROLES_LABEL" desc="Label 'Roles' that appears as a breadcrumbs on the action bar.">Roles</translation>
<translation id="2851304790534826779" key="MSG_BREADCRUMBS_SEARCH_LABEL" desc="Label 'Search' that appears as a breadcrumbs on the action bar.">Search for</translation>
<translation id="391192137756114804" key="MSG_BREADCRUMBS_SECRETS_LABEL" desc="Label 'Secrets' that appears as a breadcrumbs on the action bar.">Secrets</translation>
<translation id="5207082267245498149" key="MSG_BREADCRUMBS_SERVICES_AND_DISCOVERY_LABEL" desc="Label 'Services and discovery' that appears as a breadcrumbs on the action bar.">Services and discovery</translation>
<translation id="2218294864744563724" key="MSG_BREADCRUMBS_SERVICES_LABEL" desc="Label 'Services' that appears as a breadcrumbs on the action bar.">Services</translation>
......@@ -47,6 +48,8 @@
<translation id="4645270776145594474" key="MSG_BREADCRUMBS_STORAGE_LABEL" desc="Label 'Storage' that appears as a breadcrumbs on the action bar.">Storage</translation>
<translation id="6132384114487014259" key="MSG_BREADCRUMBS_THIRD_PARTY_RESOURCES_LABEL" desc="Label 'Third Party Resources' that appears as a breadcrumbs on the action bar.">Third Party Resources</translation>
<translation id="1656728804447365846" key="MSG_BREADCRUMBS_WORKLOADS_LABEL" desc="Label 'Workloads' that appears as a breadcrumbs on the action bar.">Workloads</translation>
<translation id="5441724995487814814" key="MSG_CHROME_CHROME_0" desc="Tooltip for global action bar create button tooltip.">Create an application or any Kubernetes resource</translation>
<translation id="152996649337797552" key="MSG_CHROME_CHROME_1" desc="Label for global action bar create button.">Create</translation>
<translation id="5023828914132135944" key="MSG_CHROME_NAV_NAV_0" desc="Cluster group menu entry.">Cluster</translation>
<translation id="9218333053624016665" key="MSG_CHROME_NAV_NAV_0" desc="Admin menu item in the nav.">Admin</translation>
<translation id="6343362656986122693" key="MSG_CHROME_NAV_NAV_1" desc="Namespaces menu item in the nav.">Namespaces</translation>
......
......@@ -51,6 +51,7 @@
<translation id="7569244915796303702" key="MSG_BREADCRUMBS_RC_LABEL" desc="Label 'Replication Controllers' that appears as a breadcrumbs on the action bar.">レプリケーションコントローラ</translation>
<translation id="837518421741773666" key="MSG_BREADCRUMBS_REPLICA_SETS_LABEL" desc="Label 'Replica Sets' that appears as a breadcrumbs on the action bar.">レプリカセット</translation>
<translation id="5794147661362074597" key="MSG_BREADCRUMBS_ROLES_LABEL" desc="Label 'Roles' that appears as a breadcrumbs on the action bar.">Roles</translation>
<translation id="2851304790534826779" key="MSG_BREADCRUMBS_SEARCH_LABEL" desc="Label 'Search' that appears as a breadcrumbs on the action bar.">Search for</translation>
<translation id="391192137756114804" key="MSG_BREADCRUMBS_SECRETS_LABEL" desc="Label 'Secrets' that appears as a breadcrumbs on the action bar.">シークレット</translation>
<translation id="5207082267245498149" key="MSG_BREADCRUMBS_SERVICES_AND_DISCOVERY_LABEL" desc="Label 'Services and discovery' that appears as a breadcrumbs on the action bar.">サービスとディスカバリー</translation>
<translation id="2218294864744563724" key="MSG_BREADCRUMBS_SERVICES_LABEL" desc="Label 'Services' that appears as a breadcrumbs on the action bar.">サービス</translation>
......@@ -59,6 +60,8 @@
<translation id="4645270776145594474" key="MSG_BREADCRUMBS_STORAGE_LABEL" desc="Label 'Storage' that appears as a breadcrumbs on the action bar.">Storage</translation>
<translation id="6132384114487014259" key="MSG_BREADCRUMBS_THIRD_PARTY_RESOURCES_LABEL" desc="Label 'Third Party Resources' that appears as a breadcrumbs on the action bar.">サードパーティーリソース</translation>
<translation id="1656728804447365846" key="MSG_BREADCRUMBS_WORKLOADS_LABEL" desc="Label 'Workloads' that appears as a breadcrumbs on the action bar.">ワークロード</translation>
<translation id="5441724995487814814" key="MSG_CHROME_CHROME_0" desc="Tooltip for global action bar create button tooltip.">Create an application or any Kubernetes resource</translation>
<translation id="152996649337797552" key="MSG_CHROME_CHROME_1" desc="Label for global action bar create button.">Create</translation>
<translation id="5023828914132135944" key="MSG_CHROME_NAV_NAV_0" desc="Cluster group menu entry.">Cluster</translation>
<translation id="9218333053624016665" key="MSG_CHROME_NAV_NAV_0" desc="Admin menu item in the nav.">管理者</translation>
<translation id="6343362656986122693" key="MSG_CHROME_NAV_NAV_1" desc="Namespaces menu item in the nav.">ネームスペース</translation>
......
......@@ -35,6 +35,7 @@
<translation id="7569244915796303702" key="MSG_BREADCRUMBS_RC_LABEL" desc="Label 'Replication Controllers' that appears as a breadcrumbs on the action bar.">复制控制(Replication Controllers)</translation>
<translation id="837518421741773666" key="MSG_BREADCRUMBS_REPLICA_SETS_LABEL" desc="Label 'Replica Sets' that appears as a breadcrumbs on the action bar.">复制集(Replica Sets)</translation>
<translation id="5794147661362074597" key="MSG_BREADCRUMBS_ROLES_LABEL" desc="Label 'Roles' that appears as a breadcrumbs on the action bar.">Roles</translation>
<translation id="2851304790534826779" key="MSG_BREADCRUMBS_SEARCH_LABEL" desc="Label 'Search' that appears as a breadcrumbs on the action bar.">Search for</translation>
<translation id="391192137756114804" key="MSG_BREADCRUMBS_SECRETS_LABEL" desc="Label 'Secrets' that appears as a breadcrumbs on the action bar.">保密式字典(Secrets)</translation>
<translation id="5207082267245498149" key="MSG_BREADCRUMBS_SERVICES_AND_DISCOVERY_LABEL" desc="Label 'Services and discovery' that appears as a breadcrumbs on the action bar.">服务和发现(Services and discovery)</translation>
<translation id="2218294864744563724" key="MSG_BREADCRUMBS_SERVICES_LABEL" desc="Label 'Services' that appears as a breadcrumbs on the action bar.">服务(Services)</translation>
......@@ -42,6 +43,8 @@
<translation id="1427200251427963672" key="MSG_BREADCRUMBS_STORAGE_CLASSES_LABEL" desc="Label 'Storage Classes' that appears as a breadcrumbs on the action bar.">Storage Classes</translation>
<translation id="6132384114487014259" key="MSG_BREADCRUMBS_THIRD_PARTY_RESOURCES_LABEL" desc="Label 'Third Party Resources' that appears as a breadcrumbs on the action bar.">Third Party Resources</translation>
<translation id="1656728804447365846" key="MSG_BREADCRUMBS_WORKLOADS_LABEL" desc="Label 'Workloads' that appears as a breadcrumbs on the action bar.">载荷(Workloads)</translation>
<translation id="5441724995487814814" key="MSG_CHROME_CHROME_0" desc="Tooltip for global action bar create button tooltip.">Create an application or any Kubernetes resource</translation>
<translation id="152996649337797552" key="MSG_CHROME_CHROME_1" desc="Label for global action bar create button.">Create</translation>
<translation id="5023828914132135944" key="MSG_CHROME_NAV_NAV_0" desc="Cluster group menu entry.">Cluster</translation>
<translation id="9218333053624016665" key="MSG_CHROME_NAV_NAV_0" desc="Admin menu item in the nav.">管理(Admin)</translation>
<translation id="6343362656986122693" key="MSG_CHROME_NAV_NAV_1" desc="Namespaces menu item in the nav.">名字空间(Namespaces)</translation>
......
......@@ -5,7 +5,7 @@
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Responsive-side-nav" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="1.4-1280---spacing" transform="translate(-77.000000, -18.000000)" fill="#FFFFFF">
<g id="1.4-1280---spacing" transform="translate(-77.000000, -18.000000)" fill="#326de6">
<g id="App-Bar">
<g id="kubernetes-logo-white" transform="translate(24.000000, 6.000000)">
<g id="Group">
......@@ -15,4 +15,4 @@
</g>
</g>
</g>
</svg>
\ No newline at end of file
</svg>
......@@ -42,7 +42,7 @@ import (
"github.com/kubernetes/dashboard/src/app/backend/resource/job"
"github.com/kubernetes/dashboard/src/app/backend/resource/logs"
"github.com/kubernetes/dashboard/src/app/backend/resource/metric"
"github.com/kubernetes/dashboard/src/app/backend/resource/namespace"
ns "github.com/kubernetes/dashboard/src/app/backend/resource/namespace"
"github.com/kubernetes/dashboard/src/app/backend/resource/node"
"github.com/kubernetes/dashboard/src/app/backend/resource/persistentvolume"
"github.com/kubernetes/dashboard/src/app/backend/resource/persistentvolumeclaim"
......@@ -58,6 +58,7 @@ import (
"github.com/kubernetes/dashboard/src/app/backend/resource/storageclass"
"github.com/kubernetes/dashboard/src/app/backend/resource/thirdpartyresource"
"github.com/kubernetes/dashboard/src/app/backend/resource/workload"
"github.com/kubernetes/dashboard/src/app/backend/search"
"github.com/kubernetes/dashboard/src/app/backend/validation"
"golang.org/x/net/xsrftoken"
errorsK8s "k8s.io/apimachinery/pkg/api/errors"
......@@ -245,11 +246,11 @@ func CreateHTTPAPIHandler(heapsterClient client.HeapsterClient,
inClusterConfig, err := restclient.InClusterConfig()
if err == nil {
// We run in a cluster, so we should use a signing key that is the same for potential replications
log.Printf("Using service account token for csrf signing")
log.Println("Using service account token for csrf signing")
csrfKey = inClusterConfig.BearerToken
} else {
// Most likely running for a dev, so no replica issues, just generate a random key
log.Printf("Using random key for csrf signing")
log.Println("Using random key for csrf signing")
bytes := make([]byte, 256)
_, err := rand.Read(bytes)
if err != nil {
......@@ -515,16 +516,16 @@ func CreateHTTPAPIHandler(heapsterClient client.HeapsterClient,
apiV1Ws.Route(
apiV1Ws.POST("/namespace").
To(apiHandler.handleCreateNamespace).
Reads(namespace.NamespaceSpec{}).
Writes(namespace.NamespaceSpec{}))
Reads(ns.NamespaceSpec{}).
Writes(ns.NamespaceSpec{}))
apiV1Ws.Route(
apiV1Ws.GET("/namespace").
To(apiHandler.handleGetNamespaces).
Writes(namespace.NamespaceList{}))
Writes(ns.NamespaceList{}))
apiV1Ws.Route(
apiV1Ws.GET("/namespace/{name}").
To(apiHandler.handleGetNamespaceDetail).
Writes(namespace.NamespaceDetail{}))
Writes(ns.NamespaceDetail{}))
apiV1Ws.Route(
apiV1Ws.GET("/namespace/{name}/event").
To(apiHandler.handleGetNamespaceEvents).
......@@ -704,6 +705,15 @@ func CreateHTTPAPIHandler(heapsterClient client.HeapsterClient,
To(apiHandler.handleGetStorageClass).
Writes(storageclass.StorageClass{}))
apiV1Ws.Route(
apiV1Ws.GET("/search").
To(apiHandler.handleSearch).
Writes(search.SearchResult{}))
apiV1Ws.Route(
apiV1Ws.GET("/search/{namespace}").
To(apiHandler.handleSearch).
Writes(search.SearchResult{}))
return wsContainer, nil
}
......@@ -1064,9 +1074,7 @@ func (apiHandler *APIHandler) handleGetReplicationControllerList(
}
// Handles get Workloads list API call.
func (apiHandler *APIHandler) handleGetWorkloads(
request *restful.Request, response *restful.Response) {
func (apiHandler *APIHandler) handleGetWorkloads(request *restful.Request, response *restful.Response) {
namespace := parseNamespacePathParameter(request)
dataSelect := parseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.NoMetrics
......@@ -1080,6 +1088,21 @@ func (apiHandler *APIHandler) handleGetWorkloads(
response.WriteHeaderAndEntity(http.StatusOK, result)
}
// Handles search API call.
func (apiHandler *APIHandler) handleSearch(request *restful.Request, response *restful.Response) {
namespace := parseNamespacePathParameter(request)
dataSelect := parseDataSelectPathParameter(request)
dataSelect.MetricQuery = dataselect.NoMetrics
result, err := search.Search(getApiClient(request), apiHandler.heapsterClient, namespace, dataSelect)
if err != nil {
handleInternalError(response, err)
return
}
response.WriteHeaderAndEntity(http.StatusOK, result)
}
func (apiHandler *APIHandler) handleGetDiscovery(
request *restful.Request, response *restful.Response) {
......@@ -1413,12 +1436,12 @@ func (apiHandler *APIHandler) handleGetReplicationControllerPods(
// Handles namespace creation API call.
func (apiHandler *APIHandler) handleCreateNamespace(request *restful.Request,
response *restful.Response) {
namespaceSpec := new(namespace.NamespaceSpec)
namespaceSpec := new(ns.NamespaceSpec)
if err := request.ReadEntity(namespaceSpec); err != nil {
handleInternalError(response, err)
return
}
if err := namespace.CreateNamespace(namespaceSpec, getApiClient(request)); err != nil {
if err := ns.CreateNamespace(namespaceSpec, getApiClient(request)); err != nil {
handleInternalError(response, err)
return
}
......@@ -1431,7 +1454,7 @@ func (apiHandler *APIHandler) handleGetNamespaces(
request *restful.Request, response *restful.Response) {
dataSelect := parseDataSelectPathParameter(request)
result, err := namespace.GetNamespaceList(getApiClient(request), dataSelect)
result, err := ns.GetNamespaceList(getApiClient(request), dataSelect)
if err != nil {
handleInternalError(response, err)
return
......@@ -1444,7 +1467,7 @@ func (apiHandler *APIHandler) handleGetNamespaces(
func (apiHandler *APIHandler) handleGetNamespaceDetail(request *restful.Request,
response *restful.Response) {
name := request.PathParameter("name")
result, err := namespace.GetNamespaceDetail(getApiClient(request), apiHandler.heapsterClient, name)
result, err := ns.GetNamespaceDetail(getApiClient(request), apiHandler.heapsterClient, name)
if err != nil {
handleInternalError(response, err)
return
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 search
import (
"github.com/kubernetes/dashboard/src/app/backend/client"
"github.com/kubernetes/dashboard/src/app/backend/resource/cluster"
"github.com/kubernetes/dashboard/src/app/backend/resource/common"
"github.com/kubernetes/dashboard/src/app/backend/resource/config"
"github.com/kubernetes/dashboard/src/app/backend/resource/configmap"
"github.com/kubernetes/dashboard/src/app/backend/resource/daemonset"
"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
"github.com/kubernetes/dashboard/src/app/backend/resource/deployment"
"github.com/kubernetes/dashboard/src/app/backend/resource/discovery"
"github.com/kubernetes/dashboard/src/app/backend/resource/ingress"
"github.com/kubernetes/dashboard/src/app/backend/resource/job"
"github.com/kubernetes/dashboard/src/app/backend/resource/namespace"
"github.com/kubernetes/dashboard/src/app/backend/resource/node"
"github.com/kubernetes/dashboard/src/app/backend/resource/persistentvolume"
pvc "github.com/kubernetes/dashboard/src/app/backend/resource/persistentvolumeclaim"
"github.com/kubernetes/dashboard/src/app/backend/resource/pod"
"github.com/kubernetes/dashboard/src/app/backend/resource/rbacroles"
"github.com/kubernetes/dashboard/src/app/backend/resource/replicaset"
rc "github.com/kubernetes/dashboard/src/app/backend/resource/replicationcontroller"
"github.com/kubernetes/dashboard/src/app/backend/resource/secret"
"github.com/kubernetes/dashboard/src/app/backend/resource/service"
"github.com/kubernetes/dashboard/src/app/backend/resource/statefulset"
"github.com/kubernetes/dashboard/src/app/backend/resource/storageclass"
"github.com/kubernetes/dashboard/src/app/backend/resource/workload"
"k8s.io/client-go/kubernetes"
)
// SearchResult is a list of resources matching search criteria found in whole cluster.
type SearchResult struct {
// Cluster.
NamespaceList namespace.NamespaceList `json:"namespaceList"`
NodeList node.NodeList `json:"nodeList"`
PersistentVolumeList persistentvolume.PersistentVolumeList `json:"persistentVolumeList"`
RoleList rbacroles.RbacRoleList `json:"roleList"`
StorageClassList storageclass.StorageClassList `json:"storageClassList"`
// Config and storage.
ConfigMapList configmap.ConfigMapList `json:"configMapList"`
PersistentVolumeClaimList pvc.PersistentVolumeClaimList `json:"persistentVolumeClaimList"`
SecretList secret.SecretList `json:"secretList"`
// Discovery and load balancing.
ServiceList service.ServiceList `json:"serviceList"`
IngressList ingress.IngressList `json:"ingressList"`
// Workloads.
DeploymentList deployment.DeploymentList `json:"deploymentList"`
ReplicaSetList replicaset.ReplicaSetList `json:"replicaSetList"`
JobList job.JobList `json:"jobList"`
ReplicationControllerList rc.ReplicationControllerList `json:"replicationControllerList"`
PodList pod.PodList `json:"podList"`
DaemonSetList daemonset.DaemonSetList `json:"daemonSetList"`
StatefulSetList statefulset.StatefulSetList `json:"statefulSetList"`
// TODO(maciaszczykm): Third party resources.
}
func Search(client *kubernetes.Clientset, heapsterClient client.HeapsterClient, nsQuery *common.NamespaceQuery,
dsQuery *dataselect.DataSelectQuery) (*SearchResult, error) {
clusterResources, err := cluster.GetCluster(client, dsQuery, &heapsterClient)
if err != nil {
return &SearchResult{}, err
}
configResources, err := config.GetConfig(client, nsQuery, dsQuery)
if err != nil {
return &SearchResult{}, err
}
discoveryResources, err := discovery.GetDiscovery(client, nsQuery, dsQuery)
if err != nil {
return &SearchResult{}, err
}
workloadsResources, err := workload.GetWorkloads(client, heapsterClient, nsQuery, dsQuery)
if err != nil {
return &SearchResult{}, err
}
return &SearchResult{
// Cluster.
NamespaceList: clusterResources.NamespaceList,
NodeList: clusterResources.NodeList,
PersistentVolumeList: clusterResources.PersistentVolumeList,
RoleList: clusterResources.RoleList,
StorageClassList: clusterResources.StorageClassList,
// Config and storage.
ConfigMapList: configResources.ConfigMapList,
PersistentVolumeClaimList: configResources.PersistentVolumeClaimList,
SecretList: configResources.SecretList,
// Discovery and load balancing.
ServiceList: discoveryResources.ServiceList,
IngressList: discoveryResources.IngressList,
// Workloads.
DeploymentList: workloadsResources.DeploymentList,
ReplicaSetList: workloadsResources.ReplicaSetList,
JobList: workloadsResources.JobList,
ReplicationControllerList: workloadsResources.ReplicationControllerList,
PodList: workloadsResources.PodList,
DaemonSetList: workloadsResources.DaemonSetList,
StatefulSetList: workloadsResources.StatefulSetList,
}, nil
}
......@@ -158,6 +158,29 @@ backendApi.Discovery;
*/
backendApi.Config;
/**
* @typedef {{
* deploymentList: !backendApi.DeploymentList,
* replicaSetList: !backendApi.ReplicaSetList,
* jobList: !backendApi.JobList,
* replicationControllerList: !backendApi.ReplicationControllerList,
* podList: !backendApi.PodList,
* daemonSetList: !backendApi.DaemonSetList,
* statefulSetList: !backendApi.StatefulSetList,
* nodeList: !backendApi.NodeList,
* namespaceList: !backendApi.NamespaceList,
* persistentVolumeList: !backendApi.PersistentVolumeList,
* roleList: !backendApi.RoleList,
* storageClassList: !backendApi.StorageClassList,
* serviceList: !backendApi.ServiceList,
* ingressList: !backendApi.IngressList,
* configMapList: !backendApi.ConfigMapList,
* persistentVolumeClaimList: !backendApi.PersistentVolumeClaimList,
* secretList: !backendApi.SecretList,
* }}
*/
backendApi.Search;
/**
* @typedef {{
* timestamp: string,
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
/**
* @fileoverview Externs for search module.
*
* @externs
*/
const searchApi = {};
/**
* @typedef {{
* q: string,
* }}
*/
searchApi.StateParams;
......@@ -56,8 +56,10 @@ $foreground-1: rgba(0, 0, 0, .87);
$foreground-2: rgba(0, 0, 0, .54);
$foreground-3: rgba(0, 0, 0, .38);
$foreground-4: rgba(0, 0, 0, .12);
$hue-1: rgba(255, 255, 255, .87);
$hue-2: rgba(255, 255, 255, .54);
$hue-3: rgba(255, 255, 255, .26);
$hue-4: rgba(255, 255, 255, .12);
$background-1: rgba(158, 158, 158, .2);
$overlay-color: $hue-2;
......
......@@ -16,13 +16,30 @@ limitations under the License.
<md-toolbar class="kd-toolbar">
<div class="md-toolbar-tools kd-toolbar-tools">
<kd-nav-hamburger flex="none"></kd-nav-hamburger>
<h2 class="kd-logo-menu"
flex="none">
<md-icon md-svg-icon="assets/images/kubernetes-logo-text.svg"
<div class="kd-logo-bar">
<md-icon md-svg-icon="assets/images/kubernetes-logo.svg"
class="kd-toolbar-logo">
</md-icon>
</h2>
<md-icon md-svg-icon="assets/images/kubernetes-logo-text.svg"
class="kd-toolbar-logo-text">
</md-icon>
</div>
<kd-search class="kd-search-bar"></kd-search>
<div class="kd-global-actions">
<md-button ng-click="$ctrl.create()">
<md-icon class="kd-global-action-icon">add</md-icon>
<md-tooltip md-direction="bottom">
[[Create an application or any Kubernetes resource|Tooltip for global action bar create button tooltip.]]
</md-tooltip>
[[Create|Label for global action bar create button.]]
</md-button>
</div>
</div>
</md-toolbar>
<md-toolbar class="kd-second-toolbar">
<div class="md-toolbar-tools kd-toolbar-tools">
<kd-nav-hamburger flex="none"></kd-nav-hamburger>
<kd-breadcrumbs class="kd-actionbar-breadcrumbs"
limit="3"
flex="auto"
......
......@@ -14,9 +14,21 @@
@import '../variables';
%kd-toolbar-logo-placeholder {
height: 4 * $baseline-grid;
margin-right: $baseline-grid;
min-height: 4 * $baseline-grid;
min-width: 4 * $baseline-grid;
width: 4 * $baseline-grid;
}
.kd-toolbar-logo {
height: 5 * $baseline-grid;
margin-right: 1em;
@extend %kd-toolbar-logo-placeholder;
}
.kd-toolbar-logo-text {
@extend %kd-toolbar-logo-placeholder;
min-width: 14 * $baseline-grid;
width: 14 * $baseline-grid;
}
......@@ -24,7 +36,36 @@
display: block;
}
.kd-toolbar {
.kd-logo-bar {
flex: 1;
}
.kd-search-bar {
flex: 2;
}
.kd-global-actions {
display: flex;
flex: 1;
justify-content: flex-end;
}
kd-chrome {
.kd-toolbar {
background-color: $content-background;
color: $primary;
height: 6 * $baseline-grid;
min-height: 6 * $baseline-grid;
padding-left: 1.75 * $baseline-grid;
.kd-global-action-icon {
color: $primary;
margin-right: .25 * $baseline-grid;
}
}
}
.kd-second-toolbar {
box-shadow: $whiteframe-shadow-1dp;
height: 7 * $baseline-grid;
min-height: 7 * $baseline-grid;
......@@ -89,11 +130,6 @@ a {
text-decoration: inherit;
}
.kd-logo-menu {
padding-left: 1.25 * $baseline-grid;
width: $nav-width - 6 * $baseline-grid;
}
.kd-success {
color: $success-color;
}
......
......@@ -13,6 +13,8 @@
// limitations under the License.
import {fillContentConfig} from 'chrome/state';
import {deployAppStateName} from 'deploy/state';
import {actionbarViewName} from './state';
/**
......@@ -97,6 +99,13 @@ export class ChromeController {
this.loading = false;
this.showLoadingSpinner = false;
}
/**
* @export
*/
create() {
this.state_.go(deployAppStateName);
}
}
/**
......
......@@ -17,6 +17,7 @@ import namespaceModule from 'common/namespace/module';
import {chromeComponent} from './component';
import navModule from './nav/module';
import {searchComponent} from './search/component';
import stateConfig from './stateconfig';
/**
......@@ -33,4 +34,5 @@ export default angular
navModule.name,
])
.config(stateConfig)
.component('kdChrome', chromeComponent);
.component('kdChrome', chromeComponent)
.component('kdSearch', searchComponent);
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
import {stateName} from 'search/state';
/**
* @final
*/
export class SearchController {
/**
* @param {!ui.router.$state} $state
* @ngInject
*/
constructor($state) {
/** @private {!ui.router.$state} */
this.state_ = $state;
this.query = '';
}
/**
* @export
*/
submit() {
this.state_.go(stateName, {q: this.query});
}
}
/** @type {!angular.Component} */
export const searchComponent = {
controller: SearchController,
templateUrl: 'chrome/search/search.html',
};
<!--
Copyright 2015 Google Inc. All Rights Reserved.
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.
-->
<form class="kd-search"
ng-submit="$ctrl.submit()">
<md-icon class="kd-search-icon">search</md-icon>
<input type="search"
placeholder="Search"
ng-model="$ctrl.query"
required>
</form>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
@import '../../variables';
.kd-search {
background-color: $foreground-4;
border-radius: $baseline-grid / 4;
display: flex;
flex-direction: row;
padding-left: $baseline-grid;
input {
background-color: transparent;
border: 0;
color: $foreground-1;
font-size: $subhead-font-size-base;
outline: none;
padding: $baseline-grid * 1.25;
width: 100%;
::-webkit-input-placeholder {
color: $foreground-2;
}
}
.kd-search-icon {
color: $foreground-2;
margin-left: $baseline-grid / 2;
padding-right: $baseline-grid * 4;
}
}
......@@ -28,6 +28,7 @@ limitations under the License.
</div>
<div ng-if="!$ctrl.shouldShowZeroState()">
<kd-content-card ng-if="$ctrl.cluster.namespaceList.namespaces.length">
<kd-content>
<kd-namespace-card-list namespace-list="$ctrl.cluster.namespaceList"
......@@ -36,6 +37,7 @@ limitations under the License.
</kd-namespace-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.cluster.nodeList.nodes.length">
<kd-content>
<kd-node-card-list node-list="$ctrl.cluster.nodeList"
......@@ -44,6 +46,7 @@ limitations under the License.
</kd-node-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.cluster.persistentVolumeList.items.length">
<kd-content>
<kd-persistent-volume-card-list persistent-volume-list="$ctrl.cluster.persistentVolumeList"
......@@ -52,6 +55,7 @@ limitations under the License.
</kd-persistent-volume-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.cluster.roleList.items.length">
<kd-content>
<kd-role-card-list role-list="$ctrl.cluster.roleList"
......@@ -60,6 +64,7 @@ limitations under the License.
</kd-role-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.cluster.storageClassList.storageClasses.length">
<kd-content>
<kd-storage-class-card-list storage-class-list="$ctrl.cluster.storageClassList"
......@@ -68,6 +73,7 @@ limitations under the License.
</kd-storage-class-card-list>
</kd-content>
</kd-content-card>
</div>
<kd-content-card ng-if="$ctrl.shouldShowZeroState()">
......
......@@ -18,15 +18,4 @@ limitations under the License.
layout-align="center center">
<div ui-view="actionbar"
class="kd-actionbar-view-part"></div>
<div ng-if="$ctrl.hasCustomActions()"
class="kd-actionbar-divider"></div>
<div>
<md-button ng-click="$ctrl.create()">
<md-icon class="kd-actionbar-icon-button">add</md-icon>
<md-tooltip md-direction="bottom">
[[Create an application or any Kubernetes resource|Tooltip for global action bar create button tooltip.]]
</md-tooltip>
[[Create|Label for global action bar create button.]]
</md-button>
</div>
</div>
......@@ -30,7 +30,7 @@
.kd-actionbar-breadcrumbs {
align-items: center;
line-height: 5 * $baseline-grid;
line-height: 2 * $baseline-grid;
max-width: 100%;
overflow: hidden;
white-space: nowrap;
......@@ -55,7 +55,7 @@
}
.kd-actionbar-divider {
border-left: 1px solid $hue-3;
border-left: 1px solid $foreground-4;
height: 4 * $baseline-grid;
margin-left: 1.5 * $baseline-grid;
margin-right: $baseline-grid;
......
......@@ -12,43 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {actionbarViewName} from 'chrome/state';
import {deployAppStateName} from 'deploy/state';
/**
* @final
*/
export class ActionbarComponent {
/**
* @param {!ui.router.$state} $state
* @ngInject
*/
constructor($state) {
/** @private {!ui.router.$state} */
this.state_ = $state;
}
/**
* @return {boolean}
* @export
*/
hasCustomActions() {
return !!this.state_.current.views && !!this.state_.current.views[actionbarViewName];
}
/**
* @export
*/
create() {
this.state_.go(deployAppStateName);
}
}
/**
* @type {!angular.Component}
*/
export const actionbarComponent = {
templateUrl: 'common/components/actionbar/actionbar.html',
transclude: true,
controller: ActionbarComponent,
};
......@@ -22,11 +22,11 @@ limitations under the License.
<a ng-switch-when="false"
href="{{::breadcrumb.stateLink}}"
class="kd-faded-breadcrumb">
{{::breadcrumb.label}}
{{breadcrumb.label}}
</a>
<div ng-switch-when="true"
class="kd-breadcrumbs-ellipsised">
<kd-middle-ellipsis display-string="{{::breadcrumb.label}}"></kd-middle-ellipsis>
<kd-middle-ellipsis display-string="{{breadcrumb.label}}"></kd-middle-ellipsis>
</div>
<md-icon ng-switch-when="false"
class="kd-faded-breadcrumb">
......
......@@ -60,6 +60,10 @@ export default class BreadcrumbsController {
this.scope_.$watch(() => this.kdFutureStateService_.state, () => {
this.initBreadcrumbs_();
});
this.scope_.$watchCollection(() => this.state_.params, () => {
this.initBreadcrumbs_();
});
}
/**
......
......@@ -30,7 +30,7 @@ const Actions = {
export class DataSelectService {
/**
* @param {!./../namespace/service.NamespaceService} kdNamespaceService
* @param {!../../chrome/state.StateParams|!../resource/resourcedetail.StateParams} $stateParams
* @param {!searchApi.StateParams|!../../chrome/state.StateParams|!../resource/resourcedetail.StateParams} $stateParams
* @param {!{setCurrentPage: function(string, number)}} paginationService
* @ngInject
*/
......@@ -43,7 +43,9 @@ export class DataSelectService {
this.actions_ = Actions;
/** @export {number} **/
this.rowsLimit = ItemsPerPage;
/** {!../../../chrome/chrome_state.StateParams|!../../resource/resourcedetail.StateParams} */
/**
* {!searchApi.StateParams|!../../../chrome/chrome_state.StateParams|!../../resource/resourcedetail.StateParams}
*/
this.stateParams_ = $stateParams;
/** @private {!{setCurrentPage: function(string, number)}} */
this.paginationService_ = paginationService;
......@@ -97,7 +99,7 @@ export class DataSelectService {
let namespace =
this.stateParams_.objectNamespace || this.stateParams_.namespace || query.namespace;
if (!dataSelectQuery) {
if (!query) {
throw new Error(`Data select query for given data select id ${dataSelectId} does not exist`);
}
......@@ -122,9 +124,26 @@ export class DataSelectService {
}
this.instances_.set(dataSelectId, query);
query = this.applySearch_(query);
return listResource.get(query).$promise;
}
/**
* @param query
* @private
*/
applySearch_(query) {
if (this.stateParams_.q) {
if (query.filterBy) {
query.filterBy += ',';
}
query.filterBy += `name,${this.stateParams_.q}`;
}
return query;
}
/**
* @param listResource {!angular.$resource}
* @param dataSelectId {string}
......
......@@ -15,6 +15,7 @@ limitations under the License.
-->
<div ng-if="!$ctrl.shouldShowZeroState()">
<kd-content-card ng-if="$ctrl.config.configMapList.items.length">
<kd-content>
<kd-config-map-card-list config-map-list="$ctrl.config.configMapList"
......@@ -40,7 +41,9 @@ limitations under the License.
</kd-secret-card-list>
</kd-content>
</kd-content-card>
</div>
<kd-content-card ng-if="$ctrl.shouldShowZeroState()">
<kd-content>
<kd-zero-state></kd-zero-state>
......
......@@ -15,6 +15,7 @@ limitations under the License.
-->
<div ng-if="!$ctrl.shouldShowZeroState()">
<kd-content-card ng-if="$ctrl.discovery.ingressList.items.length">
<kd-content>
<kd-ingress-card-list ingress-list="$ctrl.discovery.ingressList"
......@@ -32,6 +33,7 @@ limitations under the License.
</kd-service-card-list>
</kd-content>
</kd-content-card>
</div>
<kd-content-card ng-if="$ctrl.shouldShowZeroState()">
......
......@@ -42,6 +42,7 @@ import replicaSetModule from './replicaset/module';
import replicationControllerModule from './replicationcontroller/module';
import resourceQuotaModule from './resourcequota/module';
import roleModule from './role/module';
import searchModule from './search/module';
import secretModule from './secret/module';
import serviceModule from './service/module';
import statefulSetModule from './statefulset/module';
......@@ -75,6 +76,7 @@ export default angular
deploymentModule.name,
horizontalPodAutoscalerModule.name,
workloadsModule.name,
searchModule.name,
clusterModule.name,
serviceModule.name,
podModule.name,
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
/**
* @final
*/
export class SearchController {
/**
* @param {!backendApi.Search} search
* @param {!angular.Resource} kdPodListResource
* @param {!angular.Resource} kdReplicaSetListResource
* @param {!angular.Resource} kdDaemonSetListResource
* @param {!angular.Resource} kdDeploymentListResource
* @param {!angular.Resource} kdStatefulSetListResource
* @param {!angular.Resource} kdJobListResource
* @param {!angular.Resource} kdRCListResource
* @param {!angular.Resource} kdNamespaceListResource
* @param {!angular.Resource} kdNodeListResource
* @param {!angular.Resource} kdPersistentVolumeListResource
* @param {!angular.Resource} kdRoleListResource
* @param {!angular.Resource} kdStorageClassListResource
* @param {!angular.Resource} kdConfigMapListResource
* @param {!angular.Resource} kdSecretListResource
* @param {!angular.Resource} kdPersistentVolumeClaimListResource
* @param {!angular.Resource} kdServiceListResource
* @param {!angular.Resource} kdIngressListResource
* @ngInject
*/
constructor(
search, kdPodListResource, kdReplicaSetListResource, kdDaemonSetListResource,
kdDeploymentListResource, kdStatefulSetListResource, kdJobListResource, kdRCListResource,
kdNamespaceListResource, kdNodeListResource, kdPersistentVolumeListResource,
kdRoleListResource, kdStorageClassListResource, kdConfigMapListResource, kdSecretListResource,
kdPersistentVolumeClaimListResource, kdServiceListResource, kdIngressListResource) {
/** @export {!backendApi.Search} */
this.search = search;
/** @export {!angular.Resource} */
this.podListResource = kdPodListResource;
/** @export {!angular.Resource} */
this.replicaSetListResource = kdReplicaSetListResource;
/** @export {!angular.Resource} */
this.daemonSetListResource = kdDaemonSetListResource;
/** @export {!angular.Resource} */
this.deploymentListResource = kdDeploymentListResource;
/** @export {!angular.Resource} */
this.statefulSetListResource = kdStatefulSetListResource;
/** @export {!angular.Resource} */
this.jobListResource = kdJobListResource;
/** @export {!angular.Resource} */
this.rcListResource = kdRCListResource;
/** @export {!angular.Resource} */
this.kdNamespaceListResource = kdNamespaceListResource;
/** @export {!angular.Resource} */
this.kdNodeListResource = kdNodeListResource;
/** @export {!angular.Resource} */
this.kdPersistentVolumeListResource = kdPersistentVolumeListResource;
/** @export {!angular.Resource} */
this.kdRoleListResource = kdRoleListResource;
/** @export {!angular.Resource} */
this.kdStorageClassListResource = kdStorageClassListResource;
/** @export {!angular.Resource} */
this.kdConfigMapListResource = kdConfigMapListResource;
/** @export {!angular.Resource} */
this.kdSecretListResource = kdSecretListResource;
/** @export {!angular.Resource} */
this.pvcListResource = kdPersistentVolumeClaimListResource;
/** @export {!angular.Resource} */
this.kdServiceListResource = kdServiceListResource;
/** @export {!angular.Resource} */
this.kdIngressListResource = kdIngressListResource;
}
/**
* @return {boolean}
* @export
*/
shouldShowZeroState() {
/** @type {number} */
let resourcesLength = this.search.deploymentList.listMeta.totalItems +
this.search.replicaSetList.listMeta.totalItems + this.search.jobList.listMeta.totalItems +
this.search.replicationControllerList.listMeta.totalItems +
this.search.podList.listMeta.totalItems + this.search.daemonSetList.listMeta.totalItems +
this.search.statefulSetList.listMeta.totalItems + this.search.nodeList.listMeta.totalItems +
this.search.namespaceList.listMeta.totalItems +
this.search.persistentVolumeList.listMeta.totalItems +
this.search.storageClassList.listMeta.totalItems +
this.search.configMapList.listMeta.totalItems + this.search.secretList.listMeta.totalItems +
this.search.persistentVolumeClaimList.listMeta.totalItems +
this.search.serviceList.listMeta.totalItems + this.search.ingressList.listMeta.totalItems;
return resourcesLength === 0;
}
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
import chromeModule from 'chrome/module';
import componentsModule from 'common/components/module';
import filtersModule from 'common/filters/module';
import configMapModule from 'configmap/module';
import daemonSetModule from 'daemonset/module';
import deploymentModule from 'deployment/module';
import ingressModule from 'ingress/module';
import jobModule from 'job/module';
import namespaceModule from 'namespace/module';
import nodeModule from 'node/module';
import persistentVolumeModule from 'persistentvolume/module';
import persistentVolumeClaimModule from 'persistentvolumeclaim/module';
import replicaSetModule from 'replicaset/module';
import replicationControllerModule from 'replicationcontroller/module';
import roleModule from 'role/module';
import secretModule from 'secret/module';
import serviceModule from 'service/module';
import statefulSetModule from 'statefulset/module';
import storageClassModule from 'storageclass/module';
import stateConfig from './stateconfig';
/**
* Module with a view that displays resources categorized as search, e.g., Replica Sets or
* Deployments.
*/
export default angular
.module(
'kubernetesDashboard.search',
[
'ngMaterial',
'ngResource',
'ui.router',
filtersModule.name,
componentsModule.name,
chromeModule.name,
jobModule.name,
replicationControllerModule.name,
replicaSetModule.name,
deploymentModule.name,
daemonSetModule.name,
statefulSetModule.name,
nodeModule.name,
namespaceModule.name,
persistentVolumeModule.name,
roleModule.name,
storageClassModule.name,
configMapModule.name,
secretModule.name,
persistentVolumeClaimModule.name,
serviceModule.name,
ingressModule.name,
])
.config(stateConfig)
.factory('kdSearchResource', searchResource);
/**
* @param {!angular.$resource} $resource
* @return {!angular.Resource}
* @ngInject
*/
function searchResource($resource) {
return $resource('api/v1/search/:namespace');
}
<!--
Copyright 2015 Google Inc. All Rights Reserved.
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.
-->
<div ng-if="!$ctrl.shouldShowZeroState()">
<kd-content-card ng-if="$ctrl.search.configMapList.items.length">
<kd-content>
<kd-config-map-card-list config-map-list="$ctrl.search.configMapList"
with-statuses="true"
config-map-list-resource="$ctrl.kdConfigMapListResource">
</kd-config-map-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.daemonSetList.daemonSets.length">
<kd-content>
<kd-daemon-set-card-list daemon-set-list="$ctrl.search.daemonSetList"
with-statuses="true"
daemon-set-list-resource="$ctrl.daemonSetListResource">
</kd-daemon-set-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if=$ctrl.search.deploymentList.deployments.length>
<kd-content>
<kd-deployment-card-list deployment-list="$ctrl.search.deploymentList"
deployment-list-resource="$ctrl.deploymentListResource">
</kd-deployment-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.ingressList.items.length">
<kd-content>
<kd-ingress-card-list ingress-list="$ctrl.search.ingressList"
with-statuses="true"
ingress-list-resource="$ctrl.kdIngressListResource">
</kd-ingress-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.jobList.jobs.length">
<kd-content>
<kd-job-card-list job-list="$ctrl.search.jobList"
job-list-resource="$ctrl.jobListResource">
</kd-job-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.namespaceList.namespaces.length">
<kd-content>
<kd-namespace-card-list namespace-list="$ctrl.search.namespaceList"
with-statuses="true"
namespace-list-resource="$ctrl.kdNamespaceListResource">
</kd-namespace-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.nodeList.nodes.length">
<kd-content>
<kd-node-card-list node-list="$ctrl.search.nodeList"
with-statuses="true"
node-list-resource="$ctrl.kdNodeListResource">
</kd-node-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.persistentVolumeList.items.length">
<kd-content>
<kd-persistent-volume-card-list persistent-volume-list="$ctrl.search.persistentVolumeList"
with-statuses="true"
persistent-volume-list-resource="$ctrl.kdPersistentVolumeListResource">
</kd-persistent-volume-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if=$ctrl.search.persistentVolumeClaimList.items.length>
<kd-content>
<kd-persistent-volume-claim-card-list persistent-volume-claim-list="$ctrl.search.persistentVolumeClaimList"
persistent-volume-claim-list-resource="$ctrl.pvcListResource">
</kd-persistent-volume-claim-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.podList.pods.length">
<kd-content>
<kd-pod-card-list pod-list="$ctrl.search.podList"
pod-list-resource="$ctrl.podListResource"
with-statuses="true">
</kd-pod-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.replicaSetList.replicaSets.length">
<kd-content>
<kd-replica-set-card-list replica-set-list="$ctrl.search.replicaSetList"
replica-set-list-resource="$ctrl.replicaSetListResource">
</kd-replica-set-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.replicationControllerList.replicationControllers.length">
<kd-content>
<kd-replication-controller-card-list replication-controller-list="$ctrl.search.replicationControllerList"
replication-controller-list-resource="$ctrl.rcListResource">
</kd-replication-controller-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.roleList.items.length">
<kd-content>
<kd-role-card-list role-list="$ctrl.search.roleList"
with-statuses="false"
role-list-resource="$ctrl.kdRoleListResource">
</kd-role-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.secretList.secrets.length">
<kd-content>
<kd-secret-card-list secret-list="$ctrl.search.secretList"
with-statuses="true"
secret-list-resource="$ctrl.kdSecretListResource">
</kd-secret-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.serviceList.services.length">
<kd-content>
<kd-service-card-list service-list="$ctrl.search.serviceList"
with-statuses="true"
service-list-resource="$ctrl.kdServiceListResource">
</kd-service-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.statefulSetList.statefulSets.length">
<kd-content>
<kd-stateful-set-card-list stateful-set-list="$ctrl.search.statefulSetList"
stateful-set-list-resource="$ctrl.statefulSetListResource">
</kd-stateful-set-card-list>
</kd-content>
</kd-content-card>
<kd-content-card ng-if="$ctrl.search.storageClassList.storageClasses.length">
<kd-content>
<kd-storage-class-card-list storage-class-list="$ctrl.search.storageClassList"
with-statuses="true"
storage-class-list-resource="$ctrl.kdStorageClassListResource">
</kd-storage-class-card-list>
</kd-content>
</kd-content-card>
</div>
<kd-content-card ng-if="$ctrl.shouldShowZeroState()">
<kd-content>
<kd-zero-state></kd-zero-state>
</kd-content>
</kd-content-card>
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
/** Name of the state. Can be used in, e.g., $state.go method. */
export const stateName = 'search';
/** Absolute URL of the state. */
export const stateUrl = '/search?q';
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
import {stateName as chromeStateName} from 'chrome/state';
import {breadcrumbsConfig} from 'common/components/breadcrumbs/service';
import {DataSelectQueryBuilder} from 'common/dataselect/builder';
import {SearchController} from './controller';
import {stateName, stateUrl} from './state';
/**
* @return {string}
*/
function getBreadcrumbLabel() {
return `${i18n.MSG_BREADCRUMBS_SEARCH_LABEL} {{$stateParams.q}}`;
}
/**
* @param {!ui.router.$stateProvider} $stateProvider
* @ngInject
*/
export default function stateConfig($stateProvider) {
$stateProvider.state(stateName, {
url: stateUrl,
parent: chromeStateName,
resolve: {
'search': resolveSearch,
},
data: {
[breadcrumbsConfig]: {
'label': getBreadcrumbLabel(),
},
},
views: {
'': {
controller: SearchController,
controllerAs: '$ctrl',
templateUrl: 'search/search.html',
},
},
});
}
/**
* @param {!angular.$resource} kdSearchResource
* @param {!searchApi.StateParams} $stateParams
* @param {!./../common/namespace/service.NamespaceService} kdNamespaceService
* @return {!angular.$q.Promise}
* @ngInject
*/
export function resolveSearch(kdSearchResource, $stateParams, kdNamespaceService) {
let query = new DataSelectQueryBuilder()
.setNamespace($stateParams.namespace)
.setFilterBy($stateParams.q)
.build();
if (kdNamespaceService.isMultiNamespace(query.namespace)) {
query.namespace = '';
}
return kdSearchResource.get(query).$promise;
}
const i18n = {
/** @type {string} @desc Label 'Search' that appears as a breadcrumbs on the action bar. */
MSG_BREADCRUMBS_SEARCH_LABEL: goog.getMsg('Search for'),
};
// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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.
import {SearchController} from 'search/controller';
import searchModule from 'search/module';
describe('Search list controller', () => {
/** @type {!search/controller.SearchController} */
let ctrl;
beforeEach(() => {
angular.mock.module(searchModule.name);
angular.mock.inject(($controller) => {
ctrl = $controller(SearchController, {search: {search: []}});
});
});
it('should initialize search', angular.mock.inject(($controller) => {
let search = {search: 'foo-bar'};
/** @type {!SearchController} */
let ctrl = $controller(SearchController, {search: search});
expect(ctrl.search).toBe(search);
}));
it('should show zero state', () => {
// given
ctrl.search = {
deploymentList: {listMeta: {totalItems: 0}, deployments: []},
replicaSetList: {listMeta: {totalItems: 0}, replicaSets: []},
jobList: {listMeta: {totalItems: 0}, jobs: []},
replicationControllerList: {listMeta: {totalItems: 0}, replicationControllers: []},
podList: {listMeta: {totalItems: 0}, pods: []},
daemonSetList: {listMeta: {totalItems: 0}, daemonSets: []},
statefulSetList: {listMeta: {totalItems: 0}, statefulSets: []},
nodeList: {listMeta: {totalItems: 0}, statefulSets: []},
namespaceList: {listMeta: {totalItems: 0}, namespaces: []},
persistentVolumeList: {listMeta: {totalItems: 0}, persistentVolumes: []},
storageClassList: {listMeta: {totalItems: 0}, storageClasses: []},
secretList: {listMeta: {totalItems: 0}, secrets: []},
persistentVolumeClaimList: {listMeta: {totalItems: 0}, persistentVolumeClaims: []},
configMapList: {listMeta: {totalItems: 0}, configMaps: []},
serviceList: {listMeta: {totalItems: 0}, services: []},
ingressList: {listMeta: {totalItems: 0}, ingresses: []},
};
expect(ctrl.shouldShowZeroState()).toBeTruthy();
});
it('should hide zero state', () => {
// given
ctrl.search = {
deploymentList: {listMeta: {totalItems: 1}, deployments: []},
replicaSetList: {listMeta: {totalItems: 0}, replicaSets: []},
jobList: {listMeta: {totalItems: 0}, jobs: []},
replicationControllerList: {listMeta: {totalItems: 0}, replicationControllers: []},
podList: {listMeta: {totalItems: 0}, pods: []},
daemonSetList: {listMeta: {totalItems: 0}, daemonSets: []},
statefulSetList: {listMeta: {totalItems: 0}, statefulSets: []},
nodeList: {listMeta: {totalItems: 0}, statefulSets: []},
namespaceList: {listMeta: {totalItems: 0}, namespaces: []},
persistentVolumeList: {listMeta: {totalItems: 0}, persistentVolumes: []},
storageClassList: {listMeta: {totalItems: 0}, storageClasses: []},
secretList: {listMeta: {totalItems: 0}, secrets: []},
persistentVolumeClaimList: {listMeta: {totalItems: 0}, persistentVolumeClaims: []},
configMapList: {listMeta: {totalItems: 0}, configMaps: []},
serviceList: {listMeta: {totalItems: 0}, services: []},
ingressList: {listMeta: {totalItems: 0}, ingresses: []},
};
// then
expect(ctrl.shouldShowZeroState()).toBeFalsy();
});
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册