summaryrefslogtreecommitdiff
path: root/web/src/pages
diff options
context:
space:
mode:
authorTobias Urdin <tobias.urdin@binero.se>2023-03-15 23:36:45 +0000
committerTobias Urdin <tobias.urdin@binero.se>2023-04-21 11:23:56 +0000
commit59cd5de78baa31150958e6d0d6733407c0e95805 (patch)
treeed72bd938cc5d40a04de612e30547a78ea9c53cf /web/src/pages
parentde9dfa2bc416d9b1bb6159ed39a014fbba267db5 (diff)
downloadzuul-59cd5de78baa31150958e6d0d6733407c0e95805.tar.gz
web: add dark mode and theme selection
This adds a theme selection in the preferences in the config modal and adds a new dark theme. Removes the line.png image and instead uses CSS linear-gradient that is available in all browsers since around 2018, also fixes the 15 pixels spacing issue that is there today. You can select between three different themes. Auto will use your system preference to choose either the light or dark theme, changes dynamically based on your system preference. Light is the current theme. Dark is the theme added by this patch series. The UX this changes is that if somebody has their system preferences set to dark, for example in Mac OS X that is in System Settings -> Appearance -> Dark the user will get the Zuul web UI in dark by default and same for the opposite. This uses a poor man's dark mode for swagger-ui as per the comment in [1]. [1] https://github.com/swagger-api/swagger-ui/issues/5327#issuecomment-742375520 Change-Id: I01cf32f3decdb885307a76eb79d644667bbbf9a3
Diffstat (limited to 'web/src/pages')
-rw-r--r--web/src/pages/Autohold.jsx10
-rw-r--r--web/src/pages/Build.jsx8
-rw-r--r--web/src/pages/Buildset.jsx6
-rw-r--r--web/src/pages/Buildsets.jsx8
-rw-r--r--web/src/pages/ConfigErrors.jsx23
-rw-r--r--web/src/pages/FreezeJob.jsx5
-rw-r--r--web/src/pages/Job.jsx6
-rw-r--r--web/src/pages/Jobs.jsx8
-rw-r--r--web/src/pages/OpenApi.jsx6
-rw-r--r--web/src/pages/Project.jsx4
-rw-r--r--web/src/pages/Semaphore.jsx6
-rw-r--r--web/src/pages/Status.jsx3
-rw-r--r--web/src/pages/Stream.jsx11
13 files changed, 71 insertions, 33 deletions
diff --git a/web/src/pages/Autohold.jsx b/web/src/pages/Autohold.jsx
index 0d0198b45..cc91cbcd0 100644
--- a/web/src/pages/Autohold.jsx
+++ b/web/src/pages/Autohold.jsx
@@ -59,6 +59,7 @@ class AutoholdPage extends React.Component {
autohold: PropTypes.object,
isFetching: PropTypes.bool.isRequired,
fetchAutohold: PropTypes.func.isRequired,
+ preferences: PropTypes.object,
}
updateData = () => {
@@ -147,7 +148,7 @@ class AutoholdPage extends React.Component {
return (
<>
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Title headingLevel="h2">Autohold Request {autohold.id}</Title>
<Flex className="zuul-autohold-attributes">
@@ -211,7 +212,9 @@ class AutoholdPage extends React.Component {
value={
<>
<strong>Reason:</strong>
- <pre>{autohold.reason}</pre>
+ <div className={this.props.preferences.darkMode ? 'zuul-console-dark' : ''}>
+ <pre>{autohold.reason}</pre>
+ </div>
</>
}
/>
@@ -221,7 +224,7 @@ class AutoholdPage extends React.Component {
</Flex>
</Flex>
</PageSection>
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Title headingLevel="h3">
<BuildIcon
style={{
@@ -243,6 +246,7 @@ function mapStateToProps(state) {
autohold: state.autoholds.autohold,
tenant: state.tenant,
isFetching: state.autoholds.isFetching,
+ preferences: state.preferences,
}
}
diff --git a/web/src/pages/Build.jsx b/web/src/pages/Build.jsx
index 0386f8eef..c66af1060 100644
--- a/web/src/pages/Build.jsx
+++ b/web/src/pages/Build.jsx
@@ -65,6 +65,7 @@ class BuildPage extends React.Component {
activeTab: PropTypes.string.isRequired,
location: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
+ preferences: PropTypes.object,
}
state = {
@@ -250,10 +251,10 @@ class BuildPage extends React.Component {
return (
<>
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Build build={build} active={activeTab} hash={hash} />
</PageSection>
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Tabs
isFilled
activeKey={activeTab}
@@ -314,7 +315,7 @@ class BuildPage extends React.Component {
</Tabs>
</PageSection>
{!this.state.topOfPageVisible && (
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Button onClick={scrollToTop} variant="primary" style={{position: 'fixed', bottom: 20, right: 20, zIndex: 1}}>
Go to top of page <ArrowUpIcon/>
</Button>
@@ -362,6 +363,7 @@ function mapStateToProps(state, ownProps) {
isFetchingManifest: state.build.isFetchingManifest,
isFetchingOutput: state.build.isFetchingOutput,
isFetchingLogfile: state.logfile.isFetching,
+ preferences: state.preferences,
}
}
diff --git a/web/src/pages/Buildset.jsx b/web/src/pages/Buildset.jsx
index b19aefb81..6e1cded57 100644
--- a/web/src/pages/Buildset.jsx
+++ b/web/src/pages/Buildset.jsx
@@ -38,6 +38,7 @@ class BuildsetPage extends React.Component {
buildset: PropTypes.object,
isFetching: PropTypes.bool.isRequired,
fetchBuildset: PropTypes.func.isRequired,
+ preferences: PropTypes.object,
}
updateData = () => {
@@ -105,10 +106,10 @@ class BuildsetPage extends React.Component {
return (
<>
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Buildset buildset={buildset} />
</PageSection>
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Title headingLevel="h3">
<BuildIcon
style={{
@@ -134,6 +135,7 @@ function mapStateToProps(state, ownProps) {
buildset,
tenant: state.tenant,
isFetching: state.build.isFetching,
+ preferences: state.preferences,
}
}
diff --git a/web/src/pages/Buildsets.jsx b/web/src/pages/Buildsets.jsx
index 938309034..3ae3b772b 100644
--- a/web/src/pages/Buildsets.jsx
+++ b/web/src/pages/Buildsets.jsx
@@ -32,6 +32,7 @@ class BuildsetsPage extends React.Component {
tenant: PropTypes.object,
location: PropTypes.object,
history: PropTypes.object,
+ preferences: PropTypes.object,
}
constructor(props) {
@@ -230,7 +231,7 @@ class BuildsetsPage extends React.Component {
const { buildsets, fetching, filters, resultsPerPage, currentPage, itemCount } = this.state
return (
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<FilterToolbar
filterCategories={this.filterCategories}
onFilterChange={this.handleFilterChange}
@@ -268,4 +269,7 @@ class BuildsetsPage extends React.Component {
}
}
-export default connect((state) => ({ tenant: state.tenant }))(BuildsetsPage)
+export default connect((state) => ({
+ tenant: state.tenant,
+ preferences: state.preferences,
+}))(BuildsetsPage)
diff --git a/web/src/pages/ConfigErrors.jsx b/web/src/pages/ConfigErrors.jsx
index b7a85d074..b43ebf562 100644
--- a/web/src/pages/ConfigErrors.jsx
+++ b/web/src/pages/ConfigErrors.jsx
@@ -18,7 +18,12 @@ import { connect } from 'react-redux'
import {
Icon
} from 'patternfly-react'
-import { PageSection, PageSectionVariants } from '@patternfly/react-core'
+import {
+ PageSection,
+ PageSectionVariants,
+ List,
+ ListItem,
+} from '@patternfly/react-core'
import { fetchConfigErrorsAction } from '../actions/configErrors'
@@ -26,7 +31,8 @@ class ConfigErrorsPage extends React.Component {
static propTypes = {
configErrors: PropTypes.object,
tenant: PropTypes.object,
- dispatch: PropTypes.func
+ dispatch: PropTypes.func,
+ preferences: PropTypes.object,
}
updateData = () => {
@@ -36,7 +42,7 @@ class ConfigErrorsPage extends React.Component {
render () {
const { configErrors } = this.props
return (
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<div className="pull-right">
{/* Lint warning jsx-a11y/anchor-is-valid */}
{/* eslint-disable-next-line */}
@@ -45,22 +51,22 @@ class ConfigErrorsPage extends React.Component {
</a>
</div>
<div className="pull-left">
- <ul className="list-group">
+ <List isPlain isBordered>
{configErrors.map((item, idx) => {
let ctxPath = item.source_context.path
if (item.source_context.branch !== 'master') {
ctxPath += ' (' + item.source_context.branch + ')'
}
return (
- <li className="list-group-item" key={idx}>
+ <ListItem key={idx}>
<h3>{item.source_context.project} - {ctxPath}</h3>
<p style={{whiteSpace: 'pre-wrap'}}>
{item.error}
</p>
- </li>
+ </ListItem>
)
})}
- </ul>
+ </List>
</div>
</PageSection>
)
@@ -69,5 +75,6 @@ class ConfigErrorsPage extends React.Component {
export default connect(state => ({
tenant: state.tenant,
- configErrors: state.configErrors.errors
+ configErrors: state.configErrors.errors,
+ preferences: state.preferences,
}))(ConfigErrorsPage)
diff --git a/web/src/pages/FreezeJob.jsx b/web/src/pages/FreezeJob.jsx
index a78cc7b56..491a9a1ee 100644
--- a/web/src/pages/FreezeJob.jsx
+++ b/web/src/pages/FreezeJob.jsx
@@ -97,7 +97,8 @@ function FreezeJobPage(props) {
collapsed={false}
sortKeys={true}
enableClipboard={false}
- displayDataTypes={false}/>
+ displayDataTypes={false}
+ theme={props.preferences.darkMode ? 'tomorrow' : 'rjv-default'}/>
</span>
)
}
@@ -133,12 +134,14 @@ FreezeJobPage.propTypes = {
fetchFreezeJobIfNeeded: PropTypes.func,
tenant: PropTypes.object,
freezejob: PropTypes.object,
+ preferences: PropTypes.object,
}
function mapStateToProps(state) {
return {
tenant: state.tenant,
freezejob: state.freezejob,
+ preferences: state.preferences,
}
}
diff --git a/web/src/pages/Job.jsx b/web/src/pages/Job.jsx
index efb4cfddc..f29ea383d 100644
--- a/web/src/pages/Job.jsx
+++ b/web/src/pages/Job.jsx
@@ -26,7 +26,8 @@ class JobPage extends React.Component {
match: PropTypes.object.isRequired,
tenant: PropTypes.object,
remoteData: PropTypes.object,
- dispatch: PropTypes.func
+ dispatch: PropTypes.func,
+ preferences: PropTypes.object,
}
updateData = (force) => {
@@ -53,7 +54,7 @@ class JobPage extends React.Component {
const tenantJobs = remoteData.jobs[this.props.tenant.name]
const jobName = this.props.match.params.jobName
return (
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode? PageSectionVariants.dark : PageSectionVariants.light}>
{tenantJobs && tenantJobs[jobName] && <Job job={tenantJobs[jobName]} />}
</PageSection>
)
@@ -63,4 +64,5 @@ class JobPage extends React.Component {
export default connect(state => ({
tenant: state.tenant,
remoteData: state.job,
+ preferences: state.preferences,
}))(JobPage)
diff --git a/web/src/pages/Jobs.jsx b/web/src/pages/Jobs.jsx
index d0b6a1466..6484b6f48 100644
--- a/web/src/pages/Jobs.jsx
+++ b/web/src/pages/Jobs.jsx
@@ -26,7 +26,8 @@ class JobsPage extends React.Component {
static propTypes = {
tenant: PropTypes.object,
remoteData: PropTypes.object,
- dispatch: PropTypes.func
+ dispatch: PropTypes.func,
+ preferences: PropTypes.object,
}
updateData = (force) => {
@@ -51,8 +52,8 @@ class JobsPage extends React.Component {
const jobs = remoteData.jobs[this.props.tenant.name]
return (
- <PageSection variant={PageSectionVariants.light}>
- <PageSection style={{paddingRight: '5px'}}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
+ <PageSection variant={PageSectionVariants.light} style={{paddingRight: '5px'}}>
<Fetchable
isFetching={remoteData.isFetching}
fetchCallback={this.updateData}
@@ -70,4 +71,5 @@ class JobsPage extends React.Component {
export default connect(state => ({
tenant: state.tenant,
remoteData: state.jobs,
+ preferences: state.preferences,
}))(JobsPage)
diff --git a/web/src/pages/OpenApi.jsx b/web/src/pages/OpenApi.jsx
index 7ccf5f34c..ec8ef8c56 100644
--- a/web/src/pages/OpenApi.jsx
+++ b/web/src/pages/OpenApi.jsx
@@ -26,7 +26,8 @@ class OpenApiPage extends React.Component {
static propTypes = {
tenant: PropTypes.object,
remoteData: PropTypes.object,
- dispatch: PropTypes.func
+ dispatch: PropTypes.func,
+ preferences: PropTypes.object,
}
updateData = (force) => {
@@ -51,7 +52,7 @@ class OpenApiPage extends React.Component {
render() {
return (
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<div id="swaggerContainer" />
</PageSection>
)
@@ -61,4 +62,5 @@ class OpenApiPage extends React.Component {
export default connect(state => ({
tenant: state.tenant,
remoteData: state.openapi,
+ preferences: state.preferences,
}))(OpenApiPage)
diff --git a/web/src/pages/Project.jsx b/web/src/pages/Project.jsx
index 06e8612c7..0c808ec09 100644
--- a/web/src/pages/Project.jsx
+++ b/web/src/pages/Project.jsx
@@ -46,7 +46,7 @@ function ProjectPage(props) {
return (
<>
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<TextContent>
<Text component="h2">Project {projectName}</Text>
<Fetchable
@@ -70,12 +70,14 @@ ProjectPage.propTypes = {
tenant: PropTypes.object,
remoteData: PropTypes.object,
fetchProjectIfNeeded: PropTypes.func,
+ preferences: PropTypes.object,
}
function mapStateToProps(state) {
return {
tenant: state.tenant,
remoteData: state.project,
+ preferences: state.preferences,
}
}
diff --git a/web/src/pages/Semaphore.jsx b/web/src/pages/Semaphore.jsx
index a0ae8ddca..2cfdfb73f 100644
--- a/web/src/pages/Semaphore.jsx
+++ b/web/src/pages/Semaphore.jsx
@@ -25,7 +25,7 @@ import { PageSection, PageSectionVariants } from '@patternfly/react-core'
import { fetchSemaphoresIfNeeded } from '../actions/semaphores'
import Semaphore from '../containers/semaphore/Semaphore'
-function SemaphorePage({ match, semaphores, tenant, fetchSemaphoresIfNeeded, isFetching }) {
+function SemaphorePage({ match, semaphores, tenant, fetchSemaphoresIfNeeded, isFetching, preferences }) {
const semaphoreName = match.params.semaphoreName
@@ -38,7 +38,7 @@ function SemaphorePage({ match, semaphores, tenant, fetchSemaphoresIfNeeded, isF
e => e.name === semaphoreName) : undefined
return (
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Title headingLevel="h2">
Details for Semaphore <span style={{color: 'var(--pf-global--primary-color--100)'}}>{semaphoreName}</span>
</Title>
@@ -55,6 +55,7 @@ SemaphorePage.propTypes = {
tenant: PropTypes.object.isRequired,
isFetching: PropTypes.bool.isRequired,
fetchSemaphoresIfNeeded: PropTypes.func.isRequired,
+ preferences: PropTypes.object,
}
const mapDispatchToProps = { fetchSemaphoresIfNeeded }
@@ -63,6 +64,7 @@ function mapStateToProps(state) {
tenant: state.tenant,
semaphores: state.semaphores.semaphores,
isFetching: state.semaphores.isFetching,
+ preferences: state.preferences,
}
}
diff --git a/web/src/pages/Status.jsx b/web/src/pages/Status.jsx
index ac3dc7840..e61ceb3e2 100644
--- a/web/src/pages/Status.jsx
+++ b/web/src/pages/Status.jsx
@@ -197,6 +197,7 @@ class StatusPage extends React.Component {
<FormGroup controlId='status'>
<FormControl
type='text'
+ className="pf-c-form-control"
placeholder='change or project name'
defaultValue={filter}
inputRef={i => this.filter = i}
@@ -222,7 +223,7 @@ class StatusPage extends React.Component {
</Form>
)
return (
- <PageSection variant={PageSectionVariants.light}>
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<div style={{display: 'flex', float: 'right'}}>
<Fetchable
isFetching={remoteData.isFetching}
diff --git a/web/src/pages/Stream.jsx b/web/src/pages/Stream.jsx
index df2a2ad96..f058d29a2 100644
--- a/web/src/pages/Stream.jsx
+++ b/web/src/pages/Stream.jsx
@@ -31,7 +31,8 @@ class StreamPage extends React.Component {
static propTypes = {
match: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
- tenant: PropTypes.object
+ tenant: PropTypes.object,
+ preferences: PropTypes.object,
}
state = {
@@ -167,10 +168,11 @@ class StreamPage extends React.Component {
render () {
return (
- <PageSection variant={PageSectionVariants.light} >
+ <PageSection variant={this.props.preferences.darkMode ? PageSectionVariants.dark : PageSectionVariants.light}>
<Form inline>
<FormGroup controlId='stream'>
<FormControl
+ className="pf-c-form-control"
type='text'
placeholder='search'
onKeyPress={this.handleKeyPress}
@@ -201,4 +203,7 @@ class StreamPage extends React.Component {
}
-export default connect(state => ({tenant: state.tenant}))(StreamPage)
+export default connect(state => ({
+ tenant: state.tenant,
+ preferences: state.preferences,
+}))(StreamPage)