Google reCaptcha

This commit is contained in:
2020-06-26 23:32:45 +02:00
parent 9442a120ab
commit cd6c28c9b3
16 changed files with 552 additions and 115 deletions

View File

@@ -43,8 +43,12 @@ export default class API {
return this.apiCall("user/logout");
}
async getNotifications() {
return this.apiCall("notifications/fetch");
async getNotifications(onlyNew = true) {
return this.apiCall("notifications/fetch", { new: onlyNew });
}
async markNotificationsSeen() {
return this.apiCall("notifications/seen");
}
async getUser(id) {
@@ -101,6 +105,5 @@ export default class API {
async sendTestMail(receiver) {
return this.apiCall("sendTestMail", { receiver: receiver });
}
};

View File

@@ -28,7 +28,7 @@ class AdminDashboard extends React.Component {
this.state = {
loaded: false,
dialog: { onClose: () => this.hideDialog() },
notifications: { }
notifications: [ ]
};
}
@@ -75,6 +75,7 @@ class AdminDashboard extends React.Component {
this.controlObj = {
showDialog: this.showDialog.bind(this),
fetchNotifications: this.fetchNotifications.bind(this),
api: this.api
};
@@ -92,7 +93,7 @@ class AdminDashboard extends React.Component {
return <EditUser {...newProps} />
}}/>
<Route path={"/admin/group/add"}><CreateGroup {...this.controlObj} /></Route>
<Route path={"/admin/logs"}><Logs {...this.controlObj} /></Route>
<Route path={"/admin/logs"}><Logs {...this.controlObj} notifications={this.state.notifications} /></Route>
<Route path={"/admin/settings"}><Settings {...this.controlObj} /></Route>
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
<Route path={"/admin/help"}><HelpPage {...this.controlObj} /></Route>

View File

@@ -32,8 +32,8 @@ export default function HelpPage() {
<div className="col-12 col-md-8 col-lg-4">
<p>
WebBase is a php framework to simplify user management, pages and routing.
It can easily be modified and extended by writing document classes or
access the database with the available abstracted scheme. It also includes
It can easily be modified and extended by writing document classes and the database
can be accessed through the available abstracted scheme. It also includes
a REST API with access control, parameter type checking and more.
</p>
</div>

View File

@@ -1,13 +1,100 @@
import * as React from "react";
import {Link} from "react-router-dom";
import Icon from "../elements/icon";
import moment from 'moment';
export default class Logs extends React.Component {
constructor(props) {
super(props);
this.state = {
alerts: [],
notifications: []
};
this.parent = {
api : props.api,
fetchNotifications: props.fetchNotifications,
}
}
removeError(i) {
if (i >= 0 && i < this.state.alerts.length) {
let alerts = this.state.alerts.slice();
alerts.splice(i, 1);
this.setState({...this.state, alerts: alerts});
}
}
componentDidMount() {
this.parent.api.getNotifications(false).then((res) => {
if (!res.success) {
let alerts = this.state.alerts.slice();
alerts.push({ message: res.msg, title: "Error fetching Notifications" });
this.setState({ ...this.state, alerts: alerts });
} else {
this.setState({ ...this.state, notifications: res.notifications });
}
this.parent.api.markNotificationsSeen().then((res) => {
if (!res.success) {
let alerts = this.state.alerts.slice();
alerts.push({ message: res.msg, title: "Error fetching Notifications" });
this.setState({ ...this.state, alerts: alerts });
}
this.parent.fetchNotifications();
});
});
}
render() {
const colors = ["red", "green", "blue", "purple", "maroon"];
let dates = { };
for (let notification of this.state.notifications) {
let day = moment(notification["created_at"]).format('ll');
if (!dates.hasOwnProperty(day)) {
dates[day] = [];
}
let icon = "bell";
if (notification.type === "message") {
icon = "envelope";
} else if(notification.type === "warning") {
icon = "exclamation-triangle";
}
dates[day].push({ ...notification, icon: icon, timestamp: notification["created_at"] });
}
let elements = [];
for (let date in dates) {
let color = colors[Math.floor(Math.random() * colors.length)];
elements.push(
<div className={"time-label"} key={"time-label-" + date}>
<span className={"bg-" + color}>{date}</span>
</div>
);
for (let event of dates[date]) {
let timeString = moment(event.timestamp).fromNow();
elements.push(
<div>
<Icon icon={event.icon} className={"bg-" + color}/>
<div className="timeline-item">
<span className="time"><Icon icon={"clock"}/> {timeString}</span>
<h3 className="timeline-header">{event.title}</h3>
<div className="timeline-body">{event.message}</div>
</div>
</div>
);
}
}
return <>
<div className="content-header">
<div className="container-fluid">
@@ -25,13 +112,18 @@ export default class Logs extends React.Component {
</div>
</div>
<div className={"content"}>
<div className={"content-fluid"}>
<div className={"container-fluid"}>
<div className={"row"}>
<div className={"col-lg-6"}>
</div>
<div className={"col-lg-6"}>
<div className={"col-lg-8 col-12"}>
<div className="timeline">
<div className={"time-label"}>
<span className={"bg-blue"}>Today</span>
</div>
{elements}
<div>
<Icon icon={"clock"} className={"bg-gray"}/>
</div>
</div>
</div>
</div>
</div>

View File

@@ -45,6 +45,13 @@ export default class Settings extends React.Component {
isEditing: null,
keys: ["message_confirm_email", "message_accept_invite", "message_reset_password"]
},
recaptcha: {
alerts: [],
isOpen: true,
isSaving: false,
isResetting: false,
keys: ["recaptcha_enabled", "recaptcha_public_key", "recaptcha_private_key"]
},
uncategorised: {
alerts: [],
isOpen: true,
@@ -60,8 +67,9 @@ export default class Settings extends React.Component {
};
this.hiddenKeys = [
"mail_password",
"jwt_secret"
"recaptcha_private_key",
"mail_password",
"jwt_secret"
];
}
@@ -164,7 +172,7 @@ export default class Settings extends React.Component {
<div className={"card-header"} style={{cursor: "pointer"}}
onClick={() => this.toggleCollapse(category)}>
<h4 className={"card-title"}>
<Icon className={"mr-2"} icon={icon}/>
<Icon className={"mr-2"} icon={icon} type={icon==="google"?"fab":"fas"} />
{title}
</h4>
<div className={"card-tools"}>
@@ -402,6 +410,49 @@ export default class Settings extends React.Component {
return formGroups;
}
getRecaptchaForm() {
return <>
<div className={"form-group mt-2"}>
<div className={"form-check"}>
<input type={"checkbox"} className={"form-check-input"}
name={"recaptcha_enabled"} id={"recaptcha_enabled"}
checked={(this.state.settings["recaptcha_enabled"] ?? "0") === "1"}
onChange={this.onChangeValue.bind(this)}/>
<label className={"form-check-label"} htmlFor={"recaptcha_enabled"}>
Enable Google's reCaptcha
</label>
</div>
</div>
<hr className={"m-2"}/>
<label htmlFor={"recaptcha_public_key"} className={"mt-2"}>reCaptcha Site Key</label>
<div className={"input-group"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"unlock"}/>
</span>
</div>
<input type={"text"} className={"form-control"}
value={this.state.settings["recaptcha_public_key"] ?? ""}
placeholder={"Enter site key"} name={"recaptcha_public_key"}
id={"recaptcha_public_key"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["recaptcha_enabled"] ?? "0") !== "1"}/>
</div>
<label htmlFor={"recaptcha_private_key"} className={"mt-2"}>reCaptcha Secret Key</label>
<div className={"input-group mb-3"}>
<div className={"input-group-prepend"}>
<span className={"input-group-text"}>
<Icon icon={"lock"}/>
</span>
</div>
<input type={"password"} className={"form-control"}
value={this.state.settings["recaptcha_private_key"] ?? ""}
placeholder={"(unchanged)"} name={"recaptcha_private_key"}
id={"mail_password"} onChange={this.onChangeValue.bind(this)}
disabled={(this.state.settings["recaptcha_enabled"] ?? "0") !== "1"}/>
</div>
</>
}
getUncategorizedForm() {
let tr = [];
@@ -463,6 +514,7 @@ export default class Settings extends React.Component {
"general": {color: "primary", icon: "cogs", title: "General Settings", content: this.createGeneralForm()},
"mail": {color: "warning", icon: "envelope", title: "Mail Settings", content: this.createMailForm()},
"messages": {color: "info", icon: "copy", title: "Message Templates", content: this.getMessagesForm()},
"recaptcha": {color: "danger", icon: "google", title: "Google reCaptcha", content: this.getRecaptchaForm()},
"uncategorised": {color: "secondary", icon: "stream", title: "Uncategorised", content: this.getUncategorizedForm()},
};