diff --git a/Parse-Dashboard/index.js b/Parse-Dashboard/index.js index 3645fadf81..9643c59d63 100644 --- a/Parse-Dashboard/index.js +++ b/Parse-Dashboard/index.js @@ -52,6 +52,7 @@ let configUserId = program.userId || process.env.PARSE_DASHBOARD_USER_ID; let configUserPassword = program.userPassword || process.env.PARSE_DASHBOARD_USER_PASSWORD; let configSSLKey = program.sslKey || process.env.PARSE_DASHBOARD_SSL_KEY; let configSSLCert = program.sslCert || process.env.PARSE_DASHBOARD_SSL_CERT; +let configTimezone = program.timezone || process.env.PARSE_DASHBOARD_TIMEZONE; if (!program.config && !process.env.PARSE_DASHBOARD_CONFIG) { if (configServerURL && configMasterKey && configAppId) { configFromCLI = { @@ -62,6 +63,7 @@ if (!program.config && !process.env.PARSE_DASHBOARD_CONFIG) { serverURL: configServerURL, masterKey: configMasterKey, appName: configAppName, + timezone: configTimezone, }, ] } diff --git a/Parse-Dashboard/parse-dashboard-config.json b/Parse-Dashboard/parse-dashboard-config.json index 2efaa16103..e6d3efdfa4 100644 --- a/Parse-Dashboard/parse-dashboard-config.json +++ b/Parse-Dashboard/parse-dashboard-config.json @@ -4,6 +4,7 @@ "appId": "hello", "masterKey": "world", "appName": "", + "timezone": null, "iconName": "", "primaryBackgroundColor": "", "secondaryBackgroundColor": "" diff --git a/README.md b/README.md index 1b834f7432..cd09c56a12 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Parse Dashboard is a standalone dashboard for managing your Parse apps. You can * [Multiple apps](#multiple-apps) * [Single app](#single-app) * [Managing Multiple Apps](#managing-multiple-apps) + * [Timezone configuration](#timezone-configuration) * [App Icon Configuration](#app-icon-configuration) * [App Background Color Configuration](#app-background-color-configuration) * [Other Configuration Options](#other-configuration-options) @@ -69,7 +70,8 @@ You can also start the dashboard from the command line with a config file. To d "serverURL": "http://localhost:1337/parse", "appId": "myAppId", "masterKey": "myMasterKey", - "appName": "MyApp" + "appName": "MyApp", + "timezone": "America/Bahia" } ] } @@ -100,6 +102,7 @@ PARSE_DASHBOARD_SERVER_URL: "http://localhost:1337/parse" PARSE_DASHBOARD_MASTER_KEY: "myMasterKey" PARSE_DASHBOARD_APP_ID: "myAppId" PARSE_DASHBOARD_APP_NAME: "MyApp" +PARSE_DASHBOARD_TIMEZONE: "America/Bahia" PARSE_DASHBOARD_USER_ID: "user1" PARSE_DASHBOARD_USER_PASSWORD: "pass" PARSE_DASHBOARD_SSL_KEY: "sslKey" @@ -129,7 +132,26 @@ You can manage self-hosted [Parse Server](https://github.com/ParsePlatform/parse "serverURL": "http://localhost:1337/parse", // Self-hosted Parse Server "appId": "myAppId", "masterKey": "myMasterKey", - "appName": "My Parse Server App" + "appName": "My Parse Server App", + "timezone": "America/Chicago", + } + ] +} +``` + +## Timezone Configuration + +Parse Dashboard supports adding an optional timezone for each app, if you dont set any timezone, the UTC will be used like default. Just set a `timezone` with a valid timezone name. + +```json +{ + "apps": [ + { + "serverURL": "http://localhost:1337/parse", + "appId": "myAppId", + "masterKey": "myMasterKey", + "appName": "My Parse Server App", + "timezone": "America/Bahia", } ] } @@ -367,7 +389,7 @@ You can mark a user as a read-only user: "appId": "myAppId1", "masterKey": "myMasterKey1", "readOnlyMasterKey": "myReadOnlyMasterKey1", - "serverURL": "myURL1", + "serverURL": "myURL1", "port": 4040, "production": true }, @@ -375,7 +397,7 @@ You can mark a user as a read-only user: "appId": "myAppId2", "masterKey": "myMasterKey2", "readOnlyMasterKey": "myReadOnlyMasterKey2", - "serverURL": "myURL2", + "serverURL": "myURL2", "port": 4041, "production": true } @@ -410,7 +432,7 @@ You can give read only access to a user on a per-app basis: "appId": "myAppId1", "masterKey": "myMasterKey1", "readOnlyMasterKey": "myReadOnlyMasterKey1", - "serverURL": "myURL", + "serverURL": "myURL", "port": 4040, "production": true }, diff --git a/package.json b/package.json index 63001ef2d6..fe88d29229 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "json-file-plus": "^3.2.0", "package-json": "^5.0.0", "passport": "^0.4.0", - "passport-local": "^1.0.0" + "passport-local": "^1.0.0", + "timezone-support": "^1.5.5" }, "devDependencies": { "@babel/core": "7.1.2", diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index fe6b81eb8c..90cd60c83e 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import { dateStringUTC } from 'lib/DateUtils'; +import { dateString } from 'lib/DateUtils'; import getFileName from 'lib/getFileName'; import Parse from 'parse'; import Pill from 'components/Pill/Pill.react'; @@ -13,7 +13,7 @@ import React from 'react'; import styles from 'components/BrowserCell/BrowserCell.scss'; import { unselectable } from 'stylesheets/base.scss'; -let BrowserCell = ({ type, value, hidden, width, current, onSelect, onEditChange, setRelation, onPointerClick }) => { +let BrowserCell = ({ type, value, hidden, width, current, timezone, onSelect, onEditChange, setRelation, onPointerClick }) => { let content = value; let classes = [styles.cell, unselectable]; if (hidden) { @@ -39,7 +39,7 @@ let BrowserCell = ({ type, value, hidden, width, current, onSelect, onEditChange ); } else if (type === 'Date') { - content = dateStringUTC(value); + content = dateString(value, timezone); } else if (type === 'Boolean') { content = value ? 'True' : 'False'; } else if (type === 'Array') { diff --git a/src/components/PushCerts/CertsTable.react.js b/src/components/PushCerts/CertsTable.react.js index db8db279fd..bfc3cb8792 100644 --- a/src/components/PushCerts/CertsTable.react.js +++ b/src/components/PushCerts/CertsTable.react.js @@ -7,11 +7,11 @@ */ import FormTable from 'components/FormTable/FormTable.react'; import React from 'react'; -import { dateStringUTC } from 'lib/DateUtils'; +import { dateString } from 'lib/DateUtils'; const MONTH_IN_MS = 1000 * 60 * 60 * 24 * 30; -let CertsTable = ({ certs, onDelete, uploadPending }) => { +let CertsTable = ({ certs, timezone, onDelete, uploadPending }) => { let tableData = certs.map(c => { let color = ''; let expiresKeyColor = ''; @@ -36,7 +36,7 @@ let CertsTable = ({ certs, onDelete, uploadPending }) => { { key: isExpired ? 'Expired' : 'Expires', keyColor: expiresKeyColor, - value: dateStringUTC(new Date(c.expiration)), + value: dateString(new Date(c.expiration), timezone), } ] }; diff --git a/src/components/PushCerts/PushCerts.react.js b/src/components/PushCerts/PushCerts.react.js index 20a3aaedc4..fea0911d9b 100644 --- a/src/components/PushCerts/PushCerts.react.js +++ b/src/components/PushCerts/PushCerts.react.js @@ -57,6 +57,7 @@ PushCerts.propTypes = { onDelete: PropTypes.func.isRequired.describe( 'A handler for when a Push has been deleted from the server' ), + timezone: PropTypes.string, }; -export default PushCerts; \ No newline at end of file +export default PushCerts; diff --git a/src/dashboard/Account/AccountOverview.react.js b/src/dashboard/Account/AccountOverview.react.js index c6966ad5f7..8b81a137b9 100644 --- a/src/dashboard/Account/AccountOverview.react.js +++ b/src/dashboard/Account/AccountOverview.react.js @@ -23,7 +23,7 @@ import renderFlowFooterChanges from 'lib/renderFlowFooterChanges'; import styles from 'dashboard/Settings/Settings.scss'; import TextInput from 'components/TextInput/TextInput.react'; import Toolbar from 'components/Toolbar/Toolbar.react'; -import { dateStringUTC } from 'lib/DateUtils'; +import { dateString } from 'lib/DateUtils'; const DEFAULT_LABEL_WIDTH = 56; const XHR_KEY = 'AccountOverview'; @@ -136,7 +136,7 @@ export default class AccountOverview extends React.Component { }, { key: 'Expires', - value: dateStringUTC(new Date(key.expiresAt)), + value: dateString(new Date(key.expiresAt)), }, ], }))} /> diff --git a/src/dashboard/Data/Browser/BrowserTable.react.js b/src/dashboard/Data/Browser/BrowserTable.react.js index 0dd18f7157..8e5a6ca6f0 100644 --- a/src/dashboard/Data/Browser/BrowserTable.react.js +++ b/src/dashboard/Data/Browser/BrowserTable.react.js @@ -124,6 +124,7 @@ export default class BrowserTable extends React.Component { onPointerClick={this.props.onPointerClick} setRelation={this.props.setRelation} value={attr} + timezone={this.props.timezone} hidden={hidden} /> ); })} diff --git a/src/dashboard/Data/Browser/DataBrowser.react.js b/src/dashboard/Data/Browser/DataBrowser.react.js index cbef02f7df..a6014b5656 100644 --- a/src/dashboard/Data/Browser/DataBrowser.react.js +++ b/src/dashboard/Data/Browser/DataBrowser.react.js @@ -10,7 +10,7 @@ import BrowserToolbar from 'dashboard/Data/Browser/BrowserToolbar.react' import * as ColumnPreferences from 'lib/ColumnPreferences'; import ParseApp from 'lib/ParseApp'; import React from 'react'; -import PropTypes from 'lib/PropTypes'; +import PropTypes from 'lib/PropTypes'; import { SpecialClasses } from 'lib/Constants'; /** @@ -200,6 +200,7 @@ export default class DataBrowser extends React.Component { handleResize={this.handleResize.bind(this)} setEditing={this.setEditing.bind(this)} setCurrent={this.setCurrent.bind(this)} + timezone={this.context.currentApp.timezone} {...other} /> ); diff --git a/src/dashboard/Data/Jobs/Jobs.react.js b/src/dashboard/Data/Jobs/Jobs.react.js index 0a1651d989..478708b6b0 100644 --- a/src/dashboard/Data/Jobs/Jobs.react.js +++ b/src/dashboard/Data/Jobs/Jobs.react.js @@ -142,10 +142,11 @@ class Jobs extends TableView { ); } else if (this.props.params.section === 'status') { + let { timezone } = this.context.currentApp; return ( {data.jobName} - {DateUtils.dateStringUTC(new Date(data.createdAt))} + {DateUtils.dateString(new Date(data.createdAt), timezone)}
{data.message} diff --git a/src/dashboard/Data/Jobs/JobsForm.react.js b/src/dashboard/Data/Jobs/JobsForm.react.js index 643a957ba6..867a793bf2 100644 --- a/src/dashboard/Data/Jobs/JobsForm.react.js +++ b/src/dashboard/Data/Jobs/JobsForm.react.js @@ -23,7 +23,7 @@ import TextInput from 'components/TextInput/TextInput.react'; import TimeInput from 'components/TimeInput/TimeInput.react'; import Toggle from 'components/Toggle/Toggle.react'; import Toolbar from 'components/Toolbar/Toolbar.react'; -import { hoursFrom, dateStringUTC } from 'lib/DateUtils'; +import { hoursFrom, dateString } from 'lib/DateUtils'; export default class JobsForm extends DashboardView { constructor(props) { @@ -235,7 +235,7 @@ export default class JobsForm extends DashboardView { if (fields.immediate) { pieces.push(immediately, '.') } else { - pieces.push('on ', {dateStringUTC(fields.runAt)}, '.'); + pieces.push('on ', {dateString(fields.runAt, this.props.timezone)}, '.'); } if (fields.repeat) { pieces.push(' It will repeat '); diff --git a/src/dashboard/Settings/AppleCerts.react.js b/src/dashboard/Settings/AppleCerts.react.js index e30adac87a..2f119b2235 100644 --- a/src/dashboard/Settings/AppleCerts.react.js +++ b/src/dashboard/Settings/AppleCerts.react.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import PropTypes from 'lib/PropTypes'; +import PropTypes from 'lib/PropTypes'; import Modal from 'components/Modal/Modal.react'; import ParseApp from 'lib/ParseApp'; import PushCerts from 'components/PushCerts/PushCerts.react'; @@ -57,7 +57,8 @@ export default class AppleCerts extends React.Component { error={this.state.error} uploadPending={this.state.uploadPending} onUpload={this.handleUpload.bind(this)} - onDelete={this.handleDelete.bind(this)} /> + onDelete={this.handleDelete.bind(this)} + timezone={this.context.currentApp.timezone} /> {this.state.deletePending === null ? null : { + date[key] = String(date[key]); + if (date[key].length < 2) { + date[key] = '0' + date[key]; + } + }); + + full += date.hours + ':' + date.minutes + ':' + date.seconds + ' UTC'; return full; } diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js index 74b3956211..0f0f42ee37 100644 --- a/src/lib/ParseApp.js +++ b/src/lib/ParseApp.js @@ -37,6 +37,7 @@ export default class ParseApp { apiKey, serverURL, serverInfo, + timezone, production, iconName, primaryBackgroundColor, @@ -61,6 +62,7 @@ export default class ParseApp { this.production = production; this.serverURL = serverURL; this.serverInfo = serverInfo; + this.timezone = timezone; this.icon = iconName; this.primaryBackgroundColor=primaryBackgroundColor; this.secondaryBackgroundColor=secondaryBackgroundColor;