Google reCaptcha
This commit is contained in:
@@ -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 });
|
||||
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user