-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
743 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#! /usr/bin/env python3 | ||
|
||
from datetime import datetime, timezone | ||
import sys | ||
import json | ||
from pathlib import Path | ||
|
||
def main(): | ||
number_of_days = 1 | ||
try: | ||
number_of_days = int(sys.argv[1]) | ||
except Exception: | ||
pass | ||
|
||
info = {'presets':[], 'drafts':{}, 'timestamp': {'presets':'', 'drafts':''}} | ||
output_json = Path(__file__).with_name('presets-and-drafts.json') | ||
if output_json.exists(): | ||
info = json.loads(output_json.read_text()) | ||
|
||
known_draft_ids = set() | ||
for title in info['drafts']: | ||
for item in info['drafts'][title]: | ||
known_draft_ids.add(item['code']) | ||
drafts_dir = Path(__file__).parent / 'data' | ||
now = datetime.now(tz=timezone.utc) | ||
info['timestamp']['drafts'] = str(now) | ||
limit = now.timestamp() - (60*60*24*number_of_days) | ||
|
||
for subdir in drafts_dir.glob('*'): | ||
for f in subdir.glob('*.json'): | ||
mtime = f.stat().st_mtime | ||
if mtime > limit: | ||
try: | ||
data = json.loads(f.read_text()) | ||
draft_id = f.stem | ||
title = data['preset']['name'] | ||
host = data['nameHost'] | ||
guest = data['nameGuest'] | ||
if title not in info['drafts']: | ||
info['drafts'][title] = [] | ||
if draft_id not in known_draft_ids: | ||
info['drafts'][title].append({'code': draft_id, 'host':host, 'guest':guest, 'created':mtime}) | ||
known_draft_ids.add(draft_id) | ||
except json.decoder.JSONDecodeError: | ||
print(f'Not a valid json file: {f}') | ||
for title in info['drafts']: | ||
info['drafts'][title] = sorted(info['drafts'][title], reverse=True, key=lambda x: x['created']) | ||
output_json.write_text(json.dumps(info, sort_keys=True)) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#! /usr/bin/env python3 | ||
|
||
from datetime import datetime, timezone | ||
import sys | ||
import json | ||
from pathlib import Path | ||
|
||
def main(): | ||
number_of_days = 1 | ||
try: | ||
number_of_days = int(sys.argv[1]) | ||
except Exception: | ||
pass | ||
|
||
info = {'presets':[], 'drafts':{}, 'timestamp': {'presets':'', 'drafts':''}} | ||
output_json = Path(__file__).with_name('presets-and-drafts.json') | ||
if output_json.exists(): | ||
info = json.loads(output_json.read_text()) | ||
|
||
known_preset_ids = set(item['code'] for item in info['presets']) | ||
presets_dir = Path(__file__).parent / 'presets' | ||
now = datetime.now(tz=timezone.utc) | ||
info['timestamp']['presets'] = str(now) | ||
limit = now.timestamp() - (60*60*24*number_of_days) | ||
|
||
for f in presets_dir.glob('*.json'): | ||
mtime = f.stat().st_mtime | ||
if mtime > limit: | ||
try: | ||
data = json.loads(f.read_text()) | ||
preset_id = f.stem | ||
name = data['name'] | ||
if preset_id not in known_preset_ids: | ||
info['presets'].append({'code':preset_id, 'name':name, 'created':mtime}) | ||
known_preset_ids.add(preset_id) | ||
except json.decoder.JSONDecodeError: | ||
print(f'Not a valid json file: {f}') | ||
info['presets'] = sorted(info['presets'], key=lambda x: (x['name'], x['created'])) | ||
output_json.write_text(json.dumps(info, sort_keys=True)) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import * as React from "react"; | ||
import {Dispatch} from "redux"; | ||
import * as actions from "../../actions"; | ||
import {connect} from "react-redux"; | ||
import {ApplicationState, IPresetAndDraftList, IServerState} from "../../types"; | ||
import {withTranslation, WithTranslation} from "react-i18next"; | ||
import {Redirect, RouteComponentProps} from "react-router"; | ||
import PresetsAndDrafts from "./PresetsAndDrafts"; | ||
|
||
interface Props extends WithTranslation, RouteComponentProps<any> { | ||
apiKey: string | undefined; | ||
onSetApiKey: (apiKey: string | undefined) => void; | ||
} | ||
|
||
interface State { | ||
serverState: IServerState | ||
presetsAndDrafts?: IPresetAndDraftList | ||
} | ||
|
||
class AdminMain extends React.Component<Props, State> { | ||
|
||
constructor(props: Props) { | ||
super(props); | ||
this.state = { | ||
serverState: {maintenanceMode: false, hiddenPresetIds: []}, | ||
presetsAndDrafts: undefined, | ||
}; | ||
} | ||
|
||
private logout() { | ||
this.props.onSetApiKey(undefined); | ||
} | ||
|
||
private fetchState() { | ||
fetch('/api/state', { | ||
headers: { | ||
'X-Auth-Token': this.props.apiKey as string | ||
} | ||
}) | ||
.then((result) => { | ||
if (result.ok) { | ||
return result.json(); | ||
} | ||
this.props.onSetApiKey(undefined); | ||
return Promise.reject('Authorization failed'); | ||
}) | ||
.then((json) => this.setState({...this.state, serverState: json})); | ||
} | ||
|
||
private fetchPresetsAndDrafts() { | ||
fetch('/api/presets-and-drafts', { | ||
headers: { | ||
'X-Auth-Token': this.props.apiKey as string | ||
} | ||
}) | ||
.then((result) => { | ||
if (result.ok) { | ||
return result.json(); | ||
} | ||
this.props.onSetApiKey(undefined); | ||
return Promise.reject('Authorization failed'); | ||
}) | ||
.then((json) => this.setState({...this.state, presetsAndDrafts: json})); | ||
} | ||
|
||
private toggleMaintenanceMode() { | ||
fetch('/api/state', { | ||
headers: { | ||
'Content-Type': 'application/json; charset=UTF-8', | ||
'X-Auth-Token': this.props.apiKey as string, | ||
}, | ||
body: JSON.stringify({maintenanceMode: !this.state.serverState.maintenanceMode}), | ||
method: 'post' | ||
}) | ||
.then((result) => { | ||
if (result.ok) { | ||
return result.json(); | ||
} | ||
this.props.onSetApiKey(undefined); | ||
return Promise.reject('Authorization failed'); | ||
}) | ||
.then((json) => this.setState({...this.state, serverState: json})); | ||
} | ||
|
||
private saveHiddenPresetIds() { | ||
fetch('/api/state', { | ||
headers: { | ||
'Content-Type': 'application/json; charset=UTF-8', | ||
'X-Auth-Token': this.props.apiKey as string, | ||
}, | ||
body: JSON.stringify({hiddenPresetIds: this.state.serverState.hiddenPresetIds}), | ||
method: 'post' | ||
}) | ||
.then((result) => { | ||
if (result.ok) { | ||
return result.json(); | ||
} | ||
this.props.onSetApiKey(undefined); | ||
return Promise.reject('Authorization failed'); | ||
}) | ||
.then((json) => this.setState({...this.state, serverState: json})); | ||
} | ||
|
||
private reloadDraftArchive() { | ||
fetch('/api/reload-archive', { | ||
headers: { | ||
'Content-Type': 'application/json; charset=UTF-8', | ||
'X-Auth-Token': this.props.apiKey as string, | ||
}, | ||
body: JSON.stringify({hiddenPresetIds: this.state.serverState.hiddenPresetIds}), | ||
method: 'post' | ||
}) | ||
.then((result) => { | ||
if (result.ok) { | ||
return result.text(); | ||
} | ||
this.props.onSetApiKey(undefined); | ||
return Promise.reject('Authorization failed'); | ||
}) | ||
.then((text) => alert(text)); | ||
} | ||
|
||
componentDidMount() { | ||
this.fetchState(); | ||
this.fetchPresetsAndDrafts(); | ||
} | ||
|
||
public render() { | ||
|
||
if (!this.props.apiKey) { | ||
return (<Redirect to={'/admin/login'}/>); | ||
} | ||
|
||
return ( | ||
<div className={'content box'}> | ||
<h2>Admin Main</h2> | ||
<button className={'button'} onClick={() => this.logout()}>Logout</button> | ||
|
||
<h3>Presets and Drafts</h3> | ||
|
||
<PresetsAndDrafts data={this.state.presetsAndDrafts}/> | ||
|
||
<hr/> | ||
|
||
<h3>Maintenance Mode</h3> | ||
|
||
<p> | ||
If maintenance mode is active, no drafts can be created. | ||
Use this to e. g. create a window for an upgrade that does not disrupt ongoing drafts.<br/> | ||
<b>Do not forget to disable maintenance mode afterwards!</b> | ||
</p> | ||
|
||
<p className="control"> | ||
<input id="toggleMaintenance" type="checkbox" name="toggleMaintenance" | ||
className="switch is-small is-rounded is-info" | ||
checked={this.state.serverState.maintenanceMode} onChange={() => { | ||
this.toggleMaintenanceMode() | ||
}}/> | ||
<label htmlFor="toggleMaintenance">Maintenance Mode | ||
is {this.state.serverState.maintenanceMode ? 'active' : 'off'}</label> | ||
</p> | ||
|
||
|
||
<h3>Hidden Preset IDs</h3> | ||
|
||
<p> | ||
Drafts for hidden Preset IDs are not added to the list of ongoing and recent drafts.<br/> | ||
When a Preset ID is added to this list, existing drafts are not removed from the list of ongoing and | ||
recent drafts.<br/> | ||
When a Preset ID is removed from this list, existing drafts are not added to that list | ||
retroactively.<br/> | ||
Write one Preset ID per line in the textarea below. | ||
</p> | ||
|
||
<textarea className="textarea" placeholder="abcdef" id="hiddenPresetIds" | ||
value={this.state.serverState.hiddenPresetIds.join('\n')} | ||
onChange={(event) => { | ||
this.setState({ | ||
...this.state, | ||
serverState: { | ||
maintenanceMode: this.state.serverState.maintenanceMode, | ||
hiddenPresetIds: event.target.value.split('\n') | ||
} | ||
}); | ||
}} | ||
></textarea> | ||
<button className={'button'} onClick={() => this.saveHiddenPresetIds()}>Save hidden Preset IDs</button> | ||
|
||
|
||
<h3>Reload Draft Archive</h3> | ||
|
||
<p> | ||
After moving stored drafts around on the server, the draft archive has to be reloaded so that the | ||
files can be found again. | ||
</p> | ||
|
||
<button className={'button'} onClick={() => this.reloadDraftArchive()}>Reload Draft Archive</button> | ||
|
||
</div> | ||
); | ||
} | ||
} | ||
|
||
export function mapStateToProps(state: ApplicationState) { | ||
return { | ||
apiKey: state.admin.apiKey, | ||
} | ||
} | ||
|
||
export function mapDispatchToProps(dispatch: Dispatch<actions.Action>) { | ||
return { | ||
onSetApiKey: (apiKey: string | undefined) => dispatch(actions.setApiKey(apiKey)), | ||
} | ||
} | ||
|
||
export default withTranslation()(connect(mapStateToProps, mapDispatchToProps)(AdminMain)); |
Oops, something went wrong.