diff options
Diffstat (limited to 'app')
28 files changed, 952 insertions, 123 deletions
diff --git a/app/client/api/crud.fetch.js b/app/client/api/crud.fetch.js index 421510b..716ab3e 100644 --- a/app/client/api/crud.fetch.js +++ b/app/client/api/crud.fetch.js @@ -10,7 +10,7 @@ export function crud_fetch(type, tag) { }, show: id => { - return fetch(uri + id) + return fetch(uri + id, _get_headers(), _get_headers()) .then(req => req.json()) .catch(error) }, @@ -45,15 +45,17 @@ function _get_url(_url, data) { function _get_headers() { return { method: 'GET', + credentials: 'same-origin', headers: { 'Accept': 'application/json', }, } } -function post(data) { +export function post(data) { return { method: 'POST', body: JSON.stringify(data), + credentials: 'same-origin', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' @@ -64,25 +66,28 @@ export function postBody(data) { return { method: 'POST', body: data, + credentials: 'same-origin', headers: { 'Accept': 'application/json', }, } } -function put(data) { +export function put(data) { return { method: 'PUT', body: JSON.stringify(data), + credentials: 'same-origin', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, } } -function destroy(data) { +export function destroy(data) { return { method: 'DELETE', body: JSON.stringify(data), + credentials: 'same-origin', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' diff --git a/app/client/auth/auth.actions.js b/app/client/auth/auth.actions.js new file mode 100644 index 0000000..c4d9b52 --- /dev/null +++ b/app/client/auth/auth.actions.js @@ -0,0 +1,86 @@ +import types from '../types' +import { put } from '../api/crud.fetch' + +export const setToken = (data) => { + return { type: types.auth.set_token, data } +} +export const setReturnTo = (data) => { + return { type: types.auth.set_return_to, data } +} +export const setError = (data) => { + return { type: types.auth.set_error, data } +} +export const setCurrentUser = (data) => { + return { type: types.auth.set_current_user, data } +} +export function logout() { + return { type: types.auth.logout_user } +} +export function initialized() { + return { type: types.auth.initialized } +} +export function loading() { + return { type: types.auth.loading } +} + +export function InvalidCredentialsException(message) { + this.message = message + this.name = 'InvalidCredentialsException' +} + +const api = { + login: '/api/login', + logout: '/api/logout', + signup: '/api/signup', + checkin: '/api/checkin', +} + +export function login(username, password) { + return (dispatch) => { + dispatch(loading()) + fetch(api.login, put({ + username, + password + })) + .then(req => req.json()) + .then(data => { + console.log(data) + dispatch(setCurrentUser(data.user)) + }) + .catch(error => { + console.error(error) + dispatch(setError(true)) + }) + } +} + +export function signup(data) { + return (dispatch) => { + dispatch(loading()) + fetch(api.signup, put(data)) + .then(req => req.json()) + .then(data => { + console.log(data) + dispatch(setCurrentUser(data.user)) + }) + .catch(error => { + console.error(error) + dispatch(initialized()) + }) + } +} + +export function checkin() { + return (dispatch) => { + dispatch(loading()) + fetch(api.checkin, put({})) + .then(req => req.json()) + .then(data => { + dispatch(setCurrentUser(data.user)) + }) + .catch(error => { + console.error(error) + dispatch(initialized()) + }) + } +} diff --git a/app/client/auth/auth.gate.js b/app/client/auth/auth.gate.js new file mode 100644 index 0000000..087dfc6 --- /dev/null +++ b/app/client/auth/auth.gate.js @@ -0,0 +1,63 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { BrowserRouter, Route, Switch, Redirect, withRouter } from 'react-router-dom' +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; + +import * as authActions from './auth.actions'; + +import Login from './login.component'; +import Logout from './logout.component'; +import Signup from './signup.component'; + +import { randint } from '../util/math' + +class AuthRouter extends Component { + render(){ + return ( + <BrowserRouter> + <div> + <div className="diamond"></div> + <Switch> + <Route exact path='/' component={Login} /> + <Route exact path='/login' component={Login} /> + <Route exact path='/logout' component={Logout} /> + <Route exact path='/signup' component={Signup} /> + <Route component={props => { + this.props.actions.setReturnTo(props.location.pathname) + return ( + <Redirect to="/login" /> + ) + }} /> + </Switch> + </div> + </BrowserRouter> + ) + } + componentDidMount(){ + document.querySelector('.diamond').style.backgroundImage = 'linear-gradient(' + (randint(40)-5) + 'deg, #fde, #ffe)' + } +} + +class AuthGate extends Component { + render(){ + if (!this.props.auth.initialized) { + return <div className='loading'>Loading</div> + } + if (this.props.auth.isAuthenticated) return <div>{this.props.children}</div> + return <AuthRouter {...this.props} /> + } + componentDidMount(){ + this.props.actions.checkin() + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(AuthGate); diff --git a/app/client/auth/auth.reducer.js b/app/client/auth/auth.reducer.js new file mode 100644 index 0000000..80b1ec5 --- /dev/null +++ b/app/client/auth/auth.reducer.js @@ -0,0 +1,92 @@ +import types from '../types' + +const authInitialState = { + token: null, + user: {}, + groups: {}, + initialized: false, + loading: false, + isAuthenticated: false, + returnTo: null, +} + +const auth = (state = authInitialState, action) => { + switch(action.type) { + case types.auth.set_token: + return { + ...state, + token: action.data, + isAuthenticated: !!action.data, + loading: false, + error: null, + } + + case types.auth.initialized: + return { + ...state, + loading: false, + initialized: true, + error: null, + } + + case types.auth.loading: + return { + ...state, + loading: true, + error: null, + } + + case types.auth.set_current_user: + return { + ...state, + loading: false, + initialized: true, + isAuthenticated: true, + user: action.data, + error: null, + } + + case types.auth.set_return_to: + return { + ...state, + returnTo: action.data, + } + + case types.auth.logout_user: + return { + ...authInitialState + } + + case types.auth.set_error: + return { + ...state, + loading: false, + error: action.data, + } + + case types.auth.loading: + // const initial_state_el = document.querySelector('#initial_state') + // if (initial_state_el) { + // try { + // const initial_state = JSON.parse(initial_state_el.innerHTML) + // if (initial_state && initial_state.auth && initial_state.auth.user) { + // console.log(initial_state.auth.user) + // return { + // ...state, + // user: { + // ...initial_state.auth.user, + // } + // } + // } + // } catch (e) { + // console.error("error loading initial state") + // } + // } + return state + + default: + return state + } +} + +export default auth diff --git a/app/client/auth/index.js b/app/client/auth/index.js new file mode 100644 index 0000000..5e6b2b0 --- /dev/null +++ b/app/client/auth/index.js @@ -0,0 +1,11 @@ +import Gate from './auth.gate'; +import Login from './login.component'; +import Logout from './logout.component'; +import Signup from './signup.component'; + +export default { + Gate, + Login, + Logout, + Signup, +}
\ No newline at end of file diff --git a/app/client/auth/login.component.js b/app/client/auth/login.component.js new file mode 100644 index 0000000..2ef01a6 --- /dev/null +++ b/app/client/auth/login.component.js @@ -0,0 +1,89 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +// import { Link } from 'react-router-dom'; +import * as authActions from './auth.actions'; + +import { Group, Param, TextInput, Button } from '../common'; + +class Login extends Component { + state = { + username: '', + password: '', + } + constructor() { + super() + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + handleChange(value, name) { + this.setState({ + [name]: value, + error: null, + }) + } + handleSubmit(e) { + e.preventDefault() + if (this.props.auth.loading) return + this.props.actions.login(this.state.username, this.state.password) + } + render(){ + if (this.props.auth.isAuthenticated) { + let { returnTo } = this.props.auth + if (!returnTo || returnTo.match(/(login|logout|signup)/i)) { + returnTo = '/' + } + return <Redirect to={returnTo} /> + } + return ( + <form onSubmit={this.handleSubmit}> + <h1>Log in</h1><br /> + <Group> + <TextInput + autofocus + autocapitalize="off" + autocomplete="off" + title="Username" + name="username" + type="text" + value={this.state.username} + onInput={this.handleChange} + /> + <TextInput + title="Password" + name="password" + type="password" + value={this.state.password} + onInput={this.handleChange} + /> + <Button + loading={this.props.auth.loading} + > + Login + </Button> + {this.renderAuthError()} + </Group> + </form> + ) + } + renderAuthError(){ + if (this.props.auth.error) { + return ( + <div className='form-input-hint'>{"There was an error logging you in (bad password?)"}</div> + ) + } + return null + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Login); diff --git a/app/client/auth/logout.component.js b/app/client/auth/logout.component.js new file mode 100644 index 0000000..bcc3bce --- /dev/null +++ b/app/client/auth/logout.component.js @@ -0,0 +1,24 @@ +import { h, Component } from 'preact'; +// import PropTypes from 'prop-types'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import * as authActions from './auth.actions'; + +class Logout extends Component { + componentWillMount(props){ + this.props.actions.logout() + } + render(){ + return <Redirect to="/" /> + } +} + +const mapStateToProps = (state) => ({ +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators(authActions, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Logout); diff --git a/app/client/auth/signup.component.js b/app/client/auth/signup.component.js new file mode 100644 index 0000000..4882681 --- /dev/null +++ b/app/client/auth/signup.component.js @@ -0,0 +1,104 @@ +import { h, Component } from 'preact'; +import { bindActionCreators } from 'redux'; +import { connect } from 'react-redux'; +import { Redirect } from 'react-router-dom'; +import * as actions from './auth.actions'; + +import { Group, Param, TextInput, Button } from '../common'; + +class Signup extends Component { + state = { + username: '', + password: '', + password2: '', + } + constructor() { + super() + this.handleChange = this.handleChange.bind(this) + this.handleSubmit = this.handleSubmit.bind(this) + } + handleChange(value, name) { + this.setState({ + [name]: value, + }) + } + validate(){ + if (!this.state.password || this.state.password !== this.state.password2) { + return false + } + return true + } + handleSubmit(e) { + e.preventDefault() + if (this.props.auth.loading) return + if (!this.validate()) { + return this.props.actions.setError('bad password') + } + let { ...user } = this.state + this.props.actions.signup(user) + } + render(){ + if (this.props.auth.isAuthenticated) { + let { returnTo } = this.props.auth + if (!returnTo || returnTo.match(/(api|login|logout|signup)/i)) { + returnTo = '/' + } + return <Redirect to={returnTo} /> + } + return ( + <form onSubmit={this.handleSubmit}> + <h1>New account</h1><br /> + <Group> + <TextInput + autofocus + autocapitalize="off" + autocomplete="off" + title="Username" + name="username" + type="text" + value={this.state.username} + onInput={this.handleChange} + /> + <TextInput + title="Password" + name="password" + type="password" + value={this.state.password} + onInput={this.handleChange} + /> + <TextInput + title="Password again :)" + name="password2" + type="password" + value={this.state.password2} + onInput={this.handleChange} + /> + <Button + loading={this.props.auth.loading} + > + Sign up + </Button> + {this.renderAuthError()} + </Group> + </form> + ) + } + renderAuthError(){ + if (this.props.auth.error) { + return ( + <div className='form-input-hint'>{"Please doublecheck the form (o=_o~~)"}</div> + ) + } + return <div className='form-input-hint'></div> + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth, +}); + +const mapDispatchToProps = (dispatch) => ({ + actions: bindActionCreators({ ...actions }, dispatch) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(Signup); diff --git a/app/client/common/augmentationGrid.component.js b/app/client/common/augmentationGrid.component.js new file mode 100644 index 0000000..af77f6c --- /dev/null +++ b/app/client/common/augmentationGrid.component.js @@ -0,0 +1,47 @@ +import { h, Component } from 'preact' + +import Group from './group.component' +import Param from './param.component' +import Button from './button.component' +import ButtonGrid from './buttonGrid.component' + +export default class AugmentationGrid extends Component { + state = { + x: 0, y: 0, sum: 0, + } + render() { + let { + make, take, checkpoint, + onTrain, onAugment, + } = this.props + let { + x, y, sum + } = this.state + let rows = [] + return ( + <Group className='augmentationGrid'> + <ButtonGrid + x={make} + y={take} + max={5000} + onHover={(x, y) => this.setState({ x, y })} + onClick={(x, y) => { + this.setState({ sum: sum + x * y }) + onAugment(y, x) + }} + /> + <Param title='Name'>{checkpoint.name}</Param> + <Param title='Take'>{y}</Param> + <Param title='Make'>{x}</Param> + <Param title='Will add to dataset'>{x * y}</Param> + <Param title='Total added this epoch'>{sum}</Param> + <Param title='Sequence length'>{checkpoint.sequenceCount}</Param> + <Param title='Dataset size'>{checkpoint.datasetCount}</Param> + <Button onClick={() => { + this.setState({ sum: 0 }) + onTrain() + }}>Train</Button> + </Group> + ) + } +} diff --git a/app/client/common/buttonGrid.component.js b/app/client/common/buttonGrid.component.js new file mode 100644 index 0000000..6c7c105 --- /dev/null +++ b/app/client/common/buttonGrid.component.js @@ -0,0 +1,51 @@ +import { h, Component } from 'preact' + +export default class ButtonGrid extends Component { + state = { + x: 0, y: 0, + } + + render() { + const { + x: _x, + y: _y, + } = this.state + const { + x: X, + y: Y, + max = Infinity, + onClick, + onHover, + } = this.props + return ( + <table className='buttonGrid'> + <tr className='row'> + <th>{" "}</th> + {X.map(x => ( + <th className={x === _x && 'bold'}>{x}</th> + ))} + </tr> + {Y.map(y => ( + <tr className='row'> + <th className={y === _y && 'bold'}>{y}</th> + {X.map(x => ( + <td> + {x * y > max ? " " : + <button + onClick={() => onClick(x, y)} + onMouseEnter={() => { + this.setState({ x, y }) + onHover(x, y) + }} + > + {" "} + </button> + } + </td> + ))} + </tr> + ))} + </table> + ) + } +} diff --git a/app/client/common/currentTask.component.js b/app/client/common/currentTask.component.js index a4d9750..3c71a88 100644 --- a/app/client/common/currentTask.component.js +++ b/app/client/common/currentTask.component.js @@ -22,10 +22,10 @@ function CurrentTask ({ cpu, gpu, processor }) { const { last_message, pid, task } = p const { activity, epoch, epochs, dataset, module } = task return ( - <div> + <div className='currentTask'> #{pid}: <b>{module} {activity}</b> <i>{dataset}</i> - {epochs - ? <span>{epoch} epoch{util.courtesy_s(epoch)}</span> + {!!epochs + ? <span>{epochs} epoch{util.courtesy_s(epochs)}</span> : ""} {epoch ? <span>(currently #{epoch})</span> diff --git a/app/client/common/index.js b/app/client/common/index.js index 13b3189..e6baafc 100644 --- a/app/client/common/index.js +++ b/app/client/common/index.js @@ -1,4 +1,7 @@ +import AudioPlayer from './audioPlayer/audioPlayer.component' +import AugmentationGrid from './augmentationGrid.component' import Button from './button.component' +import ButtonGrid from './buttonGrid.component' import Checkbox from './checkbox.component' import CurrentTask from './currentTask.component' import { FileList, FileRow } from './fileList.component' @@ -22,11 +25,12 @@ import * as Views from './views' export { Views, - Loading, Progress, Header, + Loading, Progress, Header, AudioPlayer, FolderList, FileList, FileRow, FileUpload, Gallery, Player, Group, ParamGroup, Param, TextInput, NumberInput, Slider, Select, SelectGroup, Button, Checkbox, CurrentTask, TaskList, + ButtonGrid, AugmentationGrid, }
\ No newline at end of file diff --git a/app/client/common/taskList.component.js b/app/client/common/taskList.component.js index 710753f..c1ed38a 100644 --- a/app/client/common/taskList.component.js +++ b/app/client/common/taskList.component.js @@ -27,7 +27,7 @@ class TaskList extends Component { const task = pair[1] const { dataset } = task let dataset_link, label = dataset; - console.log(task) + // console.log(task) switch (task.activity) { case 'train': if (task.epoch === 0) { diff --git a/app/client/common/textInput.component.js b/app/client/common/textInput.component.js index d429944..d3b16ad 100644 --- a/app/client/common/textInput.component.js +++ b/app/client/common/textInput.component.js @@ -34,6 +34,9 @@ class TextInput extends Component { value={this.state.changed ? this.state.value : this.props.value} onInput={this.handleInput} onKeydown={this.handleKeydown} + autofocus={this.props.autofocus} + autoComplete={this.props.autocomplete} + autoCapitalize={this.props.autocapitalize || 'off'} placeholder={this.props.placeholder} autofocus={this.props.autofocus} className={this.props.className || ''} diff --git a/app/client/index.jsx b/app/client/index.jsx index fd4679c..9c18251 100644 --- a/app/client/index.jsx +++ b/app/client/index.jsx @@ -1,14 +1,14 @@ import { h, render } from 'preact' import { Provider } from 'react-redux' -import { BrowserRouter, Route } from 'react-router-dom' +import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom' // import client from './client' import { store, history } from './store' import * as socket from './socket' import util from './util' -import Header from './common/header.component' -import AudioPlayer from './common/audioPlayer/audioPlayer.component' +import Auth from './auth' +import { Header, AudioPlayer } from './common' import System from './system/system.component' import Dashboard from './dashboard/dashboard.component' import modules from './modules' @@ -22,16 +22,20 @@ const module_list = Object.keys(modules).map(name => { const app = ( <Provider store={store}> - <BrowserRouter> - <div> - <Route exact path='/' component={Dashboard} /> - <Route path='/system/' component={System} /> - <Route path='/dashboard/' component={Dashboard} /> - {module_list} - <Route path='/' component={Header} /> - <AudioPlayer /> - </div> - </BrowserRouter> + <Auth.Gate> + <BrowserRouter> + <Switch> + <Route exact path='/' component={Dashboard} /> + <Route exact path='/system/' component={System} /> + <Route exact path='/dashboard/' component={Dashboard} /> + <Route exact path='/logout/' component={Auth.Logout} /> + {module_list} + <Route exact path='/' component={Header} /> + <Route component={() => <Redirect to="/" />} /> + </Switch> + </BrowserRouter> + <AudioPlayer /> + </Auth.Gate> </Provider> ) diff --git a/app/client/modules/pix2pixhd/pix2pixhd.actions.js b/app/client/modules/pix2pixhd/pix2pixhd.actions.js index c1cd2b1..a17eeab 100644 --- a/app/client/modules/pix2pixhd/pix2pixhd.actions.js +++ b/app/client/modules/pix2pixhd/pix2pixhd.actions.js @@ -200,4 +200,30 @@ export const list_epochs = (checkpoint_name) => (dispatch) => { }, }) }) +} + +export const count_dataset = (checkpoint_name) => (dispatch) => { + const module = pix2pixhdModule.name + util.allProgress([ + actions.socket.count_directory({ module, dir: 'sequences/' + checkpoint_name + '/' }), + actions.socket.count_directory({ module, dir: 'datasets/' + checkpoint_name + '/train_A/' }), + ], (percent, i, n) => { + console.log('pix2pixhd load progress', i, n) + dispatch({ + type: types.app.load_progress, + progress: { i, n }, + data: { module: 'pix2pixhd' }, + }) + }).then(res => { + const [sequenceCount, datasetCount] = res //, datasets, results, output, datasetUsage, lossReport] = res + console.log(sequenceCount, datasetCount) + dispatch({ + type: types.pix2pixhd.load_dataset_count, + data: { + name: checkpoint_name, + sequenceCount, + datasetCount, + } + }) + }) }
\ No newline at end of file diff --git a/app/client/modules/pix2pixhd/pix2pixhd.reducer.js b/app/client/modules/pix2pixhd/pix2pixhd.reducer.js index c3d52a3..5a2afc0 100644 --- a/app/client/modules/pix2pixhd/pix2pixhd.reducer.js +++ b/app/client/modules/pix2pixhd/pix2pixhd.reducer.js @@ -8,6 +8,11 @@ const pix2pixhdInitialState = { folder_id: 0, data: null, results: null, + checkpoint: { + name: '', + sequenceCount: 0, + datasetCount: 0, + } } const pix2pixhdReducer = (state = pix2pixhdInitialState, action) => { @@ -21,6 +26,11 @@ const pix2pixhdReducer = (state = pix2pixhdInitialState, action) => { ...state, results: action.results, } + case types.pix2pixhd.load_dataset_count: + return { + ...state, + checkpoint: action.data, + } case types.file.destroy: console.log('file destroy', state.results) return { diff --git a/app/client/modules/pix2pixhd/views/pix2pixhd.train.js b/app/client/modules/pix2pixhd/views/pix2pixhd.train.js index 9c8aacc..06caa5a 100644 --- a/app/client/modules/pix2pixhd/views/pix2pixhd.train.js +++ b/app/client/modules/pix2pixhd/views/pix2pixhd.train.js @@ -11,7 +11,8 @@ import { FileList, FileRow, Select, SelectGroup, Group, Param, Button, TextInput, NumberInput, - CurrentTask, TaskList + CurrentTask, TaskList, + AugmentationGrid, } from '../../../common' import DatasetForm from '../../../dataset/dataset.form' import NewDatasetForm from '../../../dataset/dataset.new' @@ -32,30 +33,6 @@ class Pix2PixHDTrain extends Component { augment_take: 100, augment_make: 20, } - this.short_presets = [ - { augment_take: 100, augment_make: 5 }, - { augment_take: 200, augment_make: 5 }, - { augment_take: 200, augment_make: 3 }, - { augment_take: 50, augment_make: 10 }, - { augment_take: 100, augment_make: 10 }, - { augment_take: 1000, augment_make: 1 }, - ] - this.medium_presets = [ - { augment_take: 30, augment_make: 20 }, - { augment_take: 20, augment_make: 30 }, - { augment_take: 30, augment_make: 30 }, - { augment_take: 50, augment_make: 20 }, - { augment_take: 20, augment_make: 50 }, - { augment_take: 15, augment_make: 70 }, - ] - this.long_presets = [ - { augment_take: 2, augment_make: 100 }, - { augment_take: 2, augment_make: 200 }, - { augment_take: 5, augment_make: 100 }, - { augment_take: 5, augment_make: 200 }, - { augment_take: 10, augment_make: 100 }, - { augment_take: 10, augment_make: 200 }, - ] } componentWillMount(){ const id = this.props.match.params.id || localStorage.getItem('pix2pixhd.last_id') @@ -75,6 +52,7 @@ class Pix2PixHDTrain extends Component { if (prevState.checkpoint_name !== this.state.checkpoint_name) { this.setState({ epoch: 'latest' }) this.props.actions.list_epochs(this.state.checkpoint_name) + this.props.actions.count_dataset(this.state.checkpoint_name) } } handleChange(name, value){ @@ -84,7 +62,6 @@ class Pix2PixHDTrain extends Component { interrupt(){ this.props.actions.queue.stop_task('gpu') } - render(){ if (this.props.pix2pixhd.loading) { return <Loading progress={this.props.pix2pixhd.progress} /> @@ -164,52 +141,41 @@ class Pix2PixHDTrain extends Component { <Button title="Make a movie without augmenting" value="Generate" - onClick={() => this.props.remote.augment_task(this.state.checkpoint_name, { ...this.state, no_symlinks: true, mov: true, folder_id: this.props.pix2pixhd.data.resultsFolder.id })} - /> - </Group> - <Group title='Augmentation Presets'> - <Param title="Short Recursion"> - <div> - {this.short_presets.map(p => ( - <button onClick={() => this.props.remote.augment_task(this.state.checkpoint_name, p)}> - {p.augment_take}{'x'}{p.augment_make} - </button> - ))} - </div> - </Param> - <Param title="Medium Recursion"> - <div> - {this.medium_presets.map(p => ( - <button onClick={() => this.props.remote.augment_task(this.state.checkpoint_name, p)}> - {p.augment_take}{'x'}{p.augment_make} - </button> - ))} - </div> - </Param> - <Param title="Long Recursion"> - <div> - {this.long_presets.map(p => ( - <button onClick={() => this.props.remote.augment_task(this.state.checkpoint_name, p)}> - {p.augment_take}{'x'}{p.augment_make} - </button> - ))} - </div> - </Param> - </Group> - - <Group title='Train'> - <Button - title="Train one epoch" - value="Train" - onClick={() => this.props.remote.train_task(this.state.checkpoint_name, pix2pixhd.folder_id, 1)} + onClick={() => { + this.props.remote.augment_task(this.state.checkpoint_name, { + ...this.state, + no_symlinks: true, + mov: true, + folder_id: this.props.pix2pixhd.data.resultsFolder.id + }) + }} /> </Group> - - <Group title='Clear'> - <Button - title="Delete recursive frames" - value="Clear" - onClick={() => this.props.remote.clear_recursive_task(this.state.checkpoint_name)} + <Group title='Augmentation Grid'> + <AugmentationGrid + checkpoint={this.props.pix2pixhd.checkpoint} + take={[1,2,3,4,5,10,15,20,25,50,75,100,200,300,400,500,1000]} + make={[1,2,3,4,5,10,15,20,25,50,75,100,200,]} + onAugment={(augment_take, augment_make) => { + this.props.remote.augment_task(this.state.checkpoint_name, { + ...this.state, + augment_take, + augment_make, + }) + }} + onTrain={() => { + this.props.remote.train_task(this.state.checkpoint_name, pix2pixhd.folder_id, 1) + setTimeout(() => { // auto-generate epoch demo + this.props.remote.augment_task(this.state.checkpoint_name, { + ...this.state, + augment_take: 10, + augment_make: 150, + no_symlinks: true, + mov: true, + folder_id: this.props.pix2pixhd.data.resultsFolder.id + }) + }, 250) + }} /> </Group> diff --git a/app/client/socket/socket.actions.js b/app/client/socket/socket.actions.js index 78b0517..b80a0fa 100644 --- a/app/client/socket/socket.actions.js +++ b/app/client/socket/socket.actions.js @@ -4,6 +4,7 @@ import { socket } from './socket.connection' export const run_system_command = opt => syscall_async('run_system_command', opt) export const disk_usage = opt => syscall_async('run_system_command', { cmd: 'du', ...opt }) export const list_directory = opt => syscall_async('list_directory', opt).then(res => res.files) +export const count_directory = opt => syscall_async('count_directory', opt).then(res => res.count) export const list_sequences = opt => syscall_async('list_sequences', opt).then(res => res.sequences) export const run_script = opt => syscall_async('run_script', opt) export const upload_file = opt => syscall_async('upload_file', opt) diff --git a/app/client/store.js b/app/client/store.js index 8ffab15..654b22d 100644 --- a/app/client/store.js +++ b/app/client/store.js @@ -6,6 +6,7 @@ import createHistory from 'history/createBrowserHistory' import { routerReducer } from 'react-router-redux' // import navReducer from './nav.reducer' +import authReducer from './auth/auth.reducer' import systemReducer from './system/system.reducer' import dashboardReducer from './dashboard/dashboard.reducer' import liveReducer from './live/live.reducer' @@ -15,6 +16,7 @@ import audioPlayerReducer from './common/audioPlayer/audioPlayer.reducer' import { moduleReducer } from './modules/module.reducer' const appReducer = combineReducers({ + auth: authReducer, system: systemReducer, dashboard: dashboardReducer, live: liveReducer, diff --git a/app/client/system/system.actions.js b/app/client/system/system.actions.js index a34bd58..ccd9acd 100644 --- a/app/client/system/system.actions.js +++ b/app/client/system/system.actions.js @@ -25,6 +25,17 @@ export const listDirectory = (opt) => (dispatch) => { }) } +export const countDirectory = (opt) => (dispatch) => { + dispatch({ type: types.system.counting_directory, opt }) + socket.actions.count_directory(opt) + .then(data => { + dispatch({ + type: types.system.count_directory, + data: data, + }) + }) +} + export const changeTool = (tool) => { localStorage.setItem('system.last_tool', tool) return { type: types.app.change_tool, tool } diff --git a/app/client/types.js b/app/client/types.js index 2df494b..cfb590a 100644 --- a/app/client/types.js +++ b/app/client/types.js @@ -11,6 +11,8 @@ export default { rpc_disconnected: 'SYSTEM_RPC_DISCONNECTED', list_directory: 'SYSTEM_LIST_DIRECTORY', listing_directory: 'SYSTEM_LISTING_DIRECTORY', + count_directory: 'SYSTEM_COUNT_DIRECTORY', + counting_directory: 'SYSTEM_COUNTING_DIRECTORY', stdout: 'SYSTEM_STDOUT', stderr: 'SYSTEM_STDERR', }, @@ -34,6 +36,15 @@ export default { 'progress', 'epoch', ]), + auth: crud_type('auth', [ + 'set_token', + 'set_error', + 'set_current_user', + 'logout_user', + 'loading', + 'initialized', + 'set_return_to', + ]), socket: { connect: 'SOCKET_CONNECT', connect_error: 'SOCKET_CONNECT_ERROR', @@ -108,7 +119,7 @@ export default { 'init', 'set_folder' ]), pix2pixhd: with_type('pix2pixhd', [ - 'init', 'set_folder', 'load_results' + 'init', 'set_folder', 'load_results', 'load_dataset_count' ]), pix2wav: with_type('pix2wav', [ 'init', 'set_folder' diff --git a/app/relay/remote.js b/app/relay/remote.js index 6911572..1c9875f 100644 --- a/app/relay/remote.js +++ b/app/relay/remote.js @@ -116,6 +116,16 @@ remote.on('system', (data) => { }) }) break + case 'count_directory': + runner.count_directory(data.payload, count => { + remote.emit('system_res', { + type: 'count_directory', + dir: data.payload, + uuid: data.uuid, + count, + }) + }) + break case 'list_sequences': runner.list_sequences(data.payload, sequences => { remote.emit('system_res', { diff --git a/app/relay/runner.js b/app/relay/runner.js index 2bd0c56..44f2554 100644 --- a/app/relay/runner.js +++ b/app/relay/runner.js @@ -195,6 +195,14 @@ export function list_directory(opt, cb) { }) } +export function count_directory(opt, cb) { + const dir = module_dir(opt, opt.dir) + if (!dir) return cb([]) + fs.readdir(dir, (err, files) => { + err ? cb(err) : cb(files.length) + }) +} + // list the contents of a directory of sequences export function list_sequences(opt, cb) { list_directory(opt, (files, root_dir) => { diff --git a/app/server/db/model.js b/app/server/db/model.js index dd851bf..c829c85 100644 --- a/app/server/db/model.js +++ b/app/server/db/model.js @@ -17,9 +17,8 @@ module.exports = function modelScope(type, db_model, _props) { crud: crud, index: (query) => { - - return new Promise( (resolve, reject) => { - crud.index(query).then( (data) => { + return new Promise((resolve, reject) => { + crud.index(query).then(data => { if (! props.hasOne) { resolve(data ? data.toJSON() : []) @@ -27,13 +26,13 @@ module.exports = function modelScope(type, db_model, _props) { else { let recs = data.toJSON() const loader = new Loader () - loader.onReady( () => { + loader.onReady(() => { // console.log(type, 'ready') resolve(recs) }) // console.log('hasOne') loader.register('hasOne') - Object.keys(props.hasOne).forEach( (key,i) => { + Object.keys(props.hasOne).forEach((key, i) => { loader.register(key) // console.log('register', key) const type = props.hasOne[key] @@ -45,7 +44,7 @@ module.exports = function modelScope(type, db_model, _props) { }) // console.log('\n\n%%%%%%%%%%%%%%%%%%%%%%%% index > hasOne ' + key + '\n\n\n') // console.log(recs.length, Object.keys(id_lookup).length) - db_crud(type).show_ids(Object.keys(id_lookup)).then( (sub_recs) => { + db_crud(type).show_ids(Object.keys(id_lookup)).then(sub_recs => { // console.log(key, 'sub_recs', sub_recs) const short_key = key.replace('_id','') sub_recs.toJSON().forEach(rec => { @@ -57,49 +56,51 @@ module.exports = function modelScope(type, db_model, _props) { }) loader.ready('hasOne') } - }) // }).catch( () => res.sendStatus(500) ) + }) }) }, - show: (id) => { - return new Promise( (resolve, reject) => { - crud.show(id).then( (data) => { - if (! props.hasOne) { + show: (id, field = 'id') => { + return new Promise((resolve, reject) => { + crud.show(id, field).then(data => { + if (!data) { + resolve() + } else if (! props.hasOne) { resolve(data.toJSON()) } else { let rec = data.toJSON() const loader = new Loader () - loader.onReady( () => { + loader.onReady(() => { resolve(rec) }) loader.register('hasOne') - Object.keys(props.hasOne).forEach( (key,i) => { + Object.keys(props.hasOne).forEach((key, i) => { loader.register(key) const type = props.hasOne[key] - db_crud(type).show(rec[key + '_id']).then( (sub_rec) => { + db_crud(type).show(rec[key + '_id']).then((sub_rec) => { rec[key] = sub_rec loader.ready(key) }) }) loader.ready('hasOne') } - }) // .catch( (err) => res.sendStatus(500) ) + }) }) }, findOrCreate: (data) => { - return new Promise( (resolve, reject) => { + return new Promise((resolve, reject) => { let query = Object.assign({}, data) query.limit = 1 - crud.index(query).then( (recs) => { + crud.index(query).then((recs) => { if (recs && recs.length) { const rec = recs.at(0) // console.log('found rec', data.name) return resolve(rec) } // console.log('creating rec', data.name) - model.create(data).then( (rec) => { + model.create(data).then((rec) => { resolve(rec) }) }) @@ -107,12 +108,12 @@ module.exports = function modelScope(type, db_model, _props) { }, create: (data) => { - return new Promise( (resolve, reject) => { + return new Promise((resolve, reject) => { const should_relay = data.should_relay === 'true' - crud.create( model.sanitize(data) ).then( (rec) => { + crud.create( model.sanitize(data) ).then((rec) => { resolve(rec.toJSON()) props.afterCreate && props.afterCreate(rec, should_relay) - }).catch( (e) => { + }).catch(e => { console.error('error creating', e) reject() }) @@ -121,10 +122,10 @@ module.exports = function modelScope(type, db_model, _props) { update: (id, data) => { // console.log('update', id) - return new Promise( (resolve, reject) => { - crud.update(id, model.sanitize(data)).then( (data) => { + return new Promise((resolve, reject) => { + crud.update(id, model.sanitize(data)).then(data => { resolve(data.toJSON()) - }).catch( (e) => { + }).catch(e => { console.error('error updating', e) reject() }) @@ -132,7 +133,7 @@ module.exports = function modelScope(type, db_model, _props) { }, destroy: (id) => { - return new Promise( (resolve, reject) => { + return new Promise((resolve, reject) => { crud.show(id).then( data => { if (! data) { console.error('no record found', id) @@ -141,9 +142,9 @@ module.exports = function modelScope(type, db_model, _props) { if (type === 'file') { upload.destroyFile(data) } - crud.destroy(id).then( (destroyData) => { + crud.destroy(id).then((destroyData) => { resolve(data.toJSON()) - })// .catch( () => res.sendStatus(500) ) + }) }) }) }, diff --git a/app/server/db/models.js b/app/server/db/models.js index 24be774..8bf6d9a 100644 --- a/app/server/db/models.js +++ b/app/server/db/models.js @@ -21,7 +21,7 @@ let Task = bookshelf.Model.extend({ jsonColumns: ['opt'], }) let User = bookshelf.Model.extend({ - tableName: 'user', + tableName: 'users', hasTimestamps: true, }, { jsonColumns: ['profile'], @@ -61,7 +61,7 @@ module.exports = { // bridge.processTasks() } }), - user: model('user', Task, { + user: model('user', User, { fields: "username password realname level avatar lastseen profile created_at updated_at".split(" "), afterCreate: (user) => { console.log('created user') diff --git a/app/server/site.js b/app/server/site.js index 85c932f..717e42b 100644 --- a/app/server/site.js +++ b/app/server/site.js @@ -2,12 +2,18 @@ const express = require('express') const http = require('http') const path = require('path') const multer = require('multer')() -const upload = require('./util/upload') +const sessionstore = require('sessionstore') +const session = require('express-session') const bodyParser = require('body-parser') +const cookieParser = require('cookie-parser') +const MongoStore = require('connect-mongo')(session); const compression = require('compression') // const multer = require('multer') // const upload = multer({ dest: 'uploads/' }) +const upload = require('./util/upload') +const auth = require('./util/auth') + export const app = new express() export const server = http.createServer(app) @@ -17,6 +23,32 @@ app.use(bodyParser.urlencoded({ extended: false, limit: '100mb', })) app.use(express.query()) app.use(express.static(path.join(__dirname, '../../public'))) app.use(compression()) +app.use(cookieParser()) +var sessionSettings = { + secret: 'argonauts', + proxy: true, + key: 'cortex.sid', + cookie: { + secure: process.env.NODE_ENV === 'production', + domain: '.' + process.env.HOST_NAME, + maxAge: 43200000000, + }, + resave: true, + saveUninitialized: false, +} +if (!process.env.SESSIONS_IN_MEMORY) { + sessionSettings.store = new MongoStore({ + url: 'mongodb://127.0.0.1:28108/cortexSessionDb' + // type: 'mongodb', + // host: 'localhost', + // port: 27017, + // dbName: 'buckySessionDb', + // collectionName: 'sessions', + // timeout: 10000, + }) +} +app.use(session(sessionSettings)) +auth.route(app, serve_index) export const io = require('socket.io').listen(server) diff --git a/app/server/util/auth.js b/app/server/util/auth.js new file mode 100644 index 0000000..1515bb4 --- /dev/null +++ b/app/server/util/auth.js @@ -0,0 +1,168 @@ +import passport from 'passport' +import { Strategy as LocalStrategy } from 'passport-local' +import crypto from 'crypto' +import db from '../db' + +const { user: userModel } = db.models + +export function route(app, serve_index){ + app.use(passport.initialize()) + app.use(passport.session()) + passport.serializeUser(serializeUser) + passport.deserializeUser(deserializeUser) + passport.use(new LocalStrategy(verifyLocalUser)) + + app.get("/login", serve_index) + app.get("/signup", serve_index) + app.get("/logout", logout) + + app.put("/api/signup", + checkIfUserExists, + createUser, + login) + app.put("/api/login", + passport.authenticate("local"), + login) + app.put("/api/checkin", + ensureAuthenticated, + checkin) +} + +export function ensureAuthenticated(req, res, next) { + if (!req.isAuthenticated()) { + if (req.session) req.session.returnTo = req.path + return res.redirect('/login') + } + next() +} + +export function getUserByUsername(username) { + return userModel.show(sanitizeName(username), 'username') +} + +export function checkIfUserExists(req, res, next) { + getUserByUsername(req.body.username) + .then((user) => { + console.log('gotta user?', !!user); + user ? res.json({ error: "user exists" }) : next() + }).catch(err => { + console.error('error', err) + }) +} + +export function sanitizeName(s) { return (s || "").replace(new RegExp('[^-_a-zA-Z0-9]', 'g'), "") } +export function sanitizeUser(req_user) { + // sanitize user object + let user = JSON.parse(JSON.stringify(req_user)) + try { + user.profile = JSON.parse(user.profile) + } catch (e) { + console.error('error decoding profile') + user.profile = {} + } + delete user.password + return user +} + +export function createUser(req, res, next) { + const { username, password, password2 } = req.body + if (password !== password2) { + return res.json({ error: "passwords don't match" }) + } + let data = { + username: sanitizeName(username), + realname: sanitizeName(username), + password: makePassword(password), + lastseen: new Date(), + level: 0, + profile: {}, + } + userModel.create(data) + .then(user => { + console.log('created userrrrr', user) + req.login(user, err => { + console.log(err) + err ? next(err) : next() + }) + }) + .catch(err => { + res.json({ error }) + }) +} + +export function login(req, res) { + console.log(req.user) + if (req.isAuthenticated()) { + let returnTo = req.session.returnTo + delete req.session.returnTo + console.log(">> logged in", req.user.username) + return res.json({ + status: "OK", + user: sanitizeUser(req.user), + returnTo: returnTo || "/", + }) + } + res.json({ + error: 'bad credentials', + }) +} + +export function serializeUser(user, done) { + done(null, user.id) +} + +export function deserializeUser(id, done) { + userModel.show(id).then(user => { + done(!user, user) + }).catch(done) +} + +export function makePassword(password) { + let shasum = crypto.createHash('sha1') + shasum.update(password) + return shasum.digest('hex') +} + +export function validPassword(user, password) { + return user.password === makePassword(password) +} + +export function changePassword(req, res, next) { + if (!req.body.oldpassword && !req.body.newpassword) return next() + if (req.body.newpassword !== req.body.newpassword2) { + return res.send({ error: 'Passwords don\'t match.' }) + } + if (!validPassword(res.user, req.body.oldpassword)) { + return res.send({ error: 'Password is incorrect.' }) + } + let username = req.user.username + let newPassword = makePassword(req.body.newpassword) + res.user.password = newPassword + res.user.save() + .then(next) + .catch(err => res.send({ error: err })) +} + +export function verifyLocalUser(username, password, done) { + // handle passwords!! + getUserByUsername(username) + .then(user => { + console.log(user) + // if (err) { return done(err) } + if (! user) { return done("no user") } + if (! user || !validPassword(user, password)) { + return done(null, false, { error: { message: 'Bad username/password.' } }) + } + return done(null, user) + }) +} + +export function checkin(req, res) { + console.log(req.user) + res.json({ user: sanitizeUser(req.user) }) +} + +export const logout = (req, res) => { + req.logout() + res.redirect('/') +}
\ No newline at end of file |
