├── bower ├── .bowerrc └── bower.json ├── .gitignore ├── resources ├── assets │ ├── scss │ │ ├── _project-variables.scss │ │ └── project.scss │ └── js │ │ └── project.js └── views │ ├── partials │ ├── elements │ │ ├── spinners │ │ │ ├── pulse.blade.php │ │ │ ├── rotating-plane.blade.php │ │ │ ├── chasing-dots.blade.php │ │ │ ├── double-bounce.blade.php │ │ │ ├── three-bounce.blade.php │ │ │ ├── wave.blade.php │ │ │ ├── cube-grid.blade.php │ │ │ ├── circle.blade.php │ │ │ └── fading-circle.blade.php │ │ ├── logo-svg.blade.php │ │ └── logo-icon-svg.blade.php │ ├── page-header │ │ ├── page-header-top.blade.php │ │ ├── page-header-actions.blade.php │ │ └── page-header-bottom.blade.php │ ├── core │ │ ├── core-topbar-left-actions.blade.php │ │ ├── core-topbar-right-actions.blade.php │ │ ├── core-topbar.blade.php │ │ ├── core-page-header.blade.php │ │ └── core-sidebar.blade.php │ ├── page-header.blade.php │ ├── sidebar │ │ ├── top-actions.blade.php │ │ ├── bottom-actions.blade.php │ │ └── navigation.blade.php │ ├── breadcrumbs.blade.php │ ├── topbar │ │ ├── admin-menu.blade.php │ │ ├── shortcuts.blade.php │ │ └── user-menu.blade.php │ ├── favicon.blade.php │ ├── components │ │ └── file-picker.blade.php │ └── alerts.blade.php │ ├── reset-password │ ├── emails │ │ ├── text.blade.php │ │ └── html.blade.php │ ├── sent.blade.php │ ├── expired.blade.php │ ├── done.blade.php │ ├── invalid.blade.php │ ├── reset-template.blade.php │ ├── form.blade.php │ └── reset.blade.php │ ├── backend-users │ ├── emails │ │ ├── text.blade.php │ │ └── html.blade.php │ ├── change-password.blade.php │ ├── index.blade.php │ └── roles.blade.php │ ├── errors │ ├── 404.blade.php │ └── 403.blade.php │ ├── layouts │ ├── base-sidebar.blade.php │ └── base.blade.php │ ├── base.blade.php │ └── login │ └── default.blade.php ├── public ├── images │ ├── nodes.png │ ├── n-stack-logo.png │ ├── n-stack-logo@2x.png │ ├── n-stack-logo@3x.png │ ├── fallback_profile_image.png │ └── n-stack-logo.svg └── favicons │ ├── favicon.ico │ ├── apple-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── ms-icon-70x70.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── apple-icon-precomposed.png │ ├── browserconfig.xml │ └── manifest.json ├── src ├── Auth │ ├── Exceptions │ │ ├── InvalidUserModelException.php │ │ ├── InvalidUserRepositoryException.php │ │ ├── ResetPasswordNoUserException.php │ │ ├── InvalidTokenException.php │ │ ├── TokenExpiredException.php │ │ └── InvalidPasswordException.php │ ├── Contracts │ │ ├── CanResetPassword.php │ │ ├── Provider.php │ │ └── Authenticatable.php │ └── ResetPassword │ │ ├── Validation │ │ └── ResetPasswordValidator.php │ │ ├── ResetPasswordRoutes.php │ │ ├── ResetPasswordModel.php │ │ ├── ResetPasswordController.php │ │ └── ResetPasswordRepository.php ├── Dashboard │ ├── Tiles │ │ ├── NodesStatistics │ │ │ ├── DailyStatistic.php │ │ │ ├── MonthlyStatistic.php │ │ │ └── Statistic.php │ │ ├── IFrame.php │ │ ├── TableCount.php │ │ ├── Charts │ │ │ ├── BarChart.php │ │ │ ├── PieChart.php │ │ │ ├── DoughnutChart.php │ │ │ ├── LineChart.php │ │ │ └── Chart.php │ │ └── Tile.php │ ├── Exceptions │ │ ├── MissingConfigException.php │ │ └── UnsupportedTypeException.php │ └── DashboardCollection.php ├── Models │ ├── Role │ │ ├── Validation │ │ │ └── RoleValidator.php │ │ ├── Role.php │ │ └── RoleRepository.php │ ├── FailedJob │ │ ├── FailedJob.php │ │ └── FailedJobRepository.php │ └── User │ │ ├── Token │ │ └── Token.php │ │ └── Validation │ │ └── UserValidator.php ├── Http │ ├── Middleware │ │ ├── SSL.php │ │ ├── Auth.php │ │ └── ApiAuth.php │ └── Controllers │ │ ├── DashboardController.php │ │ ├── NStackController.php │ │ ├── FailedJobsController.php │ │ └── RolesController.php ├── Support │ ├── Facades │ │ └── Backend.php │ ├── Helpers │ │ ├── Router.php │ │ ├── QueryRestorer.php │ │ └── Auth.php │ └── FlashRestorer.php ├── Routing │ └── Router.php └── ServiceProvider.php ├── routes ├── nstack.php ├── dashboard.php ├── welcome.php ├── failed-jobs.php ├── backend-user-roles.php ├── auth.php ├── reset-password.php └── backend-users.php ├── database ├── seeds │ ├── NodesBackendSeeder.php │ └── users │ │ ├── BackendUsersSeeder.php │ │ └── BackendRolesSeeder.php └── migrations │ ├── failed-jobs │ └── 2016_06_21_165902_create_failed_jobs_table.php │ ├── users │ ├── 2015_05_16_025043_create_backend_users_roles.php │ ├── 2015_05_16_025059_create_backend_user_tokens_table.php │ └── 2015_05_16_025044_create_backend_users_table.php │ └── reset-password │ └── 2015_08_20_1052114_create_table_backend_reset_password_tokens.php ├── gulp ├── config.json ├── tasks │ ├── project-scss-wiredep.task.js │ ├── backend-scripts.task.js │ ├── backend-styles.task.js │ ├── project-styles.task.js │ ├── vendor-scripts.task.js │ └── project-scripts.task.js ├── package.json └── gulpfile.js ├── config ├── general.php ├── nstack.php ├── manager.php ├── dashboard.php ├── welcome.php ├── reset-password.php └── auth.php ├── LICENSE ├── composer.json └── README.md /bower/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache 2 | node_modules 3 | bower_components 4 | .idea -------------------------------------------------------------------------------- /resources/assets/scss/_project-variables.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #0074D9; -------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/pulse.blade.php: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/rotating-plane.blade.php: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /public/images/nodes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/images/nodes.png -------------------------------------------------------------------------------- /public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/favicon.ico -------------------------------------------------------------------------------- /public/favicons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon.png -------------------------------------------------------------------------------- /public/images/n-stack-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/images/n-stack-logo.png -------------------------------------------------------------------------------- /public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/images/n-stack-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/images/n-stack-logo@2x.png -------------------------------------------------------------------------------- /public/images/n-stack-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/images/n-stack-logo@3x.png -------------------------------------------------------------------------------- /bower/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandbox", 3 | "private": true, 4 | "dependencies": { 5 | "nodes-ui": "latest" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /public/favicons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/favicons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/favicons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/favicons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/favicons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/android-icon-36x36.png -------------------------------------------------------------------------------- /public/favicons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/android-icon-48x48.png -------------------------------------------------------------------------------- /public/favicons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/android-icon-72x72.png -------------------------------------------------------------------------------- /public/favicons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/android-icon-96x96.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/favicons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/android-icon-144x144.png -------------------------------------------------------------------------------- /public/favicons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/android-icon-192x192.png -------------------------------------------------------------------------------- /public/favicons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/favicons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /public/images/fallback_profile_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ml-archive/nodes-php-backend/HEAD/public/images/fallback_profile_image.png -------------------------------------------------------------------------------- /resources/views/partials/page-header/page-header-top.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @yield('page-header-top') 3 |
-------------------------------------------------------------------------------- /resources/views/partials/core/core-topbar-left-actions.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @yield('core-topbar-left-actions') 3 |
-------------------------------------------------------------------------------- /resources/views/partials/page-header/page-header-actions.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @yield('page-header-actions') 3 |
-------------------------------------------------------------------------------- /resources/views/partials/page-header/page-header-bottom.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @yield('page-header-bottom') 3 |
4 | -------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/chasing-dots.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/double-bounce.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
-------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/three-bounce.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
-------------------------------------------------------------------------------- /resources/views/partials/page-header.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @include('nodes.backend::partials.breadcrumbs') 3 |
4 |
5 |

@yield('page-header-title')

6 |
-------------------------------------------------------------------------------- /resources/views/partials/sidebar/top-actions.blade.php: -------------------------------------------------------------------------------- 1 | @if(!empty($__env->yieldContent('core-sidebar-top-actions'))) 2 | 5 | @endif 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/assets/scss/project.scss: -------------------------------------------------------------------------------- 1 | @import "project-variables"; 2 | 3 | // bower:scss 4 | @import "../../../bower_components/bootstrap-sass/assets/stylesheets/_bootstrap.scss"; 5 | @import "../../../bower_components/nodes-ui/scss/nodes.scss"; 6 | // endbower 7 | -------------------------------------------------------------------------------- /resources/views/partials/sidebar/bottom-actions.blade.php: -------------------------------------------------------------------------------- 1 | @if(!empty($__env->yieldContent('core-sidebar-bottom-actions'))) 2 | 5 | @endif 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Auth/Exceptions/InvalidUserModelException.php: -------------------------------------------------------------------------------- 1 | 2 | 10 | -------------------------------------------------------------------------------- /public/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/wave.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
-------------------------------------------------------------------------------- /routes/nstack.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Http\Controllers', 'prefix' => '/nstack', 'middleware' => ['web', 'backend.ssl', 'backend.auth']], function () { 4 | 5 | // NStack hook 6 | Route::get('/', [ 7 | 'as' => 'nodes.backend.nstack', 8 | 'uses' => 'NStackController@hook', 9 | ]); 10 | }); 11 | -------------------------------------------------------------------------------- /routes/dashboard.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Http\Controllers', 'prefix' => 'admin/', 'middleware' => ['web', 'backend.ssl', 'backend.auth']], function () { 4 | 5 | // Dashboard 6 | Route::get('/', [ 7 | 'as' => 'nodes.backend.dashboard', 8 | 'uses' => 'DashboardController@index', 9 | ]); 10 | }); 11 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/NodesStatistics/DailyStatistic.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DailyStatistic extends Statistic 11 | { 12 | /** 13 | * @var string 14 | */ 15 | protected $period = 'daily'; 16 | } 17 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/NodesStatistics/MonthlyStatistic.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | class MonthlyStatistic extends Statistic 10 | { 11 | /** 12 | * @var string 13 | */ 14 | protected $period = 'monthly'; 15 | } 16 | -------------------------------------------------------------------------------- /resources/assets/js/project.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | 4 | var consoleCSSLarge = 'color: #2C82C9; font-size: 18px;'; 5 | var consoleCSSDefault = 'color: #111111; font-size: 14px;'; 6 | console.log('%cWelcome to your new Nodes Backend', consoleCSSLarge); 7 | console.log('%cFeel free to remove these lines of code from the project javascript file.', consoleCSSDefault); 8 | 9 | })(); -------------------------------------------------------------------------------- /resources/views/reset-password/emails/text.blade.php: -------------------------------------------------------------------------------- 1 | Hello, 2 | 3 | We have received a request to reset the password of the user with this e-mail. 4 | If you did not request this, simply ignore and delete this e-mail. 5 | 6 | To reset your password, click the following link: 7 | {{ $domain }}/admin/login/reset/{{ $token }} 8 | 9 | This reset password request will expire in {{ $expire }} minutes. -------------------------------------------------------------------------------- /routes/welcome.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Http\Controllers', 'prefix' => '/', 'middleware' => ['web', 'backend.ssl']], function () { 4 | 5 | // Landing page when landing on the root domain, 6 | // If this does not work, it might be cause /app/Http/routes.php has it by default also 7 | Route::get('/', [ 8 | 'as' => 'nodes.backend', 9 | 'uses' => 'AuthController@login', 10 | ]); 11 | }); 12 | -------------------------------------------------------------------------------- /database/seeds/NodesBackendSeeder.php: -------------------------------------------------------------------------------- 1 | call('BackendRolesSeeder'); 18 | $this->call('BackendUsersSeeder'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Auth/Contracts/CanResetPassword.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @return string 18 | */ 19 | public function getEmailForPasswordReset(); 20 | } 21 | -------------------------------------------------------------------------------- /resources/views/partials/core/core-topbar-right-actions.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @yield('core-topbar-right-actions') 3 | @include('nodes.backend::partials.topbar.shortcuts', [ 4 | 'renderForMobile' => false 5 | ]) 6 | @include('nodes.backend::partials.topbar.user-menu', [ 7 | 'renderForMobile' => false 8 | ]) 9 | @include('nodes.backend::partials.topbar.admin-menu', [ 10 | 'renderForMobile' => false 11 | ]) 12 |
-------------------------------------------------------------------------------- /resources/views/backend-users/emails/text.blade.php: -------------------------------------------------------------------------------- 1 | Hello {{$user->name}}, 2 | 3 | You have been invited to join {{ucfirst($project)}} admin backend 4 | 5 | The backend can be accessed here: 6 | {{$url}} 7 | 8 | You can login with following credentials: 9 | 10 | E-mail: {{$user->email}} 11 | Password: {{$password}} 12 | 13 | @if($user->should_reset_password) 14 | After login in, you will be asked to change your password. 15 | @endif 16 | 17 | Best regards, 18 | {{ucfirst($project)}} 19 | -------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/cube-grid.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
-------------------------------------------------------------------------------- /resources/views/reset-password/sent.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::reset-password.reset-template') 2 | 3 | @section('feedback-header') 4 |

Reset password

5 | @endsection 6 | 7 | @section('feedback-message') 8 |

Almost there ...

9 |

We have sent you an e-mail with a link to where you can reset your password.

10 |

The link in the e-mail is only valid for 1 hour.

11 | @endsection -------------------------------------------------------------------------------- /resources/views/reset-password/expired.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::reset-password.reset-template') 2 | 3 | @section('feedback-header') 4 |

Reset password

5 | @endsection 6 | 7 | @section('feedback-message') 8 | 11 |

Your reset password request has expired. To reset your password you need to request a new token. 12 | @endsection -------------------------------------------------------------------------------- /resources/views/reset-password/done.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::reset-password.reset-template') 2 | 3 | @section('feedback-header') 4 |

Reset password

5 | @endsection 6 | 7 | @section('feedback-message') 8 |

Congratulations!

9 |

Your password has been now been updated and you can now delete the before sent e-mail.

10 | Go to login 11 | @endsection -------------------------------------------------------------------------------- /resources/views/reset-password/invalid.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::reset-password.reset-template') 2 | 3 | @section('feedback-header') 4 |

Reset password

5 | @endsection 6 | 7 | @section('feedback-message') 8 | 11 |

The token you're trying to use is invalid. Either this is because the token doesn't exist or because it has already been used.

12 | @endsection -------------------------------------------------------------------------------- /src/Models/Role/Validation/RoleValidator.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'slug' => ['required', 'unique:backend_roles,slug,{:id}'], 20 | 'title' => ['required'], 21 | ], 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/IFrame.php: -------------------------------------------------------------------------------- 1 | 19 | * @access public 20 | * @return string 21 | */ 22 | public function getType() 23 | { 24 | return 'i-frame'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Dashboard/Exceptions/MissingConfigException.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class MissingConfigException extends Exception 12 | { 13 | /** 14 | * MissingConfigException constructor. 15 | * 16 | * @param string $message 17 | */ 18 | public function __construct($message) 19 | { 20 | parent::__construct($message, 500); 21 | $this->report(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Dashboard/Exceptions/UnsupportedTypeException.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class UnsupportedTypeException extends Exception 12 | { 13 | /** 14 | * MissingConfigException constructor. 15 | * 16 | * @param string $message 17 | */ 18 | public function __construct($message) 19 | { 20 | parent::__construct($message, 500); 21 | 22 | $this->report(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Models/FailedJob/FailedJob.php: -------------------------------------------------------------------------------- 1 | 19 | * 20 | * @param \Illuminate\Http\Request $request 21 | * @param \Illuminate\Routing\Route $route 22 | * @return mixed 23 | */ 24 | public function authenticate(Request $request, Route $route); 25 | } 26 | -------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/circle.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
-------------------------------------------------------------------------------- /resources/views/reset-password/emails/html.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Reset password 4 | 5 | 6 | 7 |

Hello,

8 |

9 | We have received a request to reset the password of the user with this e-mail.
10 | If you did not request this, simply ignore and delete this e-mail. 11 |

12 |

13 | To reset your password, click the following link:
14 | {{$domain}}/admin/login/reset/{{$token}} 15 |

16 |

This reset password request will expire in {{ $expire }} minutes.

17 | 18 | -------------------------------------------------------------------------------- /gulp/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasksPath": "./gulp/tasks/", 3 | "project": { 4 | "src": { 5 | "scss": "./resources/assets/scss", 6 | "scripts": "./resources/assets/js", 7 | "views": "./resources/views" 8 | }, 9 | "dest": { 10 | "css": "./public/css", 11 | "scripts": "./public/js", 12 | "views": "" 13 | }, 14 | "bower": { 15 | "directory": "./bower_components", 16 | "bowerJson": "./bower.json" 17 | }, 18 | "nodesUI": { 19 | "bowerJson": "./bower_components/nodes-ui/bower.json" 20 | } 21 | }, 22 | "ignoredBowerPkgs": [ 23 | "!**/bower_components/bootstrap/dist/js/bootstrap.js", 24 | "!**/bower_components/blueimp-tmpl/js/tmpl.js" 25 | ] 26 | } -------------------------------------------------------------------------------- /resources/views/partials/elements/spinners/fading-circle.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
-------------------------------------------------------------------------------- /src/Http/Middleware/SSL.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class SSL 12 | { 13 | /** 14 | * @author Casper Rasmussen 15 | * @param $request 16 | * @param Closure $next 17 | * @return \Illuminate\Http\RedirectResponse 18 | */ 19 | public function handle($request, Closure $next) 20 | { 21 | if (! $request->secure() && env('APP_ENV') == 'production') { 22 | return redirect()->secure($request->getRequestUri()); 23 | } 24 | 25 | return $next($request); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /gulp/tasks/project-scss-wiredep.task.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var wiredep = require('wiredep').stream; 4 | 5 | var Elixir = require('laravel-elixir'); 6 | 7 | var gulpConfig = require('../config.json'); 8 | 9 | var Task = Elixir.Task; 10 | 11 | Elixir.extend('projectScssWiredep', function(jsOutputFile, jsOutputFolder, cssOutputFile, cssOutputFolder) { 12 | 13 | var scssRoot = gulpConfig.project.src.scss; 14 | var scssMainFile = path.join(scssRoot, 'project.scss'); 15 | 16 | new Task('project-scss-wiredep', function() { 17 | return gulp.src(scssMainFile) 18 | .pipe(wiredep()) 19 | .pipe(gulp.dest(scssRoot)); 20 | 21 | }).watch('bower.json'); 22 | 23 | }); -------------------------------------------------------------------------------- /src/Auth/ResetPassword/Validation/ResetPasswordValidator.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'token' => ['required', 'exists:backend_reset_password_tokens,token,used,0'], 22 | 'email' => ['required', 'exists:backend_users,email'], 23 | 'password' => ['required', 'confirmed', 'min:6'], 24 | ], 25 | ]; 26 | } 27 | -------------------------------------------------------------------------------- /src/Http/Controllers/DashboardController.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * @return \Illuminate\View\View 19 | */ 20 | public function index() 21 | { 22 | $dashboardCollection = new DashboardCollection(config('nodes.backend.dashboard.list')); 23 | 24 | return view('nodes.backend::dashboard.index', compact('dashboardCollection')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Auth/Exceptions/ResetPasswordNoUserException.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @param string $message 18 | * @param int $statusCode 19 | * @param string $statusCodeMessage 20 | * @param bool $report 21 | */ 22 | public function __construct($message, $statusCode = 446, $statusCodeMessage = 'No user found', $report = false) 23 | { 24 | parent::__construct($message, $statusCode, $statusCodeMessage, $report); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /config/general.php: -------------------------------------------------------------------------------- 1 | '/images/fallback_profile_image.png', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Disable "auto complete" on email fields 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Disables default "auto complete" behaviour on email fields 20 | | existing on the provided views 21 | | 22 | */ 23 | 'disable_autocomplete' => false, 24 | ]; 25 | -------------------------------------------------------------------------------- /gulp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sandbox", 3 | "private": "true", 4 | "devDependencies": { 5 | "autoprefixer": "^6.0.3", 6 | "gulp": "3.9.0", 7 | "gulp-concat": "^2.6.0", 8 | "gulp-concat-sourcemap": "^1.3.1", 9 | "gulp-copy": "0.0.2", 10 | "gulp-cssmin": "^0.1.7", 11 | "gulp-debug": "^2.1.0", 12 | "gulp-filter": "^3.0.1", 13 | "gulp-if": "^2.0.0", 14 | "gulp-postcss": "^6.0.0", 15 | "gulp-rename": "^1.2.2", 16 | "gulp-sass": "^2.0.4", 17 | "gulp-shell": "^0.4.3", 18 | "gulp-sourcemaps": "^1.5.2", 19 | "gulp-uglify": "^1.4.1", 20 | "gulp-usemin": "^0.3.14", 21 | "laravel-elixir": "*", 22 | "main-bower-files": "^2.9.0", 23 | "underscore": "^1.8.3", 24 | "wiredep": "^3.0.0-beta" 25 | } 26 | } -------------------------------------------------------------------------------- /resources/views/errors/404.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::layouts.base') 2 | 3 | @section('content') 4 | 11 |
12 |
13 |

Not found

14 |
15 |
16 |

You've tried to view a page when does not exist

17 |

If you believe this is a mistake, please contact an administrator.

18 |
19 |
20 | @endsection 21 | -------------------------------------------------------------------------------- /resources/views/partials/core/core-topbar.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 7 | @if(config('nodes.backend.general.name', 'Backend')) 8 |

9 | {{ ucfirst(config('nodes.backend.general.name', 'Backend')) }} 10 |

11 | @endif 12 |
13 |
14 | 15 |
16 | @include('nodes.backend::partials.core.core-topbar-left-actions') 17 | @include('nodes.backend::partials.core.core-topbar-right-actions') 18 |
19 | -------------------------------------------------------------------------------- /database/migrations/failed-jobs/2016_06_21_165902_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->timestamp('failed_at')->useCurrent(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('failed_jobs'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/favicons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /resources/views/backend-users/emails/html.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Invitation to {{ ucfirst($project) }} admin backend 4 | 5 | 6 | 7 |

Hello {{ $user->name }},

8 |

You have been invited to join {{ ucfirst($project) }} admin backend.

9 |

The backend can be accessed here:
{{ $url }}

10 |

11 | You can login with following credentials:
12 |
13 | E-mail: {{ $user->email }} 14 |
15 | Password: {{ $password }} 16 |

17 | @if($user->change_password) 18 |

Note: Password was randomly generated. You will be asked to change your password at your first login.

19 | @endif 20 |

Best regards
{{ ucfirst($project) }}

21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /resources/views/errors/403.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::layouts.base') 2 | 3 | @section('content') 4 | 11 |
12 |
13 |

Permission denied

14 |
15 |
16 |

You've tried to view a page or perform an action which you don't have permission to.

17 |

If you believe this is a mistake, please contact an administrator.

18 |
19 |
20 | @endsection 21 | -------------------------------------------------------------------------------- /src/Support/Facades/Backend.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @static 18 | * @return \Nodes\Backend\Auth\Authenticator 19 | */ 20 | public static function auth() 21 | { 22 | return static::$app['nodes.backend.auth']; 23 | } 24 | 25 | /** 26 | * Retrieve nodes.backend.backend router. 27 | * 28 | * @author Morten Rugaard 29 | * 30 | * @static 31 | * @return \Nodes\Backend\Routing\Router 32 | */ 33 | public static function router() 34 | { 35 | return static::$app['nodes.backend.router']; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Auth/Exceptions/InvalidTokenException.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @param string $message 18 | * @param int $code 19 | * @param array $headers 20 | * @param bool $report 21 | * @param string $severity 22 | */ 23 | public function __construct($message = 'Invalid token', $code = 442, array $headers = [], $report = false, $severity = 'error') 24 | { 25 | parent::__construct($message, $code, $headers, $report, $severity); 26 | 27 | // Set status code and status message 28 | $this->setStatusCode(442, 'Invalid token'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Auth/Exceptions/TokenExpiredException.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @param string $message 18 | * @param int $code 19 | * @param array $headers 20 | * @param bool $report 21 | * @param string $severity 22 | */ 23 | public function __construct($message = 'Token expired', $code = 443, array $headers = [], $report = false, $severity = 'error') 24 | { 25 | parent::__construct($message, $code, $headers, $report, $severity); 26 | 27 | // Set status code and status message 28 | $this->setStatusCode(443, 'Token expired'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Auth/Exceptions/InvalidPasswordException.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @param string $message Error message 18 | * @param mixed $code Error code 19 | * @param array $headers List of headers 20 | * @param bool $report Wether or not exception should be reported 21 | * @param string $severity Options: "fatal", "error", "warning", "info" 22 | */ 23 | public function __construct($message = 'Invalid password', $code = 400, $headers = [], $report = true, $severity = 'error') 24 | { 25 | parent::__construct($message, $code, $headers, $report, $severity); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/TableCount.php: -------------------------------------------------------------------------------- 1 | 15 | * @access public 16 | * @param $data 17 | * @return array 18 | */ 19 | public function prepareChartData($data) 20 | { 21 | $chartData = [ 22 | 'id' => $this->id, 23 | 'title' => $this->title, 24 | 'data' => [], 25 | 'labels' => [], 26 | ]; 27 | 28 | foreach ($data as $table => $label) { 29 | $chartData['data'][] = \DB::table($table)->count(); 30 | $chartData['labels'][] = $label; 31 | } 32 | 33 | return $chartData; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/users/2015_05_16_025043_create_backend_users_roles.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('title', 255); 18 | $table->string('slug', 255)->unique(); 19 | $table->boolean('default')->unsigned()->default(false); 20 | $table->integer('user_count')->unsigned()->default(0); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('backend_roles'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /routes/failed-jobs.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Http\Controllers', 'prefix' => 'admin/failed-jobs', 'middleware' => ['web', 'backend.ssl', 'backend.auth']], function () { 4 | 5 | // List of all failed jobs 6 | Route::get('/', [ 7 | 'as' => 'nodes.backend.failed-jobs', 8 | 'uses' => 'FailedJobsController@index', 9 | ]); 10 | 11 | // Restart one 12 | Route::post('/{id}/restart', [ 13 | 'as' => 'nodes.backend.failed-jobs.restart', 14 | 'uses' => 'FailedJobsController@restart', 15 | ])->where('id', '[0-9]+'); 16 | 17 | // Restart all 18 | Route::post('/restart-all', [ 19 | 'as' => 'nodes.backend.failed-jobs.restart-all', 20 | 'uses' => 'FailedJobsController@restartAll', 21 | ]); 22 | 23 | // Forget one 24 | Route::post('/{id}/forget', [ 25 | 'as' => 'nodes.backend.failed-jobs.forget', 26 | 'uses' => 'FailedJobsController@forget', 27 | ])->where('id', '[0-9]+'); 28 | }); 29 | -------------------------------------------------------------------------------- /gulp/tasks/backend-scripts.task.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var filter = require('gulp-filter'); 3 | var uglify = require('gulp-uglify'); 4 | var concatSourcemaps = require('gulp-concat-sourcemap'); 5 | var concat = require('gulp-concat'); 6 | var gulpIf = require('gulp-if'); 7 | 8 | var Elixir = require('laravel-elixir'); 9 | 10 | var Task = Elixir.Task; 11 | 12 | Elixir.extend('backendScripts', function(jsOutputFile, jsOutputFolder) { 13 | 14 | var jsFile = jsOutputFile || 'backend.js'; 15 | 16 | var jsSources = './vendor/nodesagency/backend/resources/assets/js/**/*.js'; 17 | 18 | if(!Elixir.config.production) { 19 | concat = concatSourcemaps; 20 | } 21 | 22 | new Task('backend-scripts', function() { 23 | 24 | return gulp.src(jsSources) 25 | .pipe(concat(jsFile, {sourcesContent: true})) 26 | .pipe(gulpIf(Elixir.config.production, uglify())) 27 | .pipe(gulp.dest(jsOutputFolder || Elixir.config.js.outputFolder)); 28 | 29 | }).watch(jsSources); 30 | 31 | }); -------------------------------------------------------------------------------- /resources/views/partials/core/core-page-header.blade.php: -------------------------------------------------------------------------------- 1 | @if( 2 | !empty($__env->yieldContent('page-header-actions')) || 3 | !empty($__env->yieldContent('page-header-top')) || 4 | !empty($__env->yieldContent('page-header-bottom')) || 5 | !empty($__env->yieldContent('breadcrumbs')) 6 | ) 7 | 29 | @endif -------------------------------------------------------------------------------- /resources/views/partials/topbar/admin-menu.blade.php: -------------------------------------------------------------------------------- 1 | @if(!empty($__env->yieldContent('core-topbar-admin-menu'))) 2 | @if($renderForMobile) 3 | 13 | @else 14 | 22 | @endif 23 | @endif 24 | 25 | 26 | -------------------------------------------------------------------------------- /database/migrations/reset-password/2015_08_20_1052114_create_table_backend_reset_password_tokens.php: -------------------------------------------------------------------------------- 1 | increments('id'); 19 | $table->string('email', 255)->index(); 20 | $table->string('token', 64)->index(); 21 | $table->boolean('used')->unsigned()->default(false); 22 | $table->timestamp('expire_at'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::drop('backend_reset_password_tokens'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Models/FailedJob/FailedJobRepository.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @param \Nodes\Backend\Models\FailedJob\FailedJob $model 18 | */ 19 | public function __construct(FailedJob $model) 20 | { 21 | $this->setupRepository($model); 22 | } 23 | 24 | /** 25 | * Retrieve all failed jobs paginated. 26 | * 27 | * @author Casper Rasmussen 28 | * 29 | * @param int $limit 30 | * @param array $fields 31 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 32 | */ 33 | public function getPaginatedForBackend($limit = 25, $fields = ['*']) 34 | { 35 | return $this->paginate($limit, $fields); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/seeds/users/BackendUsersSeeder.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * @return void 17 | */ 18 | public function run() 19 | { 20 | // Inform user 21 | $this->command->info('Creating Nodes user ...'); 22 | 23 | // Seed Nodes "super user" 24 | try { 25 | app(UserRepository::class)->createUser([ 26 | 'name' => 'Nodes ApS', 27 | 'email' => 'tech@nodes.dk', 28 | 'password' => str_random(), 29 | 'user_role' => 'developer', 30 | ]); 31 | } catch (\Exception $e) { 32 | $this->command->error($e->getMessage()); 33 | } 34 | 35 | // Inform user 36 | $this->command->info('Nodes user successfully created!'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Models/User/Token/Token.php: -------------------------------------------------------------------------------- 1 | 40 | * 41 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 42 | */ 43 | public function user() 44 | { 45 | return $this->belongsTo(\Nodes\Backend\Models\User\User::class, 'user_id'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /resources/views/partials/favicon.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nodes Agency - PHP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /routes/backend-user-roles.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Http\Controllers', 'prefix' => 'admin/backend-users/roles', 'middleware' => ['web', 'backend.ssl', 'backend.auth']], function () { 4 | // List all roles 5 | Route::get('/', [ 6 | 'as' => 'nodes.backend.users.roles', 7 | 'uses' => 'RolesController@index', 8 | ]); 9 | 10 | // Create new role 11 | Route::post('/store', [ 12 | 'as' => 'nodes.backend.users.roles.store', 13 | 'uses' => 'RolesController@store', 14 | ]); 15 | 16 | // Update role 17 | Route::patch('/{id}/update', [ 18 | 'as' => 'nodes.backend.users.roles.update', 19 | 'uses' => 'RolesController@update', 20 | ])->where('id', '[0-9]+'); 21 | 22 | // Delete role 23 | Route::delete('/{id}/destroy', [ 24 | 'as' => 'nodes.backend.users.roles.destroy', 25 | 'uses' => 'RolesController@destroy', 26 | ])->where('id', '[0-9]+'); 27 | 28 | Route::post('/{id}/default', [ 29 | 'as' => 'nodes.backend.users.roles.default', 30 | 'uses' => 'RolesController@setDefault', 31 | ])->where('id', '[0-9]+'); 32 | }); 33 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/Charts/BarChart.php: -------------------------------------------------------------------------------- 1 | 14 | * @access public 15 | * @return string 16 | */ 17 | public function getType() 18 | { 19 | return 'bar-chart'; 20 | } 21 | 22 | /** 23 | * prepareChartData 24 | * 25 | * @author Casper Rasmussen 26 | * @access protected 27 | * @param array $data 28 | * @return array 29 | */ 30 | protected function prepareChartData($data) 31 | { 32 | $chartData = [ 33 | 'id' => $this->id, 34 | 'title' => $this->title, 35 | 'data' => [], 36 | 'labels' => [], 37 | ]; 38 | 39 | foreach ($data as $key => $value) { 40 | $chartData['labels'][] = $key; 41 | $chartData['data'][] = $value; 42 | } 43 | 44 | return $chartData; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /resources/views/partials/core/core-sidebar.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Menu

4 | 5 |
6 | 7 | @include('nodes.backend::partials.topbar.user-menu', [ 8 | 'renderForMobile' => true 9 | ]) 10 | @include('nodes.backend::partials.topbar.admin-menu', [ 11 | 'renderForMobile' => true 12 | ]) 13 | @include('nodes.backend::partials.topbar.shortcuts', [ 14 | 'renderForMobile' => true 15 | ]) 16 |
17 | 18 | -------------------------------------------------------------------------------- /src/Support/Helpers/Router.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * @return \Nodes\Backend\Routing\Router 9 | */ 10 | function backend_router() 11 | { 12 | return app('nodes.backend.router'); 13 | } 14 | } 15 | 16 | if (! function_exists('backend_router_pattern')) { 17 | /** 18 | * Match route by pattern. 19 | * 20 | * @author Casper Rasmussen 21 | * 22 | * @param string|array $patterns 23 | * @return string 24 | */ 25 | function backend_router_pattern($patterns) 26 | { 27 | return app('nodes.backend.router')->pattern($patterns); 28 | } 29 | } 30 | 31 | if (! function_exists('backend_router_alias')) { 32 | /** 33 | * Match route by pattern. 34 | * 35 | * @author Casper Rasmussen 36 | * 37 | * @param string|array $aliases 38 | * @return string 39 | */ 40 | function backend_router_alias($aliases) 41 | { 42 | return app('nodes.backend.router')->alias($aliases); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/migrations/users/2015_05_16_025059_create_backend_user_tokens_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->integer('user_id')->unsigned(); 18 | $table->foreign('user_id')->references('id')->on('backend_users')->onDelete('cascade'); 19 | $table->string('token', 60); 20 | $table->dateTime('expire')->nullable(); 21 | $table->timestamps(); 22 | }); 23 | 24 | // Update "token" to be binary, so it's case sensitive 25 | DB::statement('ALTER TABLE `backend_user_tokens` CHANGE `token` `token` VARCHAR(60) BINARY NOT NULL'); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::dropIfExists('backend_user_tokens'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /resources/views/reset-password/reset-template.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::base') 2 | 3 | @section('layout') 4 | 30 | @stop -------------------------------------------------------------------------------- /src/Dashboard/Tiles/Charts/PieChart.php: -------------------------------------------------------------------------------- 1 | 18 | * @access public 19 | * @return string 20 | */ 21 | public function getType() 22 | { 23 | return 'pie-chart'; 24 | } 25 | 26 | /** 27 | * prepareChartData 28 | * 29 | * @author Casper Rasmussen 30 | * @access protected 31 | * @param array $config 32 | * @return array 33 | */ 34 | protected function prepareChartData($data) 35 | { 36 | $chartData = [ 37 | 'id' => $this->id, 38 | 'title' => $this->title, 39 | 'data' => [], 40 | 'labels' => [] 41 | ]; 42 | 43 | foreach ($data as $key => $value) { 44 | $chartData['data'][] = $value; 45 | $chartData['labels'][] = $key; 46 | } 47 | 48 | return $chartData; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /resources/views/partials/topbar/shortcuts.blade.php: -------------------------------------------------------------------------------- 1 | @if(!empty($__env->yieldContent('core-topbar-shortcuts'))) 2 | @if($renderForMobile) 3 | 13 | @else 14 | 27 | 28 | @endif 29 | @endif 30 | 31 | 32 | -------------------------------------------------------------------------------- /config/nstack.php: -------------------------------------------------------------------------------- 1 | 'https://nstack.io/deeplink', 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Credentials 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Your application credentials on NStack. These are required 20 | | to perform any kinds of actions with NStack. 21 | | 22 | */ 23 | 'credentials' => [ 24 | 'default' => [ 25 | 'appId' => null, 26 | 'masterKey' => null, 27 | ], 28 | ], 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Defaults 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Default values regarding nstack 35 | | 36 | */ 37 | 'defaults' => [ 38 | 'application' => 'default', 39 | ], 40 | ]; 41 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/Charts/DoughnutChart.php: -------------------------------------------------------------------------------- 1 | 18 | * @access public 19 | * @return string 20 | */ 21 | public function getType() 22 | { 23 | return 'doughnut-chart'; 24 | } 25 | 26 | /** 27 | * prepareChartData 28 | * 29 | * @author Casper Rasmussen 30 | * @access protected 31 | * @param array $config 32 | * @return array 33 | */ 34 | protected function prepareChartData($data) 35 | { 36 | $chartData = [ 37 | 'id' => $this->id, 38 | 'title' => $this->title, 39 | 'data' => [], 40 | 'labels' => [], 41 | ]; 42 | 43 | foreach ($data as $key => $value) { 44 | $chartData['data'][] = $value; 45 | $chartData['labels'][] = $key; 46 | } 47 | 48 | return $chartData; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /database/migrations/users/2015_05_16_025044_create_backend_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->string('image')->nullable(); 18 | $table->string('name', 255); 19 | $table->string('email', 255)->unique(); 20 | $table->string('password', 255); 21 | $table->string('user_role', 255)->default('user')->index(); 22 | $table->foreign('user_role')->references('slug')->on('backend_roles')->onDelete('cascade'); 23 | $table->boolean('change_password')->unsigned()->default(false); 24 | $table->rememberToken(); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('backend_users'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/Charts/LineChart.php: -------------------------------------------------------------------------------- 1 | 19 | * @access public 20 | * @return string 21 | */ 22 | public function getType() 23 | { 24 | return 'line-chart'; 25 | } 26 | 27 | /** 28 | * prepareChartData 29 | * 30 | * @author Casper Rasmussen 31 | * @access protected 32 | * @param array $data 33 | * @return array 34 | */ 35 | protected function prepareChartData($data) 36 | { 37 | $chartData = [ 38 | 'id' => $this->id, 39 | 'title' => $this->title, 40 | 'data' => [], 41 | 'labels' => [] 42 | ]; 43 | 44 | foreach ($data as $key => $value) { 45 | $chartData['data'][] = $value; 46 | $chartData['labels'][] = $key; 47 | } 48 | 49 | return $chartData; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /config/manager.php: -------------------------------------------------------------------------------- 1 | true, 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | Manager user 16 | |-------------------------------------------------------------------------- 17 | | 18 | | Set the email of the manager auth user 19 | | 20 | */ 21 | 'email' => 'tech@nodes.dk', 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Separate users 26 | |-------------------------------------------------------------------------- 27 | | 28 | | Should each user be created as their own 29 | | 30 | */ 31 | 'separate_users' => true, 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Role 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Set the role of users get created by manager 39 | | 40 | */ 41 | 'role' => 'developer', 42 | ]; 43 | -------------------------------------------------------------------------------- /resources/views/reset-password/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::reset-password.reset-template') 2 | 3 | @section('feedback-header') 4 |

Forgot password

5 | @endsection 6 | 7 | @section('feedback-message') 8 | {!! Form::open(['method' => 'post', 'route' => 'nodes.backend.reset-password.token']) !!} 9 |
10 | {!! Form::label('login-email', 'E-mail address', ['class' => 'sr-only']) !!} 11 | {!! 12 | Form::email( 13 | 'email', 14 | Session::get('email'), 15 | [ 16 | 'id' => 'login-email', 17 | 'class' => 'form-control', 18 | 'placeholder' => 'E-mail address', 19 | 'autocomplete' => config('nodes.backend.general.disable_autocomplete', false) ? 'on' : 'off' 20 | ] 21 | ) 22 | !!} 23 | 24 | 25 | 26 |
27 |
28 | {!! Form::submit('Reset my password', ['class' => 'btn btn-primary form-control']) !!} 29 |
30 | {!! Form::close() !!} 31 | @endsection 32 | -------------------------------------------------------------------------------- /database/seeds/users/BackendRolesSeeder.php: -------------------------------------------------------------------------------- 1 | 'Developer', 17 | 'super-admin' => 'Super admin', 18 | 'admin' => 'Admin', 19 | 'user' => 'User', 20 | ]; 21 | 22 | /** 23 | * @author Casper Rasmussen 24 | */ 25 | public function run() 26 | { 27 | // Inform user 28 | $this->command->info('Seeding backend roles ...'); 29 | 30 | // Init user repository 31 | $roleRepository = app(RoleRepository::class); 32 | 33 | foreach ($this->roles as $slug => $title) { 34 | try { 35 | $roleRepository->create([ 36 | 'title' => $title, 37 | 'slug' => $slug, 38 | 'default' => ($slug == 'user') ? 1 : 0, 39 | ]); 40 | } catch (\Exception $e) { 41 | $this->command->error($e->getMessage()); 42 | } 43 | } 44 | 45 | $this->command->info('Backend roles seeded!'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /resources/views/layouts/base-sidebar.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::base') 2 | 3 | @section('layout') 4 |
5 | 6 |
7 |
8 | @include('nodes.backend::partials.core.core-topbar', [ 9 | 'renderForMobile' => true 10 | ]) 11 |
12 |
13 | 14 |
15 |
16 |
17 | @include('nodes.backend::partials.core.core-sidebar', [ 18 | 'renderForMobile' => true 19 | ]) 20 |
21 |
22 | 23 |
24 | @include('nodes.backend::partials.core.core-page-header') 25 |
26 |
27 | @yield('content') 28 |
29 |
30 |
31 |
32 | 33 |
34 | @include('nodes.backend::partials.alerts') 35 |
36 | 37 |
38 | @endsection -------------------------------------------------------------------------------- /src/Auth/ResetPassword/ResetPasswordRoutes.php: -------------------------------------------------------------------------------- 1 | '/admin/login/reset'], function () { 4 | 5 | // Generate reset password token 6 | Route::post('/', [ 7 | 'uses' => 'ResetPasswordController@generateResetToken', 8 | 'as' => 'nodes.backend.reset-password.token', 9 | ]); 10 | 11 | // Request reset password token form 12 | Route::get('/', [ 13 | 'uses' => 'ResetPasswordController@index', 14 | 'as' => 'nodes.backend.reset-password.form', 15 | ]); 16 | 17 | // Confirmation page of e-mail has been sent 18 | Route::get('/sent', [ 19 | 'uses' => 'ResetPasswordController@sent', 20 | 'as' => 'nodes.backend.reset-password.sent', 21 | ]); 22 | 23 | // Reset password form 24 | Route::get('/{token}', [ 25 | 'uses' => 'ResetPasswordController@resetForm', 26 | 'as' => 'nodes.backend.reset-password.reset', 27 | ])->where('token', '[[:alnum:]]{64}'); 28 | 29 | // Change password 30 | Route::post('/update', [ 31 | 'uses' => 'ResetPasswordController@resetPassword', 32 | 'as' => 'nodes.backend.reset-password.change', 33 | ]); 34 | 35 | // Reset password done 36 | Route::get('/done', [ 37 | 'uses' => 'ResetPasswordController@done', 38 | 'as' => 'nodes.backend.reset-password.done', 39 | ]); 40 | }); 41 | -------------------------------------------------------------------------------- /src/Auth/Contracts/Authenticatable.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @return mixed 18 | */ 19 | public function getAuthIdentifier(); 20 | 21 | /** 22 | * Get the password for the user. 23 | * 24 | * @author Morten Rugaard 25 | * 26 | * @return string 27 | */ 28 | public function getAuthPassword(); 29 | 30 | /** 31 | * Get the token value for the "remember me" session. 32 | * 33 | * @author Morten Rugaard 34 | * 35 | * @return string 36 | */ 37 | public function getRememberToken(); 38 | 39 | /** 40 | * Set the token value for the "remember me" session. 41 | * 42 | * @author Morten Rugaard 43 | * 44 | * @param string $value 45 | * @return void 46 | */ 47 | public function setRememberToken($value); 48 | 49 | /** 50 | * Get the column name for the "remember me" token. 51 | * 52 | * @author Morten Rugaard 53 | * 54 | * @return string 55 | */ 56 | public function getRememberTokenName(); 57 | } 58 | -------------------------------------------------------------------------------- /gulp/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * 5 | * Configuration Files 6 | * 7 | */ 8 | var elixir = require('laravel-elixir'); 9 | var gulp = require('gulp'); 10 | 11 | var config = require('./gulp/config.json'); 12 | var pkg = require('./package.json'); 13 | 14 | var projectName = pkg.name; 15 | var projectUrl = 'https://' + pkg.name + '.local-like.st'; 16 | 17 | var filesToWatch = [ 18 | './public/**/*', 19 | './resources/views/**/*', 20 | './vendor/nodesagency/backend/resources/views/**/*', 21 | './nodes/backend/resources/views/**/*', 22 | ]; 23 | 24 | elixir.config.js.outputFolder = 'public/js'; 25 | elixir.config.css.outputFolder = 'public/css'; 26 | 27 | require(config.tasksPath + 'vendor-scripts.task.js'); 28 | 29 | require(config.tasksPath + 'project-scripts.task.js'); 30 | require(config.tasksPath + 'project-styles.task.js'); 31 | require(config.tasksPath + 'project-scss-wiredep.task.js'); 32 | 33 | elixir(function(mix) { 34 | 35 | mix.vendorScripts(); 36 | 37 | mix.projectScssWiredep(); 38 | mix.projectStyles(); 39 | mix.projectScripts(); 40 | 41 | mix.browserSync({ 42 | files: filesToWatch, 43 | proxy: projectUrl, 44 | https: true 45 | }); 46 | 47 | }); 48 | 49 | gulp.task('build', [ 50 | 'project-scss-wiredep', 51 | 'vendor-scripts', 52 | 'project-scripts', 53 | 'project-pages-scripts', 54 | 'project-partials-scripts', 55 | 'project-styles' 56 | ]); -------------------------------------------------------------------------------- /resources/views/base.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ ucfirst(config('nodes.project.name', 'Backend')) }} 6 | 7 | 8 | 9 | 10 | 11 | 12 | @yield('project-css') 13 | @include('nodes.backend::partials.favicon') 14 | 15 | 16 | 17 | @yield('layout') 18 | 19 | @if (env('APP_ENV') == 'local' || env('APP_ENV') == 'staging') 20 |
21 |

22 | 23 | {{ ucfirst(env('APP_ENV')) }} environment 24 |

25 |
26 | @endif 27 | 28 | 29 | 30 | @yield('project-js') 31 | 32 | 33 | -------------------------------------------------------------------------------- /routes/auth.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Http\Controllers', 'prefix' => 'admin/login', 'middleware' => ['web', 'backend.ssl']], function () { 4 | // Login form 5 | Route::get('/', [ 6 | 'as' => 'nodes.backend.login.form', 7 | 'uses' => 'AuthController@login', 8 | ]); 9 | 10 | // Authenticate login 11 | Route::post('/', [ 12 | 'as' => 'nodes.backend.login.authenticate', 13 | 'uses' => 'AuthController@authenticate', 14 | ]); 15 | 16 | // SSO form 17 | Route::get('/sso', [ 18 | 'as' => 'nodes.backend.login.sso', 19 | 'uses' => 'AuthController@sso', 20 | ]); 21 | 22 | // SSO authenticate 23 | Route::post('/sso', [ 24 | 'as' => 'nodes.backend.login.sso.authenticate', 25 | 'uses' => 'AuthController@ssoAuthenticate', 26 | ]); 27 | 28 | // Log authenticated user out 29 | Route::get('/logout', [ 30 | 'as' => 'nodes.backend.login.logout', 31 | 'uses' => 'AuthController@logout', 32 | ]); 33 | }); 34 | 35 | // Manager auth 36 | Route::group(['namespace' => 'Nodes\Backend\Http\Controllers', 'prefix' => 'admin/manager_auth/', 'middleware' => ['web', 'backend.ssl']], function () { 37 | // Authenticate Nodes Manager 38 | Route::post('/', [ 39 | 'as' => 'nodes.backend.login.manager', 40 | 'uses' => 'AuthController@manager', 41 | ]); 42 | }); 43 | -------------------------------------------------------------------------------- /routes/reset-password.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Auth\ResetPassword', 'prefix' => '/admin/login/reset', 'middleware' => ['web', 'backend.ssl']], function () { 4 | // Generate reset password token 5 | Route::post('/', [ 6 | 'uses' => 'ResetPasswordController@generateResetToken', 7 | 'as' => 'nodes.backend.reset-password.token', 8 | ]); 9 | 10 | // Request reset password token form 11 | Route::get('/', [ 12 | 'uses' => 'ResetPasswordController@index', 13 | 'as' => 'nodes.backend.reset-password.form', 14 | ]); 15 | 16 | // Confirmation page of e-mail has been sent 17 | Route::get('/sent', [ 18 | 'uses' => 'ResetPasswordController@sent', 19 | 'as' => 'nodes.backend.reset-password.sent', 20 | ]); 21 | 22 | // Reset password form 23 | Route::get('/{token}', [ 24 | 'uses' => 'ResetPasswordController@resetForm', 25 | 'as' => 'nodes.backend.reset-password.reset', 26 | ])->where('token', '[[:alnum:]]{64}'); 27 | 28 | // Change password 29 | Route::post('/update', [ 30 | 'uses' => 'ResetPasswordController@resetPassword', 31 | 'as' => 'nodes.backend.reset-password.change', 32 | ]); 33 | 34 | // Reset password done 35 | Route::get('/done', [ 36 | 'uses' => 'ResetPasswordController@done', 37 | 'as' => 'nodes.backend.reset-password.done', 38 | ]); 39 | }); 40 | -------------------------------------------------------------------------------- /src/Models/Role/Role.php: -------------------------------------------------------------------------------- 1 | 'integer', 39 | 'user_count' => 'integer', 40 | 'default' => 'boolean', 41 | ]; 42 | 43 | /** 44 | * Role has many users. 45 | * 46 | * @author Morten Rugaard 47 | * 48 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 49 | */ 50 | public function users() 51 | { 52 | return $this->hasMany(User::class, 'user_role', 'slug'); 53 | } 54 | 55 | /** 56 | * Check if role is set as default. 57 | * 58 | * @author Casper Rasmussen 59 | * 60 | * @return bool 61 | */ 62 | public function isDefault() 63 | { 64 | return (bool) $this->default; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /resources/views/layouts/base.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::base') 2 | 3 | @section('layout') 4 |
5 | 6 |
7 |
8 | @include('nodes.backend::partials.core.core-topbar', [ 9 | 'renderForMobile' => false 10 | ]) 11 |
12 |
13 | 14 |
15 |
16 |
17 | @include('nodes.backend::partials.core.core-sidebar', [ 18 | 'renderForMobile' => true 19 | ]) 20 |
21 |
22 | 23 |
24 | @include('nodes.backend::partials.core.core-page-header', [ 25 | 'renderForMobile' => true 26 | ]) 27 |
28 |
29 | @yield('content') 30 |
31 |
32 |
33 |
34 | 35 |
36 | @include('nodes.backend::partials.alerts') 37 |
38 | 39 |
40 | @endsection -------------------------------------------------------------------------------- /gulp/tasks/backend-styles.task.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var sass = require('gulp-sass'); 4 | var postcss = require('gulp-postcss'); 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | var autoprefixer = require('autoprefixer'); 7 | var cssmin = require('gulp-cssmin'); 8 | var rename = require('gulp-rename'); 9 | 10 | var Elixir = require('laravel-elixir'); 11 | 12 | var Task = Elixir.Task; 13 | 14 | Elixir.extend('backendStyles', function(jsOutputFile, jsOutputFolder, cssOutputFile, cssOutputFolder) { 15 | 16 | var cssFile = cssOutputFile || 'backend.css'; 17 | 18 | var scssRoot = './vendor/nodesagency/backend/resources/assets/scss/'; 19 | var scssSources = path.join(scssRoot, '**/*.scss'); 20 | var scssMainFile = path.join(scssRoot, 'nodes.scss'); 21 | 22 | new Task('backend-styles', function() { 23 | 24 | return gulp.src(scssMainFile) 25 | .pipe(sourcemaps.init()) 26 | .pipe(sass({ 27 | outputStyle: Elixir.config.production ? 'compressed' : 'expanded' 28 | })).on('error', sass.logError) 29 | .pipe(postcss([ 30 | autoprefixer({ 31 | browsers: ['last 2 versions', 'ie >= 10'] 32 | }) 33 | ])) 34 | .pipe(sourcemaps.write()) 35 | .pipe(rename(cssFile)) 36 | .pipe(gulp.dest(cssOutputFolder || Elixir.config.css.outputFolder)); 37 | 38 | }).watch(scssSources); 39 | 40 | }); -------------------------------------------------------------------------------- /src/Http/Middleware/Auth.php: -------------------------------------------------------------------------------- 1 | 18 | * 19 | * @param \Illuminate\Http\Request $request 20 | * @param Closure $next 21 | * @return mixed 22 | */ 23 | public function handle($request, Closure $next) 24 | { 25 | // Route is protected and requires a user session 26 | // 27 | // If user is not already logged in, we'll try and 28 | // look for the user in sessions and cookies. 29 | if (! backend_user_check()) { 30 | try { 31 | backend_user_authenticate(); 32 | } catch (\Exception $e) { 33 | // Create redirect response 34 | $redirectResponse = redirect()->route('nodes.backend.login.form', [ 35 | 'redirect_url' => $request->url() 36 | ])->with('warning', 'Oops! You\'re not logged in.'); 37 | 38 | // Apply existing flash messages 39 | (new FlashRestorer)->apply($redirectResponse); 40 | 41 | // Redirect with cookie 42 | return $redirectResponse; 43 | } 44 | } 45 | 46 | return $next($request); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Models/User/Validation/UserValidator.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'name' => ['required'], 20 | 'email' => ['required', 'email', 'unique:backend_users,email,{:id}', 'max:190'], 21 | 'password' => [ 22 | 'required_without:id', 23 | 'nullable', 24 | 'min:8', 25 | 'confirmed', 26 | 'regex:/^.*(?=.{8,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[\dX])(?=.*[!$#%]).*$/' 27 | //'regex:/^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[\d\X])(?=.*[!$#%]).*$/' 28 | ], 29 | 'user_role' => ['required', 'exists:backend_roles,slug'], 30 | 'image' => ['mimes:jpeg,png'], 31 | ], 32 | 'update-password' => [ 33 | 'password' => [ 34 | 'required_without:id', 35 | 'nullable', 36 | 'min:8', 37 | 'confirmed', 38 | 'regex:/^.*(?=.{8,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[\dX])(?=.*[!$#%]).*$/' 39 | //'regex:/^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9])(?=.*[\d\X])(?=.*[!$#%]).*$/' 40 | ], 41 | 'image' => ['mimes:jpeg,png'], 42 | ], 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /src/Http/Middleware/ApiAuth.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * @param \Illuminate\Http\Request $request 19 | * @param Closure $next 20 | * @return mixed 21 | */ 22 | public function handle($request, Closure $next) 23 | { 24 | // Route is protected and requires a user session 25 | // 26 | // If user is not already logged in, we'll try and 27 | // look for the user in sessions and cookies. 28 | if (! backend_user_check()) { 29 | try { 30 | backend_user_authenticate(); 31 | } catch (\Exception $e) { 32 | $data = [ 33 | 'message' => $e->getMessage(), 34 | 'code' => $e->getCode(), 35 | ]; 36 | 37 | if (env('APP_DEBUG')) { 38 | $data['class'] = get_class($e); 39 | $data['file'] = $e->getFile(); 40 | $data['line'] = $e->getLine(); 41 | $data['trace'] = explode("\n", $e->getTraceAsString()); 42 | } 43 | 44 | return response()->json($data, $e->getCode()); 45 | } 46 | } 47 | 48 | return $next($request); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /gulp/tasks/project-styles.task.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var path = require('path'); 3 | var sass = require('gulp-sass'); 4 | var postcss = require('gulp-postcss'); 5 | var sourcemaps = require('gulp-sourcemaps'); 6 | var autoprefixer = require('autoprefixer'); 7 | var cssmin = require('gulp-cssmin'); 8 | var rename = require('gulp-rename'); 9 | 10 | var Elixir = require('laravel-elixir'); 11 | 12 | var gulpConfig = require('../config.json'); 13 | 14 | var Task = Elixir.Task; 15 | 16 | Elixir.extend('projectStyles', function(jsOutputFile, jsOutputFolder, cssOutputFile, cssOutputFolder) { 17 | 18 | var cssFileName = cssOutputFile || 'project.css'; 19 | 20 | var scssRoot = gulpConfig.project.src.scss; 21 | var scssSources = path.join(scssRoot, '**/*.scss'); 22 | var scssMainFile = path.join(scssRoot, 'project.scss'); 23 | 24 | new Task('project-styles', function() { 25 | 26 | return gulp.src(scssMainFile) 27 | .pipe(sourcemaps.init()) 28 | .pipe(sass({ 29 | outputStyle: Elixir.config.production ? 'compressed' : 'expanded', 30 | includePaths: ['bower_components'] 31 | })).on('error', sass.logError) 32 | .pipe(postcss([ 33 | autoprefixer({ 34 | browsers: ['last 2 versions', 'ie >= 10'] 35 | }) 36 | ])) 37 | .pipe(sourcemaps.write()) 38 | .pipe(rename(cssFileName)) 39 | .pipe(gulp.dest(cssOutputFolder || Elixir.config.css.outputFolder)); 40 | 41 | }).watch(scssSources); 42 | 43 | }); -------------------------------------------------------------------------------- /resources/views/partials/elements/logo-svg.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /resources/views/partials/elements/logo-icon-svg.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /config/dashboard.php: -------------------------------------------------------------------------------- 1 | 'i-frame', 14 | | 'title' => 'Google', 15 | | 'url' => 'https://google.com' 16 | | 17 | | Table count 18 | | 'type' => 'table-count', 19 | | 'title' => 'User count', 20 | | 'tables' => [ 21 | | 'backend_users' => 'Backend users', 22 | | ] 23 | | 24 | | Nodes statistics (url is from env) 25 | | 'type' => 'nodes-statistics-daily', 26 | | 'title' => 'Daily', 27 | | 'gaId' => 'UA-50813164-10' 28 | | 29 | | 'type' => 'nodes-statistics-monthly', 30 | | 'title' => 'Monthly', 31 | | 'gaId' => 'UA-50813164-10' 32 | */ 33 | 'list' => [ 34 | [ 35 | 'type' => 'nodes-statistics-daily', 36 | 'title' => 'Daily users', 37 | 'gaId' => 'UA-50813164-10', //TODO 38 | ], 39 | [ 40 | 'type' => 'nodes-statistics-monthly', 41 | 'title' => 'Monthly users', 42 | 'gaId' => 'UA-50813164-10', //TODO 43 | ], 44 | [ 45 | 'type' => 'table-count', 46 | 'title' => 'Data', 47 | 'tables' => [ 48 | 'backend_users' => 'Backend users', 49 | 'backend_user_tokens' => 'Tokens', 50 | ], 51 | ], 52 | ], 53 | ]; 54 | -------------------------------------------------------------------------------- /public/images/n-stack-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodes/backend", 3 | "description": "Nodes backend package", 4 | "keywords": [ 5 | "nodes", 6 | "backend", 7 | "manager", 8 | "administration", 9 | "admin system", 10 | "laravel" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Morten Rugaard", 16 | "email": "moru@nodes.dk", 17 | "role": "Developer" 18 | }, { 19 | "name": "Casper Rasmussen", 20 | "email": "cr@nodes.dk", 21 | "role": "CTO" 22 | }, { 23 | "name": "Dennis Haulund Nielsen", 24 | "email": "dhni@nodes.dk", 25 | "role": "Lead Front-end Developer" 26 | }, { 27 | "name": "Bo Mouridsen", 28 | "email": "bomo@nodes.dk", 29 | "role": "Designer" 30 | } 31 | ], 32 | "require": { 33 | "composer-plugin-api": "^1.0", 34 | "laravel/framework": ">=5.4.0 <6.15.0", 35 | "laravelcollective/html": "5.5.*||5.6.*||5.7.*||5.8.*||6.0.*", 36 | "nodes/core": "^1.0", 37 | "nodes/assets": "^1.0", 38 | "nodes/database": "^1.0", 39 | "nodes/validation": "^1.0", 40 | "nodes/cache": "^1.0", 41 | "nodes/bugsnag": "^1.0||^2.0", 42 | "nodes/counter-cache": "^2.0" 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "Nodes\\Backend\\": "src/" 47 | }, 48 | "files": [ 49 | "src/Support/Helpers/Auth.php", 50 | "src/Support/Helpers/Router.php", 51 | "src/Support/Helpers/QueryRestorer.php" 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config/welcome.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'name' => 'Backend', 15 | 'email' => 'no-reply@nodes.dk', 16 | ], 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Route 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Route which will be put in the mail 24 | | 25 | */ 26 | 'route' => 'nodes.backend.login.form', 27 | 28 | /* 29 | |-------------------------------------------------------------------------- 30 | | Subject of e-mail 31 | |-------------------------------------------------------------------------- 32 | | 33 | | Enter the subject of which the reset password emails 34 | | should be sent with. 35 | | 36 | */ 37 | 'subject' => 'Welcome to backend', 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | E-mail templates 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Set the view path to e-mail templates, that will be used 45 | | to generate the reset password e-mails 46 | | 47 | */ 48 | 'views' => [ 49 | 'html' => 'nodes.backend::backend-users.emails.html', 50 | 'text' => 'nodes.backend::backend-users.emails.text', 51 | ], 52 | ]; 53 | -------------------------------------------------------------------------------- /gulp/tasks/vendor-scripts.task.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | var gulp = require('gulp'); 5 | var mainBowerFiles = require('main-bower-files'); 6 | var uglify = require('gulp-uglify'); 7 | var concatSourcemaps = require('gulp-concat-sourcemap'); 8 | var concat = require('gulp-concat'); 9 | var gulpIf = require('gulp-if'); 10 | 11 | var Elixir = require('laravel-elixir'); 12 | 13 | var gulpConfig = require('../config.json'); 14 | var NODES_BOWER_PACKAGES = require('../../bower_components/nodes-ui/bower.json'); 15 | var PROJECT_BOWER_PACKAGES = require('../../bower.json'); 16 | 17 | var Task = Elixir.Task; 18 | 19 | Elixir.extend('vendorScripts', function(jsOutputFile, jsOutputFolder) { 20 | 21 | var MERGED_PKGS = PROJECT_BOWER_PACKAGES; 22 | 23 | for(var pkg in NODES_BOWER_PACKAGES.dependencies) { 24 | if(!PROJECT_BOWER_PACKAGES.dependencies.hasOwnProperty(pkg)) { 25 | MERGED_PKGS.dependencies[pkg] = NODES_BOWER_PACKAGES.dependencies[pkg]; 26 | } 27 | } 28 | 29 | try { 30 | fs.writeFileSync('bower.json', JSON.stringify(MERGED_PKGS, null, '\t')); 31 | } catch(err) { 32 | return console.log('Error updating project bower.json file!', err); 33 | } 34 | 35 | var jsFileName = jsOutputFile || 'vendor.js'; 36 | 37 | var filterFiles = ['**/*.js'].concat(gulpConfig.ignoredBowerPkgs); 38 | 39 | if(!Elixir.config.production) { 40 | concat = concatSourcemaps; 41 | } 42 | 43 | new Task('vendor-scripts', function() { 44 | 45 | return gulp.src(mainBowerFiles({ 46 | filter: filterFiles 47 | })) 48 | .pipe(concat(jsFileName, {sourcesContent: true})) 49 | .pipe(gulpIf(Elixir.config.production, uglify())) 50 | .pipe(gulp.dest(jsOutputFolder || Elixir.config.js.outputFolder)); 51 | 52 | }).watch('bower.json'); 53 | 54 | }); -------------------------------------------------------------------------------- /gulp/tasks/project-scripts.task.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var filter = require('gulp-filter'); 3 | var uglify = require('gulp-uglify'); 4 | var concatSourcemaps = require('gulp-concat-sourcemap'); 5 | var concat = require('gulp-concat'); 6 | var gulpIf = require('gulp-if'); 7 | 8 | var Elixir = require('laravel-elixir'); 9 | 10 | var Task = Elixir.Task; 11 | 12 | Elixir.extend('projectScripts', function(jsOutputFile, jsOutputFolder) { 13 | 14 | var jsFileName = jsOutputFile || 'project.js'; 15 | 16 | var jsSources = [ 17 | './resources/assets/js/**/*.js', 18 | '!./resources/assets/js/pages/**/*.js', 19 | '!./resources/assets/js/partials/**/*.js' 20 | ]; 21 | 22 | var jsPages = './resources/assets/js/pages/**/*.js'; 23 | var jsPartials = './resources/assets/js/partials/**/*.js'; 24 | 25 | if(!Elixir.config.production) { 26 | concat = concatSourcemaps; 27 | } 28 | 29 | new Task('project-scripts', function() { 30 | return gulp.src(jsSources) 31 | .pipe(concat(jsFileName, {sourcesContent: true})) 32 | .pipe(gulpIf(Elixir.config.production, uglify())) 33 | .pipe(gulp.dest(jsOutputFolder || Elixir.config.js.outputFolder)); 34 | 35 | }).watch(jsSources); 36 | 37 | new Task('project-pages-scripts', function() { 38 | return gulp.src(jsPages) 39 | .pipe(gulpIf(Elixir.config.production, uglify())) 40 | .pipe(gulp.dest(jsOutputFolder || Elixir.config.js.outputFolder)); 41 | 42 | }).watch(jsPages); 43 | 44 | new Task('project-partials-scripts', function() { 45 | return gulp.src(jsPartials) 46 | .pipe(gulpIf(Elixir.config.production, uglify())) 47 | .pipe(gulp.dest(jsOutputFolder || Elixir.config.js.outputFolder)); 48 | 49 | }).watch(jsPartials); 50 | 51 | }); -------------------------------------------------------------------------------- /resources/views/partials/components/file-picker.blade.php: -------------------------------------------------------------------------------- 1 |
5 | 6 |
7 |
8 | 9 | 10 | Drop your file here 11 | 12 | 15 |
16 |
17 | {!! Form::file(!empty($elementName) ? $elementName : 'image', ['id' => !empty($elementId) ? $elementId : 'backendUserFormImage', 'class' => 'file-picker__file-input']) !!} 18 |
19 | 20 | 21 | Browse 22 | 23 | 24 | 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /resources/views/reset-password/reset.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::reset-password.reset-template') 2 | 3 | @section('feedback-header') 4 |

Forgot password

5 | @endsection 6 | 7 | @section('feedback-message') 8 |

Enter the e-mail address of the user who's password you wish to reset. Here after enter the user's new password.

9 | 10 | {!! Form::open(['method' => 'post', 'route' => 'nodes.backend.reset-password.change']) !!} 11 | 12 |
13 | {!! Form::label('nodesResetPasswordEmail', 'E-mail address') !!} 14 | {!! 15 | Form::email( 16 | 'email', 17 | Session::get('email'), 18 | [ 19 | 'id' => 'nodesResetPasswordEmail', 20 | 'class' => 'form-control', 21 | 'placeholder' => 'your@email.com', 22 | 'autocomplete' => config('nodes.backend.general.disable_autocomplete', false) ? 'on' : 'off' 23 | ] 24 | ) 25 | !!} 26 |
27 |
28 | {!! Form::label('nodesResetPasswordNew', 'New password') !!} 29 | {!! Form::password('password', ['id' => 'nodesResetPasswordNew', 'class' => 'form-control']) !!} 30 |
31 |
32 | {!! Form::label('nodesResetPasswordConfirmation', 'New password confirmation') !!} 33 | {!! Form::password('password_confirmation', ['id' => 'nodesResetPasswordConfirmation', 'class' => 'form-control']) !!} 34 |
35 |
36 | {!! Form::submit('Change password', ['class' => 'btn btn-primary form-control']) !!} 37 |
38 | {!! Form::close() !!} 39 | @endsection -------------------------------------------------------------------------------- /routes/backend-users.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Http\Controllers', 'prefix' => 'admin/backend-users', 'middleware' => ['web', 'backend.ssl', 'backend.auth']], function () { 4 | // List all users 5 | Route::get('/', [ 6 | 'as' => 'nodes.backend.users', 7 | 'uses' => 'UsersController@index', 8 | ]); 9 | 10 | // Create new user form 11 | Route::get('/create', [ 12 | 'as' => 'nodes.backend.users.create', 13 | 'uses' => 'UsersController@create', 14 | ]); 15 | 16 | // Save new user 17 | Route::post('/store', [ 18 | 'as' => 'nodes.backend.users.store', 19 | 'uses' => 'UsersController@store', 20 | ]); 21 | 22 | // Edit user form 23 | Route::get('/{id}/edit', [ 24 | 'as' => 'nodes.backend.users.edit', 25 | 'uses' => 'UsersController@edit', 26 | ])->where('id', '[0-9]+'); 27 | 28 | // Update user 29 | Route::patch('/update', [ 30 | 'as' => 'nodes.backend.users.update', 31 | 'uses' => 'UsersController@update', 32 | ]); 33 | 34 | // Edit authenticated user 35 | Route::get('/profile', [ 36 | 'as' => 'nodes.backend.users.profile', 37 | 'uses' => 'UsersController@profile', 38 | ]); 39 | 40 | // Delete user 41 | Route::delete('/delete/{id}', [ 42 | 'as' => 'nodes.backend.users.destroy', 43 | 'uses' => 'UsersController@delete', 44 | ])->where('id', '[0-9]+'); 45 | 46 | // Change password 47 | Route::get('/change-password', [ 48 | 'as' => 'nodes.backend.users.change-password', 49 | 'uses' => 'UsersController@changePassword', 50 | ]); 51 | 52 | // Update users password 53 | Route::patch('/update-password', [ 54 | 'as' => 'nodes.backend.users.update-password', 55 | 'uses' => 'UsersController@updatePassword', 56 | ]); 57 | }); 58 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/Charts/Chart.php: -------------------------------------------------------------------------------- 1 | chartData = $this->prepareChartData($data); 72 | } 73 | 74 | /** 75 | * Prepare the chart data. 76 | * 77 | * @author Casper Rasmussen 78 | * @param $data 79 | * @return $chartData 80 | */ 81 | abstract protected function prepareChartData($data); 82 | } 83 | -------------------------------------------------------------------------------- /src/Support/Helpers/QueryRestorer.php: -------------------------------------------------------------------------------- 1 | 47 | * @access public 48 | * @param array $params 49 | * @param array $blacklist 50 | * @return bool|\Illuminate\Http\RedirectResponse 51 | */ 52 | function query_restorer_with_flash($params = [], $blacklist = []) 53 | { 54 | if ($redirect = query_restorer($params, $blacklist)) { 55 | $redirectResponse = redirect()->to($redirect); 56 | 57 | (new FlashRestorer())->apply($redirectResponse); 58 | 59 | return $redirectResponse; 60 | } 61 | 62 | return false; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/Tile.php: -------------------------------------------------------------------------------- 1 | 38 | * 39 | * @access public 40 | * @param string $title 41 | * @param $data 42 | */ 43 | public function __construct(string $title, $data) 44 | { 45 | // Set params 46 | $this->title = $title; 47 | $this->data = $data; 48 | 49 | // Assign random id 50 | $this->id = str_random(); 51 | } 52 | 53 | /** 54 | * @author Casper Rasmussen 55 | * @return string 56 | */ 57 | abstract public function getType(); 58 | 59 | /** 60 | * @author Casper Rasmussen 61 | * @return string 62 | */ 63 | public function getTitle() 64 | { 65 | return $this->title; 66 | } 67 | 68 | /** 69 | * getData 70 | * 71 | * @author Casper Rasmussen 72 | * @access public 73 | * @return array 74 | */ 75 | public function getData() 76 | { 77 | return $this->data; 78 | } 79 | 80 | /** 81 | * @author Casper Rasmussen 82 | * @return string 83 | */ 84 | public function getId() 85 | { 86 | return $this->id; 87 | } 88 | 89 | /** 90 | * @author Casper Rasmussen 91 | * @return array 92 | */ 93 | public function getChartData() 94 | { 95 | return $this->chartData; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Auth/ResetPassword/ResetPasswordModel.php: -------------------------------------------------------------------------------- 1 | table = config('nodes.backend.reset-password.table', 'backend_reset_password_tokens'); 54 | } 55 | 56 | /** 57 | * Check if token is expired. 58 | * 59 | * @author Morten Rugaard 60 | * 61 | * @return bool 62 | */ 63 | public function isExpired() 64 | { 65 | return (bool) Carbon::now()->gt($this->expire_at); 66 | } 67 | 68 | /** 69 | * Check if token has already been used. 70 | * 71 | * @author Morten Rugaard 72 | * 73 | * @return bool 74 | */ 75 | public function isUsed() 76 | { 77 | return (bool) $this->used; 78 | } 79 | 80 | /** 81 | * Mark token as used. 82 | * 83 | * @author Morten Rugaard 84 | * 85 | * @return bool 86 | */ 87 | public function markAsUsed() 88 | { 89 | return (bool) $this->update(['used' => 1]); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Http/Controllers/NStackController.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | protected function getConfig() 21 | { 22 | return config('nodes.backend.nstack'); 23 | } 24 | 25 | /** 26 | * guardUserPermissions. 27 | * This function can be overridden for changing user permissions 28 | * 29 | * @return void 30 | * @author Casper Rasmussen 31 | */ 32 | protected function guardUserPermissions() 33 | { 34 | if (Gate::denies('backend-super-admin')) { 35 | abort(403); 36 | } 37 | } 38 | 39 | /** 40 | * NStack hook. 41 | * 42 | * @return \Illuminate\Http\RedirectResponse 43 | * @author Casper Rasmussen 44 | */ 45 | public function hook() 46 | { 47 | $this->guardUserPermissions(); 48 | 49 | // Retrieve NStack config 50 | $config = $this->getConfig(); 51 | 52 | $default = !empty($config['defaults']['application']) ? $config['defaults']['application'] : 'default'; 53 | 54 | $application = \Request::get('application', $default); 55 | 56 | $credentials = !empty($config['credentials'][$application]) ? $config['credentials'][$application] : $config['credentials']; // For backwards compatibility 57 | 58 | // Validate NStack credentials 59 | if (empty($config['url']) || empty($credentials['appId']) || empty($credentials['masterKey'])) { 60 | return redirect()->back()->with('error', 61 | 'NStack hook is not configured, setup keys in (config/nodes/backend/nstack.php)'); 62 | } 63 | 64 | //http://nstack.test/deeplink/[APP_ID]/[MASTER_KEY]] 65 | return redirect()->away($config['url'] . '/' . $credentials['appId'] . '/' . $credentials['masterKey']); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /resources/views/backend-users/change-password.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::layouts.base') 2 | 3 | @section('breadcrumbs') 4 |
  • 5 | Backend users 6 |
  • 7 |
  • Update password
  • 8 | @endsection 9 | 10 | @section('page-header-top') 11 |

    12 | Update password 13 |
    14 | The password requires three of the following five categories and be min 8 chars: 15 |
    16 | - English uppercase characters (A – Z) 17 |
    18 | - English lowercase characters (a – z) 19 |
    20 | - Base 10 digits (0 – 9) 21 |
    22 | - Non-alphanumeric (For example: !, $, #, or %) 23 |
    24 | - Unicode characters 25 |

    26 | @endsection 27 | 28 | @section('content') 29 |
    30 |
    31 | {!! Form::model(backend_user(), ['method' => 'patch', 'route' => ['nodes.backend.users.update-password']]) !!} 32 | 33 | 34 | {{-- Password --}} 35 |
    36 | 37 |
    38 | {!! Form::password('password', ['id' => 'backendUserFormPassword', 'class' => 'form-control']) !!} 39 |
    40 |
    41 | {{-- Password confirm --}} 42 |
    43 | 44 |
    45 | {!! Form::password('password_confirmation', ['id' => 'backendUserFormRepeatPassword', 'class' => 'form-control']) !!} 46 |
    47 |
    48 | 49 |
    50 | 51 |
    52 | {!! Form::close() !!} 53 |
    54 |
    55 | 56 | @endsection 57 | -------------------------------------------------------------------------------- /resources/views/partials/sidebar/navigation.blade.php: -------------------------------------------------------------------------------- 1 | 64 | -------------------------------------------------------------------------------- /config/reset-password.php: -------------------------------------------------------------------------------- 1 | 'backend_reset_password_tokens', 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | E-mail sender details 18 | |-------------------------------------------------------------------------- 19 | | 20 | | Enter the name and e-mail of which the reset password emails 21 | | should be sent as. 22 | | 23 | */ 24 | 'from' => [ 25 | 'name' => 'Backend', 26 | 'email' => 'no-reply@nodes.dk', 27 | ], 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Subject of e-mail 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Enter the subject of which the reset password emails 35 | | should be sent with. 36 | | 37 | */ 38 | 'subject' => 'Reset password', 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | E-mail templates 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Set the view path to e-mail templates, that will be used 46 | | to generate the reset password e-mails 47 | | 48 | */ 49 | 'views' => [ 50 | 'html' => 'nodes.backend::reset-password.emails.html', 51 | 'text' => 'nodes.backend::reset-password.emails.text', 52 | ], 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Token lifetime 57 | |-------------------------------------------------------------------------- 58 | | 59 | | Set the lifetime of each generated token in minutes. 60 | | Default: 60 minutes 61 | | 62 | */ 63 | 'expire' => 60, 64 | 65 | /* 66 | |-------------------------------------------------------------------------- 67 | | Secure email check 68 | |-------------------------------------------------------------------------- 69 | | 70 | | This checks if a generic message should be show regardless if the given 71 | | email exists or not (safe). False means that an error message will be 72 | | shown to the user (exposing users registered on the system) 73 | | 74 | | 75 | */ 76 | 'secure_email_check' => true, 77 | ]; 78 | -------------------------------------------------------------------------------- /src/Dashboard/DashboardCollection.php: -------------------------------------------------------------------------------- 1 | add(new IFrame($config['title'], $config['url'])); 35 | break; 36 | case 'table-count': 37 | $this->add(new TableCount($config['title'], $config['tables'])); 38 | break; 39 | case 'nodes-statistics-daily': 40 | $this->add(new DailyStatistic($config['title'], $config['gaId'])); 41 | break; 42 | case 'nodes-statistics-monthly': 43 | $this->add(new MonthlyStatistic($config['title'], $config['gaId'])); 44 | break; 45 | default: 46 | throw new UnsupportedTypeException(sprintf('%s is not supported', $config['type'])); 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * filterForType 53 | * 54 | * @author Casper Rasmussen 55 | * @access public 56 | * @param $type 57 | * @return \Illuminate\Database\Eloquent\Collection 58 | */ 59 | public function filterForType($type) 60 | { 61 | $collection = new Collection(); 62 | foreach ($this as $dashboard) { 63 | if ($dashboard->getType() == $type) { 64 | $collection->add($dashboard); 65 | } 66 | } 67 | 68 | return $collection; 69 | } 70 | 71 | /** 72 | * getChartDataForType 73 | * 74 | * @author Casper Rasmussen 75 | * @access public 76 | * @param $type 77 | * @return array 78 | */ 79 | public function getChartDataForType($type) 80 | { 81 | $chartArray = []; 82 | 83 | foreach ($this->filterForType($type) as $dashboard) { 84 | $chartArray[] = $dashboard->getChartData(); 85 | } 86 | 87 | return $chartArray; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Support/Helpers/Auth.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * @return \Nodes\Backend\Auth\Manager 10 | */ 11 | function backend_auth() 12 | { 13 | return app('nodes.backend.auth'); 14 | } 15 | } 16 | 17 | if (! function_exists('backend_user')) { 18 | /** 19 | * Retrieve current authenticated user. 20 | * 21 | * @author Casper Rasmussen 22 | * 23 | * @return \Illuminate\Database\Eloquent\Model 24 | */ 25 | function backend_user() 26 | { 27 | return app('nodes.backend.auth')->getUser(); 28 | } 29 | } 30 | 31 | if (! function_exists('backend_user_check')) { 32 | /** 33 | * Check if there there is a authed backend user. 34 | * 35 | * @author Casper Rasmussen 36 | * 37 | * @return bool 38 | */ 39 | function backend_user_check() 40 | { 41 | return app('nodes.backend.auth')->check(); 42 | } 43 | } 44 | 45 | if (! function_exists('backend_user_authenticate')) { 46 | /** 47 | * Try and authenticate user by available providers. 48 | * 49 | * @author Casper Rasmussen 50 | * 51 | * @return bool 52 | */ 53 | function backend_user_authenticate() 54 | { 55 | return app('nodes.backend.auth')->authenticate(); 56 | } 57 | } 58 | 59 | if (! function_exists('backend_user_login')) { 60 | /** 61 | * Login user. 62 | * 63 | * @author Casper Rasmussen 64 | * 65 | * @param \Nodes\Backend\Auth\Contracts\Authenticatable $user 66 | * @param bool $remember 67 | * @return \Nodes\Backend\Auth\Manager 68 | */ 69 | function backend_user_login(\Nodes\Backend\Auth\Contracts\Authenticatable $user, $remember = false) 70 | { 71 | return app('nodes.backend.auth')->createLoginSession($user, $remember); 72 | } 73 | } 74 | 75 | if (! function_exists('backend_user_logout')) { 76 | /** 77 | * Logout user. 78 | * 79 | * @author Casper Rasmussen 80 | * 81 | * @return \Nodes\Backend\Auth\Manager 82 | */ 83 | function backend_user_logout() 84 | { 85 | return app('nodes.backend.auth')->logout(); 86 | } 87 | } 88 | 89 | if (! function_exists('backend_attempt')) { 90 | /** 91 | * Attempt to authenticate a user using the given credentials. 92 | * 93 | * @author Casper Rasmussen 94 | * 95 | * @param array $credentials 96 | * @param bool $remember 97 | * @param bool $login 98 | * @return bool 99 | */ 100 | function backend_attempt(array $credentials = [], $remember = false, $login = true) 101 | { 102 | return app('nodes.backend.auth')->attempt($credentials, $remember, $login); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Dashboard/Tiles/NodesStatistics/Statistic.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | abstract class Statistic extends LineChart 16 | { 17 | /** 18 | * @var string 19 | */ 20 | protected $period; 21 | 22 | /** 23 | * @var string 24 | */ 25 | protected $gaId; 26 | 27 | 28 | /** 29 | * @author Casper Rasmussen 30 | * @return string 31 | */ 32 | public function getPeriod() 33 | { 34 | return $this->period; 35 | } 36 | 37 | 38 | /** 39 | * @author Casper Rasmussen 40 | * @return bool 41 | * @throws \Nodes\Backend\Dashboard\Exceptions\UnsupportedTypeException 42 | */ 43 | public function prepareChartData($data) 44 | { 45 | $this->gaId = $data; 46 | 47 | $url = sprintf(env('NODES_STATISTICS_HISTORY'), $this->gaId); 48 | 49 | // Generate query 50 | if ($this->period == 'monthly') { 51 | $query = [ 52 | 'from' => Carbon::now()->subMonth()->format('Y-m-d'), 53 | 'to' => Carbon::now()->format('Y-m-d'), 54 | 'group' => 'day', 55 | ]; 56 | } else { 57 | $query = [ 58 | 'from' => Carbon::now()->format('Y-m-d'), 59 | 'to' => Carbon::now()->addDay()->format('Y-m-d'), 60 | 'group' => 'hour', 61 | ]; 62 | } 63 | 64 | // Append query to url 65 | $url .= '?'.http_build_query($query); 66 | 67 | $chartData = [ 68 | 'id' => $this->id, 69 | 'title' => $this->title, 70 | 'data' => [], 71 | 'labels' => [], 72 | ]; 73 | 74 | // Look up in cache 75 | $response = \Cache::get($url); 76 | 77 | $client = new Client([ 78 | 'timeout' => 5, 79 | 'connect_timeout' => 5 80 | ]); 81 | if (! $response) { 82 | // Do api call in request 83 | try { 84 | $response = json_decode($client->get($url)->getBody(), true); 85 | 86 | \Cache::put($url, $response, 60); 87 | } catch (\Exception $e) { 88 | return false; 89 | } 90 | } 91 | 92 | // Now get total visitors and from those platforms 93 | foreach ($response['data'] as $data) { 94 | $time = Carbon::createFromFormat('Y-m-d H:i:s', $data['time']); 95 | if ($this->period == 'monthly') { 96 | $label = $time->format('m/d'); 97 | } else { 98 | $label = $time->format('m/d H:00'); 99 | } 100 | 101 | // Append 102 | $chartData['labels'][] = $label; 103 | $chartData['data'][] = ! empty($data['visit_count']) ? $data['visit_count'] : 0; 104 | } 105 | 106 | return $chartData; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Support/FlashRestorer.php: -------------------------------------------------------------------------------- 1 | error = session('error'); 46 | $this->success = session('success'); 47 | $this->info = session('info'); 48 | $this->warning = session('warning'); 49 | } 50 | 51 | /** 52 | * Set error flash. 53 | * 54 | * @author Casper Rasmussen 55 | * 56 | * @param string|array $error 57 | * @return $this 58 | */ 59 | public function setError($error) 60 | { 61 | $this->error = $error; 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Set success flash. 68 | * 69 | * @author Casper Rasmussen 70 | * 71 | * @param string|array $success 72 | * @return $this 73 | */ 74 | public function setSuccess($success) 75 | { 76 | $this->success = $success; 77 | 78 | return $this; 79 | } 80 | 81 | /** 82 | * Set info flash. 83 | * 84 | * @author Casper Rasmussen 85 | * 86 | * @param string|array $info 87 | * @return $this 88 | */ 89 | public function setInfo($info) 90 | { 91 | $this->info = $info; 92 | 93 | return $this; 94 | } 95 | 96 | /** 97 | * Set warning flash. 98 | * 99 | * @author Casper Rasmussen 100 | * 101 | * @param string|array $warning 102 | * @return $this 103 | */ 104 | public function setWarning($warning) 105 | { 106 | $this->warning = $warning; 107 | 108 | return $this; 109 | } 110 | 111 | /** 112 | * Apply flash. 113 | * 114 | * @author Casper Rasmussen 115 | * 116 | * @param \Symfony\Component\HttpFoundation\RedirectResponse $redirectResponse 117 | * @return \Symfony\Component\HttpFoundation\RedirectResponse 118 | */ 119 | public function apply(RedirectResponse &$redirectResponse) 120 | { 121 | // Apply error flash 122 | if (! empty($this->error)) { 123 | $redirectResponse->with('error', $this->error); 124 | } 125 | 126 | // Apply success flash 127 | if (! empty($this->success)) { 128 | $redirectResponse->with('success', $this->success); 129 | } 130 | 131 | // Apply info flash 132 | if (! empty($this->info)) { 133 | $redirectResponse->with('info', $this->info); 134 | } 135 | 136 | // Apply warning flash 137 | if (! empty($this->warning)) { 138 | $redirectResponse->with('warning', $this->warning); 139 | } 140 | 141 | return $redirectResponse; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /resources/views/partials/topbar/user-menu.blade.php: -------------------------------------------------------------------------------- 1 | @if(backend_user()) 2 | @if($renderForMobile) 3 | 24 | @else 25 | 62 | @endif 63 | @endif 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /resources/views/login/default.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::base') 2 | 3 | @section('layout') 4 | @if(config('nodes.backend.manager.active', true)) 5 | 6 | Nodes SSO 7 | 8 | @endif 9 | 10 | 68 | 69 | @stop 70 | -------------------------------------------------------------------------------- /src/Http/Controllers/FailedJobsController.php: -------------------------------------------------------------------------------- 1 | failedJobRepository = $failedJobRepository; 30 | } 31 | 32 | /** 33 | * List failed jobs. 34 | * 35 | * @author Casper Rasmussen 36 | * 37 | * @return \Illuminate\View\View 38 | */ 39 | public function index() 40 | { 41 | if (Gate::denies('backend-developer')) { 42 | abort(403); 43 | } 44 | 45 | // Retrieve data 46 | $failedJobs = $this->failedJobRepository->getPaginatedForBackend(); 47 | 48 | return view('nodes.backend::failed-jobs.index', compact('failedJobs')); 49 | } 50 | 51 | /** 52 | * Restart all. 53 | * 54 | * @author Casper Rasmussen 55 | * 56 | * @return \Illuminate\Http\RedirectResponse 57 | */ 58 | public function restartAll() 59 | { 60 | if (Gate::denies('backend-developer')) { 61 | abort(403); 62 | } 63 | 64 | Artisan::call('queue:retry', ['id' => ['all']]); 65 | 66 | return redirect()->route('nodes.backend.failed-jobs')->with('success', 'All failed job has been restarted'); 67 | } 68 | 69 | /** 70 | * Restart entry. 71 | * 72 | * @author Casper Rasmussen 73 | * 74 | * @param int $id 75 | * @return \Illuminate\Http\RedirectResponse 76 | */ 77 | public function restart($id) 78 | { 79 | if (Gate::denies('backend-developer')) { 80 | abort(403); 81 | } 82 | 83 | $failedJob = $this->failedJobRepository->getById($id); 84 | if (! $failedJob) { 85 | return redirect()->route('nodes.backend.failed-jobs')->with('error', 'Failed job does not exist'); 86 | } 87 | 88 | Artisan::call('queue:retry', ['id' => [$id]]); 89 | 90 | return redirect()->route('nodes.backend.failed-jobs')->with('success', sprintf('Failed job [%d] was restarted', $failedJob->id)); 91 | } 92 | 93 | /** 94 | * Forget job. 95 | * 96 | * @author Casper Rasmussen 97 | * 98 | * @param int $id 99 | * @return \Illuminate\Http\RedirectResponse 100 | */ 101 | public function forget($id) 102 | { 103 | if (Gate::denies('backend-developer')) { 104 | abort(403); 105 | } 106 | 107 | $failedJob = $this->failedJobRepository->getById($id); 108 | if (! $failedJob) { 109 | return redirect()->route('nodes.backend.failed-jobs')->with('error', 'Failed job does not exist'); 110 | } 111 | 112 | Artisan::call('queue:forget', ['id' => [$id]]); 113 | 114 | return redirect()->route('nodes.backend.failed-jobs')->with('success', 'Failed job has been removed'); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Routing/Router.php: -------------------------------------------------------------------------------- 1 | 34 | * 35 | * @param \Illuminate\Routing\Router $router 36 | */ 37 | public function __construct(IlluminateRouter $router) 38 | { 39 | $this->router = $router; 40 | } 41 | 42 | /** 43 | * Match route by pattern. 44 | * 45 | * @author Morten Rugaard 46 | * 47 | * @param string|array $patterns 48 | * @return string 49 | */ 50 | public function pattern($patterns) 51 | { 52 | // Retrieve current request 53 | $currentRoute = $this->router->current(); 54 | if (empty($currentRoute)) { 55 | return $this->inactiveClass; 56 | } 57 | 58 | // Make sure patterns is an array 59 | $patterns = ! is_array($patterns) ? [$patterns] : $patterns; 60 | 61 | // Decode route path 62 | if (method_exists($currentRoute, 'getPath')) { 63 | $uri = $currentRoute->getPath(); 64 | } else { 65 | $uri = $currentRoute->uri(); 66 | } 67 | 68 | // Check patterns and look for matches 69 | foreach ($patterns as $pattern) { 70 | if (str_is($pattern, $uri)) { 71 | return $this->activeClass; 72 | } 73 | } 74 | 75 | return $this->inactiveClass; 76 | } 77 | 78 | /** 79 | * Match route by alias. 80 | * 81 | * @author Morten Rugaard 82 | * 83 | * @param string|array $aliases 84 | * @return string 85 | */ 86 | public function alias($aliases) 87 | { 88 | // Retrieve current request 89 | $currentRoute = $this->router->current(); 90 | if (empty($currentRoute)) { 91 | return $this->inactiveClass; 92 | } 93 | 94 | // Make sure patterns is an array 95 | $aliases = ! is_array($aliases) ? [$aliases] : $aliases; 96 | 97 | // Current route's alias 98 | $routeAlias = $this->router->currentRouteName(); 99 | 100 | // Check aliases and look for matches 101 | foreach ($aliases as $alias) { 102 | if ($routeAlias == $alias) { 103 | return $this->activeClass; 104 | } 105 | } 106 | 107 | return $this->inactiveClass; 108 | } 109 | 110 | /** 111 | * Set active class. 112 | * 113 | * @author Morten Rugaard 114 | * 115 | * @param string $class 116 | * @return \Nodes\Backend\Routing\Router 117 | */ 118 | public function setActiveClass($class) 119 | { 120 | $this->activeClass = $class; 121 | 122 | return $this; 123 | } 124 | 125 | /** 126 | * Set inactive class. 127 | * 128 | * @author Morten Rugaard 129 | * 130 | * @param string $class 131 | * @return \Nodes\Backend\Routing\Router 132 | */ 133 | public function setInactiveClass($class) 134 | { 135 | $this->inactiveClass = $class; 136 | 137 | return $this; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /resources/views/backend-users/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::layouts.base') 2 | 3 | @section('breadcrumbs') 4 |
  • Backend users
  • 5 | @endsection 6 | 7 | @section('page-header-top') 8 |
    9 |

    Backend users

    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 18 | 19 |
    20 |
    21 | 22 | @can('backend-admin') 23 | 24 | 25 | 26 | 27 | @endcan 28 |
    29 |
    30 | @endsection 31 | 32 | @section('content') 33 |
    34 |
    35 |
    36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | @foreach ($users as $user) 48 | 49 | 50 | 56 | 59 | 60 | 70 | 71 | @endforeach 72 | 73 |
    IDNameE-mailRoleActions
    {{$user->id}} 51 |
    52 | 53 | {{ $user->name }} 54 |
    55 |
    57 | {{ $user->email }} 58 | {{ $user->role->title }} 61 | 62 | 63 | Edit details 64 | 65 | 66 | 67 | Delete user 68 | 69 |
    74 |
    75 |
    76 | @if ($users->total() > 1) 77 |
    78 |
    79 | 82 |
    83 |
    84 | @endif 85 | @endsection 86 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | 'Nodes\Backend\Models\User\User', 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Repository 20 | |-------------------------------------------------------------------------- 21 | | 22 | | The user repository that should be used for authentication 23 | | 24 | | If you in a project needs to extend the nodes.backend.backend user repository, 25 | | you need to change this to the namespace of your custom nodes.backend.backend user repository. 26 | | 27 | */ 28 | 'repository' => 'Nodes\Backend\Models\User\UserRepository', 29 | 30 | /* 31 | |-------------------------------------------------------------------------- 32 | | Routes 33 | |-------------------------------------------------------------------------- 34 | | 35 | | Routes used within authentication. I.e. where should the user be redirected 36 | | to when he/her is successfully authenticated. 37 | | 38 | | Note: Values needs to be route aliases. 39 | | 40 | */ 41 | 'routes' => [ 42 | 'success' => 'nodes.backend.dashboard', 43 | ], 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | Gates 48 | |-------------------------------------------------------------------------- 49 | | 50 | | There is defined a handful of gates for the standard backend 51 | | 52 | | Note: Setting this to false, means you have to define gates your self, or remove all the gate checks 53 | | 54 | */ 55 | 'gates' => [ 56 | 'define' => true, 57 | ], 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | Providers 61 | |-------------------------------------------------------------------------- 62 | | 63 | | The authentication providers that should be used when attempting to 64 | | authenticate an incoming API request. 65 | | 66 | */ 67 | 'providers' => [ 68 | 'token' => function ($app) { 69 | return new Nodes\Backend\Auth\Providers\Token; 70 | }, 71 | 'session' => function ($app) { 72 | return new Nodes\Backend\Auth\Providers\Session($app['nodes.backend.auth.model'], app(\Illuminate\Session\Store::class)); 73 | }, 74 | ], 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Token provider 79 | |-------------------------------------------------------------------------- 80 | | 81 | | Settings needed by the token provider. 82 | | 83 | */ 84 | 'token' => [ 85 | 86 | /* 87 | |-------------------------------------------------------------------------- 88 | | Database table 89 | |-------------------------------------------------------------------------- 90 | | 91 | | Name of database where access tokens are located. 92 | | 93 | */ 94 | 'table' => 'backend_user_tokens', 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Columns 99 | |-------------------------------------------------------------------------- 100 | | 101 | | Mapping of columns. These are needed to reference owner of token, 102 | | the unique token it self and the expire time of token. 103 | | 104 | */ 105 | 'columns' => [ 106 | 'user_id' => 'user_id', 107 | 'token' => 'token', 108 | 'expire' => 'expire', 109 | ], 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Lifetime 114 | |-------------------------------------------------------------------------- 115 | | 116 | | Set the lifetime of a token. This used by used as a "literal" time. 117 | | I.e. "+1 week" or "+1 month". 118 | | 119 | | @see http://php.net/manual/en/datetime.formats.relative.php 120 | | 121 | */ 122 | 'lifetime' => null, 123 | ], 124 | ]; 125 | -------------------------------------------------------------------------------- /src/Models/Role/RoleRepository.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * @param \Nodes\Backend\Models\Role\Role $model 19 | */ 20 | public function __construct(Role $model) 21 | { 22 | $this->setupRepository($model); 23 | } 24 | 25 | /** 26 | * Retrieve list of available roles. 27 | * 28 | * @author Casper Rasmussen 29 | * 30 | * @return array 31 | */ 32 | public function getList() 33 | { 34 | return $this->lists('title', 'slug'); 35 | } 36 | 37 | /** 38 | * Retrieve roles for authed user's user-role. 39 | * 40 | * @author Casper Rasmussen 41 | * 42 | * @return array 43 | */ 44 | public function getListUserLevel() 45 | { 46 | // Retrieve full list 47 | $list = $this->getList(); 48 | 49 | // If user is developer, give the full list 50 | if (\Gate::allows('backend-developer')) { 51 | return $list; 52 | } 53 | 54 | // This means user is not developer, let's unset that option 55 | unset($list['developer']); 56 | if (\Gate::allows('backend-super-admin')) { 57 | return $list; 58 | } 59 | 60 | // This means user is not super-admin, let's unset that option 61 | unset($list['super-admin']); 62 | 63 | 64 | // If user is admin, we return the list 65 | if (\Gate::allows('backend-admin')) { 66 | return $list; 67 | } 68 | 69 | // If user is not even admin, that option should not be possible either 70 | unset($list['admin']); 71 | 72 | return $list; 73 | } 74 | 75 | /** 76 | * Retrieve all users paginated. 77 | * 78 | * @author Casper Rasmussen 79 | * 80 | * @param int $limit 81 | * @param array $fields 82 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator 83 | */ 84 | public function getPaginatedForBackend($limit = 25, $fields = ['*']) 85 | { 86 | return $this->paginate($limit, $fields); 87 | } 88 | 89 | /** 90 | * @author Casper Rasmussen 91 | * @return \Nodes\Backend\Models\Role\Role 92 | * @throws \Nodes\Database\Exceptions\EntityNotFoundException 93 | */ 94 | public function getDefaultRole() 95 | { 96 | return $this->getByOrFail('default', true); 97 | } 98 | 99 | /** 100 | * Delete role and clean up after. 101 | * 102 | * @author Casper Rasmussen 103 | * 104 | * @param \Nodes\Backend\Models\Role\Role $role 105 | * @return bool 106 | * @throws \Nodes\Exceptions\Exception 107 | */ 108 | public function deleteRole(Role $role) 109 | { 110 | $defaultRole = $this->getDefaultRole(); 111 | 112 | // Check if the role which is about to be deleted is the default role 113 | if ($defaultRole->id == $role->id) { 114 | throw new Exception('Cannot delete default role', 500); 115 | } 116 | 117 | // Set all user's with deleted role 118 | // to the default role 'user'. 119 | if (! empty($defaultRole)) { 120 | $role->users()->update(['user_role' => $defaultRole->slug]); 121 | } 122 | 123 | return (bool) $role->delete(); 124 | } 125 | 126 | /** 127 | * Set role as default. 128 | * 129 | * @author Casper Rasmussen 130 | * 131 | * @param \Nodes\Backend\Models\Role\Role $role 132 | * @return \Exception 133 | */ 134 | public function setDefault(Role $role) 135 | { 136 | try { 137 | // Begin transaction 138 | $this->beginTransaction(); 139 | 140 | // Look up already default role and set it to non default 141 | $defaultRole = $this->getBy('default', true); 142 | if ($defaultRole) { 143 | $defaultRole->update(['default' => false]); 144 | } 145 | 146 | // Update current role to default 147 | $role->update(['default' => true]); 148 | 149 | // Commit transaction 150 | $this->commitTransaction(); 151 | } catch (\Exception $e) { 152 | 153 | // Rollback and throw 154 | $this->rollbackTransaction(); 155 | 156 | return $e; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | 20 | * 21 | * @return void 22 | */ 23 | public function boot() 24 | { 25 | // Register namespace for backend views 26 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'nodes.backend'); 27 | 28 | // Register middlewares 29 | $this->app['router']->aliasMiddleware('backend.auth', NodesBackendHttpMiddlewareAuth::class); 30 | $this->app['router']->aliasMiddleware('backend.api.auth', NodesBackendHttpMiddlewareApiAuth::class); 31 | $this->app['router']->aliasMiddleware('backend.ssl', NodesBackendHttpMiddlewareSSL::class); 32 | 33 | // Publish groups 34 | $this->publishGroups(); 35 | } 36 | 37 | /** 38 | * Register the service provider. 39 | * 40 | * @author Morten Rugaard 41 | * @return void 42 | */ 43 | public function register() 44 | { 45 | // Register router 46 | $this->registerRouter(); 47 | 48 | // Register auth service provider 49 | $this->app->register(\Nodes\Backend\Auth\ServiceProvider::class); 50 | } 51 | 52 | /** 53 | * Register publish groups. 54 | * 55 | * @author Morten Rugaard 56 | * 57 | * @return void 58 | */ 59 | protected function publishGroups() 60 | { 61 | // Config files 62 | $this->publishes([ 63 | __DIR__.'/../config' => config_path('nodes/backend'), 64 | ], 'config'); 65 | 66 | // Route files 67 | $this->publishes([ 68 | __DIR__.'/../routes' => base_path('project/Routes/Backend'), 69 | ], 'routes'); 70 | 71 | // View files 72 | $this->publishes([ 73 | __DIR__.'/../resources/views/base.blade.php' => resource_path('views/vendor/nodes.backend/base.blade.php'), 74 | __DIR__.'/../resources/views/layouts/base.blade.php' => resource_path('views/vendor/nodes.backend/layouts/base.blade.php'), 75 | __DIR__.'/../resources/views/partials/sidebar/navigation.blade.php' => resource_path('views/vendor/nodes.backend/partials/sidebar/navigation.blade.php'), 76 | __DIR__.'/../resources/views/errors' => resource_path('views/errors'), 77 | ], 'views'); 78 | 79 | // Assets files 80 | $this->publishes([ 81 | __DIR__.'/../resources/assets/js' => resource_path('assets/js'), 82 | __DIR__.'/../resources/assets/scss' => resource_path('assets/scss'), 83 | __DIR__.'/../public/images' => public_path('images'), 84 | ], 'assets'); 85 | 86 | // Favicons 87 | $this->publishes([ 88 | __DIR__.'/../public/favicons' => public_path('favicons'), 89 | ]); 90 | 91 | // Database files 92 | $this->publishes([ 93 | __DIR__.'/../database/migrations/reset-password' => database_path('migrations'), 94 | __DIR__.'/../database/migrations/users' => database_path('migrations'), 95 | __DIR__.'/../database/migrations/failed-jobs' => database_path('migrations'), 96 | __DIR__.'/../database/seeds/users' => database_path('seeds'), 97 | __DIR__.'/../database/seeds/NodesBackendSeeder.php' => database_path('seeds/NodesBackendSeeder.php'), 98 | ], 'database'); 99 | 100 | // Frontend files 101 | $this->publishes([ 102 | __DIR__.'/../bower/.bowerrc' => base_path('.bowerrc'), 103 | __DIR__.'/../bower/bower.json' => base_path('bower.json'), 104 | __DIR__.'/../gulp/tasks' => base_path('gulp/tasks'), 105 | __DIR__.'/../gulp/config.json' => base_path('gulp/config.json'), 106 | __DIR__.'/../gulp/gulpfile.js' => base_path('gulpfile.js'), 107 | __DIR__.'/../gulp/package.json' => base_path('package.json'), 108 | ], 'frontend'); 109 | 110 | // Route files 111 | $this->publishes([ 112 | __DIR__.'/../routes' => base_path('project/Routes/Backend'), 113 | ], 'routes'); 114 | } 115 | 116 | /** 117 | * Register backend router. 118 | * 119 | * @author Morten Rugaard 120 | * 121 | * @return void 122 | */ 123 | protected function registerRouter() 124 | { 125 | $this->app->singleton('nodes.backend.router', function ($app) { 126 | return new NodesBackendRouter($app['router']); 127 | }); 128 | 129 | $this->app->bind('Nodes\Backend\Routing\Router', function ($app) { 130 | return $app['nodes.backend.router']; 131 | }); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /resources/views/backend-users/roles.blade.php: -------------------------------------------------------------------------------- 1 | @extends('nodes.backend::layouts.base') 2 | 3 | @section('breadcrumbs') 4 |
  • Roles
  • 5 | @endsection 6 | 7 | @section('page-header-top') 8 |
    9 |

    Roles

    10 |
    11 |
    12 | 16 |
    17 | @endsection 18 | 19 | @section('content') 20 |
    21 |
    22 |
    23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | @foreach ($roles as $role) 35 | 36 | 37 | 38 | 39 | @if ($role->isDefault()) 40 | 43 | @else 44 | 47 | @endif 48 | 67 | 68 | @endforeach 69 | 70 |
    TitleSlugUsersDefaultActions
    {{ $role->title }}{{ $role->slug }}{{ $role->user_count }} 41 | 42 | 45 | 46 | 49 | {{-- Set default --}} 50 | 51 | 52 | Set role as default 53 | 54 | 55 | {{-- Edit role --}} 56 | 60 | 61 | {{-- Delete role --}} 62 | 63 | 64 | Delete Role 65 | 66 |
    71 | @if ($roles->total() > 1) 72 |
    73 |
    74 | 77 |
    78 |
    79 | @endif 80 |
    81 |
    82 | 83 | 107 | @endsection 108 | -------------------------------------------------------------------------------- /src/Auth/ResetPassword/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | resetPasswordRepository = $resetPasswordRepository; 29 | 30 | // Set title of all views in this controller 31 | view()->share('title', 'Reset password'); 32 | } 33 | 34 | /** 35 | * Form to request reset password token. 36 | * 37 | * @author Morten Rugaard 38 | * 39 | * @return \Illuminate\View\View 40 | */ 41 | public function index() 42 | { 43 | return view('nodes.backend::reset-password.form'); 44 | } 45 | 46 | /** 47 | * Gemerate reset password token. 48 | * 49 | * @author Morten Rugaard 50 | * @author Pedro Coutinho 51 | * 52 | * @return \Illuminate\Http\RedirectResponse 53 | */ 54 | public function generateResetToken() 55 | { 56 | // Retrieve received e-mail 57 | $email = Request::get('email'); 58 | 59 | // Validate e-mail 60 | if (empty($email) || ! filter_var($email, FILTER_VALIDATE_EMAIL)) { 61 | return redirect() 62 | ->route('nodes.backend.reset-password.form') 63 | ->with('error', 'Missing or invalid e-mail address'); 64 | } 65 | 66 | // Generate token and send e-mail 67 | $status = $this->resetPasswordRepository->sendResetPasswordEmail(['email' => $email]); 68 | 69 | if (empty($status)) { 70 | 71 | // Check if we should show error message if email does not exist, or just display 72 | // the same message as if the email was really sent 73 | $secureEmailCheck = config('nodes.backend.reset-password.secure_email_check', false); 74 | 75 | if (! $secureEmailCheck) { 76 | return redirect() 77 | ->route('nodes.backend.reset-password.form') 78 | ->with('error', 'Could not send reset password e-mail'); 79 | } 80 | } 81 | 82 | return redirect()->route('nodes.backend.reset-password.sent')->with('info', 'Check your mailbox'); 83 | } 84 | 85 | /** 86 | * Confirmation page of e-mail has been sent. 87 | * 88 | * @author Morten Rugaard 89 | * 90 | * @return \Illuminate\View\View 91 | */ 92 | public function sent() 93 | { 94 | return view('nodes.backend::reset-password.sent'); 95 | } 96 | 97 | /** 98 | * Reset password form. 99 | * 100 | * @author Morten Rugaard 101 | * 102 | * @param string $token 103 | * @return \Illuminate\View\View 104 | */ 105 | public function resetForm($token) 106 | { 107 | // Validate token 108 | $resetToken = $this->resetPasswordRepository->getByToken($token); 109 | if (empty($resetToken) || $resetToken->isUsed()) { 110 | return view('nodes.backend::reset-password.invalid'); 111 | } 112 | 113 | // Check if token's expiry date has been exceed 114 | if ($resetToken->isExpired()) { 115 | return view('nodes.backend::reset-password.expired'); 116 | } 117 | 118 | return view('nodes.backend::reset-password.reset', compact('token')); 119 | } 120 | 121 | /** 122 | * Reset/Update user's password. 123 | * 124 | * @author Morten Rugaard 125 | * 126 | * @param \Nodes\Backend\Auth\ResetPassword\Validation\ResetPasswordValidator $validator 127 | * 128 | * @return \Illuminate\Http\RedirectResponse 129 | */ 130 | public function resetPassword(ResetPasswordValidator $validator) 131 | { 132 | $data = Request::all(); 133 | 134 | // Validate data 135 | if (!$validator->with($data)->validate()) { 136 | return redirect() 137 | ->back() 138 | ->withInput() 139 | ->with('error', $validator->errorsBag()); 140 | } 141 | 142 | // Get token 143 | $resetToken = $this->resetPasswordRepository->getByUnexpiredToken($data['token']); 144 | if (empty($resetToken)) { 145 | return redirect()->back()->with('error', 'Could not retrieve valid reset password token'); 146 | } 147 | 148 | // Validate e-mail address 149 | if ($resetToken->email != $data['email']) { 150 | return redirect()->back()->with(['email' => $data['email'], 'error' => 'Token does not belong to e-mail address']); 151 | } 152 | 153 | // All good! Update user's password 154 | $status = $this->resetPasswordRepository->updatePasswordByEmail($data['email'], $data['password']); 155 | if (empty($status)) { 156 | return redirect()->back()->with(['email' => $data['email'], 'error' => 'Could not change user\'s password']); 157 | } 158 | 159 | // Mark token as used 160 | $resetToken->markAsUsed(); 161 | 162 | return redirect()->route('nodes.backend.reset-password.done')->with('success', 'Password was successfully changed'); 163 | } 164 | 165 | /** 166 | * Reset password confirmation. 167 | * 168 | * @author Morten Rugaard 169 | * 170 | * @return \Illuminate\View\View 171 | */ 172 | public function done() 173 | { 174 | return view('nodes.backend::reset-password.done'); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /resources/views/partials/alerts.blade.php: -------------------------------------------------------------------------------- 1 | {{-- Success alert --}} 2 | @if (Session::has('success')) 3 | 29 | @endif 30 | 31 | {{-- Error alert --}} 32 | @if (Session::has('error')) 33 | 59 | @endif 60 | 61 | {{-- Errors alert --}} 62 | @if(Session::has('errors')) 63 | 89 | @endif 90 | 91 | {{-- Warning alert --}} 92 | @if (Session::has('warning')) 93 | 119 | @endif 120 | 121 | {{-- Info alert --}} 122 | @if (Session::has('info')) 123 | 149 | @endif -------------------------------------------------------------------------------- /src/Auth/ResetPassword/ResetPasswordRepository.php: -------------------------------------------------------------------------------- 1 | setupRepository($model); 39 | $this->userModel = $container['nodes.backend.auth.model']; 40 | $this->errors = new MessageBag; 41 | } 42 | 43 | /** 44 | * Retrieve by token. 45 | * 46 | * @author Morten Rugaard 47 | * 48 | * @param string $token 49 | * @return \Nodes\Backend\Auth\ResetPassword\Model 50 | */ 51 | public function getByToken($token) 52 | { 53 | return $this->where('token', '=', $token)->first(); 54 | } 55 | 56 | /** 57 | * Retrieve by - unexpired - token. 58 | * 59 | * @author Morten Rugaard 60 | * 61 | * @param string $token 62 | * @return \Nodes\Backend\Auth\ResetPassword\Model 63 | */ 64 | public function getByUnexpiredToken($token) 65 | { 66 | return $this->where('token', '=', $token) 67 | ->where('expire_at', '>', Carbon::now()->format('Y-m-d H:i:s')) 68 | ->first(); 69 | } 70 | 71 | /** 72 | * Generate and send a email with reset password instructions. 73 | * 74 | * @author Morten Rugaard 75 | * 76 | * @param array $conditions WHERE conditions to locate user. Format: ['column' => 'value'] 77 | * @return bool 78 | * @throws \Nodes\Backend\Auth\Exception\ResetPasswordNoUserException 79 | */ 80 | public function sendResetPasswordEmail(array $conditions) 81 | { 82 | // Validate conditions 83 | if (empty($conditions)) { 84 | return false; 85 | } 86 | 87 | // Add conditions to query builder 88 | foreach ($conditions as $column => $value) { 89 | $this->userModel = $this->userModel->where($column, '=', $value); 90 | } 91 | 92 | // Retrieve user with conditions 93 | $user = $this->userModel->first(); 94 | if (empty($user)) { 95 | $this->errors->add('no-user-found', 'Could not find any user with those credentials.'); 96 | 97 | return false; 98 | } 99 | 100 | // Generate reset password token 101 | $token = $this->generateResetPasswordToken($user); 102 | 103 | // Send e-mail with instructions on how to reset password 104 | \Mail::send([ 105 | 'html' => config('nodes.backend.reset-password.views.html', 'nodes.backend::reset-password.emails.html'), 106 | 'text' => config('nodes.backend.reset-password.views.text', 'nodes.backend::reset-password.emails.text'), 107 | ], [ 108 | 'user' => $user, 109 | 'domain' => config('app.url'), 110 | 'token' => $token, 111 | 'expire' => config('nodes.backend.reset-password.expire', 60), 112 | 'project' => config('nodes.project.name'), 113 | ], function ($message) use ($user) { 114 | $message->to($user->email) 115 | ->from(config('nodes.backend.reset-password.from.email', 'no-reply@nodes.dk'), config('nodes.backend.reset-password.from.name', 'Nodes')) 116 | ->subject(config('nodes.backend.reset-password.subject', 'Reset password request')); 117 | }); 118 | 119 | return true; 120 | } 121 | 122 | /** 123 | * Generate reset token. 124 | * 125 | * @author Morten Rugaard 126 | * 127 | * @param \Illuminate\Database\Eloquent\Model $user 128 | * @return string 129 | */ 130 | protected function generateResetPasswordToken(Model $user) 131 | { 132 | // Generate new token using Laravel's encryption key 133 | $token = hash_hmac('sha256', str_random(40), config('app.key')); 134 | 135 | // Expire timestamp 136 | $expire = Carbon::now()->addMinutes(config('nodes.backend.reset-password.expire', 60)); 137 | 138 | // If user has previously tried to reset his/her password 139 | // we should just update the token of the previous entry 140 | // instead of creating a new one 141 | $resetToken = $this->where('email', '=', $user->email)->first(); 142 | if (! empty($resetToken)) { 143 | $resetToken->update(['token' => $token, 'used' => 0, 'expire_at' => $expire->format('Y-m-d H:i:s')]); 144 | } else { 145 | $this->insert(['email' => $user->email, 'token' => $token, 'expire_at' => $expire->format('Y-m-d H:i:s')]); 146 | } 147 | 148 | return $token; 149 | } 150 | 151 | /** 152 | * Update user's password by e-mail. 153 | * 154 | * @author Morten Rugaard 155 | * 156 | * @param string $email 157 | * @param string $password 158 | * @return bool 159 | */ 160 | public function updatePasswordByEmail($email, $password) 161 | { 162 | // Retrieve user by e-mail 163 | $user = $this->userModel->where('email', '=', $email)->first(); 164 | if (empty($user)) { 165 | $this->errors('no-user-found-by-email', 'Could not find any user with e-mail: ['.$email.']'); 166 | 167 | return false; 168 | } 169 | 170 | // Update user with new password 171 | return (bool) $user->update([ 172 | 'password' => $password, 173 | ]); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Backend 2 | 3 | A easy and clean backend to [Laravel](http://laravel.com/docs). 4 | 5 | [![Total downloads](https://img.shields.io/packagist/dt/nodes/backend.svg)](https://packagist.org/packages/nodes/backend) 6 | [![Monthly downloads](https://img.shields.io/packagist/dm/nodes/backend.svg)](https://packagist.org/packages/nodes/backend) 7 | [![Latest release](https://img.shields.io/packagist/v/nodes/backend.svg)](https://packagist.org/packages/nodes/backend) 8 | [![Open issues](https://img.shields.io/github/issues/nodes-php/backend.svg)](https://github.com/nodes-php/backend/issues) 9 | [![License](https://img.shields.io/packagist/l/nodes/backend.svg)](https://packagist.org/packages/nodes/backend) 10 | [![Star repository on GitHub](https://img.shields.io/github/stars/nodes-php/backend.svg?style=social&label=Star)](https://github.com/nodes-php/backend/stargazers) 11 | [![Watch repository on GitHub](https://img.shields.io/github/watchers/nodes-php/backend.svg?style=social&label=Watch)](https://github.com/nodes-php/backend/watchers) 12 | [![Fork repository on GitHub](https://img.shields.io/github/forks/nodes-php/backend.svg?style=social&label=Fork)](https://github.com/nodes-php/backend/network) 13 | [![StyleCI](https://styleci.io/repos/46918811/shield)](https://styleci.io/repos/46918811) 14 | ## 📝 Introduction 15 | One thing we at [Nodes](http://nodesagency.com) have been missing in [Laravel](http://laravel.com/docs) is a fast implemented backend which is easy to build on top of 16 | 17 | ## 📦 Installation 18 | 19 | To install this package you will need: 20 | 21 | * Laravel 5.1+ 22 | * PHP 5.5.9+ 23 | 24 | You must then modify your `composer.json` file and run `composer update` to include the latest version of the package in your project. 25 | 26 | ``` 27 | "require": { 28 | "nodes/backend": "3.1.*", 29 | } 30 | ``` 31 | 32 | Or you can run the composer require command from your terminal. 33 | 34 | ``` 35 | composer require nodes/backend 36 | ``` 37 | ## 🔧 Setup 38 | 39 | Setup service providers in config/app.php 40 | 41 | ``` 42 | Nodes\Backend\ServiceProvider::class, 43 | Nodes\Assets\ServiceProvider::class, 44 | Nodes\Validation\ServiceProvider::class, 45 | Nodes\Cache\ServiceProvider::class, 46 | Collective\Html\HtmlServiceProvider::class, 47 | Nodes\ServiceProvider::class, 48 | ``` 49 | 50 | Setup alias in config/app.php 51 | 52 | ``` 53 | 'Backend' => Nodes\Backend\Support\Facades\Backend::class, 54 | 'Form' => Collective\Html\FormFacade::class, 55 | 'Html' => Collective\Html\HtmlFacade::class, 56 | ``` 57 | 58 | Publish config file all config files at once, we need to use force on backend, since we override gulp. The regular vendor:publish is for the 3 other packages 59 | ``` 60 | php artisan vendor:publish && php artisan vendor:publish --provider="Nodes\Backend\ServiceProvider" --force 61 | ``` 62 | 63 | Publish config file for backend plugin only 64 | ``` 65 | php artisan vendor:publish --provider="Nodes\Backend\ServiceProvider" 66 | ``` 67 | 68 | Overwrite config file for backend plugin only 69 | ``` 70 | php artisan vendor:publish --provider="Nodes\Backend\ServiceProvider" --force 71 | ``` 72 | 73 | Add following to your /database/seeds/DatabaseSeeder.php 74 | ``` 75 | $this->call('NodesBackendSeeder'); 76 | ``` 77 | 78 | Dump 79 | ``` 80 | composer dump-autoload 81 | ``` 82 | 83 | Now you can call php artisan migrate --seed 84 | Which will add the new tables and seed the roles/users to get going 85 | 86 | Add to config/nodes/autoload.php 87 | ``` 88 | 'project/Routes/Backend/', 89 | ``` 90 | 91 | Run bower, npm & gulp to build css & js 92 | ``` 93 | bower install && npm install && gulp build 94 | ``` 95 | 96 | Set up CSRF by pass in App\Http\Middleware\VerifyCsrfToken.php 97 | 98 | ``` 99 | protected $except = [ 100 | 'admin/manager_auth', 101 | ]; 102 | ``` 103 | 104 | Make TokenMismatch exceptions more user friendly, add following to App\Exceptions\Handler.php 105 | 106 | ``` 107 | public function render($request, Exception $e) 108 | { 109 | // Just redirect back to previous route if there is any, else all the way back to dashboard 110 | // Instead of a ugly whoops error! 111 | if ($exception instanceof TokenMismatchException) { 112 | try { 113 | return redirect()->back()->with('error', 'Token mismatch, try again')->send(); 114 | } catch (\Throwable $e) {} 115 | } 116 | .... 117 | } 118 | ``` 119 | 120 | ## ⚙ Usage 121 | 122 | Global function 123 | ``` 124 | backend_auth - Access all other function on mananger 125 | backend_user - Retrieve user object 126 | backend_user_check - Check if there is authed user 127 | backend_user_authenticate - Try to auth with current request, pass [] as providers are registered 128 | backend_user_login - Force login another user 129 | backend_user_logout - Logout user 130 | backend_attempt - Attempt to authenticate a user using the given credentials 131 | query_restorer - Use to restore query params from cookie, handy for routing between views with queries 132 | query_restorer_with_flash - Use to restore query params from cookie, handy for routing between views with queries. Remembers the flash between reloads also 133 | backend_router - Access all other router functions 134 | backend_router_pattern - Used fx for selecting navigation item by path 135 | backend_router_alias - Used fx for selecting navigation item by route 136 | ``` 137 | 138 | Redirect with flash 139 | ``` 140 | redirect()->back()->withInput()->with('error', 'Unknown Error') // Only strings 141 | redirect()->back()->withInput()->with('errors', $myErrorBag) 142 | redirect()->back()->withInput()->with('errors', $myValidator->errorsBag()); 143 | redirect()->back()->withInput()->with('success', 'Everything is ok') 144 | redirect()->back()->withInput()->with('info', 'Insert info') 145 | redirect()->back()->withInput()->with('warning', 'Insert warning') 146 | ``` 147 | ## 🏆 Credits 148 | 149 | This package is developed and maintained by the PHP team at [Nodes](http://nodesagency.com) 150 | 151 | [![Follow Nodes PHP on Twitter](https://img.shields.io/twitter/follow/nodesphp.svg?style=social)](https://twitter.com/nodesphp) [![Tweet Nodes PHP](https://img.shields.io/twitter/url/http/nodesphp.svg?style=social)](https://twitter.com/nodesphp) 152 | 153 | ## 📄 License 154 | 155 | This package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT) 156 | -------------------------------------------------------------------------------- /src/Http/Controllers/RolesController.php: -------------------------------------------------------------------------------- 1 | roleRepository = $roleRepository; 33 | } 34 | 35 | /** 36 | * List all roles. 37 | * 38 | * @author Morten Rugaard 39 | * 40 | * @return \Illuminate\View\View 41 | */ 42 | public function index() 43 | { 44 | if (Gate::denies('backend-developer')) { 45 | abort(403); 46 | } 47 | 48 | // Retrieve all roles 49 | $roles = $this->roleRepository->getPaginatedForBackend(); 50 | 51 | return view('nodes.backend::backend-users.roles', compact('roles')); 52 | } 53 | 54 | /** 55 | * Save new role to database. 56 | * 57 | * @author Casper Rasmussen 58 | * 59 | * @param \Nodes\Backend\Models\Role\Validation\RoleValidator $roleValidator 60 | * @return \Illuminate\Http\RedirectResponse 61 | */ 62 | public function store(RoleValidator $roleValidator) 63 | { 64 | if (Gate::denies('backend-developer')) { 65 | abort(403); 66 | } 67 | 68 | // Retrieve posted data 69 | $data = Request::only('title'); 70 | 71 | // Slugify role title 72 | $data['slug'] = Str::slug($data['title']); 73 | 74 | // Validate role and redirect if invalidate 75 | if (! $roleValidator->with($data)->validate()) { 76 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Role slug already exists'); 77 | } 78 | 79 | try { 80 | $this->roleRepository->create($data); 81 | 82 | return redirect()->route('nodes.backend.users.roles')->with('success', 'Role was successfully created.'); 83 | } catch (Exception $e) { 84 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Could not create role'); 85 | } 86 | } 87 | 88 | /** 89 | * We only update title since slug is used for gates. 90 | * 91 | * @author Casper Rasmussen 92 | * 93 | * @param int $id 94 | * @return \Illuminate\Http\RedirectResponse 95 | */ 96 | public function update($id) 97 | { 98 | if (Gate::denies('backend-developer')) { 99 | abort(403); 100 | } 101 | 102 | // Retrieve posted data 103 | $data = Request::only('title'); 104 | 105 | // Retrieve role by ID 106 | $role = $this->roleRepository->getById($id); 107 | if (empty($role)) { 108 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Role does not exist'); 109 | } 110 | 111 | // Check if an update is required 112 | if ($role->title == $data['title']) { 113 | return redirect()->route('nodes.backend.users.roles')->with('info', 'Role title has not changed. No update required'); 114 | } 115 | 116 | try { 117 | $role->update($data); 118 | 119 | return redirect()->route('nodes.backend.users.roles')->with('success', 'Role was successfully updated'); 120 | } catch (Exception $e) { 121 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Could not update role'); 122 | } 123 | } 124 | 125 | /** 126 | * Delete the role 127 | * Note: This can cause quite the damage role is in use. 128 | * 129 | * @author Casper Rasmussen 130 | * 131 | * @param int $id 132 | * @return \Illuminate\Http\RedirectResponse 133 | */ 134 | public function destroy($id) 135 | { 136 | if (Gate::denies('backend-developer')) { 137 | abort(403); 138 | } 139 | 140 | // Retrieve role by ID 141 | $role = $this->roleRepository->getById($id); 142 | if (empty($role)) { 143 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Role does not exist'); 144 | } 145 | 146 | // Make sure the role we're about to delete 147 | // is not the default role 148 | if ($role->isDefault()) { 149 | return redirect()->route('nodes.backend.users.roles')->with('warning', 'You can\'t delete the default role'); 150 | } 151 | 152 | try { 153 | $this->roleRepository->deleteRole($role); 154 | 155 | return redirect()->route('nodes.backend.users.roles')->with('success', 'Role was successfully deleted'); 156 | } catch (Exception $e) { 157 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Could not delete role'); 158 | } 159 | } 160 | 161 | /** 162 | * Mark role as default. 163 | * 164 | * @author Casper Rasmussen 165 | * 166 | * @param int $id 167 | * @return \Illuminate\Http\RedirectResponse 168 | */ 169 | public function setDefault($id) 170 | { 171 | if (Gate::denies('backend-developer')) { 172 | abort(403); 173 | } 174 | 175 | // Retrieve role by ID 176 | $role = $this->roleRepository->getById($id); 177 | if (empty($role)) { 178 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Role does not exist'); 179 | } 180 | 181 | // Make sure the role we're about to mark as default 182 | // isn't already the default role 183 | if ($role->isDefault()) { 184 | return redirect()->route('nodes.backend.users.roles')->with('warning', 'Role is already default'); 185 | } 186 | 187 | try { 188 | $this->roleRepository->setDefault($role); 189 | 190 | return redirect()->route('nodes.backend.users.roles')->with('success', 'Role was successfully set default'); 191 | } catch (Exception $e) { 192 | return redirect()->route('nodes.backend.users.roles')->with('error', 'Could not set the role default'); 193 | } 194 | } 195 | } 196 | --------------------------------------------------------------------------------