├── README.md
├── src
├── config.js
├── index.css
├── customRoutes.js
├── App.test.js
├── index.js
├── users.js
├── App.css
├── Menu.js
├── App.js
├── posts.js
├── authClient.js
├── Foo.js
├── logo.svg
└── registerServiceWorker.js
├── public
├── favicon.ico
├── manifest.json
└── index.html
├── server
├── config.example.js
├── app
│ └── models
│ │ ├── user.js
│ │ └── post.js
└── server.js
├── .gitignore
├── package.json
└── .github
└── workflows
└── client.yml
/README.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | export const API_URL = 'http://localhost:8060';
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalak111/react-admin-panel/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/server/config.example.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'secret': '',
3 | 'database': 'mongodb://',
4 | 'database_user': '',
5 | 'database_pass': ''
6 | };
--------------------------------------------------------------------------------
/src/customRoutes.js:
--------------------------------------------------------------------------------
1 | // in src/customRoutes.js
2 | import React from 'react';
3 | import { Route } from 'react-router-dom';
4 | import Foo from './Foo';
5 |
6 | export default [
7 | ,
8 | ];
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | });
9 |
--------------------------------------------------------------------------------
/server/app/models/user.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 |
4 | // set up a mongoose model
5 | module.exports = mongoose.model('User', new Schema({
6 | name: String,
7 | password: String,
8 | admin: Boolean
9 | }));
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/server/app/models/post.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 | var Schema = mongoose.Schema;
3 | var URLSlugs = require('mongoose-url-slugs');
4 |
5 | // set up a mongoose model
6 | module.exports = mongoose.model('Post', new Schema({
7 | title: String,
8 | content: String
9 | }).plugin(URLSlugs('title')));
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 | #PHPStorm
3 | .idea
4 |
5 | # dependencies
6 | node_modules
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 | /server/config.js
25 |
--------------------------------------------------------------------------------
/src/users.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { List, Datagrid, EmailField, TextField } from 'admin-on-rest';
3 |
4 | export const UserList = (props) => (
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | );
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-intro {
18 | font-size: large;
19 | }
20 |
21 | @keyframes App-logo-spin {
22 | from { transform: rotate(0deg); }
23 | to { transform: rotate(360deg); }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Menu.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { MenuItemLink } from 'admin-on-rest';
3 | import PostIcon from 'material-ui/svg-icons/content/content-paste';
4 | import FooIcon from 'material-ui/svg-icons/action/code';
5 |
6 | export default ({ resources, onMenuTap, logout }) => (
7 |
8 | }/>
9 | }/>
10 | {logout}
11 |
12 | );
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-admin",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "react-scripts start",
7 | "build": "react-scripts build",
8 | "test": "react-scripts test --env=jsdom",
9 | "eject": "react-scripts eject",
10 | "server-run": "nodemon ./server/server.js"
11 | },
12 | "dependencies": {
13 | "admin-on-rest": "^1.2.2",
14 | "aor-rich-text-input": "^1.0.1",
15 | "body-parser": "^1.15.0",
16 | "cors": "^2.8.4",
17 | "express": "^4.13.4",
18 | "jsonwebtoken": "^5.7.0",
19 | "material-ui": "^0.20.2",
20 | "mongoose": "^5.7.5",
21 | "mongoose-url-slugs": "^1.0.0",
22 | "morgan": "^1.7.0",
23 | "react-router-dom": "^5.0.1",
24 | "react-scripts": "1.0.11"
25 | },
26 | "devDependencies": {
27 | "nodemon": "^1.11.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/client.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Client CI
5 |
6 | on:
7 | push
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | node-version: [12.x, 14.x, 16.x]
17 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
18 |
19 | steps:
20 | - uses: actions/checkout@v2
21 | - name: Use Node.js ${{ matrix.node-version }}
22 | uses: actions/setup-node@v2
23 | with:
24 | node-version: ${{ matrix.node-version }}
25 | - run: yarn install
26 | - run: yarn build
27 | - run: yarn test
28 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { simpleRestClient, fetchUtils, Admin, Resource } from 'admin-on-rest';
3 | import { API_URL } from './config'
4 | import authClient from './authClient';
5 | import { PostList, PostEdit, PostCreate } from './posts';
6 | import Menu from './Menu';
7 | import customRoutes from './customRoutes';
8 |
9 | const httpClient = (url, options = {}) => {
10 | if (!options.headers) {
11 | options.headers = new Headers({ Accept: 'application/json' });
12 | }
13 | options.headers.set('x-access-token', localStorage.getItem('token'));
14 | return fetchUtils.fetchJson(url, options);
15 | };
16 |
17 | const restClient = simpleRestClient(API_URL, httpClient);
18 |
19 | const App = () => (
20 |
21 |
22 |
23 | );
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/src/posts.js:
--------------------------------------------------------------------------------
1 | // in src/posts.js
2 | import React from 'react';
3 | import RichTextInput from 'aor-rich-text-input';
4 | import { List, Edit, Create, Datagrid, TextField, EditButton, DisabledInput, SimpleForm, TextInput } from 'admin-on-rest';
5 |
6 | export const PostList = (props) => (
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 |
16 | const PostTitle = ({ record }) => {
17 | return Post {record ? `"${record.title}"` : ''};
18 | };
19 |
20 | export const PostEdit = (props) => {
21 | return (
22 | } {...props}>
23 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | };
31 |
32 | export const PostCreate = (props) => (
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
--------------------------------------------------------------------------------
/src/authClient.js:
--------------------------------------------------------------------------------
1 | import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'admin-on-rest';
2 | import { API_URL } from './config';
3 |
4 | export default (type, params) => {
5 | if (type === AUTH_LOGIN) {
6 | const { username, password } = params;
7 | const request = new Request(`${API_URL}/api/authenticate`, {
8 | method: 'POST',
9 | body: JSON.stringify({ username, password }),
10 | headers: new Headers({ 'Content-Type': 'application/json' }),
11 | })
12 | return fetch(request)
13 | .then(response => {
14 | if (response.status < 200 || response.status >= 300) {
15 | throw new Error(response.statusText);
16 | }
17 | return response.json();
18 | })
19 | .then((res) => {
20 | localStorage.setItem('token', res.token);
21 | });
22 | }
23 | if (type === AUTH_LOGOUT) {
24 | localStorage.removeItem('token');
25 | return Promise.resolve();
26 | }
27 | if (type === AUTH_ERROR) {
28 | const { status } = params;
29 | if (status === 401 || status === 403) {
30 | localStorage.removeItem('token');
31 | return Promise.reject();
32 | }
33 | return Promise.resolve();
34 | }
35 | if (type === AUTH_CHECK) {
36 | return localStorage.getItem('token') ? Promise.resolve() : Promise.reject();
37 | }
38 | return Promise.resolve();
39 | }
--------------------------------------------------------------------------------
/src/Foo.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Card, CardText } from 'material-ui/Card';
3 | import { ViewTitle } from 'admin-on-rest';
4 |
5 | const Foo = () => (
6 |
7 |
8 |
9 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vulputate lorem quis laoreet imperdiet. Integer dictum ultricies luctus. Nulla vel vulputate justo. Praesent sit amet tortor nulla. Cras efficitur molestie ipsum vel sagittis. Nulla facilisi. Morbi consequat, est sed tincidunt mattis, quam ante tincidunt purus, ac efficitur lacus ipsum ac nisi. Pellentesque dignissim justo ut eros ullamcorper auctor. Vestibulum auctor rutrum mi, et tempus nunc interdum non. Vestibulum felis ipsum, tincidunt blandit metus sed, iaculis bibendum massa. Sed ultrices ultrices posuere.
10 |
11 |
12 | Cras non justo urna. Curabitur scelerisque sapien in est fringilla, id porta nisi blandit. Nulla tempus consectetur massa id dignissim. Duis ut augue est. Aliquam fringilla posuere fringilla. Phasellus scelerisque posuere facilisis. Nullam felis nisi, vestibulum in tristique sed, vehicula non enim. Nunc tristique quam leo, eu porta risus placerat eu. Integer in lectus ut lacus posuere maximus et vel mauris. Nullam rutrum ipsum in arcu pulvinar luctus. Nulla porttitor ex libero, a bibendum nunc maximus a. Etiam in congue arcu. Sed nec malesuada nisl. Cras ut molestie erat, et porta ante. Fusce ipsum ante, accumsan vitae ornare non, iaculis nec ipsum. Proin auctor convallis vestibulum.
13 |
14 |
15 | );
16 |
17 | export default Foo;
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (!isLocalhost) {
36 | // Is not local host. Just register service worker
37 | registerValidSW(swUrl);
38 | } else {
39 | // This is running on localhost. Lets check if a service worker still exists or not.
40 | checkValidServiceWorker(swUrl);
41 | }
42 | });
43 | }
44 | }
45 |
46 | function registerValidSW(swUrl) {
47 | navigator.serviceWorker
48 | .register(swUrl)
49 | .then(registration => {
50 | registration.onupdatefound = () => {
51 | const installingWorker = registration.installing;
52 | installingWorker.onstatechange = () => {
53 | if (installingWorker.state === 'installed') {
54 | if (navigator.serviceWorker.controller) {
55 | // At this point, the old content will have been purged and
56 | // the fresh content will have been added to the cache.
57 | // It's the perfect time to display a "New content is
58 | // available; please refresh." message in your web app.
59 | console.log('New content is available; please refresh.');
60 | } else {
61 | // At this point, everything has been precached.
62 | // It's the perfect time to display a
63 | // "Content is cached for offline use." message.
64 | console.log('Content is cached for offline use.');
65 | }
66 | }
67 | };
68 | };
69 | })
70 | .catch(error => {
71 | console.error('Error during service worker registration:', error);
72 | });
73 | }
74 |
75 | function checkValidServiceWorker(swUrl) {
76 | // Check if the service worker can be found. If it can't reload the page.
77 | fetch(swUrl)
78 | .then(response => {
79 | // Ensure service worker exists, and that we really are getting a JS file.
80 | if (
81 | response.status === 404 ||
82 | response.headers.get('content-type').indexOf('javascript') === -1
83 | ) {
84 | // No service worker found. Probably a different app. Reload the page.
85 | navigator.serviceWorker.ready.then(registration => {
86 | registration.unregister().then(() => {
87 | window.location.reload();
88 | });
89 | });
90 | } else {
91 | // Service worker found. Proceed as normal.
92 | registerValidSW(swUrl);
93 | }
94 | })
95 | .catch(() => {
96 | console.log(
97 | 'No internet connection found. App is running in offline mode.'
98 | );
99 | });
100 | }
101 |
102 | export function unregister() {
103 | if ('serviceWorker' in navigator) {
104 | navigator.serviceWorker.ready.then(registration => {
105 | registration.unregister();
106 | });
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | // =================================================================
2 | // get the packages we need ========================================
3 | // =================================================================
4 | var express = require('express');
5 | var app = express();
6 | var bodyParser = require('body-parser');
7 | var morgan = require('morgan');
8 | var mongoose = require('mongoose');
9 | var cors = require('cors');
10 | var jwt = require('jsonwebtoken'); // used to create, sign, and verify tokens
11 | var config = require('./config'); // get our config file
12 | var User = require('./app/models/user'); // get our mongoose model
13 | var Post = require('./app/models/post');
14 | // =================================================================
15 | // configuration ===================================================
16 | // =================================================================
17 | var port = process.env.PORT || 8060; // used to create, sign, and verify tokens
18 | mongoose.connect(config.database, {
19 | user: config.database_user,
20 | pass: config.database_pass
21 | }); // connect to database
22 | app.set('superSecret', config.secret); // secret variable
23 |
24 | // use body parser so we can get info from POST and/or URL parameters
25 | app.use(bodyParser.urlencoded({ extended: false }));
26 | app.use(bodyParser.json());
27 | // use morgan to log requests to the console
28 | app.use(morgan('dev'));
29 | app.use(cors());
30 |
31 | app.use(function(req, res, next) {
32 | res.header("Access-Control-Allow-Origin", "*");
33 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
34 | res.header("Access-Control-Expose-Headers", "X-Total-Count, Content-Range");
35 | next();
36 | });
37 |
38 | app.set('etag', false);
39 | // =================================================================
40 | // routes ==========================================================
41 | // =================================================================
42 | app.get('/setup', function(req, res) {
43 |
44 | // create a sample user
45 | var nick = new User({
46 | name: 'admin',
47 | password: 'password',
48 | admin: true
49 | });
50 | nick.save(function(err) {
51 | if (err) throw err;
52 |
53 | console.log('User saved successfully');
54 | res.json({ success: true });
55 | });
56 | });
57 |
58 | // basic route (http://localhost:8060)
59 | app.get('/', function(req, res) {
60 | res.send('Hello! The API is at http://localhost:' + port + '/api');
61 | });
62 |
63 | // ---------------------------------------------------------
64 | // get an instance of the router for api routes
65 | // ---------------------------------------------------------
66 | var apiRoutes = express.Router();
67 |
68 | // ---------------------------------------------------------
69 | // authentication (no middleware necessary since this isnt authenticated)
70 | // ---------------------------------------------------------
71 | // http://localhost:8060/api/authenticate
72 | apiRoutes.post('/authenticate', function(req, res) {
73 | // find the user
74 | User.findOne({
75 | name: req.body.username
76 | }, function(err, user) {
77 |
78 | if (err) throw err;
79 |
80 | if (!user) {
81 | res.json(400, { success: false, message: 'Authentication failed. User not found.' });
82 | } else if (user) {
83 |
84 | // check if password matches
85 | if (user.password != req.body.password) {
86 | res.json(400, { success: false, message: 'Authentication failed. Wrong password.' });
87 | } else {
88 |
89 | // if user is found and password is right
90 | // create a token
91 | var token = jwt.sign(user, app.get('superSecret'), {
92 | expiresIn: 86400 // expires in 24 hours
93 | });
94 |
95 | res.json({
96 | success: true,
97 | message: 'Enjoy your token!',
98 | token: token
99 | });
100 | }
101 |
102 | }
103 |
104 | });
105 | });
106 |
107 | // ---------------------------------------------------------
108 | // route middleware to authenticate and check token
109 | // ---------------------------------------------------------
110 | apiRoutes.use(function(req, res, next) {
111 |
112 | // check header or url parameters or post parameters for token
113 | var token = req.body.token || req.param('token') || req.headers['x-access-token'];
114 |
115 | // decode token
116 | if (token) {
117 |
118 | // verifies secret and checks exp
119 | jwt.verify(token, app.get('superSecret'), function(err, decoded) {
120 | if (err) {
121 | return res.json({ success: false, message: 'Failed to authenticate token.' });
122 | } else {
123 | // if everything is good, save to request for use in other routes
124 | req.decoded = decoded;
125 | next();
126 | }
127 | });
128 |
129 | } else {
130 |
131 | // if there is no token
132 | // return an error
133 | return res.status(403).send({
134 | success: false,
135 | message: 'No token provided.'
136 | });
137 |
138 | }
139 |
140 | });
141 |
142 | // ---------------------------------------------------------
143 | // authenticated routes
144 | // ---------------------------------------------------------
145 | apiRoutes.get('/', function(req, res) {
146 | res.json({ message: 'Welcome to the coolest API on earth!' });
147 | });
148 |
149 | apiRoutes.get('/users', function(req, res) {
150 | User.find({}, function(err, users) {
151 | res.json(users);
152 | });
153 | });
154 |
155 | apiRoutes.get('/check', function(req, res) {
156 | res.json(req.decoded);
157 | });
158 |
159 | app.use('/api', apiRoutes);
160 |
161 | app.get('/post', function (req, res) {
162 | Post.find({}, function (err, posts) {
163 | var postsMap = [];
164 |
165 | posts.forEach(function(post) {
166 | postsMap.push({id: post._id, title: post.title, content: post.content, slug: post.slug })
167 | });
168 | res.setHeader('Content-Range', posts.length);
169 | res.send(postsMap);
170 | });
171 | });
172 |
173 | app.get('/post/:id', function (req, res) {
174 | Post.findById({_id: req.params.id}, function (err, post) {
175 | res.send(post);
176 | });
177 | });
178 |
179 | app.get('/post/slug/:slug', function (req, res) {
180 | Post.find({slug: req.params.slug}, function (err, post) {
181 | res.send(post);
182 | });
183 | });
184 |
185 | app.post('/post', apiRoutes, function (req, res) {
186 | // create a sample post
187 | var post = new Post({
188 | title: req.body.content,
189 | content: req.body.title
190 | });
191 |
192 | post.save(function(err) {
193 | if (err) throw err;
194 |
195 | res.json({ success: true });
196 | });
197 | });
198 |
199 | app.put('/post/:id', apiRoutes, function (req, res) {
200 | if (typeof req.body.content === 'undefined' || typeof req.body.title === 'undefined') {
201 | res.send(400, {message: 'no content provided'})
202 | } else {
203 | Post.update({'_id': req.params.id}, {title: req.body.title, content: req.body.content}, function(err, post){
204 | if (err) return res.send(500, { error: err });
205 | return res.send({message: 'success update', post:post});
206 | });
207 | }
208 | });
209 |
210 | // =================================================================
211 | // start the server ================================================
212 | // =================================================================
213 | app.listen(port);
214 | console.log('Magic happens at http://localhost:' + port);
215 |
--------------------------------------------------------------------------------