mirror of
https://github.com/coder/coder.git
synced 2025-08-01 08:28:48 +00:00
feat: add support for multiple banners (#13081)
This commit is contained in:
committed by
GitHub
parent
a4bd50c985
commit
d8e0be6ee6
@@ -176,7 +176,7 @@ func New(options Options) Agent {
|
||||
ignorePorts: options.IgnorePorts,
|
||||
portCacheDuration: options.PortCacheDuration,
|
||||
reportMetadataInterval: options.ReportMetadataInterval,
|
||||
serviceBannerRefreshInterval: options.ServiceBannerRefreshInterval,
|
||||
notificationBannersRefreshInterval: options.ServiceBannerRefreshInterval,
|
||||
sshMaxTimeout: options.SSHMaxTimeout,
|
||||
subsystems: options.Subsystems,
|
||||
addresses: options.Addresses,
|
||||
@@ -193,7 +193,7 @@ func New(options Options) Agent {
|
||||
// that gets closed on disconnection. This is used to wait for graceful disconnection from the
|
||||
// coordinator during shut down.
|
||||
close(a.coordDisconnected)
|
||||
a.serviceBanner.Store(new(codersdk.ServiceBannerConfig))
|
||||
a.notificationBanners.Store(new([]codersdk.BannerConfig))
|
||||
a.sessionToken.Store(new(string))
|
||||
a.init()
|
||||
return a
|
||||
@@ -234,8 +234,8 @@ type agent struct {
|
||||
manifest atomic.Pointer[agentsdk.Manifest] // manifest is atomic because values can change after reconnection.
|
||||
reportMetadataInterval time.Duration
|
||||
scriptRunner *agentscripts.Runner
|
||||
serviceBanner atomic.Pointer[codersdk.ServiceBannerConfig] // serviceBanner is atomic because it is periodically updated.
|
||||
serviceBannerRefreshInterval time.Duration
|
||||
notificationBanners atomic.Pointer[[]codersdk.BannerConfig] // notificationBanners is atomic because it is periodically updated.
|
||||
notificationBannersRefreshInterval time.Duration
|
||||
sessionToken atomic.Pointer[string]
|
||||
sshServer *agentssh.Server
|
||||
sshMaxTimeout time.Duration
|
||||
@@ -274,7 +274,7 @@ func (a *agent) init() {
|
||||
sshSrv, err := agentssh.NewServer(a.hardCtx, a.logger.Named("ssh-server"), a.prometheusRegistry, a.filesystem, &agentssh.Config{
|
||||
MaxTimeout: a.sshMaxTimeout,
|
||||
MOTDFile: func() string { return a.manifest.Load().MOTDFile },
|
||||
ServiceBanner: func() *codersdk.ServiceBannerConfig { return a.serviceBanner.Load() },
|
||||
NotificationBanners: func() *[]codersdk.BannerConfig { return a.notificationBanners.Load() },
|
||||
UpdateEnv: a.updateCommandEnv,
|
||||
WorkingDirectory: func() string { return a.manifest.Load().Directory },
|
||||
})
|
||||
@@ -709,23 +709,26 @@ func (a *agent) setLifecycle(state codersdk.WorkspaceAgentLifecycle) {
|
||||
// (and must be done before the session actually starts).
|
||||
func (a *agent) fetchServiceBannerLoop(ctx context.Context, conn drpc.Conn) error {
|
||||
aAPI := proto.NewDRPCAgentClient(conn)
|
||||
ticker := time.NewTicker(a.serviceBannerRefreshInterval)
|
||||
ticker := time.NewTicker(a.notificationBannersRefreshInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
sbp, err := aAPI.GetServiceBanner(ctx, &proto.GetServiceBannerRequest{})
|
||||
bannersProto, err := aAPI.GetNotificationBanners(ctx, &proto.GetNotificationBannersRequest{})
|
||||
if err != nil {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
a.logger.Error(ctx, "failed to update service banner", slog.Error(err))
|
||||
a.logger.Error(ctx, "failed to update notification banners", slog.Error(err))
|
||||
return err
|
||||
}
|
||||
serviceBanner := agentsdk.ServiceBannerFromProto(sbp)
|
||||
a.serviceBanner.Store(&serviceBanner)
|
||||
banners := make([]codersdk.BannerConfig, 0, len(bannersProto.NotificationBanners))
|
||||
for _, bannerProto := range bannersProto.NotificationBanners {
|
||||
banners = append(banners, agentsdk.BannerConfigFromProto(bannerProto))
|
||||
}
|
||||
a.notificationBanners.Store(&banners)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -757,15 +760,18 @@ func (a *agent) run() (retErr error) {
|
||||
// redial the coder server and retry.
|
||||
connMan := newAPIConnRoutineManager(a.gracefulCtx, a.hardCtx, a.logger, conn)
|
||||
|
||||
connMan.start("init service banner", gracefulShutdownBehaviorStop,
|
||||
connMan.start("init notification banners", gracefulShutdownBehaviorStop,
|
||||
func(ctx context.Context, conn drpc.Conn) error {
|
||||
aAPI := proto.NewDRPCAgentClient(conn)
|
||||
sbp, err := aAPI.GetServiceBanner(ctx, &proto.GetServiceBannerRequest{})
|
||||
bannersProto, err := aAPI.GetNotificationBanners(ctx, &proto.GetNotificationBannersRequest{})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("fetch service banner: %w", err)
|
||||
}
|
||||
serviceBanner := agentsdk.ServiceBannerFromProto(sbp)
|
||||
a.serviceBanner.Store(&serviceBanner)
|
||||
banners := make([]codersdk.BannerConfig, 0, len(bannersProto.NotificationBanners))
|
||||
for _, bannerProto := range bannersProto.NotificationBanners {
|
||||
banners = append(banners, agentsdk.BannerConfigFromProto(bannerProto))
|
||||
}
|
||||
a.notificationBanners.Store(&banners)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
@@ -614,12 +614,12 @@ func TestAgent_Session_TTY_MOTD_Update(t *testing.T) {
|
||||
// Set new banner func and wait for the agent to call it to update the
|
||||
// banner.
|
||||
ready := make(chan struct{}, 2)
|
||||
client.SetServiceBannerFunc(func() (codersdk.ServiceBannerConfig, error) {
|
||||
client.SetNotificationBannersFunc(func() ([]codersdk.BannerConfig, error) {
|
||||
select {
|
||||
case ready <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
return test.banner, nil
|
||||
return []codersdk.BannerConfig{test.banner}, nil
|
||||
})
|
||||
<-ready
|
||||
<-ready // Wait for two updates to ensure the value has propagated.
|
||||
@@ -2193,15 +2193,15 @@ func setupAgentSSHClient(ctx context.Context, t *testing.T) *ssh.Client {
|
||||
func setupSSHSession(
|
||||
t *testing.T,
|
||||
manifest agentsdk.Manifest,
|
||||
serviceBanner codersdk.ServiceBannerConfig,
|
||||
banner codersdk.BannerConfig,
|
||||
prepareFS func(fs afero.Fs),
|
||||
opts ...func(*agenttest.Client, *agent.Options),
|
||||
) *ssh.Session {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
opts = append(opts, func(c *agenttest.Client, o *agent.Options) {
|
||||
c.SetServiceBannerFunc(func() (codersdk.ServiceBannerConfig, error) {
|
||||
return serviceBanner, nil
|
||||
c.SetNotificationBannersFunc(func() ([]codersdk.BannerConfig, error) {
|
||||
return []codersdk.BannerConfig{banner}, nil
|
||||
})
|
||||
})
|
||||
//nolint:dogsled
|
||||
|
@@ -63,7 +63,7 @@ type Config struct {
|
||||
// file will be displayed to the user upon login.
|
||||
MOTDFile func() string
|
||||
// ServiceBanner returns the configuration for the Coder service banner.
|
||||
ServiceBanner func() *codersdk.ServiceBannerConfig
|
||||
NotificationBanners func() *[]codersdk.BannerConfig
|
||||
// UpdateEnv updates the environment variables for the command to be
|
||||
// executed. It can be used to add, modify or replace environment variables.
|
||||
UpdateEnv func(current []string) (updated []string, err error)
|
||||
@@ -123,8 +123,8 @@ func NewServer(ctx context.Context, logger slog.Logger, prometheusRegistry *prom
|
||||
if config.MOTDFile == nil {
|
||||
config.MOTDFile = func() string { return "" }
|
||||
}
|
||||
if config.ServiceBanner == nil {
|
||||
config.ServiceBanner = func() *codersdk.ServiceBannerConfig { return &codersdk.ServiceBannerConfig{} }
|
||||
if config.NotificationBanners == nil {
|
||||
config.NotificationBanners = func() *[]codersdk.BannerConfig { return &[]codersdk.BannerConfig{} }
|
||||
}
|
||||
if config.WorkingDirectory == nil {
|
||||
config.WorkingDirectory = func() string {
|
||||
@@ -441,12 +441,15 @@ func (s *Server) startPTYSession(logger slog.Logger, session ptySession, magicTy
|
||||
session.DisablePTYEmulation()
|
||||
|
||||
if isLoginShell(session.RawCommand()) {
|
||||
serviceBanner := s.config.ServiceBanner()
|
||||
if serviceBanner != nil {
|
||||
err := showServiceBanner(session, serviceBanner)
|
||||
banners := s.config.NotificationBanners()
|
||||
if banners != nil {
|
||||
for _, banner := range *banners {
|
||||
err := showNotificationBanner(session, banner)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "agent failed to show service banner", slog.Error(err))
|
||||
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "service_banner").Add(1)
|
||||
s.metrics.sessionErrors.WithLabelValues(magicTypeLabel, "yes", "notification_banner").Add(1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -891,9 +894,9 @@ func isQuietLogin(fs afero.Fs, rawCommand string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// showServiceBanner will write the service banner if enabled and not blank
|
||||
// showNotificationBanner will write the service banner if enabled and not blank
|
||||
// along with a blank line for spacing.
|
||||
func showServiceBanner(session io.Writer, banner *codersdk.ServiceBannerConfig) error {
|
||||
func showNotificationBanner(session io.Writer, banner codersdk.BannerConfig) error {
|
||||
if banner.Enabled && banner.Message != "" {
|
||||
// The banner supports Markdown so we might want to parse it but Markdown is
|
||||
// still fairly readable in its raw form.
|
||||
|
@@ -138,8 +138,8 @@ func (c *Client) GetStartupLogs() []agentsdk.Log {
|
||||
return c.logs
|
||||
}
|
||||
|
||||
func (c *Client) SetServiceBannerFunc(f func() (codersdk.ServiceBannerConfig, error)) {
|
||||
c.fakeAgentAPI.SetServiceBannerFunc(f)
|
||||
func (c *Client) SetNotificationBannersFunc(f func() ([]codersdk.ServiceBannerConfig, error)) {
|
||||
c.fakeAgentAPI.SetNotificationBannersFunc(f)
|
||||
}
|
||||
|
||||
func (c *Client) PushDERPMapUpdate(update *tailcfg.DERPMap) error {
|
||||
@@ -171,31 +171,39 @@ type FakeAgentAPI struct {
|
||||
lifecycleStates []codersdk.WorkspaceAgentLifecycle
|
||||
metadata map[string]agentsdk.Metadata
|
||||
|
||||
getServiceBannerFunc func() (codersdk.ServiceBannerConfig, error)
|
||||
getNotificationBannersFunc func() ([]codersdk.BannerConfig, error)
|
||||
}
|
||||
|
||||
func (f *FakeAgentAPI) GetManifest(context.Context, *agentproto.GetManifestRequest) (*agentproto.Manifest, error) {
|
||||
return f.manifest, nil
|
||||
}
|
||||
|
||||
func (f *FakeAgentAPI) SetServiceBannerFunc(fn func() (codersdk.ServiceBannerConfig, error)) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.getServiceBannerFunc = fn
|
||||
f.logger.Info(context.Background(), "updated ServiceBannerFunc")
|
||||
}
|
||||
|
||||
func (f *FakeAgentAPI) GetServiceBanner(context.Context, *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
if f.getServiceBannerFunc == nil {
|
||||
func (*FakeAgentAPI) GetServiceBanner(context.Context, *agentproto.GetServiceBannerRequest) (*agentproto.ServiceBanner, error) {
|
||||
return &agentproto.ServiceBanner{}, nil
|
||||
}
|
||||
sb, err := f.getServiceBannerFunc()
|
||||
|
||||
func (f *FakeAgentAPI) SetNotificationBannersFunc(fn func() ([]codersdk.BannerConfig, error)) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.getNotificationBannersFunc = fn
|
||||
f.logger.Info(context.Background(), "updated notification banners")
|
||||
}
|
||||
|
||||
func (f *FakeAgentAPI) GetNotificationBanners(context.Context, *agentproto.GetNotificationBannersRequest) (*agentproto.GetNotificationBannersResponse, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
if f.getNotificationBannersFunc == nil {
|
||||
return &agentproto.GetNotificationBannersResponse{NotificationBanners: []*agentproto.BannerConfig{}}, nil
|
||||
}
|
||||
banners, err := f.getNotificationBannersFunc()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agentsdk.ProtoFromServiceBanner(sb), nil
|
||||
bannersProto := make([]*agentproto.BannerConfig, 0, len(banners))
|
||||
for _, banner := range banners {
|
||||
bannersProto = append(bannersProto, agentsdk.ProtoFromBannerConfig(banner))
|
||||
}
|
||||
return &agentproto.GetNotificationBannersResponse{NotificationBanners: bannersProto}, nil
|
||||
}
|
||||
|
||||
func (f *FakeAgentAPI) UpdateStats(ctx context.Context, req *agentproto.UpdateStatsRequest) (*agentproto.UpdateStatsResponse, error) {
|
||||
|
@@ -1859,6 +1859,154 @@ func (x *BatchCreateLogsResponse) GetLogLimitExceeded() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type GetNotificationBannersRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *GetNotificationBannersRequest) Reset() {
|
||||
*x = GetNotificationBannersRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[22]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetNotificationBannersRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetNotificationBannersRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetNotificationBannersRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[22]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetNotificationBannersRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetNotificationBannersRequest) Descriptor() ([]byte, []int) {
|
||||
return file_agent_proto_agent_proto_rawDescGZIP(), []int{22}
|
||||
}
|
||||
|
||||
type GetNotificationBannersResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
NotificationBanners []*BannerConfig `protobuf:"bytes,1,rep,name=notification_banners,json=notificationBanners,proto3" json:"notification_banners,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetNotificationBannersResponse) Reset() {
|
||||
*x = GetNotificationBannersResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetNotificationBannersResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetNotificationBannersResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetNotificationBannersResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[23]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetNotificationBannersResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetNotificationBannersResponse) Descriptor() ([]byte, []int) {
|
||||
return file_agent_proto_agent_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
func (x *GetNotificationBannersResponse) GetNotificationBanners() []*BannerConfig {
|
||||
if x != nil {
|
||||
return x.NotificationBanners
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type BannerConfig struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
|
||||
Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
|
||||
BackgroundColor string `protobuf:"bytes,3,opt,name=background_color,json=backgroundColor,proto3" json:"background_color,omitempty"`
|
||||
}
|
||||
|
||||
func (x *BannerConfig) Reset() {
|
||||
*x = BannerConfig{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[24]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *BannerConfig) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*BannerConfig) ProtoMessage() {}
|
||||
|
||||
func (x *BannerConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[24]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use BannerConfig.ProtoReflect.Descriptor instead.
|
||||
func (*BannerConfig) Descriptor() ([]byte, []int) {
|
||||
return file_agent_proto_agent_proto_rawDescGZIP(), []int{24}
|
||||
}
|
||||
|
||||
func (x *BannerConfig) GetEnabled() bool {
|
||||
if x != nil {
|
||||
return x.Enabled
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *BannerConfig) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *BannerConfig) GetBackgroundColor() string {
|
||||
if x != nil {
|
||||
return x.BackgroundColor
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type WorkspaceApp_Healthcheck struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -1872,7 +2020,7 @@ type WorkspaceApp_Healthcheck struct {
|
||||
func (x *WorkspaceApp_Healthcheck) Reset() {
|
||||
*x = WorkspaceApp_Healthcheck{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[22]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[25]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1885,7 +2033,7 @@ func (x *WorkspaceApp_Healthcheck) String() string {
|
||||
func (*WorkspaceApp_Healthcheck) ProtoMessage() {}
|
||||
|
||||
func (x *WorkspaceApp_Healthcheck) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[22]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[25]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -1936,7 +2084,7 @@ type WorkspaceAgentMetadata_Result struct {
|
||||
func (x *WorkspaceAgentMetadata_Result) Reset() {
|
||||
*x = WorkspaceAgentMetadata_Result{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[23]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[26]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -1949,7 +2097,7 @@ func (x *WorkspaceAgentMetadata_Result) String() string {
|
||||
func (*WorkspaceAgentMetadata_Result) ProtoMessage() {}
|
||||
|
||||
func (x *WorkspaceAgentMetadata_Result) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[23]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[26]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2008,7 +2156,7 @@ type WorkspaceAgentMetadata_Description struct {
|
||||
func (x *WorkspaceAgentMetadata_Description) Reset() {
|
||||
*x = WorkspaceAgentMetadata_Description{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[24]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[27]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2021,7 +2169,7 @@ func (x *WorkspaceAgentMetadata_Description) String() string {
|
||||
func (*WorkspaceAgentMetadata_Description) ProtoMessage() {}
|
||||
|
||||
func (x *WorkspaceAgentMetadata_Description) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[24]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[27]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2086,7 +2234,7 @@ type Stats_Metric struct {
|
||||
func (x *Stats_Metric) Reset() {
|
||||
*x = Stats_Metric{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[27]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[30]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2099,7 +2247,7 @@ func (x *Stats_Metric) String() string {
|
||||
func (*Stats_Metric) ProtoMessage() {}
|
||||
|
||||
func (x *Stats_Metric) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[27]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[30]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2155,7 +2303,7 @@ type Stats_Metric_Label struct {
|
||||
func (x *Stats_Metric_Label) Reset() {
|
||||
*x = Stats_Metric_Label{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[28]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[31]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2168,7 +2316,7 @@ func (x *Stats_Metric_Label) String() string {
|
||||
func (*Stats_Metric_Label) ProtoMessage() {}
|
||||
|
||||
func (x *Stats_Metric_Label) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[28]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[31]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2210,7 +2358,7 @@ type BatchUpdateAppHealthRequest_HealthUpdate struct {
|
||||
func (x *BatchUpdateAppHealthRequest_HealthUpdate) Reset() {
|
||||
*x = BatchUpdateAppHealthRequest_HealthUpdate{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[29]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[32]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -2223,7 +2371,7 @@ func (x *BatchUpdateAppHealthRequest_HealthUpdate) String() string {
|
||||
func (*BatchUpdateAppHealthRequest_HealthUpdate) ProtoMessage() {}
|
||||
|
||||
func (x *BatchUpdateAppHealthRequest_HealthUpdate) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[29]
|
||||
mi := &file_agent_proto_agent_proto_msgTypes[32]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -2594,64 +2742,87 @@ var file_agent_proto_agent_proto_rawDesc = []byte{
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, 0x69,
|
||||
0x6d, 0x69, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01,
|
||||
0x28, 0x08, 0x52, 0x10, 0x6c, 0x6f, 0x67, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65,
|
||||
0x65, 0x64, 0x65, 0x64, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74,
|
||||
0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x5f,
|
||||
0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0c, 0x0a,
|
||||
0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49,
|
||||
0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0b, 0x0a,
|
||||
0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e,
|
||||
0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xf6, 0x05, 0x0a, 0x05, 0x41, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
|
||||
0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74,
|
||||
0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61,
|
||||
0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
|
||||
0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e,
|
||||
0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53,
|
||||
0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x56, 0x0a, 0x0b,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f,
|
||||
0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
|
||||
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c, 0x69,
|
||||
0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e,
|
||||
0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4c,
|
||||
0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
||||
0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
|
||||
0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a, 0x15, 0x42, 0x61,
|
||||
0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c,
|
||||
0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x65, 0x64, 0x65, 0x64, 0x22, 0x1f, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x71, 0x0a, 0x1e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4f, 0x0a, 0x14, 0x6e, 0x6f, 0x74, 0x69, 0x66,
|
||||
0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18,
|
||||
0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x43, 0x6f, 0x6e,
|
||||
0x66, 0x69, 0x67, 0x52, 0x13, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x22, 0x6d, 0x0a, 0x0c, 0x42, 0x61, 0x6e, 0x6e,
|
||||
0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62,
|
||||
0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
|
||||
0x65, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x29, 0x0a, 0x10,
|
||||
0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6f, 0x72,
|
||||
0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x63, 0x6b, 0x67, 0x72, 0x6f, 0x75,
|
||||
0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x2a, 0x63, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x48, 0x65,
|
||||
0x61, 0x6c, 0x74, 0x68, 0x12, 0x1a, 0x0a, 0x16, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x45, 0x41, 0x4c,
|
||||
0x54, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00,
|
||||
0x12, 0x0c, 0x0a, 0x08, 0x44, 0x49, 0x53, 0x41, 0x42, 0x4c, 0x45, 0x44, 0x10, 0x01, 0x12, 0x10,
|
||||
0x0a, 0x0c, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x49, 0x5a, 0x49, 0x4e, 0x47, 0x10, 0x02,
|
||||
0x12, 0x0b, 0x0a, 0x07, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x03, 0x12, 0x0d, 0x0a,
|
||||
0x09, 0x55, 0x4e, 0x48, 0x45, 0x41, 0x4c, 0x54, 0x48, 0x59, 0x10, 0x04, 0x32, 0xef, 0x06, 0x0a,
|
||||
0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x4b, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e,
|
||||
0x69, 0x66, 0x65, 0x73, 0x74, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65,
|
||||
0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x63, 0x6f, 0x64, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66,
|
||||
0x65, 0x73, 0x74, 0x12, 0x5a, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e,
|
||||
0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x1d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76,
|
||||
0x32, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12,
|
||||
0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x22,
|
||||
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e,
|
||||
0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x54, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64,
|
||||
0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||
0x73, 0x74, 0x1a, 0x19, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x69, 0x66, 0x65, 0x63, 0x79, 0x63, 0x6c, 0x65, 0x12, 0x72, 0x0a,
|
||||
0x15, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48,
|
||||
0x65, 0x61, 0x6c, 0x74, 0x68, 0x73, 0x12, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61,
|
||||
0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64,
|
||||
0x61, 0x74, 0x65, 0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75,
|
||||
0x65, 0x73, 0x74, 0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x2c, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76,
|
||||
0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x70, 0x70,
|
||||
0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4e,
|
||||
0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12,
|
||||
0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
|
||||
0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x6e,
|
||||
0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74,
|
||||
0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67,
|
||||
0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61,
|
||||
0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65,
|
||||
0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62,
|
||||
0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67,
|
||||
0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e,
|
||||
0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f,
|
||||
0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x6f, 0x64, 0x65,
|
||||
0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68,
|
||||
0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
|
||||
0x73, 0x65, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f,
|
||||
0x61, 0x67, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
|
||||
0x74, 0x6f, 0x33,
|
||||
0x41, 0x70, 0x70, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x4e, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74,
|
||||
0x75, 0x70, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74,
|
||||
0x2e, 0x76, 0x32, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
|
||||
0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75,
|
||||
0x70, 0x12, 0x6e, 0x0a, 0x13, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x2a, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72,
|
||||
0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55,
|
||||
0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x55, 0x70, 0x64, 0x61, 0x74,
|
||||
0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||
0x65, 0x12, 0x62, 0x0a, 0x0f, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
||||
0x4c, 0x6f, 0x67, 0x73, 0x12, 0x26, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65,
|
||||
0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||
0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63,
|
||||
0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61,
|
||||
0x74, 0x63, 0x68, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73,
|
||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x77, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69,
|
||||
0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x12,
|
||||
0x2d, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32,
|
||||
0x2e, 0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x42, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e,
|
||||
0x2e, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2e, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x76, 0x32, 0x2e,
|
||||
0x47, 0x65, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42,
|
||||
0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x27,
|
||||
0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64,
|
||||
0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x67, 0x65, 0x6e,
|
||||
0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -2667,7 +2838,7 @@ func file_agent_proto_agent_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_agent_proto_agent_proto_enumTypes = make([]protoimpl.EnumInfo, 7)
|
||||
var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 30)
|
||||
var file_agent_proto_agent_proto_msgTypes = make([]protoimpl.MessageInfo, 33)
|
||||
var file_agent_proto_agent_proto_goTypes = []interface{}{
|
||||
(AppHealth)(0), // 0: coder.agent.v2.AppHealth
|
||||
(WorkspaceApp_SharingLevel)(0), // 1: coder.agent.v2.WorkspaceApp.SharingLevel
|
||||
@@ -2698,73 +2869,79 @@ var file_agent_proto_agent_proto_goTypes = []interface{}{
|
||||
(*Log)(nil), // 26: coder.agent.v2.Log
|
||||
(*BatchCreateLogsRequest)(nil), // 27: coder.agent.v2.BatchCreateLogsRequest
|
||||
(*BatchCreateLogsResponse)(nil), // 28: coder.agent.v2.BatchCreateLogsResponse
|
||||
(*WorkspaceApp_Healthcheck)(nil), // 29: coder.agent.v2.WorkspaceApp.Healthcheck
|
||||
(*WorkspaceAgentMetadata_Result)(nil), // 30: coder.agent.v2.WorkspaceAgentMetadata.Result
|
||||
(*WorkspaceAgentMetadata_Description)(nil), // 31: coder.agent.v2.WorkspaceAgentMetadata.Description
|
||||
nil, // 32: coder.agent.v2.Manifest.EnvironmentVariablesEntry
|
||||
nil, // 33: coder.agent.v2.Stats.ConnectionsByProtoEntry
|
||||
(*Stats_Metric)(nil), // 34: coder.agent.v2.Stats.Metric
|
||||
(*Stats_Metric_Label)(nil), // 35: coder.agent.v2.Stats.Metric.Label
|
||||
(*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 36: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
|
||||
(*durationpb.Duration)(nil), // 37: google.protobuf.Duration
|
||||
(*proto.DERPMap)(nil), // 38: coder.tailnet.v2.DERPMap
|
||||
(*timestamppb.Timestamp)(nil), // 39: google.protobuf.Timestamp
|
||||
(*GetNotificationBannersRequest)(nil), // 29: coder.agent.v2.GetNotificationBannersRequest
|
||||
(*GetNotificationBannersResponse)(nil), // 30: coder.agent.v2.GetNotificationBannersResponse
|
||||
(*BannerConfig)(nil), // 31: coder.agent.v2.BannerConfig
|
||||
(*WorkspaceApp_Healthcheck)(nil), // 32: coder.agent.v2.WorkspaceApp.Healthcheck
|
||||
(*WorkspaceAgentMetadata_Result)(nil), // 33: coder.agent.v2.WorkspaceAgentMetadata.Result
|
||||
(*WorkspaceAgentMetadata_Description)(nil), // 34: coder.agent.v2.WorkspaceAgentMetadata.Description
|
||||
nil, // 35: coder.agent.v2.Manifest.EnvironmentVariablesEntry
|
||||
nil, // 36: coder.agent.v2.Stats.ConnectionsByProtoEntry
|
||||
(*Stats_Metric)(nil), // 37: coder.agent.v2.Stats.Metric
|
||||
(*Stats_Metric_Label)(nil), // 38: coder.agent.v2.Stats.Metric.Label
|
||||
(*BatchUpdateAppHealthRequest_HealthUpdate)(nil), // 39: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
|
||||
(*durationpb.Duration)(nil), // 40: google.protobuf.Duration
|
||||
(*proto.DERPMap)(nil), // 41: coder.tailnet.v2.DERPMap
|
||||
(*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp
|
||||
}
|
||||
var file_agent_proto_agent_proto_depIdxs = []int32{
|
||||
1, // 0: coder.agent.v2.WorkspaceApp.sharing_level:type_name -> coder.agent.v2.WorkspaceApp.SharingLevel
|
||||
29, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck
|
||||
32, // 1: coder.agent.v2.WorkspaceApp.healthcheck:type_name -> coder.agent.v2.WorkspaceApp.Healthcheck
|
||||
2, // 2: coder.agent.v2.WorkspaceApp.health:type_name -> coder.agent.v2.WorkspaceApp.Health
|
||||
37, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration
|
||||
30, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
|
||||
31, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
|
||||
32, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry
|
||||
38, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap
|
||||
40, // 3: coder.agent.v2.WorkspaceAgentScript.timeout:type_name -> google.protobuf.Duration
|
||||
33, // 4: coder.agent.v2.WorkspaceAgentMetadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
|
||||
34, // 5: coder.agent.v2.WorkspaceAgentMetadata.description:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
|
||||
35, // 6: coder.agent.v2.Manifest.environment_variables:type_name -> coder.agent.v2.Manifest.EnvironmentVariablesEntry
|
||||
41, // 7: coder.agent.v2.Manifest.derp_map:type_name -> coder.tailnet.v2.DERPMap
|
||||
8, // 8: coder.agent.v2.Manifest.scripts:type_name -> coder.agent.v2.WorkspaceAgentScript
|
||||
7, // 9: coder.agent.v2.Manifest.apps:type_name -> coder.agent.v2.WorkspaceApp
|
||||
31, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
|
||||
33, // 11: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry
|
||||
34, // 12: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric
|
||||
34, // 10: coder.agent.v2.Manifest.metadata:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Description
|
||||
36, // 11: coder.agent.v2.Stats.connections_by_proto:type_name -> coder.agent.v2.Stats.ConnectionsByProtoEntry
|
||||
37, // 12: coder.agent.v2.Stats.metrics:type_name -> coder.agent.v2.Stats.Metric
|
||||
14, // 13: coder.agent.v2.UpdateStatsRequest.stats:type_name -> coder.agent.v2.Stats
|
||||
37, // 14: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration
|
||||
40, // 14: coder.agent.v2.UpdateStatsResponse.report_interval:type_name -> google.protobuf.Duration
|
||||
4, // 15: coder.agent.v2.Lifecycle.state:type_name -> coder.agent.v2.Lifecycle.State
|
||||
39, // 16: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp
|
||||
42, // 16: coder.agent.v2.Lifecycle.changed_at:type_name -> google.protobuf.Timestamp
|
||||
17, // 17: coder.agent.v2.UpdateLifecycleRequest.lifecycle:type_name -> coder.agent.v2.Lifecycle
|
||||
36, // 18: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
|
||||
39, // 18: coder.agent.v2.BatchUpdateAppHealthRequest.updates:type_name -> coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate
|
||||
5, // 19: coder.agent.v2.Startup.subsystems:type_name -> coder.agent.v2.Startup.Subsystem
|
||||
21, // 20: coder.agent.v2.UpdateStartupRequest.startup:type_name -> coder.agent.v2.Startup
|
||||
30, // 21: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
|
||||
33, // 21: coder.agent.v2.Metadata.result:type_name -> coder.agent.v2.WorkspaceAgentMetadata.Result
|
||||
23, // 22: coder.agent.v2.BatchUpdateMetadataRequest.metadata:type_name -> coder.agent.v2.Metadata
|
||||
39, // 23: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp
|
||||
42, // 23: coder.agent.v2.Log.created_at:type_name -> google.protobuf.Timestamp
|
||||
6, // 24: coder.agent.v2.Log.level:type_name -> coder.agent.v2.Log.Level
|
||||
26, // 25: coder.agent.v2.BatchCreateLogsRequest.logs:type_name -> coder.agent.v2.Log
|
||||
37, // 26: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration
|
||||
39, // 27: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp
|
||||
37, // 28: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration
|
||||
37, // 29: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration
|
||||
3, // 30: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type
|
||||
35, // 31: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label
|
||||
0, // 32: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth
|
||||
11, // 33: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest
|
||||
13, // 34: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest
|
||||
15, // 35: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest
|
||||
18, // 36: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest
|
||||
19, // 37: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest
|
||||
22, // 38: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest
|
||||
24, // 39: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest
|
||||
27, // 40: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest
|
||||
10, // 41: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest
|
||||
12, // 42: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner
|
||||
16, // 43: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse
|
||||
17, // 44: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle
|
||||
20, // 45: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse
|
||||
21, // 46: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup
|
||||
25, // 47: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse
|
||||
28, // 48: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse
|
||||
41, // [41:49] is the sub-list for method output_type
|
||||
33, // [33:41] is the sub-list for method input_type
|
||||
33, // [33:33] is the sub-list for extension type_name
|
||||
33, // [33:33] is the sub-list for extension extendee
|
||||
0, // [0:33] is the sub-list for field type_name
|
||||
31, // 26: coder.agent.v2.GetNotificationBannersResponse.notification_banners:type_name -> coder.agent.v2.BannerConfig
|
||||
40, // 27: coder.agent.v2.WorkspaceApp.Healthcheck.interval:type_name -> google.protobuf.Duration
|
||||
42, // 28: coder.agent.v2.WorkspaceAgentMetadata.Result.collected_at:type_name -> google.protobuf.Timestamp
|
||||
40, // 29: coder.agent.v2.WorkspaceAgentMetadata.Description.interval:type_name -> google.protobuf.Duration
|
||||
40, // 30: coder.agent.v2.WorkspaceAgentMetadata.Description.timeout:type_name -> google.protobuf.Duration
|
||||
3, // 31: coder.agent.v2.Stats.Metric.type:type_name -> coder.agent.v2.Stats.Metric.Type
|
||||
38, // 32: coder.agent.v2.Stats.Metric.labels:type_name -> coder.agent.v2.Stats.Metric.Label
|
||||
0, // 33: coder.agent.v2.BatchUpdateAppHealthRequest.HealthUpdate.health:type_name -> coder.agent.v2.AppHealth
|
||||
11, // 34: coder.agent.v2.Agent.GetManifest:input_type -> coder.agent.v2.GetManifestRequest
|
||||
13, // 35: coder.agent.v2.Agent.GetServiceBanner:input_type -> coder.agent.v2.GetServiceBannerRequest
|
||||
15, // 36: coder.agent.v2.Agent.UpdateStats:input_type -> coder.agent.v2.UpdateStatsRequest
|
||||
18, // 37: coder.agent.v2.Agent.UpdateLifecycle:input_type -> coder.agent.v2.UpdateLifecycleRequest
|
||||
19, // 38: coder.agent.v2.Agent.BatchUpdateAppHealths:input_type -> coder.agent.v2.BatchUpdateAppHealthRequest
|
||||
22, // 39: coder.agent.v2.Agent.UpdateStartup:input_type -> coder.agent.v2.UpdateStartupRequest
|
||||
24, // 40: coder.agent.v2.Agent.BatchUpdateMetadata:input_type -> coder.agent.v2.BatchUpdateMetadataRequest
|
||||
27, // 41: coder.agent.v2.Agent.BatchCreateLogs:input_type -> coder.agent.v2.BatchCreateLogsRequest
|
||||
29, // 42: coder.agent.v2.Agent.GetNotificationBanners:input_type -> coder.agent.v2.GetNotificationBannersRequest
|
||||
10, // 43: coder.agent.v2.Agent.GetManifest:output_type -> coder.agent.v2.Manifest
|
||||
12, // 44: coder.agent.v2.Agent.GetServiceBanner:output_type -> coder.agent.v2.ServiceBanner
|
||||
16, // 45: coder.agent.v2.Agent.UpdateStats:output_type -> coder.agent.v2.UpdateStatsResponse
|
||||
17, // 46: coder.agent.v2.Agent.UpdateLifecycle:output_type -> coder.agent.v2.Lifecycle
|
||||
20, // 47: coder.agent.v2.Agent.BatchUpdateAppHealths:output_type -> coder.agent.v2.BatchUpdateAppHealthResponse
|
||||
21, // 48: coder.agent.v2.Agent.UpdateStartup:output_type -> coder.agent.v2.Startup
|
||||
25, // 49: coder.agent.v2.Agent.BatchUpdateMetadata:output_type -> coder.agent.v2.BatchUpdateMetadataResponse
|
||||
28, // 50: coder.agent.v2.Agent.BatchCreateLogs:output_type -> coder.agent.v2.BatchCreateLogsResponse
|
||||
30, // 51: coder.agent.v2.Agent.GetNotificationBanners:output_type -> coder.agent.v2.GetNotificationBannersResponse
|
||||
43, // [43:52] is the sub-list for method output_type
|
||||
34, // [34:43] is the sub-list for method input_type
|
||||
34, // [34:34] is the sub-list for extension type_name
|
||||
34, // [34:34] is the sub-list for extension extendee
|
||||
0, // [0:34] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_agent_proto_agent_proto_init() }
|
||||
@@ -3038,7 +3215,7 @@ func file_agent_proto_agent_proto_init() {
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WorkspaceApp_Healthcheck); i {
|
||||
switch v := v.(*GetNotificationBannersRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -3050,7 +3227,7 @@ func file_agent_proto_agent_proto_init() {
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WorkspaceAgentMetadata_Result); i {
|
||||
switch v := v.(*GetNotificationBannersResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -3062,7 +3239,31 @@ func file_agent_proto_agent_proto_init() {
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WorkspaceAgentMetadata_Description); i {
|
||||
switch v := v.(*BannerConfig); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WorkspaceApp_Healthcheck); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WorkspaceAgentMetadata_Result); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
@@ -3074,6 +3275,18 @@ func file_agent_proto_agent_proto_init() {
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*WorkspaceAgentMetadata_Description); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Stats_Metric); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -3085,7 +3298,7 @@ func file_agent_proto_agent_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_agent_proto_agent_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*Stats_Metric_Label); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -3097,7 +3310,7 @@ func file_agent_proto_agent_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_agent_proto_agent_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
|
||||
file_agent_proto_agent_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*BatchUpdateAppHealthRequest_HealthUpdate); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
@@ -3116,7 +3329,7 @@ func file_agent_proto_agent_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_agent_proto_agent_proto_rawDesc,
|
||||
NumEnums: 7,
|
||||
NumMessages: 30,
|
||||
NumMessages: 33,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
@@ -251,6 +251,18 @@ message BatchCreateLogsResponse {
|
||||
bool log_limit_exceeded = 1;
|
||||
}
|
||||
|
||||
message GetNotificationBannersRequest {}
|
||||
|
||||
message GetNotificationBannersResponse {
|
||||
repeated BannerConfig notification_banners = 1;
|
||||
}
|
||||
|
||||
message BannerConfig {
|
||||
bool enabled = 1;
|
||||
string message = 2;
|
||||
string background_color = 3;
|
||||
}
|
||||
|
||||
service Agent {
|
||||
rpc GetManifest(GetManifestRequest) returns (Manifest);
|
||||
rpc GetServiceBanner(GetServiceBannerRequest) returns (ServiceBanner);
|
||||
@@ -260,4 +272,5 @@ service Agent {
|
||||
rpc UpdateStartup(UpdateStartupRequest) returns (Startup);
|
||||
rpc BatchUpdateMetadata(BatchUpdateMetadataRequest) returns (BatchUpdateMetadataResponse);
|
||||
rpc BatchCreateLogs(BatchCreateLogsRequest) returns (BatchCreateLogsResponse);
|
||||
rpc GetNotificationBanners(GetNotificationBannersRequest) returns (GetNotificationBannersResponse);
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ type DRPCAgentClient interface {
|
||||
UpdateStartup(ctx context.Context, in *UpdateStartupRequest) (*Startup, error)
|
||||
BatchUpdateMetadata(ctx context.Context, in *BatchUpdateMetadataRequest) (*BatchUpdateMetadataResponse, error)
|
||||
BatchCreateLogs(ctx context.Context, in *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
|
||||
GetNotificationBanners(ctx context.Context, in *GetNotificationBannersRequest) (*GetNotificationBannersResponse, error)
|
||||
}
|
||||
|
||||
type drpcAgentClient struct {
|
||||
@@ -130,6 +131,15 @@ func (c *drpcAgentClient) BatchCreateLogs(ctx context.Context, in *BatchCreateLo
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *drpcAgentClient) GetNotificationBanners(ctx context.Context, in *GetNotificationBannersRequest) (*GetNotificationBannersResponse, error) {
|
||||
out := new(GetNotificationBannersResponse)
|
||||
err := c.cc.Invoke(ctx, "/coder.agent.v2.Agent/GetNotificationBanners", drpcEncoding_File_agent_proto_agent_proto{}, in, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type DRPCAgentServer interface {
|
||||
GetManifest(context.Context, *GetManifestRequest) (*Manifest, error)
|
||||
GetServiceBanner(context.Context, *GetServiceBannerRequest) (*ServiceBanner, error)
|
||||
@@ -139,6 +149,7 @@ type DRPCAgentServer interface {
|
||||
UpdateStartup(context.Context, *UpdateStartupRequest) (*Startup, error)
|
||||
BatchUpdateMetadata(context.Context, *BatchUpdateMetadataRequest) (*BatchUpdateMetadataResponse, error)
|
||||
BatchCreateLogs(context.Context, *BatchCreateLogsRequest) (*BatchCreateLogsResponse, error)
|
||||
GetNotificationBanners(context.Context, *GetNotificationBannersRequest) (*GetNotificationBannersResponse, error)
|
||||
}
|
||||
|
||||
type DRPCAgentUnimplementedServer struct{}
|
||||
@@ -175,9 +186,13 @@ func (s *DRPCAgentUnimplementedServer) BatchCreateLogs(context.Context, *BatchCr
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
func (s *DRPCAgentUnimplementedServer) GetNotificationBanners(context.Context, *GetNotificationBannersRequest) (*GetNotificationBannersResponse, error) {
|
||||
return nil, drpcerr.WithCode(errors.New("Unimplemented"), drpcerr.Unimplemented)
|
||||
}
|
||||
|
||||
type DRPCAgentDescription struct{}
|
||||
|
||||
func (DRPCAgentDescription) NumMethods() int { return 8 }
|
||||
func (DRPCAgentDescription) NumMethods() int { return 9 }
|
||||
|
||||
func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver, interface{}, bool) {
|
||||
switch n {
|
||||
@@ -253,6 +268,15 @@ func (DRPCAgentDescription) Method(n int) (string, drpc.Encoding, drpc.Receiver,
|
||||
in1.(*BatchCreateLogsRequest),
|
||||
)
|
||||
}, DRPCAgentServer.BatchCreateLogs, true
|
||||
case 8:
|
||||
return "/coder.agent.v2.Agent/GetNotificationBanners", drpcEncoding_File_agent_proto_agent_proto{},
|
||||
func(srv interface{}, ctx context.Context, in1, in2 interface{}) (drpc.Message, error) {
|
||||
return srv.(DRPCAgentServer).
|
||||
GetNotificationBanners(
|
||||
ctx,
|
||||
in1.(*GetNotificationBannersRequest),
|
||||
)
|
||||
}, DRPCAgentServer.GetNotificationBanners, true
|
||||
default:
|
||||
return "", nil, nil, nil, false
|
||||
}
|
||||
@@ -389,3 +413,19 @@ func (x *drpcAgent_BatchCreateLogsStream) SendAndClose(m *BatchCreateLogsRespons
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
||||
type DRPCAgent_GetNotificationBannersStream interface {
|
||||
drpc.Stream
|
||||
SendAndClose(*GetNotificationBannersResponse) error
|
||||
}
|
||||
|
||||
type drpcAgent_GetNotificationBannersStream struct {
|
||||
drpc.Stream
|
||||
}
|
||||
|
||||
func (x *drpcAgent_GetNotificationBannersStream) SendAndClose(m *GetNotificationBannersResponse) error {
|
||||
if err := x.MsgSend(m, drpcEncoding_File_agent_proto_agent_proto{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return x.CloseSend()
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ import (
|
||||
type API struct {
|
||||
opts Options
|
||||
*ManifestAPI
|
||||
*ServiceBannerAPI
|
||||
*NotificationBannerAPI
|
||||
*StatsAPI
|
||||
*LifecycleAPI
|
||||
*AppsAPI
|
||||
@@ -107,7 +107,7 @@ func New(opts Options) *API {
|
||||
},
|
||||
}
|
||||
|
||||
api.ServiceBannerAPI = &ServiceBannerAPI{
|
||||
api.NotificationBannerAPI = &NotificationBannerAPI{
|
||||
appearanceFetcher: opts.AppearanceFetcher,
|
||||
}
|
||||
|
||||
|
39
coderd/agentapi/notification_banners.go
Normal file
39
coderd/agentapi/notification_banners.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package agentapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
)
|
||||
|
||||
type NotificationBannerAPI struct {
|
||||
appearanceFetcher *atomic.Pointer[appearance.Fetcher]
|
||||
}
|
||||
|
||||
// Deprecated: GetServiceBanner has been deprecated in favor of GetNotificationBanners.
|
||||
func (a *NotificationBannerAPI) GetServiceBanner(ctx context.Context, _ *proto.GetServiceBannerRequest) (*proto.ServiceBanner, error) {
|
||||
cfg, err := (*a.appearanceFetcher.Load()).Fetch(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fetch appearance: %w", err)
|
||||
}
|
||||
return agentsdk.ProtoFromServiceBanner(cfg.ServiceBanner), nil
|
||||
}
|
||||
|
||||
func (a *NotificationBannerAPI) GetNotificationBanners(ctx context.Context, _ *proto.GetNotificationBannersRequest) (*proto.GetNotificationBannersResponse, error) {
|
||||
cfg, err := (*a.appearanceFetcher.Load()).Fetch(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fetch appearance: %w", err)
|
||||
}
|
||||
banners := make([]*proto.BannerConfig, 0, len(cfg.NotificationBanners))
|
||||
for _, banner := range cfg.NotificationBanners {
|
||||
banners = append(banners, agentsdk.ProtoFromBannerConfig(banner))
|
||||
}
|
||||
return &proto.GetNotificationBannersResponse{
|
||||
NotificationBanners: banners,
|
||||
}, nil
|
||||
}
|
@@ -11,36 +11,30 @@ import (
|
||||
agentproto "github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
)
|
||||
|
||||
func TestGetServiceBanner(t *testing.T) {
|
||||
func TestGetNotificationBanners(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("OK", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := codersdk.ServiceBannerConfig{
|
||||
cfg := []codersdk.BannerConfig{{
|
||||
Enabled: true,
|
||||
Message: "hello world",
|
||||
BackgroundColor: "#000000",
|
||||
}
|
||||
Message: "The beep-bop will be boop-beeped on Saturday at 12AM PST.",
|
||||
BackgroundColor: "#00FF00",
|
||||
}}
|
||||
|
||||
var ff appearance.Fetcher = fakeFetcher{cfg: codersdk.AppearanceConfig{ServiceBanner: cfg}}
|
||||
var ff appearance.Fetcher = fakeFetcher{cfg: codersdk.AppearanceConfig{NotificationBanners: cfg}}
|
||||
ptr := atomic.Pointer[appearance.Fetcher]{}
|
||||
ptr.Store(&ff)
|
||||
|
||||
api := &ServiceBannerAPI{
|
||||
appearanceFetcher: &ptr,
|
||||
}
|
||||
|
||||
resp, err := api.GetServiceBanner(context.Background(), &agentproto.GetServiceBannerRequest{})
|
||||
api := &NotificationBannerAPI{appearanceFetcher: &ptr}
|
||||
resp, err := api.GetNotificationBanners(context.Background(), &agentproto.GetNotificationBannersRequest{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, &agentproto.ServiceBanner{
|
||||
Enabled: cfg.Enabled,
|
||||
Message: cfg.Message,
|
||||
BackgroundColor: cfg.BackgroundColor,
|
||||
}, resp)
|
||||
require.Len(t, resp.NotificationBanners, 1)
|
||||
require.Equal(t, cfg[0], agentsdk.BannerConfigFromProto(resp.NotificationBanners[0]))
|
||||
})
|
||||
|
||||
t.Run("FetchError", func(t *testing.T) {
|
||||
@@ -51,11 +45,8 @@ func TestGetServiceBanner(t *testing.T) {
|
||||
ptr := atomic.Pointer[appearance.Fetcher]{}
|
||||
ptr.Store(&ff)
|
||||
|
||||
api := &ServiceBannerAPI{
|
||||
appearanceFetcher: &ptr,
|
||||
}
|
||||
|
||||
resp, err := api.GetServiceBanner(context.Background(), &agentproto.GetServiceBannerRequest{})
|
||||
api := &NotificationBannerAPI{appearanceFetcher: &ptr}
|
||||
resp, err := api.GetNotificationBanners(context.Background(), &agentproto.GetNotificationBannersRequest{})
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, expectedErr)
|
||||
require.Nil(t, resp)
|
@@ -1,24 +0,0 @@
|
||||
package agentapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/agent/proto"
|
||||
"github.com/coder/coder/v2/coderd/appearance"
|
||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||
)
|
||||
|
||||
type ServiceBannerAPI struct {
|
||||
appearanceFetcher *atomic.Pointer[appearance.Fetcher]
|
||||
}
|
||||
|
||||
func (a *ServiceBannerAPI) GetServiceBanner(ctx context.Context, _ *proto.GetServiceBannerRequest) (*proto.ServiceBanner, error) {
|
||||
cfg, err := (*a.appearanceFetcher.Load()).Fetch(ctx)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("fetch appearance: %w", err)
|
||||
}
|
||||
return agentsdk.ProtoFromServiceBanner(cfg.ServiceBanner), nil
|
||||
}
|
54
coderd/apidoc/docs.go
generated
54
coderd/apidoc/docs.go
generated
@@ -8272,8 +8272,19 @@ const docTemplate = `{
|
||||
"logo_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"notification_banners": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
"description": "Deprecated: ServiceBanner has been replaced by NotificationBanners.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"support_links": {
|
||||
"type": "array",
|
||||
@@ -8530,6 +8541,20 @@ const docTemplate = `{
|
||||
"AutomaticUpdatesNever"
|
||||
]
|
||||
},
|
||||
"codersdk.BannerConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background_color": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.BuildInfoResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11060,20 +11085,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ServiceBannerConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background_color": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.SessionCountDeploymentStats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -11906,8 +11917,19 @@ const docTemplate = `{
|
||||
"logo_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"notification_banners": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
"description": "Deprecated: ServiceBanner has been replaced by NotificationBanners.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
54
coderd/apidoc/swagger.json
generated
54
coderd/apidoc/swagger.json
generated
@@ -7341,8 +7341,19 @@
|
||||
"logo_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"notification_banners": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
"description": "Deprecated: ServiceBanner has been replaced by NotificationBanners.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
]
|
||||
},
|
||||
"support_links": {
|
||||
"type": "array",
|
||||
@@ -7588,6 +7599,20 @@
|
||||
"enum": ["always", "never"],
|
||||
"x-enum-varnames": ["AutomaticUpdatesAlways", "AutomaticUpdatesNever"]
|
||||
},
|
||||
"codersdk.BannerConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background_color": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.BuildInfoResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -9960,20 +9985,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.ServiceBannerConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background_color": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codersdk.SessionCountDeploymentStats": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -10763,8 +10774,19 @@
|
||||
"logo_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"notification_banners": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
},
|
||||
"service_banner": {
|
||||
"$ref": "#/definitions/codersdk.ServiceBannerConfig"
|
||||
"description": "Deprecated: ServiceBanner has been replaced by NotificationBanners.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/codersdk.BannerConfig"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -32,6 +32,7 @@ type AGPLFetcher struct{}
|
||||
|
||||
func (AGPLFetcher) Fetch(context.Context) (codersdk.AppearanceConfig, error) {
|
||||
return codersdk.AppearanceConfig{
|
||||
NotificationBanners: []codersdk.BannerConfig{},
|
||||
SupportLinks: DefaultSupportLinks,
|
||||
}, nil
|
||||
}
|
||||
|
@@ -1220,6 +1220,11 @@ func (q *querier) GetLogoURL(ctx context.Context) (string, error) {
|
||||
return q.db.GetLogoURL(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetNotificationBanners(ctx context.Context) (string, error) {
|
||||
// No authz checks
|
||||
return q.db.GetNotificationBanners(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderApp, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceOAuth2ProviderApp); err != nil {
|
||||
return database.OAuth2ProviderApp{}, err
|
||||
@@ -1454,11 +1459,6 @@ func (q *querier) GetReplicasUpdatedAfter(ctx context.Context, updatedAt time.Ti
|
||||
return q.db.GetReplicasUpdatedAfter(ctx, updatedAt)
|
||||
}
|
||||
|
||||
func (q *querier) GetServiceBanner(ctx context.Context) (string, error) {
|
||||
// No authz checks
|
||||
return q.db.GetServiceBanner(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetTailnetAgents(ctx context.Context, id uuid.UUID) ([]database.TailnetAgent, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return nil, err
|
||||
@@ -3364,6 +3364,13 @@ func (q *querier) UpsertLogoURL(ctx context.Context, value string) error {
|
||||
return q.db.UpsertLogoURL(ctx, value)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertNotificationBanners(ctx context.Context, value string) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpsertNotificationBanners(ctx, value)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertOAuthSigningKey(ctx context.Context, value string) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
@@ -3382,13 +3389,6 @@ func (q *querier) UpsertProvisionerDaemon(ctx context.Context, arg database.Upse
|
||||
return q.db.UpsertProvisionerDaemon(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertServiceBanner(ctx context.Context, value string) error {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil {
|
||||
return err
|
||||
}
|
||||
return q.db.UpsertServiceBanner(ctx, value)
|
||||
}
|
||||
|
||||
func (q *querier) UpsertTailnetAgent(ctx context.Context, arg database.UpsertTailnetAgentParams) (database.TailnetAgent, error) {
|
||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceTailnetCoordinator); err != nil {
|
||||
return database.TailnetAgent{}, err
|
||||
|
@@ -525,7 +525,7 @@ func (s *MethodTestSuite) TestLicense() {
|
||||
s.Run("UpsertLogoURL", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("UpsertServiceBanner", s.Subtest(func(db database.Store, check *expects) {
|
||||
s.Run("UpsertNotificationBanners", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate)
|
||||
}))
|
||||
s.Run("GetLicenseByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
@@ -556,8 +556,8 @@ func (s *MethodTestSuite) TestLicense() {
|
||||
require.NoError(s.T(), err)
|
||||
check.Args().Asserts().Returns("value")
|
||||
}))
|
||||
s.Run("GetServiceBanner", s.Subtest(func(db database.Store, check *expects) {
|
||||
err := db.UpsertServiceBanner(context.Background(), "value")
|
||||
s.Run("GetNotificationBanners", s.Subtest(func(db database.Store, check *expects) {
|
||||
err := db.UpsertNotificationBanners(context.Background(), "value")
|
||||
require.NoError(s.T(), err)
|
||||
check.Args().Asserts().Returns("value")
|
||||
}))
|
||||
|
@@ -185,7 +185,7 @@ type data struct {
|
||||
deploymentID string
|
||||
derpMeshKey string
|
||||
lastUpdateCheck []byte
|
||||
serviceBanner []byte
|
||||
notificationBanners []byte
|
||||
healthSettings []byte
|
||||
applicationName string
|
||||
logoURL string
|
||||
@@ -2488,6 +2488,17 @@ func (q *FakeQuerier) GetLogoURL(_ context.Context) (string, error) {
|
||||
return q.logoURL, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetNotificationBanners(_ context.Context) (string, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
if q.notificationBanners == nil {
|
||||
return "", sql.ErrNoRows
|
||||
}
|
||||
|
||||
return string(q.notificationBanners), nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetOAuth2ProviderAppByID(_ context.Context, id uuid.UUID) (database.OAuth2ProviderApp, error) {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@@ -3027,17 +3038,6 @@ func (q *FakeQuerier) GetReplicasUpdatedAfter(_ context.Context, updatedAt time.
|
||||
return replicas, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetServiceBanner(_ context.Context) (string, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
if q.serviceBanner == nil {
|
||||
return "", sql.ErrNoRows
|
||||
}
|
||||
|
||||
return string(q.serviceBanner), nil
|
||||
}
|
||||
|
||||
func (*FakeQuerier) GetTailnetAgents(context.Context, uuid.UUID) ([]database.TailnetAgent, error) {
|
||||
return nil, ErrUnimplemented
|
||||
}
|
||||
@@ -8251,6 +8251,14 @@ func (q *FakeQuerier) UpsertLogoURL(_ context.Context, data string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertNotificationBanners(_ context.Context, data string) error {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
q.notificationBanners = []byte(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertOAuthSigningKey(_ context.Context, value string) error {
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
@@ -8298,14 +8306,6 @@ func (q *FakeQuerier) UpsertProvisionerDaemon(_ context.Context, arg database.Up
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpsertServiceBanner(_ context.Context, data string) error {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
q.serviceBanner = []byte(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*FakeQuerier) UpsertTailnetAgent(context.Context, database.UpsertTailnetAgentParams) (database.TailnetAgent, error) {
|
||||
return database.TailnetAgent{}, ErrUnimplemented
|
||||
}
|
||||
|
@@ -646,6 +646,13 @@ func (m metricsStore) GetLogoURL(ctx context.Context) (string, error) {
|
||||
return url, err
|
||||
}
|
||||
|
||||
func (m metricsStore) GetNotificationBanners(ctx context.Context) (string, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetNotificationBanners(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetNotificationBanners").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (database.OAuth2ProviderApp, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetOAuth2ProviderAppByID(ctx, id)
|
||||
@@ -849,13 +856,6 @@ func (m metricsStore) GetReplicasUpdatedAfter(ctx context.Context, updatedAt tim
|
||||
return replicas, err
|
||||
}
|
||||
|
||||
func (m metricsStore) GetServiceBanner(ctx context.Context) (string, error) {
|
||||
start := time.Now()
|
||||
banner, err := m.s.GetServiceBanner(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetServiceBanner").Observe(time.Since(start).Seconds())
|
||||
return banner, err
|
||||
}
|
||||
|
||||
func (m metricsStore) GetTailnetAgents(ctx context.Context, id uuid.UUID) ([]database.TailnetAgent, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetTailnetAgents(ctx, id)
|
||||
@@ -2186,6 +2186,13 @@ func (m metricsStore) UpsertLogoURL(ctx context.Context, value string) error {
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertNotificationBanners(ctx context.Context, value string) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertNotificationBanners(ctx, value)
|
||||
m.queryLatencies.WithLabelValues("UpsertNotificationBanners").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertOAuthSigningKey(ctx context.Context, value string) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertOAuthSigningKey(ctx, value)
|
||||
@@ -2200,13 +2207,6 @@ func (m metricsStore) UpsertProvisionerDaemon(ctx context.Context, arg database.
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertServiceBanner(ctx context.Context, value string) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpsertServiceBanner(ctx, value)
|
||||
m.queryLatencies.WithLabelValues("UpsertServiceBanner").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) UpsertTailnetAgent(ctx context.Context, arg database.UpsertTailnetAgentParams) (database.TailnetAgent, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpsertTailnetAgent(ctx, arg)
|
||||
|
@@ -1275,6 +1275,21 @@ func (mr *MockStoreMockRecorder) GetLogoURL(arg0 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogoURL", reflect.TypeOf((*MockStore)(nil).GetLogoURL), arg0)
|
||||
}
|
||||
|
||||
// GetNotificationBanners mocks base method.
|
||||
func (m *MockStore) GetNotificationBanners(arg0 context.Context) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetNotificationBanners", arg0)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetNotificationBanners indicates an expected call of GetNotificationBanners.
|
||||
func (mr *MockStoreMockRecorder) GetNotificationBanners(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotificationBanners", reflect.TypeOf((*MockStore)(nil).GetNotificationBanners), arg0)
|
||||
}
|
||||
|
||||
// GetOAuth2ProviderAppByID mocks base method.
|
||||
func (m *MockStore) GetOAuth2ProviderAppByID(arg0 context.Context, arg1 uuid.UUID) (database.OAuth2ProviderApp, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1710,21 +1725,6 @@ func (mr *MockStoreMockRecorder) GetReplicasUpdatedAfter(arg0, arg1 any) *gomock
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReplicasUpdatedAfter", reflect.TypeOf((*MockStore)(nil).GetReplicasUpdatedAfter), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetServiceBanner mocks base method.
|
||||
func (m *MockStore) GetServiceBanner(arg0 context.Context) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetServiceBanner", arg0)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetServiceBanner indicates an expected call of GetServiceBanner.
|
||||
func (mr *MockStoreMockRecorder) GetServiceBanner(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetServiceBanner", reflect.TypeOf((*MockStore)(nil).GetServiceBanner), arg0)
|
||||
}
|
||||
|
||||
// GetTailnetAgents mocks base method.
|
||||
func (m *MockStore) GetTailnetAgents(arg0 context.Context, arg1 uuid.UUID) ([]database.TailnetAgent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4577,6 +4577,20 @@ func (mr *MockStoreMockRecorder) UpsertLogoURL(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertLogoURL", reflect.TypeOf((*MockStore)(nil).UpsertLogoURL), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertNotificationBanners mocks base method.
|
||||
func (m *MockStore) UpsertNotificationBanners(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertNotificationBanners", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpsertNotificationBanners indicates an expected call of UpsertNotificationBanners.
|
||||
func (mr *MockStoreMockRecorder) UpsertNotificationBanners(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertNotificationBanners", reflect.TypeOf((*MockStore)(nil).UpsertNotificationBanners), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertOAuthSigningKey mocks base method.
|
||||
func (m *MockStore) UpsertOAuthSigningKey(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4606,20 +4620,6 @@ func (mr *MockStoreMockRecorder) UpsertProvisionerDaemon(arg0, arg1 any) *gomock
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertProvisionerDaemon", reflect.TypeOf((*MockStore)(nil).UpsertProvisionerDaemon), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertServiceBanner mocks base method.
|
||||
func (m *MockStore) UpsertServiceBanner(arg0 context.Context, arg1 string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpsertServiceBanner", arg0, arg1)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpsertServiceBanner indicates an expected call of UpsertServiceBanner.
|
||||
func (mr *MockStoreMockRecorder) UpsertServiceBanner(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertServiceBanner", reflect.TypeOf((*MockStore)(nil).UpsertServiceBanner), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpsertTailnetAgent mocks base method.
|
||||
func (m *MockStore) UpsertTailnetAgent(arg0 context.Context, arg1 database.UpsertTailnetAgentParams) (database.TailnetAgent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
@@ -0,0 +1 @@
|
||||
delete from site_configs where key = 'notification_banners';
|
@@ -0,0 +1,4 @@
|
||||
update site_configs SET
|
||||
key = 'notification_banners',
|
||||
value = concat('[', value, ']')
|
||||
where key = 'service_banner';
|
@@ -135,6 +135,7 @@ type sqlcQuerier interface {
|
||||
GetLicenseByID(ctx context.Context, id int32) (License, error)
|
||||
GetLicenses(ctx context.Context) ([]License, error)
|
||||
GetLogoURL(ctx context.Context) (string, error)
|
||||
GetNotificationBanners(ctx context.Context) (string, error)
|
||||
GetOAuth2ProviderAppByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderApp, error)
|
||||
GetOAuth2ProviderAppCodeByID(ctx context.Context, id uuid.UUID) (OAuth2ProviderAppCode, error)
|
||||
GetOAuth2ProviderAppCodeByPrefix(ctx context.Context, secretPrefix []byte) (OAuth2ProviderAppCode, error)
|
||||
@@ -164,7 +165,6 @@ type sqlcQuerier interface {
|
||||
GetQuotaConsumedForUser(ctx context.Context, ownerID uuid.UUID) (int64, error)
|
||||
GetReplicaByID(ctx context.Context, id uuid.UUID) (Replica, error)
|
||||
GetReplicasUpdatedAfter(ctx context.Context, updatedAt time.Time) ([]Replica, error)
|
||||
GetServiceBanner(ctx context.Context) (string, error)
|
||||
GetTailnetAgents(ctx context.Context, id uuid.UUID) ([]TailnetAgent, error)
|
||||
GetTailnetClientsForAgent(ctx context.Context, agentID uuid.UUID) ([]TailnetClient, error)
|
||||
GetTailnetPeers(ctx context.Context, id uuid.UUID) ([]TailnetPeer, error)
|
||||
@@ -421,9 +421,9 @@ type sqlcQuerier interface {
|
||||
UpsertJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg UpsertJFrogXrayScanByWorkspaceAndAgentIDParams) error
|
||||
UpsertLastUpdateCheck(ctx context.Context, value string) error
|
||||
UpsertLogoURL(ctx context.Context, value string) error
|
||||
UpsertNotificationBanners(ctx context.Context, value string) error
|
||||
UpsertOAuthSigningKey(ctx context.Context, value string) error
|
||||
UpsertProvisionerDaemon(ctx context.Context, arg UpsertProvisionerDaemonParams) (ProvisionerDaemon, error)
|
||||
UpsertServiceBanner(ctx context.Context, value string) error
|
||||
UpsertTailnetAgent(ctx context.Context, arg UpsertTailnetAgentParams) (TailnetAgent, error)
|
||||
UpsertTailnetClient(ctx context.Context, arg UpsertTailnetClientParams) (TailnetClient, error)
|
||||
UpsertTailnetClientSubscription(ctx context.Context, arg UpsertTailnetClientSubscriptionParams) error
|
||||
|
@@ -5615,6 +5615,17 @@ func (q *sqlQuerier) GetLogoURL(ctx context.Context) (string, error) {
|
||||
return value, err
|
||||
}
|
||||
|
||||
const getNotificationBanners = `-- name: GetNotificationBanners :one
|
||||
SELECT value FROM site_configs WHERE key = 'notification_banners'
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetNotificationBanners(ctx context.Context) (string, error) {
|
||||
row := q.db.QueryRowContext(ctx, getNotificationBanners)
|
||||
var value string
|
||||
err := row.Scan(&value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
const getOAuthSigningKey = `-- name: GetOAuthSigningKey :one
|
||||
SELECT value FROM site_configs WHERE key = 'oauth_signing_key'
|
||||
`
|
||||
@@ -5626,17 +5637,6 @@ func (q *sqlQuerier) GetOAuthSigningKey(ctx context.Context) (string, error) {
|
||||
return value, err
|
||||
}
|
||||
|
||||
const getServiceBanner = `-- name: GetServiceBanner :one
|
||||
SELECT value FROM site_configs WHERE key = 'service_banner'
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetServiceBanner(ctx context.Context) (string, error) {
|
||||
row := q.db.QueryRowContext(ctx, getServiceBanner)
|
||||
var value string
|
||||
err := row.Scan(&value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
const insertDERPMeshKey = `-- name: InsertDERPMeshKey :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('derp_mesh_key', $1)
|
||||
`
|
||||
@@ -5728,6 +5728,16 @@ func (q *sqlQuerier) UpsertLogoURL(ctx context.Context, value string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertNotificationBanners = `-- name: UpsertNotificationBanners :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('notification_banners', $1)
|
||||
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'notification_banners'
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) UpsertNotificationBanners(ctx context.Context, value string) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertNotificationBanners, value)
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertOAuthSigningKey = `-- name: UpsertOAuthSigningKey :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('oauth_signing_key', $1)
|
||||
ON CONFLICT (key) DO UPDATE set value = $1 WHERE site_configs.key = 'oauth_signing_key'
|
||||
@@ -5738,16 +5748,6 @@ func (q *sqlQuerier) UpsertOAuthSigningKey(ctx context.Context, value string) er
|
||||
return err
|
||||
}
|
||||
|
||||
const upsertServiceBanner = `-- name: UpsertServiceBanner :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('service_banner', $1)
|
||||
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner'
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) UpsertServiceBanner(ctx context.Context, value string) error {
|
||||
_, err := q.db.ExecContext(ctx, upsertServiceBanner, value)
|
||||
return err
|
||||
}
|
||||
|
||||
const cleanTailnetCoordinators = `-- name: CleanTailnetCoordinators :exec
|
||||
DELETE
|
||||
FROM tailnet_coordinators
|
||||
|
@@ -36,12 +36,12 @@ ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'last_update
|
||||
-- name: GetLastUpdateCheck :one
|
||||
SELECT value FROM site_configs WHERE key = 'last_update_check';
|
||||
|
||||
-- name: UpsertServiceBanner :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('service_banner', $1)
|
||||
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'service_banner';
|
||||
-- name: UpsertNotificationBanners :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('notification_banners', $1)
|
||||
ON CONFLICT (key) DO UPDATE SET value = $1 WHERE site_configs.key = 'notification_banners';
|
||||
|
||||
-- name: GetServiceBanner :one
|
||||
SELECT value FROM site_configs WHERE key = 'service_banner';
|
||||
-- name: GetNotificationBanners :one
|
||||
SELECT value FROM site_configs WHERE key = 'notification_banners';
|
||||
|
||||
-- name: UpsertLogoURL :exec
|
||||
INSERT INTO site_configs (key, value) VALUES ('logo_url', $1)
|
||||
|
@@ -277,15 +277,15 @@ func ProtoFromApp(a codersdk.WorkspaceApp) (*proto.WorkspaceApp, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ServiceBannerFromProto(sbp *proto.ServiceBanner) codersdk.ServiceBannerConfig {
|
||||
return codersdk.ServiceBannerConfig{
|
||||
func ServiceBannerFromProto(sbp *proto.ServiceBanner) codersdk.BannerConfig {
|
||||
return codersdk.BannerConfig{
|
||||
Enabled: sbp.GetEnabled(),
|
||||
Message: sbp.GetMessage(),
|
||||
BackgroundColor: sbp.GetBackgroundColor(),
|
||||
}
|
||||
}
|
||||
|
||||
func ProtoFromServiceBanner(sb codersdk.ServiceBannerConfig) *proto.ServiceBanner {
|
||||
func ProtoFromServiceBanner(sb codersdk.BannerConfig) *proto.ServiceBanner {
|
||||
return &proto.ServiceBanner{
|
||||
Enabled: sb.Enabled,
|
||||
Message: sb.Message,
|
||||
@@ -293,6 +293,22 @@ func ProtoFromServiceBanner(sb codersdk.ServiceBannerConfig) *proto.ServiceBanne
|
||||
}
|
||||
}
|
||||
|
||||
func BannerConfigFromProto(sbp *proto.BannerConfig) codersdk.BannerConfig {
|
||||
return codersdk.BannerConfig{
|
||||
Enabled: sbp.GetEnabled(),
|
||||
Message: sbp.GetMessage(),
|
||||
BackgroundColor: sbp.GetBackgroundColor(),
|
||||
}
|
||||
}
|
||||
|
||||
func ProtoFromBannerConfig(sb codersdk.BannerConfig) *proto.BannerConfig {
|
||||
return &proto.BannerConfig{
|
||||
Enabled: sb.Enabled,
|
||||
Message: sb.Message,
|
||||
BackgroundColor: sb.BackgroundColor,
|
||||
}
|
||||
}
|
||||
|
||||
func ProtoFromSubsystems(ss []codersdk.AgentSubsystem) ([]proto.Startup_Subsystem, error) {
|
||||
ret := make([]proto.Startup_Subsystem, len(ss))
|
||||
for i, s := range ss {
|
||||
|
@@ -2102,17 +2102,24 @@ func (c *Client) DeploymentStats(ctx context.Context) (DeploymentStats, error) {
|
||||
type AppearanceConfig struct {
|
||||
ApplicationName string `json:"application_name"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
ServiceBanner ServiceBannerConfig `json:"service_banner"`
|
||||
// Deprecated: ServiceBanner has been replaced by NotificationBanners.
|
||||
ServiceBanner BannerConfig `json:"service_banner"`
|
||||
NotificationBanners []BannerConfig `json:"notification_banners"`
|
||||
SupportLinks []LinkConfig `json:"support_links,omitempty"`
|
||||
}
|
||||
|
||||
type UpdateAppearanceConfig struct {
|
||||
ApplicationName string `json:"application_name"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
ServiceBanner ServiceBannerConfig `json:"service_banner"`
|
||||
// Deprecated: ServiceBanner has been replaced by NotificationBanners.
|
||||
ServiceBanner BannerConfig `json:"service_banner"`
|
||||
NotificationBanners []BannerConfig `json:"notification_banners"`
|
||||
}
|
||||
|
||||
type ServiceBannerConfig struct {
|
||||
// Deprecated: ServiceBannerConfig has been renamed to BannerConfig.
|
||||
type ServiceBannerConfig = BannerConfig
|
||||
|
||||
type BannerConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Message string `json:"message,omitempty"`
|
||||
BackgroundColor string `json:"background_color,omitempty"`
|
||||
|
21
docs/api/enterprise.md
generated
21
docs/api/enterprise.md
generated
@@ -21,6 +21,13 @@ curl -X GET http://coder-server:8080/api/v2/appearance \
|
||||
{
|
||||
"application_name": "string",
|
||||
"logo_url": "string",
|
||||
"notification_banners": [
|
||||
{
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
],
|
||||
"service_banner": {
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
@@ -64,6 +71,13 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
|
||||
{
|
||||
"application_name": "string",
|
||||
"logo_url": "string",
|
||||
"notification_banners": [
|
||||
{
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
],
|
||||
"service_banner": {
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
@@ -86,6 +100,13 @@ curl -X PUT http://coder-server:8080/api/v2/appearance \
|
||||
{
|
||||
"application_name": "string",
|
||||
"logo_url": "string",
|
||||
"notification_banners": [
|
||||
{
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
],
|
||||
"service_banner": {
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
|
60
docs/api/schemas.md
generated
60
docs/api/schemas.md
generated
@@ -751,6 +751,13 @@
|
||||
{
|
||||
"application_name": "string",
|
||||
"logo_url": "string",
|
||||
"notification_banners": [
|
||||
{
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
],
|
||||
"service_banner": {
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
@@ -769,10 +776,11 @@
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ------------------------------------------------------------ | -------- | ------------ | ----------- |
|
||||
| ---------------------- | ------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------- |
|
||||
| `application_name` | string | false | | |
|
||||
| `logo_url` | string | false | | |
|
||||
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
|
||||
| `notification_banners` | array of [codersdk.BannerConfig](#codersdkbannerconfig) | false | | |
|
||||
| `service_banner` | [codersdk.BannerConfig](#codersdkbannerconfig) | false | | Deprecated: ServiceBanner has been replaced by NotificationBanners. |
|
||||
| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
|
||||
|
||||
## codersdk.ArchiveTemplateVersionsRequest
|
||||
@@ -1172,6 +1180,24 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| `always` |
|
||||
| `never` |
|
||||
|
||||
## codersdk.BannerConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ------- | -------- | ------------ | ----------- |
|
||||
| `background_color` | string | false | | |
|
||||
| `enabled` | boolean | false | | |
|
||||
| `message` | string | false | | |
|
||||
|
||||
## codersdk.BuildInfoResponse
|
||||
|
||||
```json
|
||||
@@ -4264,24 +4290,6 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `ssh_config_options` | object | false | | |
|
||||
| » `[any property]` | string | false | | |
|
||||
|
||||
## codersdk.ServiceBannerConfig
|
||||
|
||||
```json
|
||||
{
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ------- | -------- | ------------ | ----------- |
|
||||
| `background_color` | string | false | | |
|
||||
| `enabled` | boolean | false | | |
|
||||
| `message` | string | false | | |
|
||||
|
||||
## codersdk.SessionCountDeploymentStats
|
||||
|
||||
```json
|
||||
@@ -5174,6 +5182,13 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
{
|
||||
"application_name": "string",
|
||||
"logo_url": "string",
|
||||
"notification_banners": [
|
||||
{
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
"message": "string"
|
||||
}
|
||||
],
|
||||
"service_banner": {
|
||||
"background_color": "string",
|
||||
"enabled": true,
|
||||
@@ -5185,10 +5200,11 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
### Properties
|
||||
|
||||
| Name | Type | Required | Restrictions | Description |
|
||||
| ------------------ | ------------------------------------------------------------ | -------- | ------------ | ----------- |
|
||||
| ---------------------- | ------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------- |
|
||||
| `application_name` | string | false | | |
|
||||
| `logo_url` | string | false | | |
|
||||
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
|
||||
| `notification_banners` | array of [codersdk.BannerConfig](#codersdkbannerconfig) | false | | |
|
||||
| `service_banner` | [codersdk.BannerConfig](#codersdkbannerconfig) | false | | Deprecated: ServiceBanner has been replaced by NotificationBanners. |
|
||||
|
||||
## codersdk.UpdateCheckResponse
|
||||
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -53,9 +54,11 @@ func newAppearanceFetcher(store database.Store, links []codersdk.LinkConfig) agp
|
||||
|
||||
func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfig, error) {
|
||||
var eg errgroup.Group
|
||||
var applicationName string
|
||||
var logoURL string
|
||||
var serviceBannerJSON string
|
||||
var (
|
||||
applicationName string
|
||||
logoURL string
|
||||
notificationBannersJSON string
|
||||
)
|
||||
eg.Go(func() (err error) {
|
||||
applicationName, err = f.database.GetApplicationName(ctx)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
@@ -71,9 +74,9 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
|
||||
return nil
|
||||
})
|
||||
eg.Go(func() (err error) {
|
||||
serviceBannerJSON, err = f.database.GetServiceBanner(ctx)
|
||||
notificationBannersJSON, err = f.database.GetNotificationBanners(ctx)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return xerrors.Errorf("get service banner: %w", err)
|
||||
return xerrors.Errorf("get notification banners: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -85,19 +88,25 @@ func (f *appearanceFetcher) Fetch(ctx context.Context) (codersdk.AppearanceConfi
|
||||
cfg := codersdk.AppearanceConfig{
|
||||
ApplicationName: applicationName,
|
||||
LogoURL: logoURL,
|
||||
}
|
||||
if serviceBannerJSON != "" {
|
||||
err = json.Unmarshal([]byte(serviceBannerJSON), &cfg.ServiceBanner)
|
||||
if err != nil {
|
||||
return codersdk.AppearanceConfig{}, xerrors.Errorf(
|
||||
"unmarshal json: %w, raw: %s", err, serviceBannerJSON,
|
||||
)
|
||||
}
|
||||
NotificationBanners: []codersdk.BannerConfig{},
|
||||
SupportLinks: agpl.DefaultSupportLinks,
|
||||
}
|
||||
|
||||
if len(f.supportLinks) == 0 {
|
||||
cfg.SupportLinks = agpl.DefaultSupportLinks
|
||||
} else {
|
||||
if notificationBannersJSON != "" {
|
||||
err = json.Unmarshal([]byte(notificationBannersJSON), &cfg.NotificationBanners)
|
||||
if err != nil {
|
||||
return codersdk.AppearanceConfig{}, xerrors.Errorf(
|
||||
"unmarshal notification banners json: %w, raw: %s", err, notificationBannersJSON,
|
||||
)
|
||||
}
|
||||
|
||||
// Redundant, but improves compatibility with slightly mismatched agent versions.
|
||||
// Maybe we can remove this after a grace period? -Kayla, May 6th 2024
|
||||
if len(cfg.NotificationBanners) > 0 {
|
||||
cfg.ServiceBanner = cfg.NotificationBanners[0]
|
||||
}
|
||||
}
|
||||
if len(f.supportLinks) > 0 {
|
||||
cfg.SupportLinks = f.supportLinks
|
||||
}
|
||||
|
||||
@@ -139,29 +148,32 @@ func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if appearance.ServiceBanner.Enabled {
|
||||
if err := validateHexColor(appearance.ServiceBanner.BackgroundColor); err != nil {
|
||||
for _, banner := range appearance.NotificationBanners {
|
||||
if err := validateHexColor(banner.BackgroundColor); err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid color format",
|
||||
Message: fmt.Sprintf("Invalid color format: %q", banner.BackgroundColor),
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
serviceBannerJSON, err := json.Marshal(appearance.ServiceBanner)
|
||||
if appearance.NotificationBanners == nil {
|
||||
appearance.NotificationBanners = []codersdk.BannerConfig{}
|
||||
}
|
||||
notificationBannersJSON, err := json.Marshal(appearance.NotificationBanners)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Unable to marshal service banner",
|
||||
Message: "Unable to marshal notification banners",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = api.Database.UpsertServiceBanner(ctx, string(serviceBannerJSON))
|
||||
err = api.Database.UpsertNotificationBanners(ctx, string(notificationBannersJSON))
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Unable to set service banner",
|
||||
Message: "Unable to set notification banners",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
|
@@ -6,7 +6,6 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/agent/proto"
|
||||
@@ -56,7 +55,7 @@ func TestCustomLogoAndCompanyName(t *testing.T) {
|
||||
require.Equal(t, uac.LogoURL, got.LogoURL)
|
||||
}
|
||||
|
||||
func TestServiceBanners(t *testing.T) {
|
||||
func TestNotificationBanners(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("User", func(t *testing.T) {
|
||||
@@ -68,10 +67,10 @@ func TestServiceBanners(t *testing.T) {
|
||||
adminClient, adminUser := coderdenttest.New(t, &coderdenttest.Options{DontAddLicense: true})
|
||||
basicUserClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
|
||||
|
||||
// Even without a license, the banner should return as disabled.
|
||||
// Without a license, there should be no banners.
|
||||
sb, err := basicUserClient.Appearance(ctx)
|
||||
require.NoError(t, err)
|
||||
require.False(t, sb.ServiceBanner.Enabled)
|
||||
require.Empty(t, sb.NotificationBanners)
|
||||
|
||||
coderdenttest.AddLicense(t, adminClient, coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
@@ -82,43 +81,42 @@ func TestServiceBanners(t *testing.T) {
|
||||
// Default state
|
||||
sb, err = basicUserClient.Appearance(ctx)
|
||||
require.NoError(t, err)
|
||||
require.False(t, sb.ServiceBanner.Enabled)
|
||||
require.Empty(t, sb.NotificationBanners)
|
||||
|
||||
uac := codersdk.UpdateAppearanceConfig{
|
||||
ServiceBanner: sb.ServiceBanner,
|
||||
}
|
||||
// Regular user should be unable to set the banner
|
||||
uac.ServiceBanner.Enabled = true
|
||||
|
||||
uac := codersdk.UpdateAppearanceConfig{
|
||||
NotificationBanners: []codersdk.BannerConfig{{Enabled: true}},
|
||||
}
|
||||
err = basicUserClient.UpdateAppearance(ctx, uac)
|
||||
require.Error(t, err)
|
||||
var sdkError *codersdk.Error
|
||||
require.True(t, errors.As(err, &sdkError))
|
||||
require.ErrorAs(t, err, &sdkError)
|
||||
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
|
||||
|
||||
// But an admin can
|
||||
wantBanner := uac
|
||||
wantBanner.ServiceBanner.Enabled = true
|
||||
wantBanner.ServiceBanner.Message = "Hey"
|
||||
wantBanner.ServiceBanner.BackgroundColor = "#00FF00"
|
||||
wantBanner := codersdk.UpdateAppearanceConfig{
|
||||
NotificationBanners: []codersdk.BannerConfig{{
|
||||
Enabled: true,
|
||||
Message: "The beep-bop will be boop-beeped on Saturday at 12AM PST.",
|
||||
BackgroundColor: "#00FF00",
|
||||
}},
|
||||
}
|
||||
err = adminClient.UpdateAppearance(ctx, wantBanner)
|
||||
require.NoError(t, err)
|
||||
gotBanner, err := adminClient.Appearance(ctx) //nolint:gocritic // we should assert at least once that the owner can get the banner
|
||||
require.NoError(t, err)
|
||||
gotBanner.SupportLinks = nil // clean "support links" before comparison
|
||||
require.Equal(t, wantBanner.ServiceBanner, gotBanner.ServiceBanner)
|
||||
require.Equal(t, wantBanner.NotificationBanners, gotBanner.NotificationBanners)
|
||||
|
||||
// But even an admin can't give a bad color
|
||||
wantBanner.ServiceBanner.BackgroundColor = "#bad color"
|
||||
wantBanner.NotificationBanners[0].BackgroundColor = "#bad color"
|
||||
err = adminClient.UpdateAppearance(ctx, wantBanner)
|
||||
require.Error(t, err)
|
||||
|
||||
var sdkErr *codersdk.Error
|
||||
if assert.ErrorAs(t, err, &sdkErr) {
|
||||
assert.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
|
||||
assert.Contains(t, sdkErr.Message, "Invalid color format")
|
||||
assert.Contains(t, sdkErr.Detail, "expected # prefix and 6 characters")
|
||||
}
|
||||
require.ErrorAs(t, err, &sdkErr)
|
||||
require.Equal(t, http.StatusBadRequest, sdkErr.StatusCode())
|
||||
require.Contains(t, sdkErr.Message, "Invalid color format")
|
||||
require.Contains(t, sdkErr.Detail, "expected # prefix and 6 characters")
|
||||
})
|
||||
|
||||
t.Run("Agent", func(t *testing.T) {
|
||||
@@ -141,11 +139,11 @@ func TestServiceBanners(t *testing.T) {
|
||||
},
|
||||
})
|
||||
cfg := codersdk.UpdateAppearanceConfig{
|
||||
ServiceBanner: codersdk.ServiceBannerConfig{
|
||||
NotificationBanners: []codersdk.BannerConfig{{
|
||||
Enabled: true,
|
||||
Message: "Hey",
|
||||
Message: "The beep-bop will be boop-beeped on Saturday at 12AM PST.",
|
||||
BackgroundColor: "#00FF00",
|
||||
},
|
||||
}},
|
||||
}
|
||||
err := client.UpdateAppearance(ctx, cfg)
|
||||
require.NoError(t, err)
|
||||
@@ -157,34 +155,38 @@ func TestServiceBanners(t *testing.T) {
|
||||
|
||||
agentClient := agentsdk.New(client.URL)
|
||||
agentClient.SetSessionToken(r.AgentToken)
|
||||
banner := requireGetServiceBanner(ctx, t, agentClient)
|
||||
require.Equal(t, cfg.ServiceBanner, banner)
|
||||
banners := requireGetNotificationBanners(ctx, t, agentClient)
|
||||
require.Equal(t, cfg.NotificationBanners, banners)
|
||||
|
||||
// Create an AGPL Coderd against the same database
|
||||
agplClient := coderdtest.New(t, &coderdtest.Options{Database: store, Pubsub: ps})
|
||||
agplAgentClient := agentsdk.New(agplClient.URL)
|
||||
agplAgentClient.SetSessionToken(r.AgentToken)
|
||||
banner = requireGetServiceBanner(ctx, t, agplAgentClient)
|
||||
require.Equal(t, codersdk.ServiceBannerConfig{}, banner)
|
||||
banners = requireGetNotificationBanners(ctx, t, agplAgentClient)
|
||||
require.Equal(t, []codersdk.BannerConfig{}, banners)
|
||||
|
||||
// No license means no banner.
|
||||
err = client.DeleteLicense(ctx, lic.ID)
|
||||
require.NoError(t, err)
|
||||
banner = requireGetServiceBanner(ctx, t, agentClient)
|
||||
require.Equal(t, codersdk.ServiceBannerConfig{}, banner)
|
||||
banners = requireGetNotificationBanners(ctx, t, agentClient)
|
||||
require.Equal(t, []codersdk.BannerConfig{}, banners)
|
||||
})
|
||||
}
|
||||
|
||||
func requireGetServiceBanner(ctx context.Context, t *testing.T, client *agentsdk.Client) codersdk.ServiceBannerConfig {
|
||||
func requireGetNotificationBanners(ctx context.Context, t *testing.T, client *agentsdk.Client) []codersdk.BannerConfig {
|
||||
cc, err := client.ConnectRPC(ctx)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = cc.Close()
|
||||
}()
|
||||
aAPI := proto.NewDRPCAgentClient(cc)
|
||||
sbp, err := aAPI.GetServiceBanner(ctx, &proto.GetServiceBannerRequest{})
|
||||
bannersProto, err := aAPI.GetNotificationBanners(ctx, &proto.GetNotificationBannersRequest{})
|
||||
require.NoError(t, err)
|
||||
return agentsdk.ServiceBannerFromProto(sbp)
|
||||
banners := make([]codersdk.BannerConfig, 0, len(bannersProto.NotificationBanners))
|
||||
for _, bannerProto := range bannersProto.NotificationBanners {
|
||||
banners = append(banners, agentsdk.BannerConfigFromProto(bannerProto))
|
||||
}
|
||||
return banners
|
||||
}
|
||||
|
||||
func TestCustomSupportLinks(t *testing.T) {
|
||||
|
@@ -1357,6 +1357,7 @@ export const getAppearance = async (): Promise<TypesGen.AppearanceConfig> => {
|
||||
service_banner: {
|
||||
enabled: false,
|
||||
},
|
||||
notification_banners: [],
|
||||
};
|
||||
}
|
||||
throw ex;
|
||||
|
@@ -4,12 +4,12 @@ import type { AppearanceConfig } from "api/typesGenerated";
|
||||
import type { MetadataState } from "hooks/useEmbeddedMetadata";
|
||||
import { cachedQuery } from "./util";
|
||||
|
||||
const appearanceConfigKey = ["appearance"] as const;
|
||||
export const appearanceConfigKey = ["appearance"] as const;
|
||||
|
||||
export const appearance = (metadata: MetadataState<AppearanceConfig>) => {
|
||||
return cachedQuery({
|
||||
metadata,
|
||||
queryKey: ["appearance"],
|
||||
queryKey: appearanceConfigKey,
|
||||
queryFn: () => API.getAppearance(),
|
||||
});
|
||||
};
|
||||
|
13
site/src/api/typesGenerated.ts
generated
13
site/src/api/typesGenerated.ts
generated
@@ -48,7 +48,8 @@ export interface AppHostResponse {
|
||||
export interface AppearanceConfig {
|
||||
readonly application_name: string;
|
||||
readonly logo_url: string;
|
||||
readonly service_banner: ServiceBannerConfig;
|
||||
readonly service_banner: BannerConfig;
|
||||
readonly notification_banners: readonly BannerConfig[];
|
||||
readonly support_links?: readonly LinkConfig[];
|
||||
}
|
||||
|
||||
@@ -157,6 +158,13 @@ export interface AvailableExperiments {
|
||||
readonly safe: readonly Experiment[];
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface BannerConfig {
|
||||
readonly enabled: boolean;
|
||||
readonly message?: string;
|
||||
readonly background_color?: string;
|
||||
}
|
||||
|
||||
// From codersdk/deployment.go
|
||||
export interface BuildInfoResponse {
|
||||
readonly external_url: string;
|
||||
@@ -1281,7 +1289,8 @@ export interface UpdateActiveTemplateVersion {
|
||||
export interface UpdateAppearanceConfig {
|
||||
readonly application_name: string;
|
||||
readonly logo_url: string;
|
||||
readonly service_banner: ServiceBannerConfig;
|
||||
readonly service_banner: BannerConfig;
|
||||
readonly notification_banners: readonly BannerConfig[];
|
||||
}
|
||||
|
||||
// From codersdk/updatecheck.go
|
||||
|
@@ -7,7 +7,7 @@ import { Outlet } from "react-router-dom";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { LicenseBanner } from "modules/dashboard/LicenseBanner/LicenseBanner";
|
||||
import { ServiceBanner } from "modules/dashboard/ServiceBanner/ServiceBanner";
|
||||
import { NotificationBanners } from "modules/dashboard/NotificationBanners/NotificationBanners";
|
||||
import { dashboardContentBottomPadding } from "theme/constants";
|
||||
import { docs } from "utils/docs";
|
||||
import { DeploymentBanner } from "./DeploymentBanner/DeploymentBanner";
|
||||
@@ -21,8 +21,8 @@ export const DashboardLayout: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ServiceBanner />
|
||||
{canViewDeployment && <LicenseBanner />}
|
||||
<NotificationBanners />
|
||||
|
||||
<div
|
||||
css={{
|
||||
|
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
createContext,
|
||||
type FC,
|
||||
type PropsWithChildren,
|
||||
useCallback,
|
||||
useState,
|
||||
} from "react";
|
||||
import { createContext, type FC, type PropsWithChildren } from "react";
|
||||
import { useQuery } from "react-query";
|
||||
import { appearance } from "api/queries/appearance";
|
||||
import { entitlements } from "api/queries/entitlements";
|
||||
@@ -14,21 +8,13 @@ import type {
|
||||
Entitlements,
|
||||
Experiments,
|
||||
} from "api/typesGenerated";
|
||||
import { displayError } from "components/GlobalSnackbar/utils";
|
||||
import { Loader } from "components/Loader/Loader";
|
||||
import { useEmbeddedMetadata } from "hooks/useEmbeddedMetadata";
|
||||
import { hslToHex, isHexColor, isHslColor } from "utils/colors";
|
||||
|
||||
interface Appearance {
|
||||
config: AppearanceConfig;
|
||||
isPreview: boolean;
|
||||
setPreview: (config: AppearanceConfig) => void;
|
||||
}
|
||||
|
||||
export interface DashboardValue {
|
||||
entitlements: Entitlements;
|
||||
experiments: Experiments;
|
||||
appearance: Appearance;
|
||||
appearance: AppearanceConfig;
|
||||
}
|
||||
|
||||
export const DashboardContext = createContext<DashboardValue | undefined>(
|
||||
@@ -44,34 +30,6 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const isLoading =
|
||||
!entitlementsQuery.data || !appearanceQuery.data || !experimentsQuery.data;
|
||||
|
||||
const [configPreview, setConfigPreview] = useState<AppearanceConfig>();
|
||||
|
||||
// Centralizing the logic for catching malformed configs in one spot, just to
|
||||
// be on the safe side; don't want to expose raw setConfigPreview outside
|
||||
// the provider
|
||||
const setPreview = useCallback((newConfig: AppearanceConfig) => {
|
||||
// Have runtime safety nets in place, just because so much of the codebase
|
||||
// relies on HSL for formatting, but server expects hex values. Can't catch
|
||||
// color format mismatches at the type level
|
||||
const incomingBg = newConfig.service_banner.background_color;
|
||||
let configForDispatch = newConfig;
|
||||
|
||||
if (typeof incomingBg === "string" && isHslColor(incomingBg)) {
|
||||
configForDispatch = {
|
||||
...newConfig,
|
||||
service_banner: {
|
||||
...newConfig.service_banner,
|
||||
background_color: hslToHex(incomingBg),
|
||||
},
|
||||
};
|
||||
} else if (typeof incomingBg === "string" && !isHexColor(incomingBg)) {
|
||||
displayError(`The value ${incomingBg} is not a valid hex string`);
|
||||
return;
|
||||
}
|
||||
|
||||
setConfigPreview(configForDispatch);
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader fullscreen />;
|
||||
}
|
||||
@@ -81,11 +39,7 @@ export const DashboardProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
value={{
|
||||
entitlements: entitlementsQuery.data,
|
||||
experiments: experimentsQuery.data,
|
||||
appearance: {
|
||||
config: configPreview ?? appearanceQuery.data,
|
||||
setPreview: setPreview,
|
||||
isPreview: configPreview !== undefined,
|
||||
},
|
||||
appearance: appearanceQuery.data,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@@ -25,9 +25,9 @@ export const Navbar: FC = () => {
|
||||
return (
|
||||
<NavbarView
|
||||
user={me}
|
||||
logo_url={appearance.config.logo_url}
|
||||
logo_url={appearance.logo_url}
|
||||
buildInfo={buildInfoQuery.data}
|
||||
supportLinks={appearance.config.support_links}
|
||||
supportLinks={appearance.support_links}
|
||||
onSignOut={signOut}
|
||||
canViewAuditLog={canViewAuditLog}
|
||||
canViewDeployment={canViewDeployment}
|
||||
|
@@ -0,0 +1,24 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { NotificationBannerView } from "./NotificationBannerView";
|
||||
|
||||
const meta: Meta<typeof NotificationBannerView> = {
|
||||
title: "modules/dashboard/NotificationBannerView",
|
||||
component: NotificationBannerView,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof NotificationBannerView>;
|
||||
|
||||
export const Production: Story = {
|
||||
args: {
|
||||
message: "Unfortunately, there's a radio connected to my brain.",
|
||||
backgroundColor: "#ffaff3",
|
||||
},
|
||||
};
|
||||
|
||||
export const Preview: Story = {
|
||||
args: {
|
||||
message: "バアン バン バン バン バアン ブレイバアン!",
|
||||
backgroundColor: "#4cd473",
|
||||
},
|
||||
};
|
@@ -1,28 +1,30 @@
|
||||
import { css, type Interpolation, type Theme } from "@emotion/react";
|
||||
import type { FC } from "react";
|
||||
import { InlineMarkdown } from "components/Markdown/Markdown";
|
||||
import { Pill } from "components/Pill/Pill";
|
||||
import { readableForegroundColor } from "utils/colors";
|
||||
|
||||
export interface ServiceBannerViewProps {
|
||||
message: string;
|
||||
backgroundColor: string;
|
||||
isPreview: boolean;
|
||||
export interface NotificationBannerViewProps {
|
||||
message?: string;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export const ServiceBannerView: FC<ServiceBannerViewProps> = ({
|
||||
export const NotificationBannerView: FC<NotificationBannerViewProps> = ({
|
||||
message,
|
||||
backgroundColor,
|
||||
isPreview,
|
||||
}) => {
|
||||
if (!message || !backgroundColor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div css={[styles.banner, { backgroundColor }]} className="service-banner">
|
||||
{isPreview && <Pill type="info">Preview</Pill>}
|
||||
<div
|
||||
css={[
|
||||
styles.wrapper,
|
||||
{ color: readableForegroundColor(backgroundColor) },
|
||||
]}
|
||||
css={styles.banner}
|
||||
style={{ backgroundColor }}
|
||||
className="service-banner"
|
||||
>
|
||||
<div
|
||||
css={styles.wrapper}
|
||||
style={{ color: readableForegroundColor(backgroundColor) }}
|
||||
>
|
||||
<InlineMarkdown>{message}</InlineMarkdown>
|
||||
</div>
|
@@ -0,0 +1,28 @@
|
||||
import type { FC } from "react";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { NotificationBannerView } from "./NotificationBannerView";
|
||||
|
||||
export const NotificationBanners: FC = () => {
|
||||
const { appearance, entitlements } = useDashboard();
|
||||
const notificationBanners = appearance.notification_banners;
|
||||
|
||||
const isEntitled =
|
||||
entitlements.features.appearance.entitlement !== "not_entitled";
|
||||
if (!isEntitled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{notificationBanners
|
||||
.filter((banner) => banner.enabled)
|
||||
.map((banner) => (
|
||||
<NotificationBannerView
|
||||
key={banner.message}
|
||||
message={banner.message}
|
||||
backgroundColor={banner.background_color}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
@@ -1,21 +0,0 @@
|
||||
import type { FC } from "react";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
import { ServiceBannerView } from "./ServiceBannerView";
|
||||
|
||||
export const ServiceBanner: FC = () => {
|
||||
const { appearance } = useDashboard();
|
||||
const { message, background_color, enabled } =
|
||||
appearance.config.service_banner;
|
||||
|
||||
if (!enabled || message === undefined || background_color === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ServiceBannerView
|
||||
message={message}
|
||||
backgroundColor={background_color}
|
||||
isPreview={appearance.isPreview}
|
||||
/>
|
||||
);
|
||||
};
|
@@ -1,25 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ServiceBannerView } from "./ServiceBannerView";
|
||||
|
||||
const meta: Meta<typeof ServiceBannerView> = {
|
||||
title: "modules/dashboard/ServiceBannerView",
|
||||
component: ServiceBannerView,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ServiceBannerView>;
|
||||
|
||||
export const Production: Story = {
|
||||
args: {
|
||||
message: "weeeee",
|
||||
backgroundColor: "#FFFFFF",
|
||||
},
|
||||
};
|
||||
|
||||
export const Preview: Story = {
|
||||
args: {
|
||||
message: "weeeee",
|
||||
backgroundColor: "#000000",
|
||||
isPreview: true,
|
||||
},
|
||||
};
|
@@ -18,12 +18,6 @@ import {
|
||||
} from "testHelpers/entities";
|
||||
import { WorkspaceStatusBadge } from "./WorkspaceStatusBadge";
|
||||
|
||||
const MockedAppearance = {
|
||||
config: MockAppearanceConfig,
|
||||
isPreview: false,
|
||||
setPreview: () => {},
|
||||
};
|
||||
|
||||
const meta: Meta<typeof WorkspaceStatusBadge> = {
|
||||
title: "modules/workspaces/WorkspaceStatusBadge",
|
||||
component: WorkspaceStatusBadge,
|
||||
@@ -41,7 +35,7 @@ const meta: Meta<typeof WorkspaceStatusBadge> = {
|
||||
value={{
|
||||
entitlements: MockEntitlementsWithScheduling,
|
||||
experiments: MockExperiments,
|
||||
appearance: MockedAppearance,
|
||||
appearance: MockAppearanceConfig,
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
|
@@ -2,7 +2,7 @@ import type { FC } from "react";
|
||||
import { Helmet } from "react-helmet-async";
|
||||
import { useMutation, useQueryClient } from "react-query";
|
||||
import { getErrorMessage } from "api/errors";
|
||||
import { updateAppearance } from "api/queries/appearance";
|
||||
import { appearanceConfigKey, updateAppearance } from "api/queries/appearance";
|
||||
import type { UpdateAppearanceConfig } from "api/typesGenerated";
|
||||
import { displayError, displaySuccess } from "components/GlobalSnackbar/utils";
|
||||
import { useDashboard } from "modules/dashboard/useDashboard";
|
||||
@@ -20,16 +20,12 @@ const AppearanceSettingsPage: FC = () => {
|
||||
|
||||
const onSaveAppearance = async (
|
||||
newConfig: Partial<UpdateAppearanceConfig>,
|
||||
preview: boolean,
|
||||
) => {
|
||||
const newAppearance = { ...appearance.config, ...newConfig };
|
||||
if (preview) {
|
||||
appearance.setPreview(newAppearance);
|
||||
return;
|
||||
}
|
||||
const newAppearance = { ...appearance, ...newConfig };
|
||||
|
||||
try {
|
||||
await updateAppearanceMutation.mutateAsync(newAppearance);
|
||||
await queryClient.invalidateQueries(appearanceConfigKey);
|
||||
displaySuccess("Successfully updated appearance settings!");
|
||||
} catch (error) {
|
||||
displayError(
|
||||
@@ -45,7 +41,7 @@ const AppearanceSettingsPage: FC = () => {
|
||||
</Helmet>
|
||||
|
||||
<AppearanceSettingsPageView
|
||||
appearance={appearance.config}
|
||||
appearance={appearance}
|
||||
onSaveAppearance={onSaveAppearance}
|
||||
isEntitled={
|
||||
entitlements.features.appearance.entitlement !== "not_entitled"
|
||||
|
@@ -9,10 +9,17 @@ const meta: Meta<typeof AppearanceSettingsPageView> = {
|
||||
application_name: "Foobar",
|
||||
logo_url: "https://github.com/coder.png",
|
||||
service_banner: {
|
||||
enabled: true,
|
||||
message: "hello world",
|
||||
background_color: "white",
|
||||
enabled: false,
|
||||
message: "",
|
||||
background_color: "#00ff00",
|
||||
},
|
||||
notification_banners: [
|
||||
{
|
||||
enabled: true,
|
||||
message: "The beep-bop will be boop-beeped on Saturday at 12AM PST.",
|
||||
background_color: "#ffaff3",
|
||||
},
|
||||
],
|
||||
},
|
||||
isEntitled: false,
|
||||
},
|
||||
|
@@ -1,13 +1,8 @@
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Button from "@mui/material/Button";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import Link from "@mui/material/Link";
|
||||
import Switch from "@mui/material/Switch";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { useFormik } from "formik";
|
||||
import { type FC, useState } from "react";
|
||||
import { BlockPicker } from "react-color";
|
||||
import type { FC } from "react";
|
||||
import type { UpdateAppearanceConfig } from "api/typesGenerated";
|
||||
import {
|
||||
Badges,
|
||||
@@ -15,35 +10,29 @@ import {
|
||||
EnterpriseBadge,
|
||||
EntitledBadge,
|
||||
} from "components/Badges/Badges";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import colors from "theme/tailwindColors";
|
||||
import { getFormHelpers } from "utils/formUtils";
|
||||
import { Fieldset } from "../Fieldset";
|
||||
import { Header } from "../Header";
|
||||
import { NotificationBannerSettings } from "./NotificationBannerSettings";
|
||||
|
||||
export type AppearanceSettingsPageViewProps = {
|
||||
appearance: UpdateAppearanceConfig;
|
||||
isEntitled: boolean;
|
||||
onSaveAppearance: (
|
||||
newConfig: Partial<UpdateAppearanceConfig>,
|
||||
preview: boolean,
|
||||
) => void;
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
const fallbackBgColor = colors.neutral[500];
|
||||
|
||||
export const AppearanceSettingsPageView: FC<
|
||||
AppearanceSettingsPageViewProps
|
||||
> = ({ appearance, isEntitled, onSaveAppearance }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const applicationNameForm = useFormik<{
|
||||
application_name: string;
|
||||
}>({
|
||||
initialValues: {
|
||||
application_name: appearance.application_name,
|
||||
},
|
||||
onSubmit: (values) => onSaveAppearance(values, false),
|
||||
onSubmit: (values) => onSaveAppearance(values),
|
||||
});
|
||||
const applicationNameFieldHelpers = getFormHelpers(applicationNameForm);
|
||||
|
||||
@@ -53,33 +42,10 @@ export const AppearanceSettingsPageView: FC<
|
||||
initialValues: {
|
||||
logo_url: appearance.logo_url,
|
||||
},
|
||||
onSubmit: (values) => onSaveAppearance(values, false),
|
||||
onSubmit: (values) => onSaveAppearance(values),
|
||||
});
|
||||
const logoFieldHelpers = getFormHelpers(logoForm);
|
||||
|
||||
const serviceBannerForm = useFormik<UpdateAppearanceConfig["service_banner"]>(
|
||||
{
|
||||
initialValues: {
|
||||
message: appearance.service_banner.message,
|
||||
enabled: appearance.service_banner.enabled,
|
||||
background_color:
|
||||
appearance.service_banner.background_color ?? fallbackBgColor,
|
||||
},
|
||||
onSubmit: (values) =>
|
||||
onSaveAppearance(
|
||||
{
|
||||
service_banner: values,
|
||||
},
|
||||
false,
|
||||
),
|
||||
},
|
||||
);
|
||||
const serviceBannerFieldHelpers = getFormHelpers(serviceBannerForm);
|
||||
|
||||
const [backgroundColor, setBackgroundColor] = useState(
|
||||
serviceBannerForm.values.background_color,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
@@ -159,123 +125,13 @@ export const AppearanceSettingsPageView: FC<
|
||||
/>
|
||||
</Fieldset>
|
||||
|
||||
<Fieldset
|
||||
title="Service Banner"
|
||||
subtitle="Configure a banner that displays a message to all users."
|
||||
onSubmit={serviceBannerForm.handleSubmit}
|
||||
button={
|
||||
!isEntitled && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
onSaveAppearance(
|
||||
{
|
||||
service_banner: {
|
||||
message:
|
||||
"👋 **This** is a service banner. The banner's color and text are editable.",
|
||||
background_color: "#004852",
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
}}
|
||||
>
|
||||
Show Preview
|
||||
</Button>
|
||||
)
|
||||
<NotificationBannerSettings
|
||||
isEntitled={isEntitled}
|
||||
notificationBanners={appearance.notification_banners || []}
|
||||
onSubmit={(notificationBanners) =>
|
||||
onSaveAppearance({ notification_banners: notificationBanners })
|
||||
}
|
||||
validation={
|
||||
!isEntitled && (
|
||||
<p>
|
||||
Your license does not include Service Banners.{" "}
|
||||
<Link href="mailto:sales@coder.com">Contact sales</Link> to learn
|
||||
more.
|
||||
</p>
|
||||
)
|
||||
}
|
||||
>
|
||||
{isEntitled && (
|
||||
<Stack>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={serviceBannerForm.values.enabled}
|
||||
onChange={async () => {
|
||||
const newState = !serviceBannerForm.values.enabled;
|
||||
const newBanner = {
|
||||
...serviceBannerForm.values,
|
||||
enabled: newState,
|
||||
};
|
||||
onSaveAppearance(
|
||||
{
|
||||
service_banner: newBanner,
|
||||
},
|
||||
false,
|
||||
);
|
||||
await serviceBannerForm.setFieldValue("enabled", newState);
|
||||
}}
|
||||
data-testid="switch-service-banner"
|
||||
/>
|
||||
}
|
||||
label="Enabled"
|
||||
/>
|
||||
<Stack spacing={0}>
|
||||
<TextField
|
||||
{...serviceBannerFieldHelpers("message", {
|
||||
helperText:
|
||||
"Markdown bold, italics, and links are supported.",
|
||||
})}
|
||||
fullWidth
|
||||
label="Message"
|
||||
multiline
|
||||
inputProps={{
|
||||
"aria-label": "Message",
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack spacing={0}>
|
||||
<h3>{"Background Color"}</h3>
|
||||
<BlockPicker
|
||||
color={backgroundColor}
|
||||
onChange={async (color) => {
|
||||
setBackgroundColor(color.hex);
|
||||
await serviceBannerForm.setFieldValue(
|
||||
"background_color",
|
||||
color.hex,
|
||||
);
|
||||
onSaveAppearance(
|
||||
{
|
||||
service_banner: {
|
||||
...serviceBannerForm.values,
|
||||
background_color: color.hex,
|
||||
},
|
||||
},
|
||||
true,
|
||||
);
|
||||
}}
|
||||
triangle="hide"
|
||||
colors={["#004852", "#D65D0F", "#4CD473", "#D94A5D", "#5A00CF"]}
|
||||
styles={{
|
||||
default: {
|
||||
input: {
|
||||
color: "white",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
body: {
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "black",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
)}
|
||||
</Fieldset>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -0,0 +1,24 @@
|
||||
import { action } from "@storybook/addon-actions";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { NotificationBannerDialog } from "./NotificationBannerDialog";
|
||||
|
||||
const meta: Meta<typeof NotificationBannerDialog> = {
|
||||
title: "pages/DeploySettingsPage/NotificationBannerDialog",
|
||||
component: NotificationBannerDialog,
|
||||
args: {
|
||||
banner: {
|
||||
enabled: true,
|
||||
message: "The beep-bop will be boop-beeped on Saturday at 12AM PST.",
|
||||
background_color: "#ffaff3",
|
||||
},
|
||||
onCancel: action("onCancel"),
|
||||
onUpdate: () => Promise.resolve(void action("onUpdate")),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof NotificationBannerDialog>;
|
||||
|
||||
const Example: Story = {};
|
||||
|
||||
export { Example as NotificationBannerDialog };
|
@@ -0,0 +1,138 @@
|
||||
import { type Interpolation, type Theme, useTheme } from "@emotion/react";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { useFormik } from "formik";
|
||||
import type { FC } from "react";
|
||||
import { BlockPicker } from "react-color";
|
||||
import type { BannerConfig } from "api/typesGenerated";
|
||||
import { Dialog, DialogActionButtons } from "components/Dialogs/Dialog";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { NotificationBannerView } from "modules/dashboard/NotificationBanners/NotificationBannerView";
|
||||
import { getFormHelpers } from "utils/formUtils";
|
||||
|
||||
interface NotificationBannerDialogProps {
|
||||
banner: BannerConfig;
|
||||
onCancel: () => void;
|
||||
onUpdate: (banner: Partial<BannerConfig>) => Promise<void>;
|
||||
}
|
||||
|
||||
export const NotificationBannerDialog: FC<NotificationBannerDialogProps> = ({
|
||||
banner,
|
||||
onCancel,
|
||||
onUpdate,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const bannerForm = useFormik<{
|
||||
message: string;
|
||||
background_color: string;
|
||||
}>({
|
||||
initialValues: {
|
||||
message: banner.message ?? "",
|
||||
background_color: banner.background_color ?? "#004852",
|
||||
},
|
||||
onSubmit: (banner) => onUpdate(banner),
|
||||
});
|
||||
const bannerFieldHelpers = getFormHelpers(bannerForm);
|
||||
|
||||
return (
|
||||
<Dialog css={styles.dialogWrapper} open onClose={onCancel}>
|
||||
{/* Banner preview */}
|
||||
<div css={{ position: "fixed", top: 0, left: 0, right: 0 }}>
|
||||
<NotificationBannerView
|
||||
message={bannerForm.values.message}
|
||||
backgroundColor={bannerForm.values.background_color}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div css={styles.dialogContent}>
|
||||
<h3 css={styles.dialogTitle}>Notification banner</h3>
|
||||
<Stack>
|
||||
<div>
|
||||
<h4 css={styles.settingName}>Message</h4>
|
||||
<TextField
|
||||
{...bannerFieldHelpers("message", {
|
||||
helperText: "Markdown bold, italics, and links are supported.",
|
||||
})}
|
||||
fullWidth
|
||||
inputProps={{
|
||||
"aria-label": "Message",
|
||||
placeholder: "Enter a message for the banner",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h4 css={styles.settingName}>Background color</h4>
|
||||
<BlockPicker
|
||||
color={bannerForm.values.background_color}
|
||||
onChange={async (color) => {
|
||||
await bannerForm.setFieldValue("background_color", color.hex);
|
||||
}}
|
||||
triangle="hide"
|
||||
colors={["#004852", "#D65D0F", "#4CD473", "#D94A5D", "#5A00CF"]}
|
||||
styles={{
|
||||
default: {
|
||||
input: {
|
||||
color: "white",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
body: {
|
||||
backgroundColor: "black",
|
||||
color: "white",
|
||||
},
|
||||
card: {
|
||||
backgroundColor: "black",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
|
||||
<DialogActions>
|
||||
<DialogActionButtons
|
||||
cancelText="Cancel"
|
||||
confirmLoading={bannerForm.isSubmitting}
|
||||
confirmText="Update"
|
||||
disabled={bannerForm.isSubmitting}
|
||||
onCancel={onCancel}
|
||||
onConfirm={bannerForm.handleSubmit}
|
||||
/>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
dialogWrapper: (theme) => ({
|
||||
"& .MuiPaper-root": {
|
||||
background: theme.palette.background.paper,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
width: "100%",
|
||||
maxWidth: 500,
|
||||
},
|
||||
"& .MuiDialogActions-spacing": {
|
||||
padding: "0 40px 40px",
|
||||
},
|
||||
}),
|
||||
dialogContent: (theme) => ({
|
||||
color: theme.palette.text.secondary,
|
||||
padding: "40px 40px 20px",
|
||||
}),
|
||||
dialogTitle: (theme) => ({
|
||||
margin: 0,
|
||||
marginBottom: 16,
|
||||
color: theme.palette.text.primary,
|
||||
fontWeight: 400,
|
||||
fontSize: 20,
|
||||
}),
|
||||
settingName: (theme) => ({
|
||||
marginTop: 0,
|
||||
marginBottom: 8,
|
||||
color: theme.palette.text.primary,
|
||||
fontSize: 16,
|
||||
lineHeight: "150%",
|
||||
fontWeight: 600,
|
||||
}),
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
@@ -0,0 +1,77 @@
|
||||
import type { Interpolation, Theme } from "@emotion/react";
|
||||
import Checkbox from "@mui/material/Checkbox";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import type { FC } from "react";
|
||||
import type { BannerConfig } from "api/typesGenerated";
|
||||
import {
|
||||
MoreMenu,
|
||||
MoreMenuContent,
|
||||
MoreMenuItem,
|
||||
MoreMenuTrigger,
|
||||
ThreeDotsButton,
|
||||
} from "components/MoreMenu/MoreMenu";
|
||||
|
||||
interface NotificationBannerItemProps {
|
||||
enabled: boolean;
|
||||
backgroundColor?: string;
|
||||
message?: string;
|
||||
onUpdate: (banner: Partial<BannerConfig>) => Promise<void>;
|
||||
onEdit: () => void;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export const NotificationBannerItem: FC<NotificationBannerItemProps> = ({
|
||||
enabled,
|
||||
backgroundColor = "#004852",
|
||||
message,
|
||||
onUpdate,
|
||||
onEdit,
|
||||
onDelete,
|
||||
}) => {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={enabled}
|
||||
onClick={() => void onUpdate({ enabled: !enabled })}
|
||||
/>
|
||||
</TableCell>
|
||||
|
||||
<TableCell css={!enabled && styles.disabled}>
|
||||
{message || <em>No message</em>}
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<div css={styles.colorSample} style={{ backgroundColor }}></div>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<MoreMenu>
|
||||
<MoreMenuTrigger>
|
||||
<ThreeDotsButton />
|
||||
</MoreMenuTrigger>
|
||||
<MoreMenuContent>
|
||||
<MoreMenuItem onClick={() => onEdit()}>Edit…</MoreMenuItem>
|
||||
<MoreMenuItem onClick={() => onDelete()} danger>
|
||||
Delete…
|
||||
</MoreMenuItem>
|
||||
</MoreMenuContent>
|
||||
</MoreMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
disabled: (theme) => ({
|
||||
color: theme.roles.inactive.fill.outline,
|
||||
}),
|
||||
|
||||
colorSample: {
|
||||
width: 24,
|
||||
height: 24,
|
||||
borderRadius: 4,
|
||||
},
|
||||
} satisfies Record<string, Interpolation<Theme>>;
|
@@ -0,0 +1,202 @@
|
||||
import { type CSSObject, useTheme } from "@emotion/react";
|
||||
import AddIcon from "@mui/icons-material/AddOutlined";
|
||||
import Button from "@mui/material/Button";
|
||||
import Link from "@mui/material/Link";
|
||||
import Table from "@mui/material/Table";
|
||||
import TableBody from "@mui/material/TableBody";
|
||||
import TableCell from "@mui/material/TableCell";
|
||||
import TableContainer from "@mui/material/TableContainer";
|
||||
import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import { type FC, useState } from "react";
|
||||
import type { BannerConfig } from "api/typesGenerated";
|
||||
import { ConfirmDialog } from "components/Dialogs/ConfirmDialog/ConfirmDialog";
|
||||
import { EmptyState } from "components/EmptyState/EmptyState";
|
||||
import { Stack } from "components/Stack/Stack";
|
||||
import { NotificationBannerDialog } from "./NotificationBannerDialog";
|
||||
import { NotificationBannerItem } from "./NotificationBannerItem";
|
||||
|
||||
interface NotificationBannerSettingsProps {
|
||||
isEntitled: boolean;
|
||||
notificationBanners: readonly BannerConfig[];
|
||||
onSubmit: (banners: readonly BannerConfig[]) => Promise<void>;
|
||||
}
|
||||
|
||||
export const NotificationBannerSettings: FC<
|
||||
NotificationBannerSettingsProps
|
||||
> = ({ isEntitled, notificationBanners, onSubmit }) => {
|
||||
const theme = useTheme();
|
||||
const [banners, setBanners] = useState(notificationBanners);
|
||||
const [editingBannerId, setEditingBannerId] = useState<number | null>(null);
|
||||
const [deletingBannerId, setDeletingBannerId] = useState<number | null>(null);
|
||||
|
||||
const addBanner = () => {
|
||||
setBanners([
|
||||
...banners,
|
||||
{ enabled: true, message: "", background_color: "#004852" },
|
||||
]);
|
||||
setEditingBannerId(banners.length);
|
||||
};
|
||||
|
||||
const updateBanner = (i: number, banner: Partial<BannerConfig>) => {
|
||||
const newBanners = [...banners];
|
||||
newBanners[i] = { ...banners[i], ...banner };
|
||||
setBanners(newBanners);
|
||||
return newBanners;
|
||||
};
|
||||
|
||||
const removeBanner = (i: number) => {
|
||||
const newBanners = [...banners];
|
||||
newBanners.splice(i, 1);
|
||||
setBanners(newBanners);
|
||||
return newBanners;
|
||||
};
|
||||
|
||||
const editingBanner = editingBannerId !== null && banners[editingBannerId];
|
||||
const deletingBanner = deletingBannerId !== null && banners[deletingBannerId];
|
||||
|
||||
// If we're not editing a new banner, remove all empty banners. This makes canceling the
|
||||
// "new" dialog more intuitive, by not persisting an empty banner.
|
||||
if (editingBannerId === null && banners.some((banner) => !banner.message)) {
|
||||
setBanners(banners.filter((banner) => banner.message));
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
css={{
|
||||
borderRadius: 8,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
marginTop: 32,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
<div css={{ padding: "24px 24px 0" }}>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<h3
|
||||
css={{
|
||||
fontSize: 20,
|
||||
margin: 0,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Notification Banners
|
||||
</h3>
|
||||
<Button
|
||||
disabled={!isEntitled}
|
||||
onClick={() => addBanner()}
|
||||
startIcon={<AddIcon />}
|
||||
>
|
||||
New
|
||||
</Button>
|
||||
</Stack>
|
||||
<div
|
||||
css={{
|
||||
color: theme.palette.text.secondary,
|
||||
fontSize: 14,
|
||||
marginTop: 8,
|
||||
}}
|
||||
>
|
||||
Display message banners to all users.
|
||||
</div>
|
||||
|
||||
<div
|
||||
css={[
|
||||
theme.typography.body2 as CSSObject,
|
||||
{ paddingTop: 16, margin: "0 -32px" },
|
||||
]}
|
||||
>
|
||||
<TableContainer css={{ borderRadius: 0, borderBottom: "none" }}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="1%">Enabled</TableCell>
|
||||
<TableCell>Message</TableCell>
|
||||
<TableCell width="2%">Color</TableCell>
|
||||
<TableCell width="1%" />
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{!isEntitled || banners.length < 1 ? (
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
css={{ minHeight: 160 }}
|
||||
message="No notification banners"
|
||||
/>
|
||||
</TableCell>
|
||||
) : (
|
||||
banners.map((banner, i) => (
|
||||
<NotificationBannerItem
|
||||
key={banner.message}
|
||||
enabled={banner.enabled && Boolean(banner.message)}
|
||||
backgroundColor={banner.background_color}
|
||||
message={banner.message}
|
||||
onEdit={() => setEditingBannerId(i)}
|
||||
onUpdate={async (banner) => {
|
||||
const newBanners = updateBanner(i, banner);
|
||||
await onSubmit(newBanners);
|
||||
}}
|
||||
onDelete={() => setDeletingBannerId(i)}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isEntitled && (
|
||||
<footer
|
||||
css={[
|
||||
theme.typography.body2 as CSSObject,
|
||||
{
|
||||
background: theme.palette.background.paper,
|
||||
padding: "16px 24px",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<div css={{ color: theme.palette.text.secondary }}>
|
||||
<p>
|
||||
Your license does not include Service Banners.{" "}
|
||||
<Link href="mailto:sales@coder.com">Contact sales</Link> to
|
||||
learn more.
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{editingBanner && (
|
||||
<NotificationBannerDialog
|
||||
banner={editingBanner}
|
||||
onCancel={() => setEditingBannerId(null)}
|
||||
onUpdate={async (banner) => {
|
||||
const newBanners = updateBanner(editingBannerId, banner);
|
||||
setEditingBannerId(null);
|
||||
await onSubmit(newBanners);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{deletingBanner && (
|
||||
<ConfirmDialog
|
||||
type="delete"
|
||||
open
|
||||
title="Delete this banner?"
|
||||
description={deletingBanner.message}
|
||||
onClose={() => setDeletingBannerId(null)}
|
||||
onConfirm={async () => {
|
||||
const newBanners = removeBanner(deletingBannerId);
|
||||
setDeletingBannerId(null);
|
||||
await onSubmit(newBanners);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
@@ -12,8 +12,7 @@ interface FieldsetProps {
|
||||
isSubmitting?: boolean;
|
||||
}
|
||||
|
||||
export const Fieldset: FC<FieldsetProps> = (props) => {
|
||||
const {
|
||||
export const Fieldset: FC<FieldsetProps> = ({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
@@ -21,7 +20,7 @@ export const Fieldset: FC<FieldsetProps> = (props) => {
|
||||
button,
|
||||
onSubmit,
|
||||
isSubmitting,
|
||||
} = props;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
@@ -30,6 +29,7 @@ export const Fieldset: FC<FieldsetProps> = (props) => {
|
||||
borderRadius: 8,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
marginTop: 32,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
|
@@ -8,12 +8,6 @@ import type { WorkspacePermissions } from "./permissions";
|
||||
import { Workspace } from "./Workspace";
|
||||
import { WorkspaceBuildLogsSection } from "./WorkspaceBuildLogsSection";
|
||||
|
||||
const MockedAppearance = {
|
||||
config: Mocks.MockAppearanceConfig,
|
||||
isPreview: false,
|
||||
setPreview: () => {},
|
||||
};
|
||||
|
||||
const permissions: WorkspacePermissions = {
|
||||
readWorkspace: true,
|
||||
updateWorkspace: true,
|
||||
@@ -43,7 +37,7 @@ const meta: Meta<typeof Workspace> = {
|
||||
value={{
|
||||
entitlements: Mocks.MockEntitlementsWithScheduling,
|
||||
experiments: Mocks.MockExperiments,
|
||||
appearance: MockedAppearance,
|
||||
appearance: Mocks.MockAppearanceConfig,
|
||||
}}
|
||||
>
|
||||
<ProxyContext.Provider
|
||||
|
@@ -13,7 +13,7 @@ import { Margins } from "components/Margins/Margins";
|
||||
import { useAuthenticated } from "contexts/auth/RequireAuth";
|
||||
import { useEffectEvent } from "hooks/hookPolyfills";
|
||||
import { Navbar } from "modules/dashboard/Navbar/Navbar";
|
||||
import { ServiceBanner } from "modules/dashboard/ServiceBanner/ServiceBanner";
|
||||
import { NotificationBanners } from "modules/dashboard/NotificationBanners/NotificationBanners";
|
||||
import { workspaceChecks, type WorkspacePermissions } from "./permissions";
|
||||
import { WorkspaceReadyPage } from "./WorkspaceReadyPage";
|
||||
|
||||
@@ -106,7 +106,7 @@ export const WorkspacePage: FC = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ServiceBanner />
|
||||
<NotificationBanners />
|
||||
<div css={{ height: "100%", display: "flex", flexDirection: "column" }}>
|
||||
<Navbar />
|
||||
{pageError ? (
|
||||
|
@@ -91,12 +91,6 @@ const allWorkspaces = [
|
||||
...Object.values(additionalWorkspaces),
|
||||
];
|
||||
|
||||
const MockedAppearance = {
|
||||
config: MockAppearanceConfig,
|
||||
isPreview: false,
|
||||
setPreview: () => {},
|
||||
};
|
||||
|
||||
type FilterProps = ComponentProps<typeof WorkspacesPageView>["filterProps"];
|
||||
|
||||
const defaultFilterProps = getDefaultFilterProps<FilterProps>({
|
||||
@@ -153,7 +147,7 @@ const meta: Meta<typeof WorkspacesPageView> = {
|
||||
value={{
|
||||
entitlements: MockEntitlementsWithScheduling,
|
||||
experiments: MockExperiments,
|
||||
appearance: MockedAppearance,
|
||||
appearance: MockAppearanceConfig,
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
|
@@ -2355,6 +2355,7 @@ export const MockAppearanceConfig: TypesGen.AppearanceConfig = {
|
||||
service_banner: {
|
||||
enabled: false,
|
||||
},
|
||||
notification_banners: [],
|
||||
};
|
||||
|
||||
export const MockWorkspaceBuildParameter1: TypesGen.WorkspaceBuildParameter = {
|
||||
|
@@ -28,11 +28,7 @@ export const withDashboardProvider = (
|
||||
value={{
|
||||
entitlements,
|
||||
experiments,
|
||||
appearance: {
|
||||
config: MockAppearanceConfig,
|
||||
isPreview: false,
|
||||
setPreview: () => {},
|
||||
},
|
||||
appearance: MockAppearanceConfig,
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
|
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
const (
|
||||
CurrentMajor = 2
|
||||
CurrentMinor = 0
|
||||
CurrentMinor = 1
|
||||
)
|
||||
|
||||
var CurrentVersion = apiversion.New(CurrentMajor, CurrentMinor).WithBackwardCompat(1)
|
||||
|
@@ -177,7 +177,6 @@ func handleTestSubprocess(t *testing.T) {
|
||||
testName += *clientName
|
||||
}
|
||||
|
||||
//nolint:parralleltest
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
||||
switch *role {
|
||||
|
Reference in New Issue
Block a user