Skip to content

Commit

Permalink
console: Backport app status badge
Browse files Browse the repository at this point in the history
  • Loading branch information
PavelJankoski committed May 16, 2024
1 parent 6fa8244 commit a1737ce
Show file tree
Hide file tree
Showing 20 changed files with 747 additions and 78 deletions.
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
/>
)
4 changes: 3 additions & 1 deletion pkg/webui/components/header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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

import { IconStar, IconPlus, IconInbox, IconMenu2 } from '@ttn-lw/components/icon'
import Button from '@ttn-lw/components/button'
Expand All @@ -36,14 +37,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

0 comments on commit a1737ce

Please sign in to comment.