From f8385d11184aa9f0040ea157de1f35b847828486 Mon Sep 17 00:00:00 2001 From: Yassine Bounekhla <56373201+rudream@users.noreply.github.com> Date: Tue, 12 Nov 2024 20:13:05 -0500 Subject: [PATCH] add sticky mode support to sidenav (#48869) --- .../v1/sidenav_preferences.pb.go | 161 +++++++++ .../userpreferences/v1/userpreferences.pb.go | 202 ++++++----- .../v1/sidenav_preferences.proto | 28 ++ .../userpreferences/v1/userpreferences.proto | 3 + .../v1/sidenav_preferences_pb.ts | 43 +++ .../userpreferences/v1/userpreferences_pb.ts | 17 +- .../userpreferencesv1/service_test.go | 3 +- lib/services/local/userpreferences.go | 1 + lib/services/local/userpreferences_test.go | 22 ++ lib/web/userpreferences.go | 15 +- web/packages/design/src/Icon/Icons.story.tsx | 1 + .../design/src/Icon/Icons/ArrowLineLeft.tsx | 65 ++++ .../design/src/Icon/assets/ArrowLineLeft.svg | 4 + web/packages/design/src/Icon/index.ts | 1 + .../Navigation/SideNavigation/Navigation.tsx | 331 ++++++++---------- .../SideNavigation/ResourcesSection.tsx | 43 ++- .../src/Navigation/SideNavigation/Search.tsx | 60 ++-- .../src/Navigation/SideNavigation/Section.tsx | 280 +++++++++++++-- .../userPreferences/userPreferences.test.ts | 4 + .../userPreferences/userPreferences.ts | 4 + 20 files changed, 932 insertions(+), 356 deletions(-) create mode 100644 api/gen/proto/go/userpreferences/v1/sidenav_preferences.pb.go create mode 100644 api/proto/teleport/userpreferences/v1/sidenav_preferences.proto create mode 100644 gen/proto/ts/teleport/userpreferences/v1/sidenav_preferences_pb.ts create mode 100644 web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx create mode 100644 web/packages/design/src/Icon/assets/ArrowLineLeft.svg diff --git a/api/gen/proto/go/userpreferences/v1/sidenav_preferences.pb.go b/api/gen/proto/go/userpreferences/v1/sidenav_preferences.pb.go new file mode 100644 index 0000000000000..7e4ecb079a216 --- /dev/null +++ b/api/gen/proto/go/userpreferences/v1/sidenav_preferences.pb.go @@ -0,0 +1,161 @@ +// Copyright 2024 Gravitational, Inc +// +// 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. + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.35.1 +// protoc (unknown) +// source: teleport/userpreferences/v1/sidenav_preferences.proto + +package userpreferencesv1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// SideNavDrawerMode is the sidenav drawer behavior preference in the frontend. +type SideNavDrawerMode int32 + +const ( + SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_UNSPECIFIED SideNavDrawerMode = 0 + // SIDE_NAV_DRAWER_MODE_COLLAPSED means the sidenav drawer collapses automatically when no longer hovering over it. + SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_COLLAPSED SideNavDrawerMode = 1 + // SIDE_NAV_DRAWER_MODE_STICKY means the sidenav drawer remains expanded at all times. + SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_STICKY SideNavDrawerMode = 2 +) + +// Enum value maps for SideNavDrawerMode. +var ( + SideNavDrawerMode_name = map[int32]string{ + 0: "SIDE_NAV_DRAWER_MODE_UNSPECIFIED", + 1: "SIDE_NAV_DRAWER_MODE_COLLAPSED", + 2: "SIDE_NAV_DRAWER_MODE_STICKY", + } + SideNavDrawerMode_value = map[string]int32{ + "SIDE_NAV_DRAWER_MODE_UNSPECIFIED": 0, + "SIDE_NAV_DRAWER_MODE_COLLAPSED": 1, + "SIDE_NAV_DRAWER_MODE_STICKY": 2, + } +) + +func (x SideNavDrawerMode) Enum() *SideNavDrawerMode { + p := new(SideNavDrawerMode) + *p = x + return p +} + +func (x SideNavDrawerMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SideNavDrawerMode) Descriptor() protoreflect.EnumDescriptor { + return file_teleport_userpreferences_v1_sidenav_preferences_proto_enumTypes[0].Descriptor() +} + +func (SideNavDrawerMode) Type() protoreflect.EnumType { + return &file_teleport_userpreferences_v1_sidenav_preferences_proto_enumTypes[0] +} + +func (x SideNavDrawerMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SideNavDrawerMode.Descriptor instead. +func (SideNavDrawerMode) EnumDescriptor() ([]byte, []int) { + return file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescGZIP(), []int{0} +} + +var File_teleport_userpreferences_v1_sidenav_preferences_proto protoreflect.FileDescriptor + +var file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDesc = []byte{ + 0x0a, 0x35, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x69, + 0x64, 0x65, 0x6e, 0x61, 0x76, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2a, 0x7e, 0x0a, 0x11, 0x53, 0x69, 0x64, 0x65, 0x4e, 0x61, 0x76, 0x44, + 0x72, 0x61, 0x77, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x24, 0x0a, 0x20, 0x53, 0x49, 0x44, + 0x45, 0x5f, 0x4e, 0x41, 0x56, 0x5f, 0x44, 0x52, 0x41, 0x57, 0x45, 0x52, 0x5f, 0x4d, 0x4f, 0x44, + 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x22, 0x0a, 0x1e, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x4e, 0x41, 0x56, 0x5f, 0x44, 0x52, 0x41, 0x57, + 0x45, 0x52, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x4c, 0x4c, 0x41, 0x50, 0x53, 0x45, + 0x44, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x53, 0x49, 0x44, 0x45, 0x5f, 0x4e, 0x41, 0x56, 0x5f, + 0x44, 0x52, 0x41, 0x57, 0x45, 0x52, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54, 0x49, 0x43, + 0x4b, 0x59, 0x10, 0x02, 0x42, 0x59, 0x5a, 0x57, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, + 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, + 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x76, 0x31, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescOnce sync.Once + file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescData = file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDesc +) + +func file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescGZIP() []byte { + file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescOnce.Do(func() { + file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescData = protoimpl.X.CompressGZIP(file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescData) + }) + return file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDescData +} + +var file_teleport_userpreferences_v1_sidenav_preferences_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_teleport_userpreferences_v1_sidenav_preferences_proto_goTypes = []any{ + (SideNavDrawerMode)(0), // 0: teleport.userpreferences.v1.SideNavDrawerMode +} +var file_teleport_userpreferences_v1_sidenav_preferences_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_teleport_userpreferences_v1_sidenav_preferences_proto_init() } +func file_teleport_userpreferences_v1_sidenav_preferences_proto_init() { + if File_teleport_userpreferences_v1_sidenav_preferences_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_teleport_userpreferences_v1_sidenav_preferences_proto_goTypes, + DependencyIndexes: file_teleport_userpreferences_v1_sidenav_preferences_proto_depIdxs, + EnumInfos: file_teleport_userpreferences_v1_sidenav_preferences_proto_enumTypes, + }.Build() + File_teleport_userpreferences_v1_sidenav_preferences_proto = out.File + file_teleport_userpreferences_v1_sidenav_preferences_proto_rawDesc = nil + file_teleport_userpreferences_v1_sidenav_preferences_proto_goTypes = nil + file_teleport_userpreferences_v1_sidenav_preferences_proto_depIdxs = nil +} diff --git a/api/gen/proto/go/userpreferences/v1/userpreferences.pb.go b/api/gen/proto/go/userpreferences/v1/userpreferences.pb.go index c1a857b203bee..62eaf64e6e225 100644 --- a/api/gen/proto/go/userpreferences/v1/userpreferences.pb.go +++ b/api/gen/proto/go/userpreferences/v1/userpreferences.pb.go @@ -51,6 +51,8 @@ type UserPreferences struct { UnifiedResourcePreferences *UnifiedResourcePreferences `protobuf:"bytes,5,opt,name=unified_resource_preferences,json=unifiedResourcePreferences,proto3" json:"unified_resource_preferences,omitempty"` // access_graph is the preferences for Access Graph. AccessGraph *AccessGraphUserPreferences `protobuf:"bytes,6,opt,name=access_graph,json=accessGraph,proto3" json:"access_graph,omitempty"` + // side_nav_drawer_mode is the sidenav drawer behavior preference in the frontend. + SideNavDrawerMode SideNavDrawerMode `protobuf:"varint,7,opt,name=side_nav_drawer_mode,json=sideNavDrawerMode,proto3,enum=teleport.userpreferences.v1.SideNavDrawerMode" json:"side_nav_drawer_mode,omitempty"` } func (x *UserPreferences) Reset() { @@ -118,6 +120,13 @@ func (x *UserPreferences) GetAccessGraph() *AccessGraphUserPreferences { return nil } +func (x *UserPreferences) GetSideNavDrawerMode() SideNavDrawerMode { + if x != nil { + return x.SideNavDrawerMode + } + return SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_UNSPECIFIED +} + // GetUserPreferencesRequest is a request to get the user preferences. type GetUserPreferencesRequest struct { state protoimpl.MessageState @@ -268,84 +277,94 @@ var file_teleport_userpreferences_v1_userpreferences_proto_rawDesc = []byte{ 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x29, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, - 0x27, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, - 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x68, 0x65, - 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x3e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x72, 0x65, + 0x35, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x69, 0x64, + 0x65, 0x6e, 0x61, 0x76, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x3e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x6e, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, + 0xc6, 0x04, 0x0a, 0x0f, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x05, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, + 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x54, 0x68, 0x65, 0x6d, 0x65, 0x52, 0x05, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x4d, 0x0a, + 0x07, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, + 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x62, + 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x52, 0x07, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x64, 0x0a, 0x13, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x55, + 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x12, + 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x73, 0x12, 0x79, 0x0a, 0x1c, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe5, 0x03, 0x0a, 0x0f, 0x55, 0x73, 0x65, - 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x05, - 0x74, 0x68, 0x65, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, - 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x68, 0x65, 0x6d, 0x65, 0x52, - 0x05, 0x74, 0x68, 0x65, 0x6d, 0x65, 0x12, 0x4d, 0x0a, 0x07, 0x6f, 0x6e, 0x62, 0x6f, 0x61, 0x72, - 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x73, 0x52, 0x1a, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x5a, 0x0a, + 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, + 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, 0x55, 0x73, 0x65, + 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x0b, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, 0x12, 0x5f, 0x0a, 0x14, 0x73, 0x69, 0x64, + 0x65, 0x5f, 0x6e, 0x61, 0x76, 0x5f, 0x64, 0x72, 0x61, 0x77, 0x65, 0x72, 0x5f, 0x6d, 0x6f, 0x64, + 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4f, 0x6e, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x55, 0x73, 0x65, - 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x07, 0x6f, 0x6e, - 0x62, 0x6f, 0x61, 0x72, 0x64, 0x12, 0x64, 0x0a, 0x13, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, - 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, - 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x12, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, - 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x79, 0x0a, 0x1c, 0x75, - 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x37, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, - 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x55, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x1a, 0x75, 0x6e, 0x69, 0x66, - 0x69, 0x65, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50, 0x72, 0x65, 0x66, 0x65, - 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x5a, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x5f, 0x67, 0x72, 0x61, 0x70, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, - 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, - 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, - 0x70, 0x68, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x52, 0x06, 0x61, 0x73, 0x73, 0x69, 0x73, 0x74, - 0x22, 0x2b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, - 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x04, 0x08, - 0x01, 0x10, 0x02, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6c, 0x0a, - 0x1a, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0b, 0x70, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x0b, - 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x22, 0x7e, 0x0a, 0x1c, 0x55, - 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, - 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x4e, 0x0a, 0x0b, 0x70, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, - 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x0b, - 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, - 0x03, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x32, 0x8c, 0x02, 0x0a, 0x16, - 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x85, 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x73, - 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x36, 0x2e, - 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, - 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x69, 0x64, 0x65, 0x4e, 0x61, 0x76, 0x44, 0x72, 0x61, + 0x77, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x11, 0x73, 0x69, 0x64, 0x65, 0x4e, 0x61, 0x76, + 0x44, 0x72, 0x61, 0x77, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x65, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, + 0x52, 0x06, 0x61, 0x73, 0x73, 0x69, 0x73, 0x74, 0x22, 0x2b, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x52, 0x08, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x6c, 0x0a, 0x1a, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x4e, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x22, 0x7e, 0x0a, 0x1c, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, + 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x4e, 0x0a, 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, + 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, + 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x32, 0x8c, 0x02, 0x0a, 0x16, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x85, + 0x01, 0x0a, 0x12, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, + 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x36, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, - 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, - 0x0a, 0x15, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x39, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, - 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, - 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x59, 0x5a, 0x57, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, - 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, - 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, - 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, - 0x63, 0x65, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, + 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6a, 0x0a, 0x15, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, + 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, + 0x39, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x70, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, + 0x73, 0x65, 0x72, 0x74, 0x55, 0x73, 0x65, 0x72, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, + 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x42, 0x59, 0x5a, 0x57, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2f, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x70, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, + 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -371,25 +390,27 @@ var file_teleport_userpreferences_v1_userpreferences_proto_goTypes = []any{ (*ClusterUserPreferences)(nil), // 6: teleport.userpreferences.v1.ClusterUserPreferences (*UnifiedResourcePreferences)(nil), // 7: teleport.userpreferences.v1.UnifiedResourcePreferences (*AccessGraphUserPreferences)(nil), // 8: teleport.userpreferences.v1.AccessGraphUserPreferences - (*emptypb.Empty)(nil), // 9: google.protobuf.Empty + (SideNavDrawerMode)(0), // 9: teleport.userpreferences.v1.SideNavDrawerMode + (*emptypb.Empty)(nil), // 10: google.protobuf.Empty } var file_teleport_userpreferences_v1_userpreferences_proto_depIdxs = []int32{ - 4, // 0: teleport.userpreferences.v1.UserPreferences.theme:type_name -> teleport.userpreferences.v1.Theme - 5, // 1: teleport.userpreferences.v1.UserPreferences.onboard:type_name -> teleport.userpreferences.v1.OnboardUserPreferences - 6, // 2: teleport.userpreferences.v1.UserPreferences.cluster_preferences:type_name -> teleport.userpreferences.v1.ClusterUserPreferences - 7, // 3: teleport.userpreferences.v1.UserPreferences.unified_resource_preferences:type_name -> teleport.userpreferences.v1.UnifiedResourcePreferences - 8, // 4: teleport.userpreferences.v1.UserPreferences.access_graph:type_name -> teleport.userpreferences.v1.AccessGraphUserPreferences - 0, // 5: teleport.userpreferences.v1.GetUserPreferencesResponse.preferences:type_name -> teleport.userpreferences.v1.UserPreferences - 0, // 6: teleport.userpreferences.v1.UpsertUserPreferencesRequest.preferences:type_name -> teleport.userpreferences.v1.UserPreferences - 1, // 7: teleport.userpreferences.v1.UserPreferencesService.GetUserPreferences:input_type -> teleport.userpreferences.v1.GetUserPreferencesRequest - 3, // 8: teleport.userpreferences.v1.UserPreferencesService.UpsertUserPreferences:input_type -> teleport.userpreferences.v1.UpsertUserPreferencesRequest - 2, // 9: teleport.userpreferences.v1.UserPreferencesService.GetUserPreferences:output_type -> teleport.userpreferences.v1.GetUserPreferencesResponse - 9, // 10: teleport.userpreferences.v1.UserPreferencesService.UpsertUserPreferences:output_type -> google.protobuf.Empty - 9, // [9:11] is the sub-list for method output_type - 7, // [7:9] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 4, // 0: teleport.userpreferences.v1.UserPreferences.theme:type_name -> teleport.userpreferences.v1.Theme + 5, // 1: teleport.userpreferences.v1.UserPreferences.onboard:type_name -> teleport.userpreferences.v1.OnboardUserPreferences + 6, // 2: teleport.userpreferences.v1.UserPreferences.cluster_preferences:type_name -> teleport.userpreferences.v1.ClusterUserPreferences + 7, // 3: teleport.userpreferences.v1.UserPreferences.unified_resource_preferences:type_name -> teleport.userpreferences.v1.UnifiedResourcePreferences + 8, // 4: teleport.userpreferences.v1.UserPreferences.access_graph:type_name -> teleport.userpreferences.v1.AccessGraphUserPreferences + 9, // 5: teleport.userpreferences.v1.UserPreferences.side_nav_drawer_mode:type_name -> teleport.userpreferences.v1.SideNavDrawerMode + 0, // 6: teleport.userpreferences.v1.GetUserPreferencesResponse.preferences:type_name -> teleport.userpreferences.v1.UserPreferences + 0, // 7: teleport.userpreferences.v1.UpsertUserPreferencesRequest.preferences:type_name -> teleport.userpreferences.v1.UserPreferences + 1, // 8: teleport.userpreferences.v1.UserPreferencesService.GetUserPreferences:input_type -> teleport.userpreferences.v1.GetUserPreferencesRequest + 3, // 9: teleport.userpreferences.v1.UserPreferencesService.UpsertUserPreferences:input_type -> teleport.userpreferences.v1.UpsertUserPreferencesRequest + 2, // 10: teleport.userpreferences.v1.UserPreferencesService.GetUserPreferences:output_type -> teleport.userpreferences.v1.GetUserPreferencesResponse + 10, // 11: teleport.userpreferences.v1.UserPreferencesService.UpsertUserPreferences:output_type -> google.protobuf.Empty + 10, // [10:12] is the sub-list for method output_type + 8, // [8:10] is the sub-list for method input_type + 8, // [8:8] is the sub-list for extension type_name + 8, // [8:8] is the sub-list for extension extendee + 0, // [0:8] is the sub-list for field type_name } func init() { file_teleport_userpreferences_v1_userpreferences_proto_init() } @@ -400,6 +421,7 @@ func file_teleport_userpreferences_v1_userpreferences_proto_init() { file_teleport_userpreferences_v1_access_graph_proto_init() file_teleport_userpreferences_v1_cluster_preferences_proto_init() file_teleport_userpreferences_v1_onboard_proto_init() + file_teleport_userpreferences_v1_sidenav_preferences_proto_init() file_teleport_userpreferences_v1_theme_proto_init() file_teleport_userpreferences_v1_unified_resource_preferences_proto_init() type x struct{} diff --git a/api/proto/teleport/userpreferences/v1/sidenav_preferences.proto b/api/proto/teleport/userpreferences/v1/sidenav_preferences.proto new file mode 100644 index 0000000000000..728b90c4d3598 --- /dev/null +++ b/api/proto/teleport/userpreferences/v1/sidenav_preferences.proto @@ -0,0 +1,28 @@ +// Copyright 2024 Gravitational, Inc +// +// 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. + +syntax = "proto3"; + +package teleport.userpreferences.v1; + +option go_package = "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1;userpreferencesv1"; + +// SideNavDrawerMode is the sidenav drawer behavior preference in the frontend. +enum SideNavDrawerMode { + SIDE_NAV_DRAWER_MODE_UNSPECIFIED = 0; + // SIDE_NAV_DRAWER_MODE_COLLAPSED means the sidenav drawer collapses automatically when no longer hovering over it. + SIDE_NAV_DRAWER_MODE_COLLAPSED = 1; + // SIDE_NAV_DRAWER_MODE_STICKY means the sidenav drawer remains expanded at all times. + SIDE_NAV_DRAWER_MODE_STICKY = 2; +} diff --git a/api/proto/teleport/userpreferences/v1/userpreferences.proto b/api/proto/teleport/userpreferences/v1/userpreferences.proto index 7537cbc21ea9c..25d7fe4884379 100644 --- a/api/proto/teleport/userpreferences/v1/userpreferences.proto +++ b/api/proto/teleport/userpreferences/v1/userpreferences.proto @@ -20,6 +20,7 @@ import "google/protobuf/empty.proto"; import "teleport/userpreferences/v1/access_graph.proto"; import "teleport/userpreferences/v1/cluster_preferences.proto"; import "teleport/userpreferences/v1/onboard.proto"; +import "teleport/userpreferences/v1/sidenav_preferences.proto"; import "teleport/userpreferences/v1/theme.proto"; import "teleport/userpreferences/v1/unified_resource_preferences.proto"; @@ -40,6 +41,8 @@ message UserPreferences { UnifiedResourcePreferences unified_resource_preferences = 5; // access_graph is the preferences for Access Graph. AccessGraphUserPreferences access_graph = 6; + // side_nav_drawer_mode is the sidenav drawer behavior preference in the frontend. + SideNavDrawerMode side_nav_drawer_mode = 7; } // GetUserPreferencesRequest is a request to get the user preferences. diff --git a/gen/proto/ts/teleport/userpreferences/v1/sidenav_preferences_pb.ts b/gen/proto/ts/teleport/userpreferences/v1/sidenav_preferences_pb.ts new file mode 100644 index 0000000000000..c092fca00e3b8 --- /dev/null +++ b/gen/proto/ts/teleport/userpreferences/v1/sidenav_preferences_pb.ts @@ -0,0 +1,43 @@ +/* eslint-disable */ +// @generated by protobuf-ts 2.9.3 with parameter eslint_disable,add_pb_suffix,server_grpc1,ts_nocheck +// @generated from protobuf file "teleport/userpreferences/v1/sidenav_preferences.proto" (package "teleport.userpreferences.v1", syntax proto3) +// tslint:disable +// @ts-nocheck +// +// Copyright 2024 Gravitational, Inc +// +// 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. +// +/** + * SideNavDrawerMode is the sidenav drawer behavior preference in the frontend. + * + * @generated from protobuf enum teleport.userpreferences.v1.SideNavDrawerMode + */ +export enum SideNavDrawerMode { + /** + * @generated from protobuf enum value: SIDE_NAV_DRAWER_MODE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + /** + * SIDE_NAV_DRAWER_MODE_COLLAPSED means the sidenav drawer collapses automatically when no longer hovering over it. + * + * @generated from protobuf enum value: SIDE_NAV_DRAWER_MODE_COLLAPSED = 1; + */ + COLLAPSED = 1, + /** + * SIDE_NAV_DRAWER_MODE_STICKY means the sidenav drawer remains expanded at all times. + * + * @generated from protobuf enum value: SIDE_NAV_DRAWER_MODE_STICKY = 2; + */ + STICKY = 2 +} diff --git a/gen/proto/ts/teleport/userpreferences/v1/userpreferences_pb.ts b/gen/proto/ts/teleport/userpreferences/v1/userpreferences_pb.ts index dddf6e429f8e3..2c768f07491f2 100644 --- a/gen/proto/ts/teleport/userpreferences/v1/userpreferences_pb.ts +++ b/gen/proto/ts/teleport/userpreferences/v1/userpreferences_pb.ts @@ -29,6 +29,7 @@ import { UnknownFieldHandler } from "@protobuf-ts/runtime"; import type { PartialMessage } from "@protobuf-ts/runtime"; import { reflectionMergePartial } from "@protobuf-ts/runtime"; import { MessageType } from "@protobuf-ts/runtime"; +import { SideNavDrawerMode } from "./sidenav_preferences_pb"; import { AccessGraphUserPreferences } from "./access_graph_pb"; import { UnifiedResourcePreferences } from "./unified_resource_preferences_pb"; import { ClusterUserPreferences } from "./cluster_preferences_pb"; @@ -70,6 +71,12 @@ export interface UserPreferences { * @generated from protobuf field: teleport.userpreferences.v1.AccessGraphUserPreferences access_graph = 6; */ accessGraph?: AccessGraphUserPreferences; + /** + * side_nav_drawer_mode is the sidenav drawer behavior preference in the frontend. + * + * @generated from protobuf field: teleport.userpreferences.v1.SideNavDrawerMode side_nav_drawer_mode = 7; + */ + sideNavDrawerMode: SideNavDrawerMode; } /** * GetUserPreferencesRequest is a request to get the user preferences. @@ -112,12 +119,14 @@ class UserPreferences$Type extends MessageType { { no: 3, name: "onboard", kind: "message", T: () => OnboardUserPreferences }, { no: 4, name: "cluster_preferences", kind: "message", T: () => ClusterUserPreferences }, { no: 5, name: "unified_resource_preferences", kind: "message", T: () => UnifiedResourcePreferences }, - { no: 6, name: "access_graph", kind: "message", T: () => AccessGraphUserPreferences } + { no: 6, name: "access_graph", kind: "message", T: () => AccessGraphUserPreferences }, + { no: 7, name: "side_nav_drawer_mode", kind: "enum", T: () => ["teleport.userpreferences.v1.SideNavDrawerMode", SideNavDrawerMode, "SIDE_NAV_DRAWER_MODE_"] } ]); } create(value?: PartialMessage): UserPreferences { const message = globalThis.Object.create((this.messagePrototype!)); message.theme = 0; + message.sideNavDrawerMode = 0; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -142,6 +151,9 @@ class UserPreferences$Type extends MessageType { case /* teleport.userpreferences.v1.AccessGraphUserPreferences access_graph */ 6: message.accessGraph = AccessGraphUserPreferences.internalBinaryRead(reader, reader.uint32(), options, message.accessGraph); break; + case /* teleport.userpreferences.v1.SideNavDrawerMode side_nav_drawer_mode */ 7: + message.sideNavDrawerMode = reader.int32(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -169,6 +181,9 @@ class UserPreferences$Type extends MessageType { /* teleport.userpreferences.v1.AccessGraphUserPreferences access_graph = 6; */ if (message.accessGraph) AccessGraphUserPreferences.internalBinaryWrite(message.accessGraph, writer.tag(6, WireType.LengthDelimited).fork(), options).join(); + /* teleport.userpreferences.v1.SideNavDrawerMode side_nav_drawer_mode = 7; */ + if (message.sideNavDrawerMode !== 0) + writer.tag(7, WireType.Varint).int32(message.sideNavDrawerMode); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); diff --git a/lib/auth/userpreferences/userpreferencesv1/service_test.go b/lib/auth/userpreferences/userpreferencesv1/service_test.go index 62a129989d3a3..b377e9b6a5a84 100644 --- a/lib/auth/userpreferences/userpreferencesv1/service_test.go +++ b/lib/auth/userpreferences/userpreferencesv1/service_test.go @@ -55,7 +55,8 @@ func TestService_GetUserPreferences(t *testing.T) { req: &userpreferencesv1.GetUserPreferencesRequest{}, want: &userpreferencesv1.GetUserPreferencesResponse{ Preferences: &userpreferencesv1.UserPreferences{ - Theme: userpreferencesv1.Theme_THEME_UNSPECIFIED, + Theme: userpreferencesv1.Theme_THEME_UNSPECIFIED, + SideNavDrawerMode: userpreferencesv1.SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_COLLAPSED, UnifiedResourcePreferences: &userpreferencesv1.UnifiedResourcePreferences{ DefaultTab: userpreferencesv1.DefaultTab_DEFAULT_TAB_ALL, ViewMode: userpreferencesv1.ViewMode_VIEW_MODE_CARD, diff --git a/lib/services/local/userpreferences.go b/lib/services/local/userpreferences.go index c542ea88f43f2..9ee14bf4a96c2 100644 --- a/lib/services/local/userpreferences.go +++ b/lib/services/local/userpreferences.go @@ -50,6 +50,7 @@ func DefaultUserPreferences() *userpreferencesv1.UserPreferences { ClusterPreferences: &userpreferencesv1.ClusterUserPreferences{ PinnedResources: &userpreferencesv1.PinnedResourcesUserPreferences{}, }, + SideNavDrawerMode: userpreferencesv1.SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_COLLAPSED, } } diff --git a/lib/services/local/userpreferences_test.go b/lib/services/local/userpreferences_test.go index f156f127dc1e8..8abe30a1ec6ee 100644 --- a/lib/services/local/userpreferences_test.go +++ b/lib/services/local/userpreferences_test.go @@ -103,6 +103,7 @@ func TestUserPreferencesCRUD(t *testing.T) { Theme: userpreferencesv1.Theme_THEME_DARK, UnifiedResourcePreferences: defaultPref.UnifiedResourcePreferences, ClusterPreferences: defaultPref.ClusterPreferences, + SideNavDrawerMode: defaultPref.SideNavDrawerMode, }, }, { @@ -124,6 +125,7 @@ func TestUserPreferencesCRUD(t *testing.T) { AvailableResourceMode: userpreferencesv1.AvailableResourceMode_AVAILABLE_RESOURCE_MODE_ACCESSIBLE, }, ClusterPreferences: defaultPref.ClusterPreferences, + SideNavDrawerMode: defaultPref.SideNavDrawerMode, }, }, { @@ -145,6 +147,7 @@ func TestUserPreferencesCRUD(t *testing.T) { AvailableResourceMode: userpreferencesv1.AvailableResourceMode_AVAILABLE_RESOURCE_MODE_NONE, }, ClusterPreferences: defaultPref.ClusterPreferences, + SideNavDrawerMode: defaultPref.SideNavDrawerMode, }, }, { @@ -165,6 +168,7 @@ func TestUserPreferencesCRUD(t *testing.T) { expected: &userpreferencesv1.UserPreferences{ Theme: defaultPref.Theme, UnifiedResourcePreferences: defaultPref.UnifiedResourcePreferences, + SideNavDrawerMode: defaultPref.SideNavDrawerMode, Onboard: &userpreferencesv1.OnboardUserPreferences{ PreferredResources: []userpreferencesv1.Resource{userpreferencesv1.Resource_RESOURCE_DATABASES}, MarketingParams: &userpreferencesv1.MarketingParams{ @@ -192,6 +196,7 @@ func TestUserPreferencesCRUD(t *testing.T) { Theme: defaultPref.Theme, UnifiedResourcePreferences: defaultPref.UnifiedResourcePreferences, Onboard: defaultPref.Onboard, + SideNavDrawerMode: defaultPref.SideNavDrawerMode, ClusterPreferences: &userpreferencesv1.ClusterUserPreferences{ PinnedResources: &userpreferencesv1.PinnedResourcesUserPreferences{ ResourceIds: []string{"node1", "node2"}, @@ -199,6 +204,21 @@ func TestUserPreferencesCRUD(t *testing.T) { }, }, }, + { + name: "update sidenav preference only", + req: &userpreferencesv1.UpsertUserPreferencesRequest{ + Preferences: &userpreferencesv1.UserPreferences{ + SideNavDrawerMode: userpreferencesv1.SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_STICKY, + }, + }, + expected: &userpreferencesv1.UserPreferences{ + Theme: defaultPref.Theme, + UnifiedResourcePreferences: defaultPref.UnifiedResourcePreferences, + Onboard: defaultPref.Onboard, + ClusterPreferences: defaultPref.ClusterPreferences, + SideNavDrawerMode: userpreferencesv1.SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_STICKY, + }, + }, { name: "update all the settings at once", req: &userpreferencesv1.UpsertUserPreferencesRequest{ @@ -210,6 +230,7 @@ func TestUserPreferencesCRUD(t *testing.T) { LabelsViewMode: userpreferencesv1.LabelsViewMode_LABELS_VIEW_MODE_COLLAPSED, AvailableResourceMode: userpreferencesv1.AvailableResourceMode_AVAILABLE_RESOURCE_MODE_NONE, }, + SideNavDrawerMode: userpreferencesv1.SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_STICKY, Onboard: &userpreferencesv1.OnboardUserPreferences{ PreferredResources: []userpreferencesv1.Resource{userpreferencesv1.Resource_RESOURCE_KUBERNETES}, MarketingParams: &userpreferencesv1.MarketingParams{ @@ -248,6 +269,7 @@ func TestUserPreferencesCRUD(t *testing.T) { ResourceIds: []string{"node1", "node2"}, }, }, + SideNavDrawerMode: userpreferencesv1.SideNavDrawerMode_SIDE_NAV_DRAWER_MODE_STICKY, }, }, } diff --git a/lib/web/userpreferences.go b/lib/web/userpreferences.go index 810dc3cc10006..e6476022eccc5 100644 --- a/lib/web/userpreferences.go +++ b/lib/web/userpreferences.go @@ -66,12 +66,13 @@ type AccessGraphPreferencesResponse struct { // UserPreferencesResponse is the JSON response for the user preferences. type UserPreferencesResponse struct { - Assist AssistUserPreferencesResponse `json:"assist"` - Theme userpreferencesv1.Theme `json:"theme"` - UnifiedResourcePreferences UnifiedResourcePreferencesResponse `json:"unifiedResourcePreferences"` - Onboard OnboardUserPreferencesResponse `json:"onboard"` - ClusterPreferences ClusterUserPreferencesResponse `json:"clusterPreferences,omitempty"` - AccessGraph AccessGraphPreferencesResponse `json:"accessGraph,omitempty"` + Assist AssistUserPreferencesResponse `json:"assist"` + Theme userpreferencesv1.Theme `json:"theme"` + UnifiedResourcePreferences UnifiedResourcePreferencesResponse `json:"unifiedResourcePreferences"` + Onboard OnboardUserPreferencesResponse `json:"onboard"` + ClusterPreferences ClusterUserPreferencesResponse `json:"clusterPreferences,omitempty"` + AccessGraph AccessGraphPreferencesResponse `json:"accessGraph,omitempty"` + SideNavDrawerMode userpreferencesv1.SideNavDrawerMode `json:"sideNavDrawerMode"` } func (h *Handler) getUserClusterPreferences(_ http.ResponseWriter, r *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (interface{}, error) { @@ -152,6 +153,7 @@ func makePreferenceRequest(req UserPreferencesResponse) *userpreferencesv1.Upser AccessGraph: &userpreferencesv1.AccessGraphUserPreferences{ HasBeenRedirected: req.AccessGraph.HasBeenRedirected, }, + SideNavDrawerMode: req.SideNavDrawerMode, }, } } @@ -185,6 +187,7 @@ func userPreferencesResponse(resp *userpreferencesv1.UserPreferences) *UserPrefe ClusterPreferences: clusterPreferencesResponse(resp.ClusterPreferences), UnifiedResourcePreferences: unifiedResourcePreferencesResponse(resp.UnifiedResourcePreferences), AccessGraph: accessGraphPreferencesResponse(resp.AccessGraph), + SideNavDrawerMode: resp.SideNavDrawerMode, } return jsonResp diff --git a/web/packages/design/src/Icon/Icons.story.tsx b/web/packages/design/src/Icon/Icons.story.tsx index 388e996fcbe73..47807a6715ae9 100644 --- a/web/packages/design/src/Icon/Icons.story.tsx +++ b/web/packages/design/src/Icon/Icons.story.tsx @@ -52,6 +52,7 @@ export const Icons = () => ( + diff --git a/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx b/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx new file mode 100644 index 0000000000000..524da7bfd894e --- /dev/null +++ b/web/packages/design/src/Icon/Icons/ArrowLineLeft.tsx @@ -0,0 +1,65 @@ +/** + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/* MIT License + +Copyright (c) 2020 Phosphor Icons + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +import React from 'react'; + +import { Icon, IconProps } from '../Icon'; + +/* + +THIS FILE IS GENERATED. DO NOT EDIT. + +*/ + +export function ArrowLineLeft({ size = 24, color, ...otherProps }: IconProps) { + return ( + + + + + ); +} diff --git a/web/packages/design/src/Icon/assets/ArrowLineLeft.svg b/web/packages/design/src/Icon/assets/ArrowLineLeft.svg new file mode 100644 index 0000000000000..e7d1cc90b253c --- /dev/null +++ b/web/packages/design/src/Icon/assets/ArrowLineLeft.svg @@ -0,0 +1,4 @@ + + + + diff --git a/web/packages/design/src/Icon/index.ts b/web/packages/design/src/Icon/index.ts index 6942c618d7fa5..be8d7320be71b 100644 --- a/web/packages/design/src/Icon/index.ts +++ b/web/packages/design/src/Icon/index.ts @@ -38,6 +38,7 @@ export { ArrowDown } from './Icons/ArrowDown'; export { ArrowFatLinesUp } from './Icons/ArrowFatLinesUp'; export { ArrowForward } from './Icons/ArrowForward'; export { ArrowLeft } from './Icons/ArrowLeft'; +export { ArrowLineLeft } from './Icons/ArrowLineLeft'; export { ArrowRight } from './Icons/ArrowRight'; export { ArrowSquareOut } from './Icons/ArrowSquareOut'; export { ArrowUp } from './Icons/ArrowUp'; diff --git a/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx b/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx index 84be28f8192f9..8be5438ee2c4e 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx +++ b/web/packages/teleport/src/Navigation/SideNavigation/Navigation.tsx @@ -23,22 +23,19 @@ import React, { useRef, useMemo, } from 'react'; -import styled, { useTheme } from 'styled-components'; +import styled from 'styled-components'; import { matchPath, useHistory } from 'react-router'; -import { Text, Flex, Box, P2 } from 'design'; +import { Flex, Box } from 'design'; -import { ToolTipInfo } from 'shared/components/ToolTip'; +import { SideNavDrawerMode } from 'gen-proto-ts/teleport/userpreferences/v1/sidenav_preferences_pb'; import cfg from 'teleport/config'; import { useFeatures } from 'teleport/FeaturesContext'; +import useStickyClusterId from 'teleport/useStickyClusterId'; +import { useUser } from 'teleport/User/UserContext'; -import { - Section, - RightPanel, - SubsectionItem, - verticalPadding, -} from './Section'; +import { DefaultSection, rightPanelWidth } from './Section'; import { zIndexMap } from './zIndexMap'; import { @@ -47,7 +44,7 @@ import { SidenavCategory, } from './categories'; import { SearchSection } from './Search'; -import { ResourcesSection } from './ResourcesSection'; +import { getResourcesSection, ResourcesSection } from './ResourcesSection'; import type * as history from 'history'; import type { TeleportFeature } from 'teleport/types'; @@ -224,7 +221,7 @@ function useDebounceClose( clearTimeout(timeoutRef.current); } - // If we're closing the drarwer as opposed to switching to a different section (value is null and isClosing is true), apply debounce. + // If we're closing the drawer as opposed to switching to a different section (value is null and isClosing is true), apply debounce. if (value === null && isClosing) { timeoutRef.current = setTimeout(() => { setDebouncedValue(null); @@ -247,6 +244,8 @@ function useDebounceClose( export function Navigation() { const features = useFeatures(); const history = useHistory(); + const { clusterId } = useStickyClusterId(); + const { preferences, updatePreferences } = useUser(); const [targetSection, setTargetSection] = useState( null ); @@ -266,13 +265,44 @@ export function Navigation() { }, []); const currentView = useMemo( () => getNavSubsectionForRoute(features, history.location), - [history.location] + [features, history.location] ); - const navSections = getNavigationSections(features).filter( - section => section.subsections.length + const stickyMode = preferences.sideNavDrawerMode === SideNavDrawerMode.STICKY; + + const toggleStickyMode = () => { + // Close the drawer right away if they're disabling sticky mode. + if (stickyMode) { + setIsClosing(false); + setPreviousExpandedSection(null); + setTargetSection(null); + } + updatePreferences({ + sideNavDrawerMode: stickyMode + ? SideNavDrawerMode.COLLAPSED + : SideNavDrawerMode.STICKY, + }); + }; + + const navSections = useMemo( + () => + getNavigationSections(features).filter( + section => section.subsections.length + ), + [features] ); - const topMenuSection = getTopMenuSection(features); + + const topMenuSection = useMemo(() => getTopMenuSection(features), [features]); + + const resourcesSection = useMemo(() => { + const searchParams = new URLSearchParams(location.search); + return getResourcesSection({ + clusterId, + preferences, + updatePreferences, + searchParams, + }); + }, [clusterId, preferences, updatePreferences]); const handleSetExpandedSection = useCallback( (section: NavigationSection) => { @@ -288,11 +318,43 @@ export function Navigation() { [debouncedSection] ); - const resetExpandedSection = useCallback((closeAfterDelay = true) => { - setIsClosing(closeAfterDelay); - setPreviousExpandedSection(null); - setTargetSection(null); - }, []); + const combinedSideNavSections = useMemo( + () => [resourcesSection, ...navSections], + [resourcesSection, navSections] + ); + const currentPageSection = useMemo(() => { + return combinedSideNavSections.find( + section => section.category === currentView?.category + ); + }, [combinedSideNavSections, currentView]); + + const collapseDrawer = useCallback( + (closeAfterDelay = true) => { + if (stickyMode && currentPageSection) { + setPreviousExpandedSection(debouncedSection); + setTargetSection(currentPageSection); + } else { + setIsClosing(closeAfterDelay); + setPreviousExpandedSection(null); + setTargetSection(null); + } + }, + [currentPageSection, stickyMode, debouncedSection] + ); + + useEffect(() => { + // Whenever the user changes page, if stickyMode is enabled and the page is part of the sidenav, the drawer should be expanded with the current page section. + if (!stickyMode) { + return; + } + + // If the page is not part of the sidenav, such as Account Settings, curentPageSection will be undefined, and the drawer should be collapsed. + if (currentPageSection) { + handleSetExpandedSection(currentPageSection); + } else { + collapseDrawer(false); + } + }, [currentPageSection]); // Handler for navigation actions const handleNavigation = useCallback( @@ -304,12 +366,14 @@ export function Navigation() { clearTimeout(navigationTimeoutRef.current); } - // Add a small delay to the close to allow the user to see some feedback (see the section they clicked become active). - navigationTimeoutRef.current = setTimeout(() => { - resetExpandedSection(false); - }, 150); + if (!stickyMode) { + // Add a small delay to the close to allow the user to see some feedback (see the section they clicked become active). + navigationTimeoutRef.current = setTimeout(() => { + collapseDrawer(false); + }, 150); + } }, - [resetExpandedSection, history] + [collapseDrawer, history] ); // Hide the nav if the current feature has hideNavigation set to true. @@ -325,196 +389,85 @@ export function Navigation() { if (hideNav) { return null; } - return ( - resetExpandedSection()} - onKeyUp={e => e.key === 'Escape' && resetExpandedSection()} + onMouseLeave={() => collapseDrawer()} + onKeyUp={e => e.key === 'Escape' && collapseDrawer(false)} onBlur={(event: React.FocusEvent) => { if (!event.currentTarget.contains(event.relatedTarget)) { - resetExpandedSection(); + collapseDrawer(); } }} - css={` - position: relative; - width: var(--sidenav-width); - z-index: ${zIndexMap.sideNavContainer}; - `} + className={ + stickyMode && + currentPageSection && + !!debouncedSection && + !debouncedSection.standalone + ? 'sticky-mode' + : '' + } > - {navSections.map(section => ( - - {section.category === 'Add New' && } -
handleSetExpandedSection(section)} - aria-controls={`panel-${debouncedSection?.category}`} - onClick={() => { - if (section.standalone) { - handleNavigation(section.subsections[0].route); - } - }} - isExpanded={ - !!debouncedSection && - !debouncedSection.standalone && - section.category === debouncedSection?.category - } - > - handleSetExpandedSection(section)} - onMouseEnter={() => handleSetExpandedSection(section)} - > - - - - - {section.category} - - - {!section.standalone && - section.subsections.map(subsection => ( - { - e.preventDefault(); - handleNavigation(subsection.route); - }} - > - - {subsection.title} - - ))} - - {cfg.edition === 'oss' && } - {cfg.edition === 'community' && } - - -
-
- ))} + {navSections.map(section => { + const isExpanded = + !!debouncedSection && + !debouncedSection.standalone && + section.category === debouncedSection?.category; + + return ( + + {section.category === 'Add New' && } + handleSetExpandedSection(section)} + currentPageSection={currentPageSection} + stickyMode={stickyMode} + toggleStickyMode={toggleStickyMode} + $active={section.category === currentView?.category} + aria-controls={`panel-${debouncedSection?.category}`} + handleNavigation={handleNavigation} + isExpanded={isExpanded} + /> + + ); + })}
-
- ); -} - -function AGPLFooter() { - const theme = useTheme(); - return ( - - {/* This is an independently compiled AGPL-3.0 version of Teleport. You */} - {/* can find the official release on{' '} */} - This is an independently compiled AGPL-3.0 version of Teleport. -
- Visit{' '} - - the Downloads page - {' '} - for the official release. - - } - /> + ); } -function CommunityFooter() { - const theme = useTheme(); - return ( - - - Upgrade to Teleport Enterprise - {' '} - for SSO, just-in-time access requests, Access Graph, and much more! - - } - /> - ); -} - -function LicenseFooter({ - title, - subText, - infoContent, -}: { - title: string; - subText: string; - infoContent: JSX.Element; -}) { - return ( - - - {title} - - {infoContent} - - - {subText} - - ); -} - -const StyledFooterBox = styled(Box)` - line-height: 20px; - border-top: ${props => props.theme.borders[1]} - ${props => props.theme.colors.spotBackground[0]}; -`; +const Container = styled(Box)` + position: relative; + width: var(--sidenav-width); + z-index: ${zIndexMap.sideNavContainer}; -const SubText = styled(Text)` - color: ${props => props.theme.colors.text.disabled}; - font-size: ${props => props.theme.fontSizes[1]}px; + &.sticky-mode { + margin-right: ${rightPanelWidth}px; + } `; const Divider = styled.div` diff --git a/web/packages/teleport/src/Navigation/SideNavigation/ResourcesSection.tsx b/web/packages/teleport/src/Navigation/SideNavigation/ResourcesSection.tsx index 37b393a91c184..3fdb2c004363b 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/ResourcesSection.tsx +++ b/web/packages/teleport/src/Navigation/SideNavigation/ResourcesSection.tsx @@ -35,18 +35,19 @@ import { useUser } from 'teleport/User/UserContext'; import { NavigationSubsection, NavigationSection } from './Navigation'; import { - Section, + CustomChildrenSection, RightPanel, verticalPadding, SubsectionItem, + RightPanelHeader, } from './Section'; import { CustomNavigationSubcategory, NavigationCategory } from './categories'; /** - * getResourcesSectionForSearch returns a NavigationSection for resources, - * this is only used for the sake of indexing these subsections in the sidenav search. + * getResourcesSection returns a NavigationSection for resources, + * this is used for the sake of indexing these subsections in the sidenav search. */ -export function getResourcesSectionForSearch( +export function getResourcesSection( subsectionProps: GetSubsectionProps ): NavigationSection { return { @@ -174,7 +175,13 @@ function getResourcesSubsections({ searchableTags: ['resources', 'resources', 'pinned resources'], category: NavigationCategory.Resources, exact: false, - customRouteMatchFn: () => isPinnedOnly && currentKinds.length !== 1, + customRouteMatchFn: currentViewRoute => + !!matchPath(currentViewRoute, { + path: cfg.routes.unifiedResources, + exact: false, + }) && + isPinnedOnly && + currentKinds.length !== 1, onClick: () => setPinnedUserPreference(true), }, { @@ -240,11 +247,17 @@ export function ResourcesSection({ previousExpandedSection, handleSetExpandedSection, currentView, + stickyMode, + toggleStickyMode, + canToggleStickyMode, }: { expandedSection: NavigationSection; previousExpandedSection: NavigationSection; currentView: NavigationSubsection; handleSetExpandedSection: (section: NavigationSection) => void; + stickyMode: boolean; + toggleStickyMode: () => void; + canToggleStickyMode: boolean; }) { const { clusterId } = useStickyClusterId(); const { preferences, updatePreferences } = useUser(); @@ -268,12 +281,11 @@ export function ResourcesSection({ const currentViewRoute = currentView?.route; return ( -
null} - setExpandedSection={() => handleSetExpandedSection(section)} + onExpandSection={() => handleSetExpandedSection(section)} aria-controls={`panel-${expandedSection?.category}`} isExpanded={isExpanded} > @@ -285,15 +297,16 @@ export function ResourcesSection({ > - - - Resources - - + {subsections .filter(section => !section.subCategory) .map(section => ( @@ -336,7 +349,7 @@ export function ResourcesSection({ ))} -
+ ); } diff --git a/web/packages/teleport/src/Navigation/SideNavigation/Search.tsx b/web/packages/teleport/src/Navigation/SideNavigation/Search.tsx index 6cbf21ec96aac..567e727af771d 100644 --- a/web/packages/teleport/src/Navigation/SideNavigation/Search.tsx +++ b/web/packages/teleport/src/Navigation/SideNavigation/Search.tsx @@ -23,21 +23,18 @@ import styled from 'styled-components'; import { Box, Flex, P3, Text } from 'design'; import { height, space, color } from 'design/system'; -import useStickyClusterId from 'teleport/useStickyClusterId'; -import { useUser } from 'teleport/User/UserContext'; import { storageService } from 'teleport/services/storageService'; import { RecentHistory, RecentHistoryItem } from '../RecentHistory'; import { NavigationSection, NavigationSubsection } from './Navigation'; import { - Section, + CustomChildrenSection, RightPanel, - verticalPadding, getSubsectionStyles, + RightPanelHeader, } from './Section'; import { CustomNavigationCategory } from './categories'; -import { getResourcesSectionForSearch } from './ResourcesSection'; export function SearchSection({ navigationSections, @@ -45,41 +42,32 @@ export function SearchSection({ previousExpandedSection, handleSetExpandedSection, currentView, + stickyMode, + toggleStickyMode, + canToggleStickyMode, }: { navigationSections: NavigationSection[]; expandedSection: NavigationSection; previousExpandedSection: NavigationSection; currentView: NavigationSubsection; handleSetExpandedSection: (section: NavigationSection) => void; + stickyMode: boolean; + toggleStickyMode: () => void; + canToggleStickyMode: boolean; }) { const section: NavigationSection = { category: CustomNavigationCategory.Search, subsections: [], }; - const { clusterId } = useStickyClusterId(); - const { preferences, updatePreferences } = useUser(); - - const searchParams = new URLSearchParams(location.search); - - const searchableNavSections: NavigationSection[] = [ - getResourcesSectionForSearch({ - clusterId, - preferences, - updatePreferences, - searchParams, - }), - ...navigationSections, - ]; const isExpanded = expandedSection?.category === CustomNavigationCategory.Search; return ( -
null} - setExpandedSection={() => handleSetExpandedSection(section)} + onExpandSection={() => handleSetExpandedSection(section)} aria-controls={`panel-${expandedSection?.category}`} isExpanded={isExpanded} > @@ -89,12 +77,25 @@ export function SearchSection({ id={`panel-${section.category}`} onFocus={() => handleSetExpandedSection(section)} > - + + + + -
+ ); } @@ -146,11 +147,6 @@ function SearchContent({ return ( - - - Search - - . */ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import { NavLink } from 'react-router-dom'; -import styled, { css } from 'styled-components'; +import styled, { css, useTheme } from 'styled-components'; -import { Box } from 'design'; +import { Box, ButtonIcon, Flex, P2, Text } from 'design'; import { Theme } from 'design/theme'; +import { ArrowLineLeft } from 'design/Icon'; +import { HoverTooltip, ToolTipInfo } from 'shared/components/ToolTip'; -import { NavigationSection } from './Navigation'; +import cfg from 'teleport/config'; + +import { NavigationSection, NavigationSubsection } from './Navigation'; import { zIndexMap } from './zIndexMap'; import { CategoryIcon } from './CategoryIcon'; -export function Section({ - section, - $active, - setExpandedSection, - onClick, - children, - isExpanded, -}: { +type SharedSectionProps = { section: NavigationSection; $active: boolean; - setExpandedSection: () => void; - onClick: (event: React.MouseEvent) => void; - isExpanded?: boolean; - children?: JSX.Element; + isExpanded: boolean; + onExpandSection: () => void; +}; + +/** + * DefaultSection is a NavigationSection with default children automatically generated from the subsections it contains, + * this will just be a regular list of subsection items (eg. Identity section). + */ +export function DefaultSection({ + $active, + section, + isExpanded, + handleNavigation, + previousExpandedSection, + stickyMode, + toggleStickyMode, + currentPageSection, + currentView, + onExpandSection, +}: SharedSectionProps & { + currentView?: NavigationSubsection; + handleNavigation?: (route: string) => void; + currentPageSection?: NavigationSection; + stickyMode: boolean; + toggleStickyMode: () => void; + previousExpandedSection: NavigationSection; }) { return ( <> { + if (section.standalone) { + handleNavigation(section.subsections[0].route); + } + }} + isExpanded={isExpanded} + tabIndex={section.standalone ? 0 : -1} + > + + {section.category} + + + + + + + {!section.standalone && + section.subsections.map(subsection => ( + { + e.preventDefault(); + handleNavigation(subsection.route); + }} + > + + {subsection.title} + + ))} + + {cfg.edition === 'oss' && } + {cfg.edition === 'community' && } + + + + ); +} + +/** + * CustomChildrenSection is a NavigationSection with custom children (e.g. search section). + */ +export function CustomChildrenSection({ + section, + $active, + isExpanded, + children, + onExpandSection, +}: PropsWithChildren) { + return ( + <> + @@ -60,9 +154,9 @@ export function Section({ ); } -const rightPanelWidth = '236px'; +export const rightPanelWidth = 236; -export const RightPanel = styled(Box).attrs({ pt: 2, px: '5px' })<{ +export const RightPanel = styled(Box).attrs({ px: '5px' })<{ isVisible: boolean; skipAnimation: boolean; }>` @@ -70,7 +164,7 @@ export const RightPanel = styled(Box).attrs({ pt: 2, px: '5px' })<{ left: var(--sidenav-width); height: 100%; scrollbar-color: ${p => p.theme.colors.spotBackground[2]} transparent; - width: ${rightPanelWidth}; + width: ${rightPanelWidth}px; background: ${p => p.theme.colors.levels.surface}; z-index: ${zIndexMap.sideNavExpandedPanel}; border-right: 1px solid ${p => p.theme.colors.spotBackground[1]}; @@ -94,6 +188,64 @@ export const RightPanel = styled(Box).attrs({ pt: 2, px: '5px' })<{ } `; +export function RightPanelHeader({ + title, + stickyMode, + toggleStickyMode, + canToggleStickyMode, +}: { + title: string; + stickyMode: boolean; + toggleStickyMode: () => void; + canToggleStickyMode: boolean; +}) { + return ( + + + {title} + + + {canToggleStickyMode ? ( + + + + ) : ( + + )} + + + ); +} + +const StickyToggleBtn = styled(ButtonIcon)` + position: absolute; + right: 0; + top: 4px; + height: 40px; + width: 40px; + border-radius: ${props => props.theme.radii[2]}px; + color: ${props => props.theme.colors.text.muted}; + + &:hover:enabled, + &:focus-visible:enabled { + color: ${props => props.theme.colors.text.main}; + } +`; + +const AnimatedArrow = styled(ArrowLineLeft)<{ $isSticky: boolean }>` + transition: transform 0.3s ease-in-out; + transform: ${props => (props.$isSticky ? 'none' : 'rotate(180deg)')}; +`; + export const CategoryButton = styled.button<{ $active: boolean; isExpanded: boolean; @@ -253,3 +405,87 @@ export function getSubsectionStyles(theme: Theme, active: boolean) { } `; } + +function AGPLFooter() { + const theme = useTheme(); + return ( + + {/* This is an independently compiled AGPL-3.0 version of Teleport. You */} + {/* can find the official release on{' '} */} + This is an independently compiled AGPL-3.0 version of Teleport. +
+ Visit{' '} + + the Downloads page + {' '} + for the official release. + + } + /> + ); +} + +function CommunityFooter() { + const theme = useTheme(); + return ( + + + Upgrade to Teleport Enterprise + {' '} + for SSO, just-in-time access requests, Access Graph, and much more! + + } + /> + ); +} + +function LicenseFooter({ + title, + subText, + infoContent, +}: { + title: string; + subText: string; + infoContent: JSX.Element; +}) { + return ( + + + {title} + + {infoContent} + + + {subText} + + ); +} + +const StyledFooterBox = styled(Box)` + line-height: 20px; + border-top: ${props => props.theme.borders[1]} + ${props => props.theme.colors.spotBackground[0]}; +`; + +const SubText = styled(Text)` + color: ${props => props.theme.colors.text.disabled}; + font-size: ${props => props.theme.fontSizes[1]}px; +`; diff --git a/web/packages/teleport/src/services/userPreferences/userPreferences.test.ts b/web/packages/teleport/src/services/userPreferences/userPreferences.test.ts index c7de8fb670eaf..d5464b3c10ad4 100644 --- a/web/packages/teleport/src/services/userPreferences/userPreferences.test.ts +++ b/web/packages/teleport/src/services/userPreferences/userPreferences.test.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { SideNavDrawerMode } from 'gen-proto-ts/teleport/userpreferences/v1/sidenav_preferences_pb'; import { Theme } from 'gen-proto-ts/teleport/userpreferences/v1/theme_pb'; import { UserPreferences } from 'gen-proto-ts/teleport/userpreferences/v1/userpreferences_pb'; @@ -41,6 +42,7 @@ test('should convert the old cluster user preferences format to the new one', () clusterPreferences: { pinnedResources: ['resource1', 'resource2'], }, + sideNavDrawerMode: SideNavDrawerMode.COLLAPSED, }; const actualUserPreferences: UserPreferences = { @@ -48,6 +50,7 @@ test('should convert the old cluster user preferences format to the new one', () clusterPreferences: { pinnedResources: { resourceIds: ['resource1', 'resource2'] }, }, + sideNavDrawerMode: SideNavDrawerMode.COLLAPSED, }; // when we grab the user preferences from the local storage, we check if it is in the old format @@ -70,6 +73,7 @@ test('should convert the user preferences back to the old format when updating', clusterPreferences: { pinnedResources: { resourceIds: ['resource1', 'resource2'] }, }, + sideNavDrawerMode: SideNavDrawerMode.COLLAPSED, }; const convertedPreferences = convertUserPreferences(actualUserPreferences); diff --git a/web/packages/teleport/src/services/userPreferences/userPreferences.ts b/web/packages/teleport/src/services/userPreferences/userPreferences.ts index 7f88ec16a8116..0d5b3a3fe3eb6 100644 --- a/web/packages/teleport/src/services/userPreferences/userPreferences.ts +++ b/web/packages/teleport/src/services/userPreferences/userPreferences.ts @@ -32,6 +32,8 @@ import { Theme } from 'gen-proto-ts/teleport/userpreferences/v1/theme_pb'; import { OnboardUserPreferences } from 'gen-proto-ts/teleport/userpreferences/v1/onboard_pb'; +import { SideNavDrawerMode } from 'gen-proto-ts/teleport/userpreferences/v1/sidenav_preferences_pb'; + import { getPrefersDark } from 'teleport/ThemeProvider'; import cfg from 'teleport/config'; @@ -43,6 +45,7 @@ interface BackendClusterUserPreferences { export interface BackendUserPreferences { theme: Theme; + sideNavDrawerMode: SideNavDrawerMode; onboard?: OnboardUserPreferences; clusterPreferences?: BackendClusterUserPreferences; unifiedResourcePreferences?: UnifiedResourcePreferences; @@ -101,6 +104,7 @@ export function makeDefaultUserPreferences(): UserPreferences { availableResourceMode: AvailableResourceMode.ALL, }, clusterPreferences: makeDefaultUserClusterPreferences(), + sideNavDrawerMode: SideNavDrawerMode.COLLAPSED, }; }