Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Console header status badge #7084

Merged
merged 1 commit into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/console/console.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ func generateConsoleCSPString(config *Config, nonce string, others ...webui.Cont
ConnectionSource: append([]string{
"'self'",
config.UI.SentryDSN,
config.UI.StatusPage,
"gravatar.com",
"www.gravatar.com",
}, baseURLs...),
Expand Down
1 change: 1 addition & 0 deletions pkg/identityserver/identityserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func GenerateCSPString(config *oauth.Config, nonce string) string {
ConnectionSource: append([]string{
"'self'",
config.UI.SentryDSN,
config.UI.StatusPage,
"gravatar.com",
"www.gravatar.com",
}, baseURLs...),
Expand Down
64 changes: 64 additions & 0 deletions pkg/webui/components/alert-banner/alert-banner.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright © 2024 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

.alert-banner
position: absolute
right: 0
left: 0
z-index: $zi.slight
padding: $cs.m
opacity: 0
pointer-events: none
transition: opacity $ad.m
&.visible
opacity: 1
pointer-events: auto

.link > span
text-decoration: underline

.title
line-height: normal

.error
background-color: var(--c-bg-error-light)
color: var(--c-text-error-bold)
.link, .closeIcon
color: var(--c-text-error-bold)
&:hover
color: var(--c-text-error-normal-hover)

.success
background-color: var(--c-bg-success-light)
color: var(--c-text-success-bold)
.link, .closeIcon
color: var(--c-text-success-bold)
&:hover
color: var(--c-text-success-normal-hover)

.warning
background-color: var(--c-bg-warning-light)
color: var(--c-text-warning-bold)
.link, .closeIcon
color: var(--c-text-warning-bold)
&:hover
color: var(--c-text-warning-normal-hover)

.info
background-color: var(--c-bg-info-light)
color: var(--c-text-info-bold)
.link, .closeIcon
color: var(--c-text-info-bold)
&:hover
color: var(--c-text-info-normal-hover)
84 changes: 84 additions & 0 deletions pkg/webui/components/alert-banner/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright © 2024 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React, { useContext, useRef, useState } from 'react'

import AlertBanner from '@ttn-lw/components/alert-banner/index'

import PropTypes from '@ttn-lw/lib/prop-types'

const AlertBannerContext = React.createContext()
const { Provider } = AlertBannerContext

const AlertBannerProvider = ({ children }) => {
const [type, setType] = useState('info')
const [open, setOpen] = useState(false)
const [title, setTitle] = useState('')
const [subtitle, setSubtitle] = useState(undefined)
const [titleValues, setTitleValues] = useState(undefined)
const [subtitleValues, setSubtitleValues] = useState(undefined)
const timeoutRef = useRef(null)
const showBanner = ({ type, title, duration, subtitle, titleValues, subtitleValues }) => {
if (window.innerWidth <= 768) {
clearTimeout(timeoutRef.current)
setType(type)
setTitle(title)
setSubtitle(subtitle)
setTitleValues(titleValues)
setSubtitleValues(subtitleValues)
setOpen(true)
if (duration) {
timeoutRef.current = setTimeout(() => {
setOpen(false)
}, duration)
}
}
}
const closeBanner = () => {
setOpen(false)
clearTimeout(timeoutRef.current)
}
const value = {
showBanner,
}

return (
<Provider value={value}>
<AlertBanner
open={open}
handleClose={closeBanner}
type={type}
title={title}
titleValues={titleValues}
subtitle={subtitle}
subtitleValues={subtitleValues}
/>
{children}
</Provider>
)
}

AlertBannerProvider.propTypes = {
children: PropTypes.node.isRequired,
}

const useAlertBanner = () => {
const context = useContext(AlertBannerContext)
if (!context) {
throw new Error('useAlertBanner must be used within a AlertBannerProvider')
}
return context
}

export { AlertBannerProvider, useAlertBanner }
104 changes: 104 additions & 0 deletions pkg/webui/components/alert-banner/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright © 2024 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React, { useEffect, useRef } from 'react'
import classnames from 'classnames'

import Icon, { IconX } from '@ttn-lw/components/icon'

import Message from '@ttn-lw/lib/components/message'

import PropTypes from '@ttn-lw/lib/prop-types'
import from from '@ttn-lw/lib/from'

import style from './alert-banner.styl'

const AlertBanner = ({
className,
type,
open,
title,
subtitle,
titleValues,
subtitleValues,
handleClose,
}) => {
const ref = useRef(null)

useEffect(() => {
const handleCloseBanner = () => {
if (window.innerWidth > 768 && open) {
handleClose()
}
}

const header = document.getElementById('header')
if (header) {
const headerHeight = header.offsetHeight
ref.current.style.top = `${headerHeight}px`
}

window.addEventListener('resize', handleCloseBanner)
return () => {
window.removeEventListener('resize', handleCloseBanner)
}
}, [handleClose, open])

return (
<div
ref={ref}
className={classnames(
style.alertBanner,
className,
style[type],
...from(style, {
visible: open,
}),
)}
>
<div className="d-flex al-center j-between mb-cs-xxs">
<Message
className={classnames(style.title, 'fw-bold', 'fs-l')}
content={title}
values={titleValues}
/>
<Icon
className={classnames(style.closeIcon, 'cursor-pointer')}
icon={IconX}
onClick={handleClose}
/>
</div>
{subtitle && <Message content={subtitle} values={subtitleValues} />}
</div>
)
}

AlertBanner.propTypes = {
className: PropTypes.string,
handleClose: PropTypes.func.isRequired,
open: PropTypes.bool.isRequired,
subtitle: PropTypes.message,
subtitleValues: PropTypes.object,
title: PropTypes.message.isRequired,
titleValues: PropTypes.object,
type: PropTypes.oneOf(['info', 'success', 'warning', 'error']).isRequired,
}

AlertBanner.defaultProps = {
className: undefined,
subtitle: undefined,
titleValues: undefined,
subtitleValues: undefined,
}
export default AlertBanner
62 changes: 62 additions & 0 deletions pkg/webui/components/alert-banner/story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright © 2024 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import React from 'react'

import AlertBanner from '.'

export default {
title: 'AlertBanner',
component: [AlertBanner],
}

export const Info = () => (
<AlertBanner
title="Maintenance"
subtitle="Maintenance scheduled in 1 hour."
type="info"
handleClose={() => {}}
open
/>
)

export const Warning = () => (
<AlertBanner
title="Connection issues"
subtitle="The console is having trouble connecting to the internet."
type="warning"
handleClose={() => {}}
open
/>
)

export const Error = () => (
<AlertBanner
title="Network offline"
subtitle="The console lost internet connection. Changes cannot be saved."
type="error"
handleClose={() => {}}
open
/>
)

export const Success = () => (
<AlertBanner
title="Online"
subtitle="Your connection has been restored."
type="success"
handleClose={() => {}}
open
/>
)
5 changes: 4 additions & 1 deletion pkg/webui/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { IconStar, IconPlus, IconInbox, IconMenu2 } from '@ttn-lw/components/ico
import Button from '@ttn-lw/components/button'
import ProfileDropdown from '@ttn-lw/components/profile-dropdown'

import AppStatusBadge from '@console/containers/app-status-badge'

import PropTypes from '@ttn-lw/lib/prop-types'

import style from './header.styl'
Expand All @@ -36,14 +38,15 @@ const Header = ({
showNotificationDot,
...rest
}) => (
<header {...rest} className={classnames(className, style.container)}>
<header {...rest} className={classnames(className, style.container)} id="header">
<div className={classnames('breadcrumbs', 'md:d-none')} />
<div className="d-none md:d-flex al-center gap-cs-xs">
<Button secondary icon={IconMenu2} onClick={onMenuClick} />
<Logo className={style.logo} />
</div>

<div className="d-flex al-center gap-cs-xs">
<AppStatusBadge />
<Button
secondary
icon={IconPlus}
Expand Down
Loading
Loading