react collapse + charts

This commit is contained in:
Roman Hergenreder 2020-06-17 21:39:46 +02:00
parent 6a1f4d6752
commit dd29a96161
10 changed files with 3041 additions and 140 deletions

1462
admin/dist/main.js vendored

File diff suppressed because one or more lines are too long

130
admin/package-lock.json generated

@ -1349,11 +1349,6 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
}, },
"@reach/observe-rect": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@reach/observe-rect/-/observe-rect-1.1.0.tgz",
"integrity": "sha512-kE+jvoj/OyJV24C03VvLt5zclb9ArJi04wWXMMFwQvdZjdHoBlN4g0ZQFjyy/ejPF1Z/dpUD5dhRdBiUmIGZTA=="
},
"@sheerun/mutationobserver-shim": { "@sheerun/mutationobserver-shim": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz", "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.3.tgz",
@ -3435,6 +3430,32 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
}, },
"chart.js": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz",
"integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==",
"requires": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"chartjs-color": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
"requires": {
"chartjs-color-string": "^0.6.0",
"color-convert": "^1.9.3"
}
},
"chartjs-color-string": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
"requires": {
"color-name": "^1.0.0"
}
},
"chokidar": { "chokidar": {
"version": "3.4.0", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
@ -4245,80 +4266,6 @@
"type": "^1.0.1" "type": "^1.0.1"
} }
}, },
"d3-array": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.4.0.tgz",
"integrity": "sha512-KQ41bAF2BMakf/HdKT865ALd4cgND6VcIztVQZUTt0+BH3RWy6ZYnHghVXf6NFjt2ritLr8H1T8LreAAlfiNcw=="
},
"d3-color": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.1.tgz",
"integrity": "sha512-p2sTHSLCJI2QKunbGb7ocOh7DgTAn8IrLx21QRc/BSnodXM4sv6aLQlnfpvehFMLZEfBc6g9pH9SWQccFYfJ9Q=="
},
"d3-delaunay": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz",
"integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==",
"requires": {
"delaunator": "4"
}
},
"d3-format": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.4.tgz",
"integrity": "sha512-TWks25e7t8/cqctxCmxpUuzZN11QxIA7YrMbram94zMQ0PXjE4LVIMe/f6a4+xxL8HQ3OsAFULOINQi1pE62Aw=="
},
"d3-interpolate": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz",
"integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==",
"requires": {
"d3-color": "1"
}
},
"d3-path": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz",
"integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="
},
"d3-scale": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-3.2.1.tgz",
"integrity": "sha512-huz5byJO/6MPpz6Q8d4lg7GgSpTjIZW/l+1MQkzKfu2u8P6hjaXaStOpmyrD6ymKoW87d2QVFCKvSjLwjzx/rA==",
"requires": {
"d3-array": "1.2.0 - 2",
"d3-format": "1",
"d3-interpolate": "^1.2.0",
"d3-time": "1",
"d3-time-format": "2"
}
},
"d3-shape": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz",
"integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==",
"requires": {
"d3-path": "1"
}
},
"d3-time": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
"integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="
},
"d3-time-format": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.3.tgz",
"integrity": "sha512-RAHNnD8+XvC4Zc4d2A56Uw0yJoM7bsvOlJR33bclxq399Rak/b9bhvu/InjxdWhPtkgU53JJcleJTGkNRnN6IA==",
"requires": {
"d3-time": "1"
}
},
"d3-voronoi": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
"integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
},
"damerau-levenshtein": { "damerau-levenshtein": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz",
@ -4494,11 +4441,6 @@
} }
} }
}, },
"delaunator": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz",
"integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag=="
},
"delayed-stream": { "delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -10526,18 +10468,20 @@
"whatwg-fetch": "^3.0.0" "whatwg-fetch": "^3.0.0"
} }
}, },
"react-charts": { "react-chartjs-2": {
"version": "2.0.0-beta.7", "version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-charts/-/react-charts-2.0.0-beta.7.tgz", "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.9.0.tgz",
"integrity": "sha512-iUspg9rnx7kD0H/wsK67HNUioOgKgJ8WRXr/Tk3EGP2qcFb9Vo7pjDk4oz1jH12TC+mqL+HFxNYraMkhWd6CUw==", "integrity": "sha512-IYwqUUnQRAJ9SNA978vxulHJTcUFTJk2LDVfbAyk0TnJFZZG7+6U/2flsE4MCw6WCbBjTTypy8T82Ch7XrPtRw==",
"requires": { "requires": {
"@reach/observe-rect": "^1.1.0", "lodash": "^4.17.4",
"d3-delaunay": "^5.2.1", "prop-types": "^15.5.8"
"d3-scale": "^3.2.1",
"d3-shape": "^1.3.7",
"d3-voronoi": "^1.1.2"
} }
}, },
"react-collapse": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-collapse/-/react-collapse-5.0.1.tgz",
"integrity": "sha512-cN2tkxBWizhPQ2JHfe0aUSJtmMthKA17NZkTElpiQ2snQAAi1hssXZ2fv88rAPNNvG5ss4t0PbOZT0TIl9Lk3Q=="
},
"react-dev-utils": { "react-dev-utils": {
"version": "10.2.1", "version": "10.2.1",
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",

@ -6,8 +6,11 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0", "@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1", "@testing-library/user-event": "^7.2.1",
"chart.js": "^2.9.3",
"moment": "^2.26.0", "moment": "^2.26.0",
"react": "^16.13.1", "react": "^16.13.1",
"react-chartjs-2": "^2.9.0",
"react-collapse": "^5.0.1",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "3.4.1", "react-scripts": "3.4.1",

@ -1,2 +1,6 @@
.page-link { color: #222629; } .page-link { color: #222629; }
.page-link:hover { color: black; } .page-link:hover { color: black; }
.ReactCollapse--collapse {
transition: height 500ms;
}

@ -3,6 +3,7 @@ import {Link} from "react-router-dom";
import Alert from "../elements/alert"; import Alert from "../elements/alert";
import Icon from "../elements/icon"; import Icon from "../elements/icon";
import ReactTooltip from 'react-tooltip' import ReactTooltip from 'react-tooltip'
import {Collapse} from "react-collapse/lib/Collapse";
export default class CreateUser extends React.Component { export default class CreateUser extends React.Component {
@ -39,24 +40,6 @@ export default class CreateUser extends React.Component {
errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>) errors.push(<Alert key={"error-" + i} onClose={() => this.removeError(i)} {...this.state.errors[i]}/>)
} }
let passwordForm = null;
if (!this.state.sendInvite) {
passwordForm = <div className={"mt-2"}>
<div className={"form-group"}>
<label htmlFor={"password"}>Password</label>
<input type={"password"} className={"form-control"} placeholder={"Password"}
id={"password"} name={"password"} value={this.state.password}
onChange={this.onChangeInput.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"confirmPassword"}>Confirm Password</label>
<input type={"password"} className={"form-control"} placeholder={"Confirm Password"}
id={"confirmPassword"} name={"confirmPassword"} value={this.state.confirmPassword}
onChange={this.onChangeInput.bind(this)}/>
</div>
</div>
}
return <> return <>
<div className="content-header"> <div className="content-header">
<div className="container-fluid"> <div className="container-fluid">
@ -102,7 +85,24 @@ export default class CreateUser extends React.Component {
data-type={"info"} data-place={"right"} data-effect={"solid"}/> data-type={"info"} data-place={"right"} data-effect={"solid"}/>
</label> </label>
</div> </div>
{passwordForm}
<Collapse isOpened={!this.state.sendInvite}>
<div className={"mt-2"}>
<div className={"form-group"}>
<label htmlFor={"password"}>Password</label>
<input type={"password"} className={"form-control"} placeholder={"Password"}
id={"password"} name={"password"} value={this.state.password}
onChange={this.onChangeInput.bind(this)}/>
</div>
<div className={"form-group"}>
<label htmlFor={"confirmPassword"}>Confirm Password</label>
<input type={"password"} className={"form-control"} placeholder={"Confirm Password"}
id={"confirmPassword"} name={"confirmPassword"} value={this.state.confirmPassword}
onChange={this.onChangeInput.bind(this)}/>
</div>
</div>
</Collapse>
<Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}> <Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}>
<Icon icon={"arrow-left"}/> <Icon icon={"arrow-left"}/>
&nbsp;Back &nbsp;Back

@ -1,6 +1,9 @@
import * as React from "react"; import * as React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import Icon from "../elements/icon"; import Icon from "../elements/icon";
import { Bar } from 'react-chartjs-2';
import {Collapse} from 'react-collapse';
import moment from 'moment';
export default class Overview extends React.Component { export default class Overview extends React.Component {
@ -10,6 +13,10 @@ export default class Overview extends React.Component {
showDialog: props.showDialog, showDialog: props.showDialog,
notifications: props.notification, notifications: props.notification,
api: props.api, api: props.api,
};
this.state = {
chartVisible : true,
} }
} }
@ -24,6 +31,22 @@ export default class Overview extends React.Component {
let pageCount = 0; let pageCount = 0;
let visitorCount = 0; let visitorCount = 0;
const colors = [
'#ff4444', '#ffbb33', '#00C851', '#33b5e5',
'#ff4444', '#ffbb33', '#00C851', '#33b5e5',
'#ff4444', '#ffbb33', '#00C851', '#33b5e5'
];
let chartOptions = {};
let chartData = {
labels: moment.monthsShort(),
datasets: [{
label: 'Unique Visitors ' + moment().year(),
borderWidth: 1,
data: [ 10, 20, 30, 0, 15, 5, 40, 100, 6, 3, 10, 20 ],
backgroundColor: colors,
}]
};
return <> return <>
<div className={"content-header"}> <div className={"content-header"}>
@ -100,27 +123,29 @@ export default class Overview extends React.Component {
<div className="card-header"> <div className="card-header">
<h3 className="card-title">Unique Visitors this year</h3> <h3 className="card-title">Unique Visitors this year</h3>
<div className="card-tools"> <div className="card-tools">
<button type="button" className="btn btn-tool" data-card-widget="collapse"> <button type="button" className={"btn btn-tool"} onClick={(e) => {
e.preventDefault();
this.setState({ ...this.state, chartVisible: !this.state.chartVisible });
}}>
<Icon icon={"minus"} /> <Icon icon={"minus"} />
</button> </button>
<button type="button" className="btn btn-tool" data-card-widget="remove">
<Icon icon={"times"} />
</button>
</div> </div>
</div> </div>
<div className="card-body"> <Collapse isOpened={this.state.chartVisible}>
<div className="chart"> <div className="card-body">
<div className="chartjs-size-monitor"> <div className="chart">
<div className="chartjs-size-monitor-expand"> <div className="chartjs-size-monitor">
<div/> <div className="chartjs-size-monitor-expand">
</div> <div/>
<div className="chartjs-size-monitor-shrink"> </div>
<div/> <div className="chartjs-size-monitor-shrink">
<div/>
</div>
</div> </div>
<Bar data={chartData} options={chartOptions} />
</div> </div>
<BarChart data={data} series={series} axes={axes} tooltip />
</div> </div>
</div> </Collapse>
</div> </div>
</div> </div>
</div> </div>

@ -27,6 +27,10 @@ class SendMail extends Request {
try { try {
$mailConfig = $this->user->getConfiguration()->getMail(); $mailConfig = $this->user->getConfiguration()->getMail();
if (!$mailConfig) {
return $this->createError("Mail is not configured yet.");
}
$mail = new PHPMailer; $mail = new PHPMailer;
$mail->IsSMTP(); $mail->IsSMTP();
$mail->setFrom($this->getParam('from'), $this->getParam('fromName')); $mail->setFrom($this->getParam('from'), $this->getParam('fromName'));

@ -4,6 +4,8 @@ namespace Api\User;
use Api\Parameter\StringType; use Api\Parameter\StringType;
use \Api\Request; use \Api\Request;
use Api\SendMail;
use DateTime;
use Driver\SQL\Condition\Compare; use Driver\SQL\Condition\Compare;
class Invite extends Request { class Invite extends Request {
@ -33,17 +35,26 @@ class Invite extends Request {
$token = generateRandomString(36); $token = generateRandomString(36);
$valid_until = (new DateTime())->modify("+48 hour"); $valid_until = (new DateTime())->modify("+48 hour");
$sql = $this->user->getSQL(); $sql = $this->user->getSQL();
$res = $sql->insert("UserInvite", array("name", "email","token","valid_until")) $res = $sql->insert("UserInvitation", array("username", "email", "token", "valid_until"))
->addRow($username, $email, $token,$valid_until) ->addRow($username, $email, $token, $valid_until)
->execute(); ->execute();
$this->success = ($res !== FALSE); $this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError(); $this->lastError = $sql->getLastError();
//send validation mail //send validation mail
if($this->success) { if($this->success) {
$request = new SendEmail($this->user); $request = new SendMail($this->user);
$link = "http://localhost/acceptInvitation?token=$token";
$this->success = $request->execute(array( $this->success = $request->execute(array(
"from" => "...", "to" => $email)); "from" => "webmaster@romanh.de",
"to" => $email,
"subject" => "Account Invitation for web-base@localhost",
"body" =>
"Hello,<br>
you were invited to create an account on web-base@localhost. Click on the following link to confirm the registration, it is 48h valid from now.
If the invitation was not intended, you can simply ignore this email.<br><br><a href=\"$link\">$link</a>"
)
);
$this->lastError = $request->getLastError(); $this->lastError = $request->getLastError();
} }
return $this->success; return $this->success;

@ -94,9 +94,7 @@ class Configuration {
public function __construct() { public function __construct() {
parent::__construct('$host', $port, '$login', '$password');$properties parent::__construct('$host', $port, '$login', '$password');$properties
} }
} }", false
?>", false
); );
} else { } else {
return false; return false;

1462
js/admin.min.js vendored

File diff suppressed because one or more lines are too long