summaryrefslogtreecommitdiff
path: root/web/src
diff options
context:
space:
mode:
Diffstat (limited to 'web/src')
-rw-r--r--web/src/App.jsx15
-rw-r--r--web/src/actions/timezone.js20
-rw-r--r--web/src/containers/build/Summary.jsx6
-rw-r--r--web/src/containers/timezone/SelectTz.jsx142
-rw-r--r--web/src/pages/Builds.jsx17
-rw-r--r--web/src/pages/Status.jsx11
-rw-r--r--web/src/reducers/index.js12
-rw-r--r--web/src/reducers/timezone.js22
8 files changed, 228 insertions, 17 deletions
diff --git a/web/src/App.jsx b/web/src/App.jsx
index 619cc499e..1b412f572 100644
--- a/web/src/App.jsx
+++ b/web/src/App.jsx
@@ -31,12 +31,12 @@ import {
import * as moment from 'moment'
import ErrorBoundary from './containers/ErrorBoundary'
+import SelectTz from './containers/timezone/SelectTz'
import logo from './images/logo.png'
-import { routes } from './routes'
+import { clearError } from './actions/errors'
import { fetchConfigErrorsAction } from './actions/configErrors'
+import { routes } from './routes'
import { setTenantAction } from './actions/tenant'
-import { clearError } from './actions/errors'
-
class App extends React.Component {
static propTypes = {
@@ -44,6 +44,7 @@ class App extends React.Component {
configErrors: PropTypes.array,
info: PropTypes.object,
tenant: PropTypes.object,
+ timezone: PropTypes.string,
location: PropTypes.object,
history: PropTypes.object,
dispatch: PropTypes.func
@@ -166,7 +167,7 @@ class App extends React.Component {
type='error'
onDismiss={() => {this.props.dispatch(clearError(error.id))}}
>
- <span title={moment(error.date).format()}>
+ <span title={moment.utc(error.date).tz(this.props.timezone).format()}>
<strong>{error.text}</strong> ({error.status})&nbsp;
{error.url}
</span>
@@ -273,6 +274,9 @@ class App extends React.Component {
</Link>
</li>
)}
+ <li>
+ <SelectTz/>
+ </li>
</ul>
{showErrors && this.renderConfigErrors(configErrors)}
</div>
@@ -299,6 +303,7 @@ export default withRouter(connect(
errors: state.errors,
configErrors: state.configErrors,
info: state.info,
- tenant: state.tenant
+ tenant: state.tenant,
+ timezone: state.timezone
})
)(App))
diff --git a/web/src/actions/timezone.js b/web/src/actions/timezone.js
new file mode 100644
index 000000000..738eb6884
--- /dev/null
+++ b/web/src/actions/timezone.js
@@ -0,0 +1,20 @@
+// 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.
+
+export const TIMEZONE_SET = 'TIMEZONE_SET'
+
+export function setTimezoneAction (name) {
+ return {
+ type: TIMEZONE_SET,
+ timezone: name
+ }
+}
diff --git a/web/src/containers/build/Summary.jsx b/web/src/containers/build/Summary.jsx
index c03f24c3c..dbccdbc59 100644
--- a/web/src/containers/build/Summary.jsx
+++ b/web/src/containers/build/Summary.jsx
@@ -28,6 +28,7 @@ class Summary extends React.Component {
static propTypes = {
build: PropTypes.object,
tenant: PropTypes.object,
+ timezone: PropTypes.string,
}
render () {
@@ -75,6 +76,9 @@ class Summary extends React.Component {
value = 'false'
}
}
+ if (column === 'start_time' || column === 'end_time') {
+ value = moment.utc(value).tz(this.props.timezone).format('YYYY-MM-DD HH:mm:ss')
+ }
if (column === 'duration') {
value = moment.duration(value, 'seconds')
.format('h [hr] m [min] s [sec]')
@@ -124,4 +128,4 @@ class Summary extends React.Component {
}
-export default connect(state => ({tenant: state.tenant}))(Summary)
+export default connect(state => ({tenant: state.tenant, timezone: state.timezone}))(Summary)
diff --git a/web/src/containers/timezone/SelectTz.jsx b/web/src/containers/timezone/SelectTz.jsx
new file mode 100644
index 000000000..78092d2ab
--- /dev/null
+++ b/web/src/containers/timezone/SelectTz.jsx
@@ -0,0 +1,142 @@
+// 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 PropTypes from 'prop-types'
+import React from 'react'
+import Select from 'react-select'
+import moment from 'moment-timezone'
+import { Icon } from 'patternfly-react'
+import { connect } from 'react-redux'
+import { setTimezoneAction } from '../../actions/timezone'
+
+class SelectTz extends React.Component {
+ static propTypes = {
+ dispatch: PropTypes.func
+ }
+
+ state = {
+ availableTz: moment.tz.names().map(item => ({value: item, label: item})),
+ defaultValue: {value: 'UTC', label: 'UTC'}
+ }
+
+ componentDidMount () {
+ this.loadState()
+ }
+
+ handleChange = (selectedTz) => {
+ const tz = selectedTz.value
+
+ this.setCookie('zuul_tz_string', tz)
+ this.updateState(tz)
+ }
+
+ setCookie (name, value) {
+ document.cookie = name + '=' + value + '; path=/'
+ }
+
+ loadState = () => {
+ function readCookie (name, defaultValue) {
+ let nameEQ = name + '='
+ let ca = document.cookie.split(';')
+ for (let i = 0; i < ca.length; i++) {
+ let c = ca[i]
+ while (c.charAt(0) === ' ') {
+ c = c.substring(1, c.length)
+ }
+ if (c.indexOf(nameEQ) === 0) {
+ return c.substring(nameEQ.length, c.length)
+ }
+ }
+ return defaultValue
+ }
+ let tz = readCookie('zuul_tz_string', '')
+ if (tz) {
+ this.updateState(tz)
+ }
+ }
+
+ updateState = (tz) => {
+
+ this.setState({
+ currentValue: {value: tz, label: tz}
+ })
+
+ let timezoneAction = setTimezoneAction(tz)
+ this.props.dispatch(timezoneAction)
+ }
+
+ render() {
+ const textColor = '#d1d1d1'
+ const containerStyles= {
+ border: 'solid #2b2b2b',
+ borderWidth: '0 0 0 1px',
+ cursor: 'pointer',
+ display: 'initial',
+ fontSize: '11px',
+ padding: '6px'
+ }
+ const iconStyles = {
+ padding: '5px'
+ }
+ const customStyles = {
+ container: () => ({
+ display: 'inline-block',
+ }),
+ control: () => ({
+ width: 'auto',
+ display: 'flex'
+ }),
+ singleValue: () => ({
+ color: textColor,
+ }),
+ input: (provided) => ({
+ ...provided,
+ color: textColor
+ }),
+ dropdownIndicator:(provided) => ({
+ ...provided,
+ padding: '3px'
+ }),
+ indicatorSeparator: () => {},
+ menu: (provided) => ({
+ ...provided,
+ width: 'auto',
+ right: '0',
+ top: '22px',
+ })
+ }
+ return (
+ <div style={containerStyles}>
+ <Icon style={iconStyles} type="fa" name="clock-o" />
+ <Select
+ styles={customStyles}
+ value={this.state.currentValue}
+ onChange={this.handleChange}
+ options={this.state.availableTz}
+ noOptionsMessage={() => 'No api found'}
+ placeholder={'Select Tz'}
+ defaultValue={this.state.defaultValue}
+ theme={(theme) => ({
+ ...theme,
+ borderRadius: 0,
+ spacing: {
+ ...theme.spacing,
+ baseUnit: 2,
+ },
+ })}
+ />
+ </div>
+ )
+ }
+}
+
+export default connect()(SelectTz)
diff --git a/web/src/pages/Builds.jsx b/web/src/pages/Builds.jsx
index 694608373..6dc0a4836 100644
--- a/web/src/pages/Builds.jsx
+++ b/web/src/pages/Builds.jsx
@@ -17,7 +17,7 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import { Table } from 'patternfly-react'
-import * as moment from 'moment'
+import * as moment from 'moment-timezone'
import 'moment-duration-format'
import { fetchBuilds } from '../api'
@@ -26,12 +26,12 @@ import TableFilters from '../containers/TableFilters'
class BuildsPage extends TableFilters {
static propTypes = {
- tenant: PropTypes.object
+ tenant: PropTypes.object,
+ timezone: PropTypes.string
}
constructor () {
super()
-
this.prepareTableHeaders()
this.state = {
builds: null,
@@ -60,7 +60,8 @@ class BuildsPage extends TableFilters {
}
componentDidUpdate (prevProps) {
- if (this.props.tenant.name !== prevProps.tenant.name) {
+ if (this.props.tenant.name !== prevProps.tenant.name ||
+ this.props.timezone !== prevProps.timezone) {
this.updateData(this.getFilterFromUrl())
}
}
@@ -84,6 +85,11 @@ class BuildsPage extends TableFilters {
{moment.duration(value, 'seconds').format('h [hr] m [min] s [sec]')}
</Table.Cell>
)
+ const timeFormat = (value) => (
+ <Table.Cell>
+ {moment.utc(value).tz(this.props.timezone).format('YYYY-MM-DD HH:mm:ss')}
+ </Table.Cell>
+ )
this.columns = []
this.filterTypes = []
const myColumns = [
@@ -103,6 +109,7 @@ class BuildsPage extends TableFilters {
prop = 'job_name'
} else if (column === 'start time') {
prop = 'start_time'
+ formatter = timeFormat
} else if (column === 'change') {
prop = 'change'
formatter = linkChangeFormat
@@ -169,4 +176,4 @@ class BuildsPage extends TableFilters {
}
}
-export default connect(state => ({tenant: state.tenant}))(BuildsPage)
+export default connect(state => ({tenant: state.tenant, timezone: state.timezone}))(BuildsPage)
diff --git a/web/src/pages/Status.jsx b/web/src/pages/Status.jsx
index ad169e556..84555b5d2 100644
--- a/web/src/pages/Status.jsx
+++ b/web/src/pages/Status.jsx
@@ -13,6 +13,7 @@
// License for the specific language governing permissions and limitations
// under the License.
+import * as moment from 'moment-timezone'
import * as React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
@@ -33,6 +34,7 @@ class StatusPage extends Refreshable {
static propTypes = {
location: PropTypes.object,
tenant: PropTypes.object,
+ timezone: PropTypes.string,
remoteData: PropTypes.object,
dispatch: PropTypes.func
}
@@ -111,6 +113,12 @@ class StatusPage extends Refreshable {
this.visibilityChangeEvent, this.visibilityListener)
}
+ componentDidUpdate (prevProps) {
+ if (this.props.timezone !== prevProps.timezo) {
+ this.loadState()
+ }
+ }
+
setFilter = (filter) => {
this.filter.value = filter
this.setState({filter: filter})
@@ -184,7 +192,7 @@ class StatusPage extends Refreshable {
<p>Zuul version: <span>{status.zuul_version}</span></p>
{status.last_reconfigured ? (
<p>Last reconfigured: <span>
- {new Date(status.last_reconfigured).toString()}
+ {moment.utc(status.last_reconfigured).tz(this.props.timezone).format('llll')}
</span></p>) : ''}
</React.Fragment>
)
@@ -258,5 +266,6 @@ class StatusPage extends Refreshable {
export default connect(state => ({
tenant: state.tenant,
+ timezone: state.timezone,
remoteData: state.status,
}))(StatusPage)
diff --git a/web/src/reducers/index.js b/web/src/reducers/index.js
index 18abb44e3..84fe8e6fd 100644
--- a/web/src/reducers/index.js
+++ b/web/src/reducers/index.js
@@ -24,30 +24,32 @@ import jobs from './jobs'
import labels from './labels'
import logfile from './logfile'
import nodes from './nodes'
+import openapi from './openapi'
import project from './project'
import projects from './projects'
import status from './status'
import tenant from './tenant'
import tenants from './tenants'
-import openapi from './openapi'
+import timezone from './timezone'
const reducers = {
- change,
build,
+ change,
+ configErrors,
+ errors,
info,
job,
jobs,
labels,
logfile,
nodes,
+ openapi,
project,
projects,
- configErrors,
- errors,
status,
tenant,
tenants,
- openapi,
+ timezone,
}
export default combineReducers(reducers)
diff --git a/web/src/reducers/timezone.js b/web/src/reducers/timezone.js
new file mode 100644
index 000000000..c382ab0d3
--- /dev/null
+++ b/web/src/reducers/timezone.js
@@ -0,0 +1,22 @@
+// 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 { TIMEZONE_SET } from '../actions/timezone'
+
+export default (state = 'UTC', action) => {
+ switch (action.type) {
+ case TIMEZONE_SET:
+ return action.timezone
+ default:
+ return state
+ }
+ }