Group Creation
This commit is contained in:
parent
d8846ff132
commit
d80de63765
@ -2,8 +2,21 @@
|
|||||||
|
|
||||||
namespace Api {
|
namespace Api {
|
||||||
|
|
||||||
|
use Driver\SQL\Condition\Compare;
|
||||||
|
|
||||||
class GroupsAPI extends Request {
|
class GroupsAPI extends Request {
|
||||||
|
|
||||||
|
protected function groupExists($name) {
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
$res = $sql->select($sql->count())
|
||||||
|
->from("Group")
|
||||||
|
->where(new Compare("name", $name))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->success = ($res !== FALSE);
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
return $this->success && $res[0]["count"] > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -12,6 +25,7 @@ namespace Api\Groups {
|
|||||||
|
|
||||||
use Api\GroupsAPI;
|
use Api\GroupsAPI;
|
||||||
use Api\Parameter\Parameter;
|
use Api\Parameter\Parameter;
|
||||||
|
use Api\Parameter\StringType;
|
||||||
|
|
||||||
class Fetch extends GroupsAPI {
|
class Fetch extends GroupsAPI {
|
||||||
|
|
||||||
@ -96,4 +110,53 @@ namespace Api\Groups {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Create extends GroupsAPI {
|
||||||
|
public function __construct($user, $externalCall = false) {
|
||||||
|
parent::__construct($user, $externalCall, array(
|
||||||
|
'name' => new StringType('name', 32),
|
||||||
|
'color' => new StringType('color', 10),
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->loginRequired = true;
|
||||||
|
$this->requiredGroup = array(USER_GROUP_ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute($values = array()) {
|
||||||
|
if (!parent::execute($values)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->getParam("name");
|
||||||
|
if (preg_match("/^[a-zA-Z][a-zA-Z0-9_-]*$/", $name) !== 1) {
|
||||||
|
return $this->createError("Invalid name");
|
||||||
|
}
|
||||||
|
|
||||||
|
$color = $this->getParam("color");
|
||||||
|
if (preg_match("/^#[a-fA-F0-9]{3,6}$/", $color) !== 1) {
|
||||||
|
return $this->createError("Invalid color");
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = $this->groupExists($name);
|
||||||
|
if (!$this->success) {
|
||||||
|
return false;
|
||||||
|
} else if ($exists) {
|
||||||
|
return $this->createError("A group with this name already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = $this->user->getSQL();
|
||||||
|
$res = $sql->insert("Group", array("name", "color"))
|
||||||
|
->addRow($name, $color)
|
||||||
|
->returning("uid")
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$this->success = ($res !== FALSE);
|
||||||
|
$this->lastError = $sql->getLastError();
|
||||||
|
|
||||||
|
if ($this->success) {
|
||||||
|
$this->result["uid"] = $sql->getLastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->success;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -118,7 +118,7 @@ namespace Api\User {
|
|||||||
public function __construct($user, $externalCall = false) {
|
public function __construct($user, $externalCall = false) {
|
||||||
parent::__construct($user, $externalCall, array(
|
parent::__construct($user, $externalCall, array(
|
||||||
'username' => new StringType('username', 32),
|
'username' => new StringType('username', 32),
|
||||||
'email' => new StringType('email', 64, true),
|
'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL),
|
||||||
'password' => new StringType('password'),
|
'password' => new StringType('password'),
|
||||||
'confirmPassword' => new StringType('confirmPassword'),
|
'confirmPassword' => new StringType('confirmPassword'),
|
||||||
));
|
));
|
||||||
@ -501,7 +501,7 @@ If the invitation was not intended, you can simply ignore this email.<br><br><a
|
|||||||
public function __construct($user, $externalCall = false) {
|
public function __construct($user, $externalCall = false) {
|
||||||
parent::__construct($user, $externalCall, array(
|
parent::__construct($user, $externalCall, array(
|
||||||
"username" => new StringType("username", 32),
|
"username" => new StringType("username", 32),
|
||||||
"email" => new StringType("email", 64),
|
'email' => new Parameter('email', Parameter::TYPE_EMAIL),
|
||||||
"password" => new StringType("password"),
|
"password" => new StringType("password"),
|
||||||
"confirmPassword" => new StringType("confirmPassword"),
|
"confirmPassword" => new StringType("confirmPassword"),
|
||||||
));
|
));
|
||||||
@ -608,7 +608,7 @@ If the registration was not intended, you can simply ignore this email.<br><br><
|
|||||||
parent::__construct($user, $externalCall, array(
|
parent::__construct($user, $externalCall, array(
|
||||||
'id' => new Parameter('id', Parameter::TYPE_INT),
|
'id' => new Parameter('id', Parameter::TYPE_INT),
|
||||||
'username' => new StringType('username', 32, true, NULL),
|
'username' => new StringType('username', 32, true, NULL),
|
||||||
'email' => new StringType('email', 64, true, NULL),
|
'email' => new Parameter('email', Parameter::TYPE_EMAIL, true, NULL),
|
||||||
'password' => new StringType('password', -1, true, NULL),
|
'password' => new StringType('password', -1, true, NULL),
|
||||||
'groups' => new Parameter('groups', Parameter::TYPE_ARRAY, true, NULL),
|
'groups' => new Parameter('groups', Parameter::TYPE_ARRAY, true, NULL),
|
||||||
));
|
));
|
||||||
|
18
js/admin.min.js
vendored
18
js/admin.min.js
vendored
File diff suppressed because one or more lines are too long
@ -80,6 +80,10 @@ export default class API {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveRoutes(routes) {
|
async saveRoutes(routes) {
|
||||||
return this.apiCall("routes/save", { "routes": routes });
|
return this.apiCall("routes/save", { routes: routes });
|
||||||
|
}
|
||||||
|
|
||||||
|
async createGroup(name, color) {
|
||||||
|
return this.apiCall("groups/create", { name: name, color: color });
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -17,6 +17,7 @@ import PageOverview from "./views/pages";
|
|||||||
import HelpPage from "./views/help";
|
import HelpPage from "./views/help";
|
||||||
import Footer from "./footer";
|
import Footer from "./footer";
|
||||||
import EditUser from "./views/edituser";
|
import EditUser from "./views/edituser";
|
||||||
|
import CreateGroup from "./views/addgroup";
|
||||||
|
|
||||||
class AdminDashboard extends React.Component {
|
class AdminDashboard extends React.Component {
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ class AdminDashboard extends React.Component {
|
|||||||
let newProps = {...props, ...this.controlObj};
|
let newProps = {...props, ...this.controlObj};
|
||||||
return <EditUser {...newProps} />
|
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} /></Route>
|
||||||
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
<Route path={"/admin/pages"}><PageOverview {...this.controlObj} /></Route>
|
||||||
<Route path={"/admin/help"}><HelpPage {...this.controlObj} /></Route>
|
<Route path={"/admin/help"}><HelpPage {...this.controlObj} /></Route>
|
||||||
|
115
src/src/views/addgroup.js
Normal file
115
src/src/views/addgroup.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import Alert from "../elements/alert";
|
||||||
|
import {Link} from "react-router-dom";
|
||||||
|
import * as React from "react";
|
||||||
|
import Icon from "../elements/icon";
|
||||||
|
import ReactTooltip from "react-tooltip";
|
||||||
|
|
||||||
|
export default class CreateGroup extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
alerts: [],
|
||||||
|
isSubmitting: false,
|
||||||
|
name: "",
|
||||||
|
color: "#123456"
|
||||||
|
};
|
||||||
|
|
||||||
|
this.parent = {
|
||||||
|
api: props.api,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAlert(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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
|
||||||
|
let alerts = [];
|
||||||
|
for (let i = 0; i < this.state.alerts.length; i++) {
|
||||||
|
alerts.push(<Alert key={"error-" + i} onClose={() => this.removeAlert(i)} {...this.state.alerts[i]}/>)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className="content-header">
|
||||||
|
<div className="container-fluid">
|
||||||
|
<div className="row mb-2">
|
||||||
|
<div className="col-sm-6">
|
||||||
|
<h1 className="m-0 text-dark">Create a new user</h1>
|
||||||
|
</div>
|
||||||
|
<div className="col-sm-6">
|
||||||
|
<ol className="breadcrumb float-sm-right">
|
||||||
|
<li className="breadcrumb-item"><Link to={"/admin/dashboard"}>Home</Link></li>
|
||||||
|
<li className="breadcrumb-item"><Link to={"/admin/users"}>Users</Link></li>
|
||||||
|
<li className="breadcrumb-item active">Add User</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={"content"}>
|
||||||
|
<div className={"row"}>
|
||||||
|
<div className={"col-lg-6 pl-5 pr-5"}>
|
||||||
|
{alerts}
|
||||||
|
<form role={"form"} onSubmit={(e) => this.submitForm(e)}>
|
||||||
|
<div className={"form-group"}>
|
||||||
|
<label htmlFor={"name"}>Group Name</label>
|
||||||
|
<input type={"text"} className={"form-control"} placeholder={"Name"}
|
||||||
|
name={"name"} id={"name"} maxLength={32} value={this.state.name}
|
||||||
|
onChange={this.onChangeInput.bind(this)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* TODO: add color picker */}
|
||||||
|
<div className={"form-group"}>
|
||||||
|
<label htmlFor={"color"}>Color</label>
|
||||||
|
<input type={"text"} className={"form-control"} placeholder={"Color"}
|
||||||
|
id={"color"} name={"color"} maxLength={64} value={this.state.color}
|
||||||
|
onChange={this.onChangeInput.bind(this)}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Link to={"/admin/users"} className={"btn btn-info mt-2 mr-2"}>
|
||||||
|
<Icon icon={"arrow-left"}/>
|
||||||
|
Back
|
||||||
|
</Link>
|
||||||
|
{ this.state.isSubmitting
|
||||||
|
? <button type={"submit"} className={"btn btn-primary mt-2"} disabled>Loading… <Icon icon={"circle-notch"} /></button>
|
||||||
|
: <button type={"submit"} className={"btn btn-primary mt-2"}>Submit</button>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ReactTooltip/>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeInput(event) {
|
||||||
|
const target = event.target;
|
||||||
|
const value = target.value;
|
||||||
|
const name = target.name;
|
||||||
|
this.setState({ ...this.state, [name]: value });
|
||||||
|
}
|
||||||
|
|
||||||
|
submitForm(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const name = this.state.name;
|
||||||
|
const color = this.state.color;
|
||||||
|
this.setState({ ...this.state, isSubmitting: true });
|
||||||
|
this.parent.api.createGroup(name, color).then((res) => {
|
||||||
|
let alerts = this.state.alerts.slice();
|
||||||
|
if (res.success) {
|
||||||
|
alerts.push({message: "Group was successfully created", title: "Success!", type: "success"});
|
||||||
|
this.setState({ ...this.state, name: "", color: "", alerts: alerts, isSubmitting: false });
|
||||||
|
} else {
|
||||||
|
alerts.push({message: res.msg, title: "Error creating Group", type: "danger"});
|
||||||
|
this.setState({ ...this.state, name: "", color: "", alerts: alerts, isSubmitting: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -327,7 +327,7 @@ export default class UserOverview extends React.Component {
|
|||||||
<div className={"card-header border-0"}>
|
<div className={"card-header border-0"}>
|
||||||
<h3 className={"card-title"}>Groups</h3>
|
<h3 className={"card-title"}>Groups</h3>
|
||||||
<div className={"card-tools"}>
|
<div className={"card-tools"}>
|
||||||
<Link href={"#"} className={"btn btn-tool btn-sm"} to={"/admin/users/addgroup"} >
|
<Link href={"#"} className={"btn btn-tool btn-sm"} to={"/admin/group/add"} >
|
||||||
<Icon icon={"plus"}/>
|
<Icon icon={"plus"}/>
|
||||||
</Link>
|
</Link>
|
||||||
<a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchGroups()}>
|
<a href={"#"} className={"btn btn-tool btn-sm"} onClick={() => this.fetchGroups()}>
|
||||||
|
Loading…
Reference in New Issue
Block a user