├── .gitignore
├── LICENSE
├── README.md
├── build.sh
├── docs
├── 00_Overview.md
└── 01_How_It_Works.md
├── favicon.ico
├── hcp.code-workspace
├── index.php
└── lib
├── css
├── app-dark.css
├── app.css
├── bootstrap-table.css
├── bootstrap.min.css
├── color-modes.css
├── fonts
│ ├── bootstrap-icons.css
│ ├── bootstrap-icons.woff2
│ ├── nunito-latin-400-normal.woff2
│ ├── nunito-latin-600-normal.woff2
│ └── nunito-latin-700-normal.woff2
├── style.css
└── table-datatable.css
├── img
├── Blank-300x150.png
├── favicon.svg
└── logo.svg
├── js
├── app.js
├── bootstrap-editable.js
├── bootstrap-table-editable.js
├── bootstrap-table-export.js
├── bootstrap-table.js
├── bootstrap.bundle.min.js
├── chart.umd.min.js
├── color-modes.js
├── dark.js
├── initTheme.js
├── perfect-scrollbar.min.js
├── simple-datatables.js
├── simple-umd-datatables.js
└── tableExport.js
└── php
├── .editorconfig
├── .vscode
├── extensions.json
└── settings.json
├── db.php
├── home.tpl.example
├── init.php
├── plugin.php
├── plugins
├── accounts.php
├── auth.php
├── dkim.php
├── domains.php
├── home.php
├── infomail.php
├── infosys.php
├── processes.php
├── records.php
├── sshm.php
├── valias.php
├── vhosts.php
└── vmails.php
├── theme.php
├── themes
├── bootstrap4
│ ├── accounts.php
│ ├── auth.php
│ ├── dkim.php
│ ├── domains.php
│ ├── home.php
│ ├── infomail.php
│ ├── infosys.php
│ ├── processes.php
│ ├── records.php
│ ├── theme.php
│ ├── valias.php
│ ├── vhosts.php
│ └── vmails.php
└── bootstrap5
│ ├── accounts.php
│ ├── auth.php
│ ├── dkim.php
│ ├── domains.php
│ ├── home.php
│ ├── infomail.php
│ ├── infosys.php
│ ├── processes.php
│ ├── records.php
│ ├── sshm.php
│ ├── theme.php
│ ├── valias.php
│ ├── vhosts.php
│ └── vmails.php
└── util.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .ht*
2 | *kate-swp
3 | netserva.php
4 | adminer*
5 | phpinfo.php
6 | status/
7 | phpmyadmin/
8 | webmail/
9 | lib/uploads/*.jpg
10 | whmcs/
11 | .well-known/
12 | rspamd/
13 | phpliteadmin.*
14 | .php-cs-fixer.cache
15 | bs5-*
16 | .vscode
17 | hcp.code-workspace
18 | sysadm/
19 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # ~/.sh/build.sh 20170301 - 20240904
3 | # Copyright (C) 2015-2023 Mark Constable (AGPL-3.0)
4 |
5 | [[ $1 =~ '-h' ]] && echo "Usage: [bash] build.sh [path(pwd)]
6 |
7 | Example:
8 |
9 | su - sysadm
10 | cd var/www/html/hcp
11 | bash build.sh .
12 | " && exit 1
13 |
14 | [[ $1 ]] && cd $1
15 |
16 | echo " (AGPL-3.0)
22 | // This is single script concatenation of all PHP files in lib/php at
23 | // https://github.com/markc/hcp
24 | " >netserva.php
25 |
26 | (
27 | cat lib/php/db.php
28 | cat lib/php/init.php
29 | cat lib/php/plugin.php
30 | cat lib/php/plugins/accounts.php
31 | cat lib/php/plugins/auth.php
32 | cat lib/php/plugins/dkim.php
33 | cat lib/php/plugins/domains.php
34 | cat lib/php/plugins/home.php
35 | cat lib/php/plugins/infomail.php
36 | cat lib/php/plugins/infosys.php
37 | cat lib/php/plugins/processes.php
38 | cat lib/php/plugins/records.php
39 | cat lib/php/plugins/valias.php
40 | cat lib/php/plugins/vhosts.php
41 | cat lib/php/plugins/vmails.php
42 | cat lib/php/theme.php
43 | cat lib/php/themes/bootstrap5/theme.php
44 | cat lib/php/themes/bootstrap5/accounts.php
45 | cat lib/php/themes/bootstrap5/auth.php
46 | cat lib/php/themes/bootstrap5/dkim.php
47 | cat lib/php/themes/bootstrap5/domains.php
48 | cat lib/php/themes/bootstrap5/home.php
49 | cat lib/php/themes/bootstrap5/infomail.php
50 | cat lib/php/themes/bootstrap5/infosys.php
51 | cat lib/php/themes/bootstrap5/processes.php
52 | cat lib/php/themes/bootstrap5/records.php
53 | cat lib/php/themes/bootstrap5/valias.php
54 | cat lib/php/themes/bootstrap5/vhosts.php
55 | cat lib/php/themes/bootstrap5/vmails.php
56 | cat lib/php/util.php
57 | cat index.php
58 | ) | sed \
59 | -e '/^?>/d' \
60 | -e '/^>netserva.php
64 |
65 | chmod 640 netserva.php
66 |
--------------------------------------------------------------------------------
/docs/00_Overview.md:
--------------------------------------------------------------------------------
1 | Here's a refactored and streamlined version of the documentation:
2 |
3 | # NetServa HCP: Architecture Overview
4 |
5 | ## Core Concepts
6 |
7 | 1. **Single Entry Point (index.php)**
8 | - Acts as the front controller
9 | - Contains global configuration
10 | - Implements autoloading via spl_autoload_register()
11 |
12 | 2. **Global Object ($this->g)**
13 | - Stores configuration, input, and output data
14 | - $this->g->out accumulates content for final rendering
15 |
16 | 3. **Encapsulated Rendering**
17 | - Plugin and theme classes generate HTML content
18 | - Content is assigned to $this->g->out['main'] and other keys
19 |
20 | 4. **Flexible Output**
21 | - Init class __toString() method handles final rendering
22 | - Supports HTML, plain text, and JSON outputs
23 | - Enables easy API integration
24 |
25 | ## Key Features
26 |
27 | - **Modular Structure**: Separate classes for different functionalities
28 | - **Configuration Override**: Optional lib/.ht_conf.php for environment-specific settings
29 | - **Autoloading**: Dynamically loads classes based on naming conventions
30 | - **Single Output**: Accumulates content before sending to browser
31 |
32 | ## Security Measures
33 |
34 | - **Centralized Request Handling**: All requests processed through index.php
35 | - **Nginx Protection**: Rule blocks direct access to .ht* files, including lib/.ht_conf.php
36 | - **Layered Approach**: Combines server-level and application-level security
37 |
38 | ## Benefits
39 |
40 | - Separation of concerns
41 | - Flexibility in output formats
42 | - Modular and maintainable codebase
43 | - Balanced security and convenience
44 | - Efficient performance through single output
45 |
46 | This architecture creates a robust, flexible, and secure foundation for web application development, suitable for various environments and scalable for larger projects.
--------------------------------------------------------------------------------
/docs/01_How_It_Works.md:
--------------------------------------------------------------------------------
1 | Certainly. I'll expand on these concepts based on how the netserva.php script works:
2 |
3 | # NetServa HCP: Detailed Architecture and Security Overview
4 |
5 | ## Core Architecture
6 |
7 | 1. **Single Entry Point (index.php)**
8 | - In Netserva PHP, this is implemented at the bottom of the file.
9 | - Defines constants like DS (Directory Separator) and INC (Include Path).
10 | - Sets up the autoloader using spl_autoload_register().
11 | - Initializes the main application by creating an instance of the Init class.
12 | - Example:
13 | ```php
14 | $config = new Config();
15 | echo new Init($config);
16 | ```
17 |
18 | 2. **Global Object ($this->g)**
19 | - Implemented as an anonymous class passed to the Init constructor.
20 | - Contains crucial arrays like $cfg (configuration), $in (input), $out (output), $db (database settings), $nav1 and $nav2 (navigation), $dns (DNS settings), and $acl (Access Control Levels).
21 | - These arrays hold all the necessary data for the application to function, providing a centralized data structure.
22 |
23 | 3. **Encapsulated Rendering**
24 | - Each plugin (e.g., Plugins_Accounts, Plugins_Auth) and theme (e.g., Themes_Bootstrap_Theme) class contains methods for generating specific parts of the HTML output.
25 | - Methods typically return strings of HTML content.
26 | - Content is assigned to $this->g->out['main'] or other relevant keys.
27 | - Example from Themes_Bootstrap_Home:
28 | ```php
29 | public function list(array $in): string
30 | {
31 | return <<
33 | HTML;
34 | }
35 | ```
36 |
37 | 4. **Flexible Output**
38 | - The Init class's __toString() method handles final rendering.
39 | - Checks $this->g->in['x'] to determine output format (HTML, text, or JSON).
40 | - For HTML, it calls $this->g->t->html() to render the full page.
41 | - For text, it strips HTML tags from the main content.
42 | - For JSON, it encodes the relevant data.
43 | - Allows for easy API integration by returning JSON when requested.
44 |
45 | ## Key Features
46 |
47 | - **Modular Structure**:
48 | - The script is divided into multiple classes (Db, Init, Plugin, various Plugins_* classes, Theme, various Themes_* classes).
49 | - Each class handles a specific aspect of functionality, promoting code organization and reusability.
50 |
51 | - **Configuration Override**:
52 | - The $cfg array in the global object includes a 'file' key pointing to 'lib/.ht_conf.php'.
53 | - This file, if it exists, can override default settings without modifying the main script.
54 |
55 | - **Autoloading**:
56 | - The autoloader function dynamically loads class files based on the class name.
57 | - It converts class names to file paths, allowing for a clean and organized file structure.
58 | - Example:
59 | ```php
60 | spl_autoload_register(function (string $className): void {
61 | $filePath = INC . str_replace(['\\', '_'], [DS, DS], strtolower($className)) . '.php';
62 | if (DBG) { error_log("filePath=$filePath"); }
63 | if (!is_file($filePath)) {
64 | throw new \LogicException("Class $className not found");
65 | }
66 | require $filePath;
67 | });
68 | ```
69 |
70 | - **Single Output**:
71 | - Content is accumulated in $this->g->out throughout script execution.
72 | - The Init::__toString() method renders all accumulated content at once, improving performance and allowing for proper header setting.
73 |
74 | ## Security Measures
75 |
76 | - **Centralized Request Handling**:
77 | - All requests go through index.php, allowing for consistent security checks and input validation.
78 |
79 | - **Nginx Protection**:
80 | - An Nginx rule (not visible in the PHP code) blocks direct access to .ht* files, including lib/.ht_conf.php.
81 | - This protects sensitive configuration data from being directly accessed via web requests.
82 |
83 | - **Layered Approach**:
84 | - Combines server-level protection (Nginx rules) with application-level security measures implemented throughout the PHP code.
85 |
86 | - **Configuration Management**:
87 | - Sensitive information like database passwords can be stored in separate, protected files (e.g., 'lib/.ht_pw' for database password).
88 |
89 | - **File Permissions**:
90 | - Relies on proper server configuration and file permissions to restrict access to sensitive files.
91 |
92 | ## Benefits
93 |
94 | - **Separation of Concerns**: Content generation (in plugins and themes) is separated from output formatting (in Init::__toString()).
95 | - **Flexibility**: The same core logic can output different formats (HTML, text, JSON) without major changes.
96 | - **Modularity**: Different parts of the application (plugins, themes) can contribute to the output independently.
97 | - **Performance**: Accumulating content before sending reduces the number of writes to the output buffer.
98 | - **Security**: The layered security approach provides robust protection for sensitive data and configurations.
99 |
100 | This architecture in Netserva PHP demonstrates a super lightweight yetsophisticated approach to web application development, balancing flexibility, security, and performance. It's particularly well-suited for applications that need to handle various types of output and integrate with different environments or APIs.
101 |
102 | ## More details
103 |
104 | 1. Autoloading:
105 | The code uses a custom autoloader to dynamically load class files based on their names. This allows for efficient loading of classes only when they're needed.
106 |
107 | 2. Configuration:
108 | A `Config` class is defined to hold various configuration settings for the application, including database settings, navigation menus, and access control levels.
109 |
110 | 3. Main Application Flow:
111 | The main execution starts by creating a `Config` object and then passing it to an `Init` class constructor. The `Init` class likely handles the initialization of the application and routing of requests.
112 |
113 | 4. Class Structure:
114 | The code defines several classes that handle different aspects of the application:
115 | - `Db`: Handles database operations
116 | - `Init`: Initializes the application
117 | - `Plugin`: Base class for various plugins (e.g., Accounts, Auth, Domains)
118 | - `Theme`: Handles the rendering of the user interface
119 | - `Util`: Provides utility functions
120 |
121 | 5. Plugins:
122 | There are several plugin classes (e.g., `Plugins_Accounts`, `Plugins_Auth`, `Plugins_Domains`) that extend the `Plugin` base class. These likely handle specific functionality areas of the application.
123 |
124 | 6. Themes:
125 | The application supports theming, with a base `Theme` class and specific theme implementations like `Themes_Bootstrap5_Theme`.
126 |
127 | 7. Utility Functions:
128 | The `Util` class provides various helper functions for tasks like logging, encoding, session management, and password handling.
129 |
130 | 8. Database Abstraction:
131 | The `Db` class provides an abstraction layer for database operations, supporting both MySQL and SQLite.
132 |
133 | 9. Security Features:
134 | The code includes security measures such as CSRF protection, password hashing, and input sanitization.
135 |
136 | 10. Modular Structure:
137 | The application is designed in a modular way, allowing for easy extension and modification of functionality through plugins and themes.
138 |
139 | 11. Execution:
140 | The application likely determines the requested action based on URL parameters, initializes the appropriate plugin and theme, and then renders the response.
141 |
142 | This structure allows for a flexible and extensible web application that can handle various aspects of server management and hosting control. The use of classes and object-oriented programming principles makes the code organized and maintainable.
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markc/hcp_bs5/c297d2fe20f0b891dca26016bc86bb3774435044/favicon.ico
--------------------------------------------------------------------------------
/hcp.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": "."
5 | },
6 | {
7 | "path": "../../.sh"
8 | }
9 | ],
10 | "settings": {}
11 | }
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 | [
67 | 'email' => 'test@example.com',
68 | 'admpw' => 'admin123',
69 | 'file' => LIB . '.ht_conf.php',
70 | 'hash' => 'SHA512-CRYPT',
71 | 'host' => '',
72 | 'perp' => 25,
73 | 'self' => '/',
74 | ],
75 | 'in' => [
76 | 'a' => '',
77 | 'd' => '',
78 | 'g' => null,
79 | 'i' => null,
80 | 'l' => '',
81 | 'm' => 'list',
82 | 'o' => 'home',
83 | 'r' => 'local',
84 | 't' => 'bootstrap5',
85 | 'x' => '',
86 | ],
87 | 'out' => [
88 | 'doc' => 'NetServa',
89 | 'css' => '',
90 | 'log' => '',
91 | 'nav1' => '',
92 | 'nav2' => '',
93 | 'nav3' => '',
94 | 'head' => 'NetServa HCP',
95 | 'main' => 'Welcome to NetServa HCP',
96 | 'foot' => 'Copyright (C) 2015-2024 Netserva HCP (AGPL-3.0)',
97 | 'js' => '',
98 | 'end' => '',
99 | ],
100 | 'db' => [
101 | 'host' => '127.0.0.1',
102 | 'name' => 'sysadm',
103 | 'pass' => LIB . DS . '.ht_pw',
104 | //'path' => '/var/lib/sqlite/sysadm/sysadm.db', // orignal path
105 | 'path' => 'sysadm/sysadm.db',
106 | 'port' => '3306',
107 | 'sock' => '',
108 | 'type' => 'sqlite',
109 | 'user' => 'sysadm',
110 | ],
111 | 'nav1' => [
112 | 'non' => [
113 | ['Webmail', 'webmail/', 'bi bi-envelope-fill'],
114 | ['Phpmyadmin', 'phpmyadmin/', 'bi bi-globe'],
115 | ],
116 | 'usr' => [
117 | ['Webmail', 'webmail/', 'bi bi-envelope-fill'],
118 | ['Phpmyadmin', 'phpmyadmin/', 'bi bi-globe'],
119 | ],
120 | 'adm' => [
121 | ['Manage', [
122 | ['Accounts', '?o=accounts', 'bi bi-people-fill'],
123 | ['SSH Manager', '?o=sshm', 'bi bi-key'],
124 | ['Vhosts', '?o=vhosts', 'bi bi-globe2'],
125 | ['Mailboxes', '?o=vmails', 'bi bi-envelope-fill'],
126 | ['Aliases', '?o=valias', 'bi bi-envelope-paper-fill'],
127 | ['DKIM', '?o=dkim', 'bi bi-person-vcard-fill'],
128 | ['Domains', '?o=domains', 'bi bi-globe-americas'],
129 | ], 'bi bi-stack'],
130 | ['Stats', [
131 | ['Sys Info', '?o=infosys', 'bi bi-speedometer2'],
132 | ['Processes', '?o=processes', 'bi bi-bezier2'],
133 | ['Mail Info', '?o=infomail', 'bi bi-envelope-open'],
134 | ], 'bi bi-graph-up-arrow'],
135 | ['Links', [
136 | ['Webmail', 'webmail/', 'bi bi-envelope-fill'],
137 | ['Phpmyadmin', 'phpmyadmin/', 'bi bi-globe'],
138 | ], 'bi bi-list'],
139 | ['Sites', [
140 | ['local', '?r=local', 'bi bi-globe', 'r'],
141 | ['mgo', '?r=mgo', 'bi bi-globe', 'r'],
142 | ['vmd1', '?r=vmd1', 'bi bi-globe', 'r'],
143 | ], 'bi bi-globe'],
144 | ],
145 | ],
146 | 'nav2' => [
147 | ['local', '?r=local', 'bi bi-globe'],
148 | ['mgo', '?r=mgo', 'bi bi-globe'],
149 | ['vmd1', '?r=vmd1', 'bi bi-globe'],
150 | ],
151 | 'dns' => [
152 | 'a' => '127.0.0.1',
153 | 'mx' => '',
154 | 'ns1' => 'ns1.',
155 | 'ns2' => 'ns2.',
156 | 'prio' => 0,
157 | 'ttl' => 300,
158 | 'soa' => [
159 | 'primary' => 'ns1.',
160 | 'email' => 'admin.',
161 | 'refresh' => 7200,
162 | 'retry' => 540,
163 | 'expire' => 604800,
164 | 'ttl' => 3600,
165 | ],
166 | 'db' => [
167 | 'host' => '127.0.0.1',
168 | 'name' => 'pdns',
169 | 'pass' => 'lib' . DS . '.ht_dns_pw',
170 | 'path' => 'sysadm/pdns.db',
171 | 'port' => '3306',
172 | 'sock' => '',
173 | 'type' => 'sqlite',
174 | 'user' => 'sysadm',
175 | ],
176 | ],
177 | 'acl' => [
178 | AclLevel::SuperAdmin->value => AclLevel::SuperAdmin->name,
179 | AclLevel::Admin->value => AclLevel::Admin->name,
180 | AclLevel::User->value => AclLevel::User->name,
181 | AclLevel::Suspended->value => AclLevel::Suspended->name,
182 | AclLevel::Anonymous->value => AclLevel::Anonymous->name,
183 | ],
184 | ];
185 |
186 | $this->cfg = array_merge($defaults['cfg'], $cfg);
187 | $this->in = array_merge($defaults['in'], $in);
188 | $this->out = array_merge($defaults['out'], $out);
189 | $this->db = array_merge($defaults['db'], $db);
190 | $this->nav1 = array_merge($defaults['nav1'], $nav1);
191 | $this->nav2 = array_merge($defaults['nav2'], $nav2);
192 | $this->dns = array_merge($defaults['dns'], $dns);
193 | $this->acl = array_merge($defaults['acl'], $acl);
194 | $this->t = $t;
195 | }
196 | }
197 |
198 | $config = new Config();
199 | echo new Init($config);
200 |
--------------------------------------------------------------------------------
/lib/css/bootstrap-table.css:
--------------------------------------------------------------------------------
1 | /**
2 | * @author zhixin wen
3 | * version: 1.11.1
4 | * https://github.com/wenzhixin/bootstrap-table/
5 | */
6 |
7 | .bootstrap-table .table {
8 | margin-bottom: 0 !important;
9 | border-bottom: 1px solid #dddddd;
10 | border-collapse: collapse !important;
11 | border-radius: 1px;
12 | }
13 |
14 | .bootstrap-table .table:not(.table-condensed),
15 | .bootstrap-table .table:not(.table-condensed) > tbody > tr > th,
16 | .bootstrap-table .table:not(.table-condensed) > tfoot > tr > th,
17 | .bootstrap-table .table:not(.table-condensed) > thead > tr > td,
18 | .bootstrap-table .table:not(.table-condensed) > tbody > tr > td,
19 | .bootstrap-table .table:not(.table-condensed) > tfoot > tr > td {
20 | padding: 8px;
21 | }
22 |
23 | .bootstrap-table .table.table-no-bordered > thead > tr > th,
24 | .bootstrap-table .table.table-no-bordered > tbody > tr > td {
25 | border-right: 2px solid transparent;
26 | }
27 |
28 | .bootstrap-table .table.table-no-bordered > tbody > tr > td:last-child {
29 | border-right: none;
30 | }
31 |
32 | .fixed-table-container {
33 | position: relative;
34 | clear: both;
35 | border: 1px solid #dddddd;
36 | border-radius: 4px;
37 | -webkit-border-radius: 4px;
38 | -moz-border-radius: 4px;
39 | }
40 |
41 | .fixed-table-container.table-no-bordered {
42 | border: 1px solid transparent;
43 | }
44 |
45 | .fixed-table-footer,
46 | .fixed-table-header {
47 | overflow: hidden;
48 | }
49 |
50 | .fixed-table-footer {
51 | border-top: 1px solid #dddddd;
52 | }
53 |
54 | .fixed-table-body {
55 | overflow-x: auto;
56 | overflow-y: auto;
57 | height: 100%;
58 | }
59 |
60 | .fixed-table-container table {
61 | width: 100%;
62 | }
63 |
64 | .fixed-table-container thead th {
65 | height: 0;
66 | padding: 0;
67 | margin: 0;
68 | border-left: 1px solid #dddddd;
69 | }
70 |
71 | .fixed-table-container thead th:focus {
72 | outline: 0 solid transparent;
73 | }
74 |
75 | .fixed-table-container thead th:first-child {
76 | border-left: none;
77 | border-top-left-radius: 4px;
78 | -webkit-border-top-left-radius: 4px;
79 | -moz-border-radius-topleft: 4px;
80 | }
81 |
82 | .fixed-table-container thead th .th-inner,
83 | .fixed-table-container tbody td .th-inner {
84 | padding: 8px;
85 | line-height: 24px;
86 | vertical-align: top;
87 | overflow: hidden;
88 | text-overflow: ellipsis;
89 | white-space: nowrap;
90 | }
91 |
92 | .fixed-table-container thead th .sortable {
93 | cursor: pointer;
94 | background-position: right;
95 | background-repeat: no-repeat;
96 | padding-right: 30px;
97 | }
98 |
99 | .fixed-table-container thead th .both {
100 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAQAAADYWf5HAAAAkElEQVQoz7X QMQ5AQBCF4dWQSJxC5wwax1Cq1e7BAdxD5SL+Tq/QCM1oNiJidwox0355mXnG/DrEtIQ6azioNZQxI0ykPhTQIwhCR+BmBYtlK7kLJYwWCcJA9M4qdrZrd8pPjZWPtOqdRQy320YSV17OatFC4euts6z39GYMKRPCTKY9UnPQ6P+GtMRfGtPnBCiqhAeJPmkqAAAAAElFTkSuQmCC');
101 | }
102 |
103 | .fixed-table-container thead th .asc {
104 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZ0lEQVQ4y2NgGLKgquEuFxBPAGI2ahhWCsS/gDibUoO0gPgxEP8H4ttArEyuQYxAPBdqEAxPBImTY5gjEL9DM+wTENuQahAvEO9DMwiGdwAxOymGJQLxTyD+jgWDxCMZRsEoGAVoAADeemwtPcZI2wAAAABJRU5ErkJggg==');
105 | }
106 |
107 | .fixed-table-container thead th .desc {
108 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAAZUlEQVQ4y2NgGAWjYBSggaqGu5FA/BOIv2PBIPFEUgxjB+IdQPwfC94HxLykus4GiD+hGfQOiB3J8SojEE9EM2wuSJzcsFMG4ttQgx4DsRalkZENxL+AuJQaMcsGxBOAmGvopk8AVz1sLZgg0bsAAAAASUVORK5CYII= ');
109 | }
110 |
111 | .fixed-table-container th.detail {
112 | width: 30px;
113 | }
114 |
115 | .fixed-table-container tbody td {
116 | border-left: 1px solid #dddddd;
117 | }
118 |
119 | .fixed-table-container tbody tr:first-child td {
120 | border-top: none;
121 | }
122 |
123 | .fixed-table-container tbody td:first-child {
124 | border-left: none;
125 | }
126 |
127 | /* the same color with .active */
128 | .fixed-table-container tbody .selected td {
129 | background-color: #f5f5f5;
130 | }
131 |
132 | .fixed-table-container .bs-checkbox {
133 | text-align: center;
134 | }
135 |
136 | .fixed-table-container .bs-checkbox .th-inner {
137 | padding: 8px 0;
138 | }
139 |
140 | .fixed-table-container input[type="radio"],
141 | .fixed-table-container input[type="checkbox"] {
142 | margin: 0 auto !important;
143 | }
144 |
145 | .fixed-table-container .no-records-found {
146 | text-align: center;
147 | }
148 |
149 | .fixed-table-pagination div.pagination,
150 | .fixed-table-pagination .pagination-detail {
151 | margin-top: 10px;
152 | margin-bottom: 10px;
153 | }
154 |
155 | .fixed-table-pagination div.pagination .pagination {
156 | margin: 0;
157 | }
158 |
159 | .fixed-table-pagination .pagination a {
160 | padding: 6px 12px;
161 | line-height: 1.428571429;
162 | }
163 |
164 | .fixed-table-pagination .pagination-info {
165 | line-height: 34px;
166 | margin-right: 5px;
167 | }
168 |
169 | .fixed-table-pagination .btn-group {
170 | position: relative;
171 | display: inline-block;
172 | vertical-align: middle;
173 | }
174 |
175 | .fixed-table-pagination .dropup .dropdown-menu {
176 | margin-bottom: 0;
177 | }
178 |
179 | .fixed-table-pagination .page-list {
180 | display: inline-block;
181 | }
182 |
183 | .fixed-table-toolbar .columns-left {
184 | margin-right: 5px;
185 | }
186 |
187 | .fixed-table-toolbar .columns-right {
188 | margin-left: 5px;
189 | }
190 |
191 | .fixed-table-toolbar .columns label {
192 | display: block;
193 | padding: 3px 20px;
194 | clear: both;
195 | font-weight: normal;
196 | line-height: 1.428571429;
197 | }
198 |
199 | .fixed-table-toolbar .bs-bars,
200 | .fixed-table-toolbar .search,
201 | .fixed-table-toolbar .columns {
202 | position: relative;
203 | margin-top: 10px;
204 | margin-bottom: 10px;
205 | line-height: 34px;
206 | }
207 |
208 | .fixed-table-pagination li.disabled a {
209 | pointer-events: none;
210 | cursor: default;
211 | }
212 |
213 | .fixed-table-loading {
214 | display: none;
215 | position: absolute;
216 | top: 42px;
217 | right: 0;
218 | bottom: 0;
219 | left: 0;
220 | z-index: 99;
221 | background-color: #fff;
222 | text-align: center;
223 | }
224 |
225 | .fixed-table-body .card-view .title {
226 | font-weight: bold;
227 | display: inline-block;
228 | min-width: 30%;
229 | text-align: left !important;
230 | }
231 |
232 | /* support bootstrap 2 */
233 | .fixed-table-body thead th .th-inner {
234 | box-sizing: border-box;
235 | }
236 |
237 | .table th, .table td {
238 | vertical-align: middle;
239 | box-sizing: border-box;
240 | }
241 |
242 | .fixed-table-toolbar .dropdown-menu {
243 | text-align: left;
244 | max-height: 300px;
245 | overflow: auto;
246 | }
247 |
248 | .fixed-table-toolbar .btn-group > .btn-group {
249 | display: inline-block;
250 | margin-left: -1px !important;
251 | }
252 |
253 | .fixed-table-toolbar .btn-group > .btn-group > .btn {
254 | border-radius: 0;
255 | }
256 |
257 | .fixed-table-toolbar .btn-group > .btn-group:first-child > .btn {
258 | border-top-left-radius: 4px;
259 | border-bottom-left-radius: 4px;
260 | }
261 |
262 | .fixed-table-toolbar .btn-group > .btn-group:last-child > .btn {
263 | border-top-right-radius: 4px;
264 | border-bottom-right-radius: 4px;
265 | }
266 |
267 | .bootstrap-table .table > thead > tr > th {
268 | vertical-align: bottom;
269 | border-bottom: 1px solid #ddd;
270 | }
271 |
272 | /* support bootstrap 3 */
273 | .bootstrap-table .table thead > tr > th {
274 | padding: 0;
275 | margin: 0;
276 | }
277 |
278 | .bootstrap-table .fixed-table-footer tbody > tr > td {
279 | padding: 0 !important;
280 | }
281 |
282 | .bootstrap-table .fixed-table-footer .table {
283 | border-bottom: none;
284 | border-radius: 0;
285 | padding: 0 !important;
286 | }
287 |
288 | .bootstrap-table .pull-right .dropdown-menu {
289 | right: 0;
290 | left: auto;
291 | }
292 |
293 | /* calculate scrollbar width */
294 | p.fixed-table-scroll-inner {
295 | width: 100%;
296 | height: 200px;
297 | }
298 |
299 | div.fixed-table-scroll-outer {
300 | top: 0;
301 | left: 0;
302 | visibility: hidden;
303 | width: 200px;
304 | height: 150px;
305 | overflow: hidden;
306 | }
307 |
308 | /* for get correct heights */
309 | .fixed-table-toolbar:after, .fixed-table-pagination:after {
310 | content: "";
311 | display: block;
312 | clear: both;
313 | }
314 |
--------------------------------------------------------------------------------
/lib/css/color-modes.css:
--------------------------------------------------------------------------------
1 | .bd-placeholder-img {
2 | font-size: 1.125rem;
3 | text-anchor: middle;
4 | -webkit-user-select: none;
5 | -moz-user-select: none;
6 | user-select: none;
7 | }
8 |
9 | @media (min-width: 768px) {
10 | .bd-placeholder-img-lg {
11 | font-size: 3.5rem;
12 | }
13 | }
14 |
15 | .b-example-divider {
16 | width: 100%;
17 | height: 3rem;
18 | background-color: rgba(0, 0, 0, .1);
19 | border: solid rgba(0, 0, 0, .15);
20 | border-width: 1px 0;
21 | box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
22 | }
23 |
24 | .b-example-vr {
25 | flex-shrink: 0;
26 | width: 1.5rem;
27 | height: 100vh;
28 | }
29 |
30 | .bi {
31 | vertical-align: -.125em;
32 | fill: currentColor;
33 | }
34 |
35 | .nav-scroller {
36 | position: relative;
37 | z-index: 2;
38 | height: 2.75rem;
39 | overflow-y: hidden;
40 | }
41 |
42 | .nav-scroller .nav {
43 | display: flex;
44 | flex-wrap: nowrap;
45 | padding-bottom: 1rem;
46 | margin-top: -1px;
47 | overflow-x: auto;
48 | text-align: center;
49 | white-space: nowrap;
50 | -webkit-overflow-scrolling: touch;
51 | }
52 |
53 | .btn-bd-primary {
54 | --bd-violet-bg: #712cf9;
55 | --bd-violet-rgb: 112.520718, 44.062154, 249.437846;
56 |
57 | --bs-btn-font-weight: 600;
58 | --bs-btn-color: var(--bs-white);
59 | --bs-btn-bg: var(--bd-violet-bg);
60 | --bs-btn-border-color: var(--bd-violet-bg);
61 | --bs-btn-hover-color: var(--bs-white);
62 | --bs-btn-hover-bg: #6528e0;
63 | --bs-btn-hover-border-color: #6528e0;
64 | --bs-btn-focus-shadow-rgb: var(--bd-violet-rgb);
65 | --bs-btn-active-color: var(--bs-btn-hover-color);
66 | --bs-btn-active-bg: #5a23c8;
67 | --bs-btn-active-border-color: #5a23c8;
68 | }
69 |
70 | .bd-mode-toggle {
71 | z-index: 1500;
72 | }
73 |
--------------------------------------------------------------------------------
/lib/css/fonts/bootstrap-icons.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markc/hcp_bs5/c297d2fe20f0b891dca26016bc86bb3774435044/lib/css/fonts/bootstrap-icons.woff2
--------------------------------------------------------------------------------
/lib/css/fonts/nunito-latin-400-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markc/hcp_bs5/c297d2fe20f0b891dca26016bc86bb3774435044/lib/css/fonts/nunito-latin-400-normal.woff2
--------------------------------------------------------------------------------
/lib/css/fonts/nunito-latin-600-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markc/hcp_bs5/c297d2fe20f0b891dca26016bc86bb3774435044/lib/css/fonts/nunito-latin-600-normal.woff2
--------------------------------------------------------------------------------
/lib/css/fonts/nunito-latin-700-normal.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markc/hcp_bs5/c297d2fe20f0b891dca26016bc86bb3774435044/lib/css/fonts/nunito-latin-700-normal.woff2
--------------------------------------------------------------------------------
/lib/css/style.css:
--------------------------------------------------------------------------------
1 | .dataTable-wrapper.no-header .dataTable-container {
2 | border-top: 1px solid #d9d9d9;
3 | }
4 |
5 | .dataTable-wrapper.no-footer .dataTable-container {
6 | border-bottom: 1px solid #d9d9d9;
7 | }
8 |
9 | .dataTable-top,
10 | .dataTable-bottom {
11 | padding: 8px 10px;
12 | }
13 |
14 | .dataTable-top > nav:first-child,
15 | .dataTable-top > div:first-child,
16 | .dataTable-bottom > nav:first-child,
17 | .dataTable-bottom > div:first-child {
18 | float: left;
19 | }
20 |
21 | .dataTable-top > nav:last-child,
22 | .dataTable-top > div:last-child,
23 | .dataTable-bottom > nav:last-child,
24 | .dataTable-bottom > div:last-child {
25 | float: right;
26 | }
27 |
28 | .dataTable-selector {
29 | padding: 6px;
30 | }
31 |
32 | .dataTable-input {
33 | padding: 6px 12px;
34 | }
35 |
36 | .dataTable-info {
37 | margin: 7px 0;
38 | }
39 |
40 | /* PAGER */
41 | .dataTable-pagination ul {
42 | margin: 0;
43 | padding-left: 0;
44 | }
45 |
46 | .dataTable-pagination li {
47 | list-style: none;
48 | float: left;
49 | }
50 |
51 | .dataTable-pagination a {
52 | border: 1px solid transparent;
53 | float: left;
54 | margin-left: 2px;
55 | padding: 6px 12px;
56 | position: relative;
57 | text-decoration: none;
58 | color: #333;
59 | }
60 |
61 | .dataTable-pagination a:hover {
62 | background-color: #d9d9d9;
63 | }
64 |
65 | .dataTable-pagination .active a,
66 | .dataTable-pagination .active a:focus,
67 | .dataTable-pagination .active a:hover {
68 | background-color: #d9d9d9;
69 | cursor: default;
70 | }
71 |
72 | .dataTable-pagination .ellipsis a,
73 | .dataTable-pagination .disabled a,
74 | .dataTable-pagination .disabled a:focus,
75 | .dataTable-pagination .disabled a:hover {
76 | cursor: not-allowed;
77 | }
78 |
79 | .dataTable-pagination .disabled a,
80 | .dataTable-pagination .disabled a:focus,
81 | .dataTable-pagination .disabled a:hover {
82 | cursor: not-allowed;
83 | opacity: 0.4;
84 | }
85 |
86 | .dataTable-pagination .pager a {
87 | font-weight: bold;
88 | }
89 |
90 | /* TABLE */
91 | .dataTable-table {
92 | max-width: 100%;
93 | width: 100%;
94 | border-spacing: 0;
95 | border-collapse: separate;
96 | }
97 |
98 | .dataTable-table > tbody > tr > td,
99 | .dataTable-table > tbody > tr > th,
100 | .dataTable-table > tfoot > tr > td,
101 | .dataTable-table > tfoot > tr > th,
102 | .dataTable-table > thead > tr > td,
103 | .dataTable-table > thead > tr > th {
104 | vertical-align: top;
105 | padding: 8px 10px;
106 | }
107 |
108 | .dataTable-table > thead > tr > th {
109 | vertical-align: bottom;
110 | text-align: left;
111 | border-bottom: 1px solid #d9d9d9;
112 | }
113 |
114 | .dataTable-table > tfoot > tr > th {
115 | vertical-align: bottom;
116 | text-align: left;
117 | border-top: 1px solid #d9d9d9;
118 | }
119 |
120 | .dataTable-table th {
121 | vertical-align: bottom;
122 | text-align: left;
123 | }
124 |
125 | .dataTable-table th a {
126 | text-decoration: none;
127 | color: inherit;
128 | }
129 |
130 | .dataTable-sorter {
131 | display: inline-block;
132 | height: 100%;
133 | position: relative;
134 | width: 100%;
135 | }
136 |
137 | .dataTable-sorter::before,
138 | .dataTable-sorter::after {
139 | content: "";
140 | height: 0;
141 | width: 0;
142 | position: absolute;
143 | right: 4px;
144 | border-left: 4px solid transparent;
145 | border-right: 4px solid transparent;
146 | opacity: 0.2;
147 | }
148 |
149 | .dataTable-sorter::before {
150 | border-top: 4px solid #000;
151 | bottom: 0px;
152 | }
153 |
154 | .dataTable-sorter::after {
155 | border-bottom: 4px solid #000;
156 | border-top: 4px solid transparent;
157 | top: 0px;
158 | }
159 |
160 | .asc .dataTable-sorter::after,
161 | .desc .dataTable-sorter::before {
162 | opacity: 0.6;
163 | }
164 |
165 | .dataTables-empty {
166 | text-align: center;
167 | }
168 |
169 | .dataTable-top::after, .dataTable-bottom::after {
170 | clear: both;
171 | content: " ";
172 | display: table;
173 | }
174 |
175 | table.dataTable-table:focus tr.dataTable-cursor > td:first-child {
176 | border-left: 3px blue solid;
177 | }
178 |
179 | table.dataTable-table:focus {
180 | outline: solid 1px black;
181 | outline-offset: -1px;
182 | }
--------------------------------------------------------------------------------
/lib/css/table-datatable.css:
--------------------------------------------------------------------------------
1 | .dataTable-wrapper.no-footer .dataTable-container{border-bottom:none}.dataTable-selector{padding:.375rem 1.75rem .375rem .75rem}.dataTable-dropdown{display:inline-flex;align-items:center}.dataTable-dropdown label{white-space:nowrap;margin-left:15px}.page-item.active .page-link{color:#fff!important}
2 |
--------------------------------------------------------------------------------
/lib/img/Blank-300x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/markc/hcp_bs5/c297d2fe20f0b891dca26016bc86bb3774435044/lib/img/Blank-300x150.png
--------------------------------------------------------------------------------
/lib/img/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/img/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/js/bootstrap-table-editable.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author zhixin wen
3 | * extensions: https://github.com/vitalets/x-editable
4 | */
5 |
6 | (function($) {
7 |
8 | 'use strict';
9 |
10 | $.extend($.fn.bootstrapTable.defaults, {
11 | editable: true,
12 | onEditableInit: function() {
13 | return false;
14 | },
15 | onEditableSave: function(field, row, oldValue, $el) {
16 | return false;
17 | },
18 | onEditableShown: function(field, row, $el, editable) {
19 | return false;
20 | },
21 | onEditableHidden: function(field, row, $el, reason) {
22 | return false;
23 | }
24 | });
25 |
26 | $.extend($.fn.bootstrapTable.Constructor.EVENTS, {
27 | 'editable-init.bs.table': 'onEditableInit',
28 | 'editable-save.bs.table': 'onEditableSave',
29 | 'editable-shown.bs.table': 'onEditableShown',
30 | 'editable-hidden.bs.table': 'onEditableHidden'
31 | });
32 |
33 | var BootstrapTable = $.fn.bootstrapTable.Constructor,
34 | _initTable = BootstrapTable.prototype.initTable,
35 | _initBody = BootstrapTable.prototype.initBody;
36 |
37 | BootstrapTable.prototype.initTable = function() {
38 | var that = this;
39 | _initTable.apply(this, Array.prototype.slice.apply(arguments));
40 |
41 | if (!this.options.editable) {
42 | return;
43 | }
44 |
45 | $.each(this.columns, function(i, column) {
46 | if (!column.editable) {
47 | return;
48 | }
49 |
50 | var editableOptions = {},
51 | editableDataMarkup = [],
52 | editableDataPrefix = 'editable-';
53 |
54 | var processDataOptions = function(key, value) {
55 | // Replace camel case with dashes.
56 | var dashKey = key.replace(/([A-Z])/g, function($1) {
57 | return "-" + $1.toLowerCase();
58 | });
59 | if (dashKey.slice(0, editableDataPrefix.length) == editableDataPrefix) {
60 | var dataKey = dashKey.replace(editableDataPrefix, 'data-');
61 | editableOptions[dataKey] = value;
62 | }
63 | };
64 |
65 | $.each(that.options, processDataOptions);
66 |
67 | column.formatter = column.formatter || function(value, row, index) {
68 | return value;
69 | };
70 | column._formatter = column._formatter ? column._formatter : column.formatter;
71 | column.formatter = function(value, row, index) {
72 | var result = column._formatter ? column._formatter(value, row, index) : value;
73 |
74 | $.each(column, processDataOptions);
75 |
76 | $.each(editableOptions, function(key, value) {
77 | editableDataMarkup.push(' ' + key + '="' + value + '"');
78 | });
79 |
80 | var _dont_edit_formatter = false;
81 | if (column.editable.hasOwnProperty('noeditFormatter')) {
82 | _dont_edit_formatter = column.editable.noeditFormatter(value, row, index);
83 | }
84 |
85 | if (_dont_edit_formatter === false) {
86 | return ['' + ' '
92 | ].join('');
93 | } else {
94 | return _dont_edit_formatter;
95 | }
96 |
97 | };
98 | });
99 | };
100 |
101 | BootstrapTable.prototype.initBody = function() {
102 | var that = this;
103 | _initBody.apply(this, Array.prototype.slice.apply(arguments));
104 |
105 | if (!this.options.editable) {
106 | return;
107 | }
108 |
109 | $.each(this.columns, function(i, column) {
110 | if (!column.editable) {
111 | return;
112 | }
113 |
114 | that.$body.find('a[data-name="' + column.field + '"]').editable(column.editable)
115 | .off('save').on('save', function(e, params) {
116 | var data = that.getData(),
117 | index = $(this).parents('tr[data-index]').data('index'),
118 | row = data[index],
119 | oldValue = row[column.field];
120 |
121 | $(this).data('value', params.submitValue);
122 | row[column.field] = params.submitValue;
123 | that.trigger('editable-save', column.field, row, oldValue, $(this));
124 | that.resetFooter();
125 | });
126 | that.$body.find('a[data-name="' + column.field + '"]').editable(column.editable)
127 | .off('shown').on('shown', function(e, editable) {
128 | var data = that.getData(),
129 | index = $(this).parents('tr[data-index]').data('index'),
130 | row = data[index];
131 |
132 | that.trigger('editable-shown', column.field, row, $(this), editable);
133 | });
134 | that.$body.find('a[data-name="' + column.field + '"]').editable(column.editable)
135 | .off('hidden').on('hidden', function(e, reason) {
136 | var data = that.getData(),
137 | index = $(this).parents('tr[data-index]').data('index'),
138 | row = data[index];
139 |
140 | that.trigger('editable-hidden', column.field, row, $(this), reason);
141 | });
142 | });
143 | this.trigger('editable-init');
144 | };
145 |
146 | })(jQuery);
147 |
--------------------------------------------------------------------------------
/lib/js/bootstrap-table-export.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author zhixin wen
3 | * extensions: https://github.com/kayalshri/tableExport.jquery.plugin
4 | */
5 |
6 | (function ($) {
7 | 'use strict';
8 | var sprintf = $.fn.bootstrapTable.utils.sprintf;
9 |
10 | var TYPE_NAME = {
11 | json: 'JSON',
12 | xml: 'XML',
13 | png: 'PNG',
14 | csv: 'CSV',
15 | txt: 'TXT',
16 | sql: 'SQL',
17 | doc: 'MS-Word',
18 | excel: 'MS-Excel',
19 | xlsx: 'MS-Excel (OpenXML)',
20 | powerpoint: 'MS-Powerpoint',
21 | pdf: 'PDF'
22 | };
23 |
24 | $.extend($.fn.bootstrapTable.defaults, {
25 | showExport: false,
26 | exportDataType: 'basic', // basic, all, selected
27 | // 'json', 'xml', 'png', 'csv', 'txt', 'sql', 'doc', 'excel', 'powerpoint', 'pdf'
28 | exportTypes: ['json', 'xml', 'csv', 'txt', 'sql', 'excel'],
29 | exportOptions: {}
30 | });
31 |
32 | $.extend($.fn.bootstrapTable.defaults.icons, {
33 | export: 'glyphicon-export icon-share'
34 | });
35 |
36 | $.extend($.fn.bootstrapTable.locales, {
37 | formatExport: function () {
38 | return 'Export data';
39 | }
40 | });
41 | $.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales);
42 |
43 | var BootstrapTable = $.fn.bootstrapTable.Constructor,
44 | _initToolbar = BootstrapTable.prototype.initToolbar;
45 |
46 | BootstrapTable.prototype.initToolbar = function () {
47 | this.showToolbar = this.options.showExport;
48 |
49 | _initToolbar.apply(this, Array.prototype.slice.apply(arguments));
50 |
51 | if (this.options.showExport) {
52 | var that = this,
53 | $btnGroup = this.$toolbar.find('>.btn-group'),
54 | $export = $btnGroup.find('div.export');
55 |
56 | if (!$export.length) {
57 | $export = $([
58 | '',
59 | '',
65 | sprintf(' ', this.options.iconsPrefix, this.options.icons.export),
66 | ' ',
67 | ' ',
68 | '',
70 | '
'].join('')).appendTo($btnGroup);
71 |
72 | var $menu = $export.find('.dropdown-menu'),
73 | exportTypes = this.options.exportTypes;
74 |
75 | if (typeof this.options.exportTypes === 'string') {
76 | var types = this.options.exportTypes.slice(1, -1).replace(/ /g, '').split(',');
77 |
78 | exportTypes = [];
79 | $.each(types, function (i, value) {
80 | exportTypes.push(value.slice(1, -1));
81 | });
82 | }
83 | $.each(exportTypes, function (i, type) {
84 | if (TYPE_NAME.hasOwnProperty(type)) {
85 | $menu.append(['',
86 | '',
87 | TYPE_NAME[type],
88 | ' ',
89 | ' '].join(''));
90 | }
91 | });
92 |
93 | $menu.find('li').click(function () {
94 | var type = $(this).data('type'),
95 | doExport = function () {
96 | that.$el.tableExport($.extend({}, that.options.exportOptions, {
97 | type: type,
98 | escape: false
99 | }));
100 | };
101 |
102 | if (that.options.exportDataType === 'all' && that.options.pagination) {
103 | that.$el.one(that.options.sidePagination === 'server' ? 'post-body.bs.table' : 'page-change.bs.table', function () {
104 | doExport();
105 | that.togglePagination();
106 | });
107 | that.togglePagination();
108 | } else if (that.options.exportDataType === 'selected') {
109 | var data = that.getData(),
110 | selectedData = that.getAllSelections();
111 |
112 | // Quick fix #2220
113 | if (that.options.sidePagination === 'server') {
114 | data = {total: that.options.totalRows};
115 | data[that.options.dataField] = that.getData();
116 |
117 | selectedData = {total: that.options.totalRows};
118 | selectedData[that.options.dataField] = that.getAllSelections();
119 | }
120 |
121 | that.load(selectedData);
122 | doExport();
123 | that.load(data);
124 | } else {
125 | doExport();
126 | }
127 | });
128 | }
129 | }
130 | };
131 | })(jQuery);
132 |
--------------------------------------------------------------------------------
/lib/js/color-modes.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
3 | * Copyright 2011-2023 The Bootstrap Authors
4 | * Licensed under the Creative Commons Attribution 3.0 Unported License.
5 | */
6 |
7 | (() => {
8 | 'use strict'
9 |
10 | const getStoredTheme = () => localStorage.getItem('theme')
11 | const setStoredTheme = theme => localStorage.setItem('theme', theme)
12 |
13 | const getPreferredTheme = () => {
14 | const storedTheme = getStoredTheme()
15 | if (storedTheme) {
16 | return storedTheme
17 | }
18 |
19 | return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
20 | }
21 |
22 | const setTheme = theme => {
23 | if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
24 | document.documentElement.setAttribute('data-bs-theme', 'dark')
25 | } else {
26 | document.documentElement.setAttribute('data-bs-theme', theme)
27 | }
28 | }
29 |
30 | setTheme(getPreferredTheme())
31 |
32 | const showActiveTheme = (theme, focus = false) => {
33 | const themeSwitcher = document.querySelector('#bd-theme')
34 |
35 | if (!themeSwitcher) {
36 | return
37 | }
38 |
39 | const themeSwitcherText = document.querySelector('#bd-theme-text')
40 | const activeThemeIcon = document.querySelector('.theme-icon-active use')
41 | const btnToActive = document.querySelector(`[data-bs-theme-value="${theme}"]`)
42 | const svgOfActiveBtn = btnToActive.querySelector('svg use').getAttribute('href')
43 |
44 | document.querySelectorAll('[data-bs-theme-value]').forEach(element => {
45 | element.classList.remove('active')
46 | element.setAttribute('aria-pressed', 'false')
47 | })
48 |
49 | btnToActive.classList.add('active')
50 | btnToActive.setAttribute('aria-pressed', 'true')
51 | activeThemeIcon.setAttribute('href', svgOfActiveBtn)
52 | const themeSwitcherLabel = `${themeSwitcherText.textContent} (${btnToActive.dataset.bsThemeValue})`
53 | themeSwitcher.setAttribute('aria-label', themeSwitcherLabel)
54 |
55 | if (focus) {
56 | themeSwitcher.focus()
57 | }
58 | }
59 |
60 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
61 | const storedTheme = getStoredTheme()
62 | if (storedTheme !== 'light' && storedTheme !== 'dark') {
63 | setTheme(getPreferredTheme())
64 | }
65 | })
66 |
67 | window.addEventListener('DOMContentLoaded', () => {
68 | showActiveTheme(getPreferredTheme())
69 |
70 | document.querySelectorAll('[data-bs-theme-value]')
71 | .forEach(toggle => {
72 | toggle.addEventListener('click', () => {
73 | const theme = toggle.getAttribute('data-bs-theme-value')
74 | setStoredTheme(theme)
75 | setTheme(theme)
76 | showActiveTheme(theme, true)
77 | })
78 | })
79 | })
80 | })()
81 |
--------------------------------------------------------------------------------
/lib/js/dark.js:
--------------------------------------------------------------------------------
1 |
2 | const THEME_KEY = "theme"
3 |
4 | function toggleDarkTheme() {
5 | setTheme(
6 | document.documentElement.getAttribute("data-bs-theme") === 'dark'
7 | ? "light"
8 | : "dark"
9 | )
10 | }
11 |
12 | /**
13 | * Set theme for mazer
14 | * @param {"dark"|"light"} theme
15 | * @param {boolean} persist
16 | */
17 | function setTheme(theme, persist = false) {
18 | document.body.classList.add(theme)
19 | document.documentElement.setAttribute('data-bs-theme', theme)
20 |
21 | if (persist) {
22 | localStorage.setItem(THEME_KEY, theme)
23 | }
24 | }
25 |
26 | /**
27 | * Init theme from setTheme()
28 | */
29 | function initTheme() {
30 | //If the user manually set a theme, we'll load that
31 | const storedTheme = localStorage.getItem(THEME_KEY)
32 | if (storedTheme) {
33 | return setTheme(storedTheme)
34 | }
35 | //Detect if the user set his preferred color scheme to dark
36 | if (!window.matchMedia) {
37 | return
38 | }
39 |
40 | //Media query to detect dark preference
41 | const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
42 |
43 | //Register change listener
44 | mediaQuery.addEventListener("change", (e) =>
45 | setTheme(e.matches ? "dark" : "light", true)
46 | )
47 | return setTheme(mediaQuery.matches ? "dark" : "light", true)
48 | }
49 |
50 | window.addEventListener('DOMContentLoaded', () => {
51 | const toggler = document.getElementById("toggle-dark")
52 | const theme = localStorage.getItem(THEME_KEY)
53 |
54 | if(toggler) {
55 | toggler.checked = theme === "dark"
56 |
57 | toggler.addEventListener("input", (e) => {
58 | setTheme(e.target.checked ? "dark" : "light", true)
59 | })
60 | }
61 |
62 | });
63 |
64 | initTheme()
65 |
66 |
--------------------------------------------------------------------------------
/lib/js/initTheme.js:
--------------------------------------------------------------------------------
1 | const body = document.body;
2 | const theme = localStorage.getItem('theme')
3 |
4 | if (theme)
5 | document.documentElement.setAttribute('data-bs-theme', theme)
6 |
--------------------------------------------------------------------------------
/lib/js/simple-datatables.js:
--------------------------------------------------------------------------------
1 | let dataTable = new simpleDatatables.DataTable(
2 | document.getElementById("table1")
3 | )
4 | // Move "per page dropdown" selector element out of label
5 | // to make it work with bootstrap 5. Add bs5 classes.
6 | function adaptPageDropdown() {
7 | const selector = dataTable.wrapper.querySelector(".dataTable-selector")
8 | selector.parentNode.parentNode.insertBefore(selector, selector.parentNode)
9 | selector.classList.add("form-select")
10 | }
11 |
12 | // Add bs5 classes to pagination elements
13 | function adaptPagination() {
14 | const paginations = dataTable.wrapper.querySelectorAll(
15 | "ul.dataTable-pagination-list"
16 | )
17 |
18 | for (const pagination of paginations) {
19 | pagination.classList.add(...["pagination", "pagination-primary"])
20 | }
21 |
22 | const paginationLis = dataTable.wrapper.querySelectorAll(
23 | "ul.dataTable-pagination-list li"
24 | )
25 |
26 | for (const paginationLi of paginationLis) {
27 | paginationLi.classList.add("page-item")
28 | }
29 |
30 | const paginationLinks = dataTable.wrapper.querySelectorAll(
31 | "ul.dataTable-pagination-list li a"
32 | )
33 |
34 | for (const paginationLink of paginationLinks) {
35 | paginationLink.classList.add("page-link")
36 | }
37 | }
38 |
39 | const refreshPagination = () => {
40 | adaptPagination()
41 | }
42 |
43 | // Patch "per page dropdown" and pagination after table rendered
44 | dataTable.on("datatable.init", () => {
45 | adaptPageDropdown()
46 | refreshPagination()
47 | })
48 | dataTable.on("datatable.update", refreshPagination)
49 | dataTable.on("datatable.sort", refreshPagination)
50 |
51 | // Re-patch pagination after the page was changed
52 | dataTable.on("datatable.page", adaptPagination)
53 |
--------------------------------------------------------------------------------
/lib/php/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: https://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | [*]
7 | indent_style = space
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 |
13 | [*.{js,ts,json,css,html}]
14 | indent_size = 2
15 |
16 | [*.php]
17 | indent_size = 4
18 |
19 | [*.go]
20 | indent_style = tab
21 |
--------------------------------------------------------------------------------
/lib/php/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "bmewburn.vscode-intelephense-client"
4 | ]
5 | }
--------------------------------------------------------------------------------
/lib/php/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.detectIndentation": false
3 | }
4 |
--------------------------------------------------------------------------------
/lib/php/home.tpl.example:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Your Page Title
4 |
Lorem ipsum...
5 |
6 |
--------------------------------------------------------------------------------
/lib/php/plugin.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugin
6 | {
7 | protected string $buf = '';
8 | protected ?object $dbh = null;
9 | protected string $tbl = '';
10 | protected array $in = [];
11 |
12 | public function __construct(
13 | protected readonly object $g,
14 | Theme $t
15 | ) {
16 | elog(__METHOD__);
17 |
18 | $this->validateAccess($t->g->in['o'], $t->g->in['m'], $t->g->cfg['self']);
19 | $this->initializePlugin($t);
20 | $this->setupDatabase($t);
21 | $this->executeMethod($t->g->in['m']);
22 | }
23 |
24 | protected function validateAccess(string $o, string $m, string $self): void
25 | {
26 | $allowedPlugins = ['auth', 'home'];
27 | $allowedMethods = ['list', 'create', 'resetpw'];
28 | if (!util::is_usr() && (!in_array($o, $allowedPlugins, true) || ($o === 'auth' && !in_array($m, $allowedMethods, true)))) {
29 | util::redirect($self . '?o=auth');
30 | }
31 | }
32 |
33 | protected function initializePlugin(Theme $t): void
34 | {
35 | $this->in = util::esc($this->in);
36 | }
37 |
38 | protected function setupDatabase(Theme $t): void
39 | {
40 | if ($this->tbl === '') {
41 | return;
42 | }
43 |
44 | db::$dbh = $this->dbh ?? db::$dbh ?? new db($t->g->db);
45 | db::$tbl = $this->tbl;
46 | }
47 |
48 | protected function executeMethod(string $method): void
49 | {
50 | if (!method_exists($this, $method)) {
51 | throw new RuntimeException("Method '$method' not found");
52 | }
53 |
54 | try {
55 | $this->buf .= $this->$method();
56 | } catch (Throwable $e) {
57 | error_log("Error executing method '$method': " . $e->getMessage());
58 | $this->buf .= "An error occurred while processing your request.";
59 | }
60 | }
61 |
62 | public function __toString() : string
63 | {
64 | elog(__METHOD__);
65 |
66 | return $this->buf;
67 | }
68 |
69 | protected function create(): string
70 | {
71 | elog(__METHOD__);
72 |
73 | if (!util::is_post()) {
74 | return $this->g->t->create($this->in);
75 | }
76 |
77 | try {
78 | $now = (new DateTimeImmutable())->format('Y-m-d H:i:s');
79 | $this->in['updated'] = $now;
80 | $this->in['created'] = $now;
81 |
82 | $lid = db::create($this->in);
83 | if (!$lid) {
84 | throw new RuntimeException('Failed to create item');
85 | }
86 |
87 | util::log("Item number $lid created", 'success');
88 | util::relist();
89 | } catch (Throwable $e) {
90 | error_log("Create error: " . $e->getMessage());
91 | util::log('Error creating item: ' . $e->getMessage());
92 | return $this->g->t->create($this->in);
93 | }
94 |
95 | return '';
96 | }
97 |
98 | protected function read() : string
99 | {
100 | elog(__METHOD__);
101 |
102 | return $this->g->t->read(db::read('*', 'id', $this->g->in['i'], '', 'one'));
103 | }
104 |
105 | protected function update(): string
106 | {
107 | elog(__METHOD__);
108 |
109 | if (!util::is_post()) {
110 | return $this->read();
111 | }
112 |
113 | try {
114 | $this->in['updated'] = (new DateTimeImmutable())->format('Y-m-d H:i:s');
115 | $itemId = $this->g->in['i'];
116 |
117 | if (!db::update($this->in, [['id', '=', $itemId]])) {
118 | throw new RuntimeException('Update operation failed');
119 | }
120 |
121 | util::log("Item number $itemId updated", 'success');
122 | util::relist();
123 | } catch (Throwable $e) {
124 | error_log("Update error: " . $e->getMessage());
125 | util::log('Error updating item: ' . $e->getMessage());
126 | return $this->read();
127 | }
128 |
129 | return '';
130 | }
131 |
132 | protected function delete(): string
133 | {
134 | elog(__METHOD__);
135 |
136 | if (!util::is_post()) {
137 | return 'Invalid request method';
138 | }
139 |
140 | try {
141 | $itemId = $this->g->in['i'];
142 | if (empty($itemId)) {
143 | throw new RuntimeException('No item ID provided');
144 | }
145 |
146 | if (!db::delete([['id', '=', $itemId]])) {
147 | throw new RuntimeException('Delete operation failed');
148 | }
149 |
150 | util::log("Item number $itemId removed", 'success');
151 | util::relist();
152 | } catch (Throwable $e) {
153 | error_log("Delete error: " . $e->getMessage());
154 | util::log('Error deleting item: ' . $e->getMessage());
155 | }
156 |
157 | return '';
158 | }
159 |
160 | protected function list() : string
161 | {
162 | elog(__METHOD__);
163 |
164 | return $this->g->t->list(db::read('*', '', '', 'ORDER BY `updated` DESC'));
165 | }
166 |
167 | public function __call(string $name, array $args): string
168 | {
169 | $message = sprintf(
170 | '%s() name = %s, args = %s',
171 | __METHOD__,
172 | $name,
173 | var_export($args, true)
174 | );
175 | elog($message);
176 |
177 | return sprintf('Plugin::%s() not implemented', $name);
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/lib/php/plugins/accounts.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | declare(strict_types=1);
6 |
7 | class Plugins_Accounts extends Plugin
8 | {
9 | protected string $tbl = 'accounts';
10 | protected array $in = [
11 | 'grp' => 1,
12 | 'acl' => 2,
13 | 'vhosts' => 1,
14 | 'login' => '',
15 | 'fname' => '',
16 | 'lname' => '',
17 | 'altemail' => '',
18 | ];
19 |
20 | protected function create() : string
21 | {
22 | elog(__METHOD__);
23 |
24 | if (util::is_adm()) return parent::create();
25 | util::log('You are not authorized to perform this action, please contact your administrator.');
26 | util::relist();
27 | return '';
28 | }
29 |
30 | protected function read() : string
31 | {
32 | elog(__METHOD__);
33 |
34 | $usr = db::read('*', 'id', $this->g->in['i'], '', 'one');
35 | if (!$usr) {
36 | util::log('User not found.');
37 | util::relist();
38 | return '';
39 | }
40 |
41 | if (util::is_acl(0)) {
42 | // superadmin
43 | } elseif (util::is_acl(1)) { // normal admin
44 | if ((int)$_SESSION['usr']['grp'] !== (int)$usr['grp']) {
45 | util::log('You are not authorized to perform this action.');
46 | util::relist();
47 | return '';
48 | }
49 | } else { // Other users
50 | if ((int)$_SESSION['usr']['id'] !== (int)$usr['id']) {
51 | util::log('You are not authorized to perform this action.');
52 | util::relist();
53 | return '';
54 | }
55 | }
56 | return $this->g->t->read($usr);
57 | }
58 |
59 | protected function delete() : string
60 | {
61 | elog(__METHOD__);
62 |
63 | if (util::is_post()) return parent::delete();
64 | return '';
65 | }
66 |
67 | protected function list() : string
68 | {
69 | elog(__METHOD__);
70 |
71 | if ($this->g->in['x'] === 'json') {
72 | $columns = [
73 | ['dt' => null, 'db' => 'id'],
74 | ['dt' => 0, 'db' => 'login', 'formatter' => function(string $d, array $row) : string {
75 | return '' . $d . ' ';
76 | }],
77 | ['dt' => 1, 'db' => 'fname'],
78 | ['dt' => 2, 'db' => 'lname'],
79 | ['dt' => 3, 'db' => 'altemail'],
80 | ['dt' => 4, 'db' => 'acl', 'formatter' => function(string $d) : string {
81 | return $this->g->acl[(int)$d];
82 | }],
83 | ['dt' => 5, 'db' => 'grp'],
84 | ];
85 | $jsonData = json_encode(db::simple($_GET, 'accounts', 'id', $columns), JSON_PRETTY_PRINT);
86 | $this->g->out['main'] = $jsonData;
87 | return $jsonData;
88 | }
89 | return $this->g->t->list($this->in);
90 | }
91 |
92 | protected function switch_user() : string
93 | {
94 | elog(__METHOD__);
95 |
96 | if (util::is_adm() && !is_null($this->g->in['i'])) {
97 | $usr = db::read('id,acl,grp,login,fname,lname,webpw,cookie', 'id', $this->g->in['i'], '', 'one');
98 | if ($usr) {
99 | $_SESSION['usr'] = $usr;
100 | util::log('Switch to user: ' . $usr['login'], 'success');
101 | }
102 | } else {
103 | util::log('Not authorized to switch users');
104 | }
105 | util::relist();
106 | return '';
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/lib/php/plugins/auth.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | declare(strict_types=1);
6 |
7 | class Plugins_Auth extends Plugin
8 | {
9 | private const OTP_LENGTH = 10;
10 | private const REMEMBER_ME_EXP = 604800; // 7 days;
11 |
12 | protected string $tbl = 'accounts';
13 | protected array $in = [
14 | 'id' => null,
15 | 'acl' => null,
16 | 'grp' => null,
17 | 'login' => '',
18 | 'webpw' => '',
19 | 'remember' => '',
20 | 'otp' => '',
21 | 'passwd1' => '',
22 | 'passwd2' => '',
23 | ];
24 |
25 | // forgotpw
26 | public function create() : string
27 | {
28 | elog(__METHOD__);
29 |
30 | $u = (string)$this->in['login'];
31 |
32 | if (util::is_post()) {
33 | if (filter_var($u, FILTER_VALIDATE_EMAIL)) {
34 | if ($usr = db::read('id,acl', 'login', $u, '', 'one')) {
35 | if ($usr['acl'] != 9) {
36 | $newpass = util::genpw(self::OTP_LENGTH);
37 | if ($this->mail_forgotpw($u, $newpass, 'From: ' . $this->g->cfg['email'])) {
38 | db::update([
39 | 'otp' => $newpass,
40 | 'otpttl' => time()
41 | ], [['id', '=', $usr['id']]]);
42 | util::log('Sent reset password key for "' . $u . '" so please check your mailbox and click on the supplied link.', 'success');
43 | } else util::log('Problem sending message to ' . $u, 'danger');
44 | util::redirect($this->g->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list');
45 | } else util::log('Account is disabled, contact your System Administrator');
46 | } else util::log('User does not exist');
47 | } else util::log('You must provide a valid email address');
48 | }
49 | return $this->g->t->create(['login' => $u]);
50 | }
51 |
52 | // login
53 | public function list() : string
54 | {
55 | elog(__METHOD__);
56 |
57 | $u = (string)$this->in['login'];
58 | $p = (string)$this->in['webpw'];
59 |
60 | if ($u) {
61 | if (!empty($this->g->cfg['admpw']) && $u === 'admin@example.com' && $p === 'admin123') {
62 | $_SESSION['usr'] = [
63 | 'id' => 0,
64 | 'grp' => 0,
65 | 'acl' => 0,
66 | 'login' => $u,
67 | 'fname' => 'Admin',
68 | 'lname' => 'User'
69 | ];
70 | $_SESSION['adm'] = 0;
71 | util::log($u . ' is now logged in', 'success');
72 | $_SESSION['m'] = 'list';
73 | util::redirect($this->g->cfg['self']);
74 | }
75 | if ($usr = db::read('id,grp,acl,login,fname,lname,webpw,cookie', 'login', $u, '', 'one')) {
76 | $id = (int)$usr['id'];
77 | $acl = (int)$usr['acl'];
78 | $login = (string)$usr['login'];
79 | $webpw = (string)$usr['webpw'];
80 |
81 | if ($acl !== 9) {
82 | if (password_verify(html_entity_decode($p, ENT_QUOTES, 'UTF-8'), $webpw)) {
83 | if ($this->in['remember']) {
84 | $uniq = util::random_token(32);
85 | db::update(['cookie' => $uniq], [['id', '=', $id]]);
86 | util::put_cookie('remember', $uniq, self::REMEMBER_ME_EXP);
87 | }
88 | $_SESSION['usr'] = $usr;
89 | util::log($login.' is now logged in', 'success');
90 | if ($acl === 0) $_SESSION['adm'] = $id;
91 | $_SESSION['m'] = 'list';
92 | util::redirect($this->g->cfg['self']);
93 | } else util::log('Invalid Email Or Password');
94 | } else util::log('Account is disabled, contact your System Administrator');
95 | } else util::log('Invalid Email Or Password');
96 | }
97 | return $this->g->t->list(['login' => $u]);
98 | }
99 |
100 | // resetpw
101 | public function update() : string
102 | {
103 | elog(__METHOD__);
104 |
105 | if (!(util::is_usr() || isset($_SESSION['resetpw']))) {
106 | util::log('Session expired! Please login and try again.');
107 | util::relist();
108 | }
109 |
110 | $i = (util::is_usr()) ? (int)$_SESSION['usr']['id'] : (int)$_SESSION['resetpw']['usr']['id'];
111 | $u = (util::is_usr()) ? (string)$_SESSION['usr']['login'] : (string)$_SESSION['resetpw']['usr']['login'];
112 |
113 | if (util::is_post()) {
114 | if ($usr = db::read('login,acl,otpttl', 'id', (string)$i, '', 'one')) {
115 | $p1 = html_entity_decode($this->in['passwd1'], ENT_QUOTES, 'UTF-8');
116 | $p2 = html_entity_decode($this->in['passwd2'], ENT_QUOTES, 'UTF-8');
117 | if (util::chkpw($p1, $p2)) {
118 | if (util::is_usr() || ($usr['otpttl'] && ((int)$usr['otpttl'] + 3600) > time())) {
119 | if (!is_null($usr['acl'])) {
120 | if (db::update([
121 | 'webpw' => password_hash($p1, PASSWORD_DEFAULT),
122 | 'otp' => '',
123 | 'otpttl' => 0,
124 | 'updated' => date('Y-m-d H:i:s'),
125 | ], [['id', '=', $i]])) {
126 | util::log('Password reset for ' . $usr['login'], 'success');
127 | if (util::is_usr()) {
128 | util::redirect($this->g->cfg['self']);
129 | } else {
130 | unset($_SESSION['resetpw']);
131 | util::relist();
132 | }
133 | } else util::log('Problem updating database');
134 | } else util::log($usr['login'] . ' is not allowed access');
135 | } else util::log('Your one time password key has expired');
136 | }
137 | } else util::log('User does not exist');
138 | }
139 | return $this->g->t->update(['id' => $i, 'login' => $u]);
140 | }
141 |
142 | public function delete() : string
143 | {
144 | elog(__METHOD__);
145 |
146 | if(util::is_usr()){
147 | $u = (string)$_SESSION['usr']['login'];
148 | $id = (int)$_SESSION['usr']['id'];
149 | if (isset($_SESSION['adm']) && $_SESSION['usr']['id'] === $_SESSION['adm']){
150 | unset($_SESSION['adm']);
151 | }
152 | unset($_SESSION['usr']);
153 | if(isset($_COOKIE['remember'])){
154 | db::update(['cookie' => ''], [['id', '=', $id]]);
155 | setcookie('remember', '', strtotime('-1 hour', 0));
156 | }
157 | util::log($u . ' is now logged out', 'success');
158 | }
159 | util::redirect($this->g->cfg['self']);
160 | return '';
161 | }
162 |
163 | // Utilities
164 | public function resetpw() : string
165 | {
166 | elog(__METHOD__);
167 |
168 | $otp = html_entity_decode((string)$this->in['otp']);
169 | if (strlen($otp) === self::OTP_LENGTH) {
170 | if ($usr = db::read('id,acl,login,otp,otpttl', 'otp', $otp, '', 'one')) {
171 | $id = (int)$usr['id'];
172 | $acl = (int)$usr['acl'];
173 | $login = (string)$usr['login'];
174 | $otpttl = (int)$usr['otpttl'];
175 |
176 | if ($otpttl && (($otpttl + 3600) > time())) {
177 | if ($acl != 3) { // suspended
178 | $_SESSION['resetpw'] = [ 'usr'=> $usr ];
179 | return $this->g->t->update(['id' => $id, 'login' => $login]);
180 | } else util::log($login . ' is not allowed access');
181 | } else util::log('Your one time password key has expired');
182 | } else util::log('Your one time password key no longer exists');
183 | } else util::log('Incorrect one time password key');
184 | util::redirect($this->g->cfg['self']);
185 | return '';
186 | }
187 |
188 | private function mail_forgotpw(string $email, string $newpass, string $headers = '') : bool
189 | {
190 | elog(__METHOD__);
191 |
192 | $host = $_SERVER['REQUEST_SCHEME'] . '://'
193 | . $this->g->cfg['host']
194 | . $this->g->cfg['self'];
195 | return mail(
196 | $email,
197 | 'Reset password for ' . $this->g->cfg['host'],
198 | 'Here is your new OTP (one time password) key that is valid for one hour.
199 |
200 | Please click on the link below and continue with reseting your password.
201 |
202 | If you did not request this action then please ignore this message.
203 |
204 | ' . $host . '?o=auth&m=resetpw&otp=' . $newpass,
205 | $headers
206 | );
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/lib/php/plugins/dkim.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_Dkim extends Plugin
6 | {
7 | protected
8 | $in = [
9 | 'dnstxt' => '',
10 | 'domain' => '',
11 | 'keylen' => '2048',
12 | 'select' => 'mail',
13 | ];
14 |
15 | public function create() : string
16 | {
17 | elog(__METHOD__);
18 |
19 | if (util::is_post()){
20 | $domain = escapeshellarg($this->in['domain']);
21 | $select = escapeshellarg($this->in['select']);
22 | $keylen = escapeshellarg($this->in['keylen']);
23 | util::exe('dkim add ' . $domain . ' ' . $select . ' ' . $keylen);
24 | }
25 | util::redirect( $this->g->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list');
26 | }
27 |
28 | public function read() : string
29 | {
30 | elog(__METHOD__);
31 |
32 | $domain = explode('._domainkey.', $this->in['dnstxt'])[1]; // too fragile?
33 | $domain_esc = escapeshellarg($domain);
34 | exec("sudo dkim show $domain_esc 2>&1", $retArr, $retVal);
35 | $buf = '
36 | ' . $retArr[0] . '
37 | ' . $retArr[1] . '
';
38 | return $this->g->t->read(['buf' => $buf, 'domain' => $domain]);
39 | }
40 |
41 | public function update() : string
42 | {
43 | elog(__METHOD__);
44 |
45 | //return $this->list(); // override parent update()
46 | util::redirect( $this->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list');
47 | }
48 |
49 | public function delete() : string
50 | {
51 | elog(__METHOD__);
52 |
53 | if (util::is_post()){
54 | $domain = escapeshellarg($this->in['domain']);
55 | util::exe('dkim del ' . $domain);
56 | }
57 | util::redirect( $this->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list');
58 | }
59 |
60 | public function list() : string
61 | {
62 | elog(__METHOD__);
63 |
64 | $buf = '';
65 | exec("sudo dkim list 2>&1", $retArr, $retVal);
66 | foreach($retArr as $line) $buf .= '
67 | ' . $line . ' ';
68 | return $this->g->t->list(['buf' => $buf . '
']);
69 | }
70 | }
71 |
72 | ?>
73 |
--------------------------------------------------------------------------------
/lib/php/plugins/home.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_Home extends Plugin
6 | {
7 | public function list() : string
8 | {
9 | elog(__METHOD__);
10 |
11 | if (file_exists(INC . 'home.tpl')) {
12 | ob_start();
13 | include INC . 'home.tpl';
14 | return ob_get_clean();
15 | }
16 | return $this->g->t->list([]);
17 | }
18 | }
19 |
20 | ?>
21 |
--------------------------------------------------------------------------------
/lib/php/plugins/infomail.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_InfoMail extends Plugin
6 | {
7 | protected $pflog = '/tmp/pflogsumm.log';
8 |
9 | public function list() : string
10 | {
11 | elog(__METHOD__);
12 |
13 | return $this->g->t->list([
14 | 'mailq' => shell_exec('mailq'),
15 | 'pflogs' => is_readable($this->pflog)
16 | ? file_get_contents($this->pflog)
17 | : 'none',
18 | 'pflog_time' => is_readable($this->pflog)
19 | ? round(abs(date('U') - filemtime($this->pflog)) / 60, 0) . ' min.'
20 | : '0 min.',
21 | ]);
22 | }
23 |
24 | public function pflog_renew()
25 | {
26 | elog(__METHOD__);
27 |
28 | $this->pflogs = shell_exec('sudo pflogs');
29 | return $this->list();
30 | }
31 | }
32 |
33 | ?>
34 |
--------------------------------------------------------------------------------
/lib/php/plugins/infosys.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_InfoSys extends Plugin
6 | {
7 | public function list() : string
8 | {
9 | elog(__METHOD__);
10 |
11 | $mem = $dif = $cpu = [];
12 | $cpu_name = $procs = '';
13 | $cpu_num = 0;
14 | $os = 'Unknown OS';
15 |
16 | $pmi = explode("\n", trim(file_get_contents('/proc/meminfo')));
17 |
18 | $lavg = sys_getloadavg();
19 | $lav = "1m: " . number_format($lavg[0], 2) .
20 | ", 5m: " . number_format($lavg[1], 2) .
21 | ", 15m: " . number_format($lavg[2], 2);
22 | elog("lav=$lav");
23 | join(', ', sys_getloadavg());
24 |
25 | $stat1 = file('/proc/stat');
26 | sleep(1);
27 | $stat2 = file('/proc/stat');
28 |
29 | if (is_readable('/proc/cpuinfo')) {
30 | $tmp = trim(file_get_contents('/proc/cpuinfo'));
31 | $ret = preg_match_all('/model name.+/', $tmp, $matches);
32 | $cpu_name = $ret ? explode(': ', $matches[0][0])[1] : 'Unknown CPU';
33 | $cpu_num = count($matches[0]);
34 | }
35 |
36 | if (is_readable('/etc/os-release')) {
37 | $tmp = explode("\n", trim(file_get_contents('/etc/os-release')));
38 | $osr = [];
39 | foreach ($tmp as $line) {
40 | list($k, $v) = explode('=', $line);
41 | $osr[$k] = trim($v, '" ');
42 | }
43 | $os = $osr['PRETTY_NAME'] ?? 'Unknown OS';
44 | }
45 |
46 | foreach ($pmi as $line) {
47 | list($k, $v) = explode(':', $line);
48 | list($mem[$k],) = explode(' ', trim($v));
49 | }
50 |
51 | $info1 = explode(" ", preg_replace("!cpu +!", "", $stat1[0]));
52 | $info2 = explode(" ", preg_replace("!cpu +!", "", $stat2[0]));
53 | $dif['user'] = $info2[0] - $info1[0];
54 | $dif['nice'] = $info2[1] - $info1[1];
55 | $dif['sys'] = $info2[2] - $info1[2];
56 | $dif['idle'] = $info2[3] - $info1[3];
57 | $total = array_sum($dif);
58 | foreach($dif as $x=>$y) $cpu[$x] = round($y / $total * 100, 2);
59 | $cpu_all = sprintf("User: %01.2f, System: %01.2f, Nice: %01.2f, Idle: %01.2f", $cpu['user'], $cpu['sys'], $cpu['nice'], $cpu['idle']);
60 | $cpu_pcnt = intval(round(100 - $cpu['idle']));
61 |
62 | $dt = (float) disk_total_space('/');
63 | $df = (float) disk_free_space('/');
64 | $du = (float) $dt - $df;
65 | $dp = floor(($du / $dt) * 100);
66 |
67 | $mt = (float) $mem['MemTotal'] * 1000;
68 | //$mf = (float) ($mem['MemFree'] + $mem['Cached'] + $mem['Buffers'] + $mem['SReclaimable']) * 1024;
69 | //$mu = (float) ($mem['MemTotal'] - $mem['MemFree'] - $mem['Cached'] - $mem['SReclaimable'] - $mem['Buffers'] - $mem['Shmem']) * 1024;
70 | $mu = (float) ($mem['MemTotal'] - $mem['MemFree'] - $mem['Cached'] - $mem['SReclaimable'] - $mem['Buffers']) * 1000;
71 | $mf = (float) $mt - $mu;
72 | $mp = floor(($mu / $mt) * 100);
73 |
74 | $ip = gethostbyname(gethostname());
75 | $hn = gethostbyaddr($ip);
76 | $knl = is_readable('/proc/version')
77 | ? explode(' ', trim(file_get_contents('/proc/version')))[2]
78 | : 'Unknown';
79 |
80 | return $this->g->t->list([
81 | 'dsk_color' => $dp > 90 ? 'danger' : ($dp > 80 ? 'warning' : 'default'),
82 | 'dsk_free' => util::numfmtsi($df),
83 | 'dsk_pcnt' => $dp,
84 | 'dsk_text' => $dp > 5 ? $dp. '%' : '',
85 | 'dsk_total' => util::numfmtsi($dt),
86 | 'dsk_used' => util::numfmtsi($du),
87 | 'mem_color' => $mp > 90 ? 'danger' : ($mp > 80 ? 'warning' : 'default'),
88 | 'mem_free' => util::numfmt($mf),
89 | 'mem_pcnt' => $mp,
90 | 'mem_text' => $mp > 5 ? $mp . '%' : '',
91 | 'mem_total' => util::numfmt($mt),
92 | 'mem_used' => util::numfmt($mu),
93 | 'os_name' => $os,
94 | 'uptime' => util::sec2time(intval(explode(' ', (string) file_get_contents('/proc/uptime'))[0])),
95 | 'loadav' => $lav,
96 | 'hostname' => $hn,
97 | 'host_ip' => $ip,
98 | 'kernel' => $knl,
99 | 'cpu_all' => $cpu_all,
100 | 'cpu_name' => $cpu_name,
101 | 'cpu_num' => $cpu_num,
102 | 'cpu_color' => $cpu_pcnt > 90 ? 'danger' : ($cpu_pcnt > 80 ? 'warning' : 'default'),
103 | 'cpu_pcnt' => $cpu_pcnt,
104 | 'cpu_text' => $cpu_pcnt > 5 ? $cpu_pcnt. '%' : '',
105 | ]);
106 | }
107 | }
108 |
109 | ?>
110 |
--------------------------------------------------------------------------------
/lib/php/plugins/processes.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_Processes extends Plugin
6 | {
7 | public function list() : string
8 | {
9 | elog(__METHOD__);
10 |
11 | return $this->g->t->list(['procs' => shell_exec('sudo processes')]);
12 | }
13 | }
14 |
15 | ?>
16 |
--------------------------------------------------------------------------------
/lib/php/plugins/records.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_Records extends Plugin
6 | {
7 | protected
8 | $tbl = 'records',
9 | $in = [
10 | 'content' => '',
11 | 'name' => '',
12 | 'prio' => 0,
13 | 'ttl' => 300,
14 | 'type' => '',
15 | ];
16 |
17 | public function __construct(Theme $t)
18 | {
19 | elog(__METHOD__);
20 |
21 | if ($t->g->dns['db']['type'])
22 | $this->dbh = new db($t->g->dns['db']);
23 | parent::__construct($t);
24 | }
25 |
26 | protected function create() : string
27 | {
28 | elog(__METHOD__);
29 |
30 | if (util::is_post()) {
31 | $in = $this->validate($this->in);
32 | if (!empty($in)) {
33 | $in['created'] = $in['updated'];
34 | $lid = db::create($in);
35 | $this->update_domains($in['domain_id'], $in['updated'] );
36 | util::log('Created DNS record ID: ' . $lid . ' for ' . $in['name'], 'success');
37 | }
38 | $i = intval(util::enc($_POST['did']));
39 | util::redirect( $this->g->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list&i=' . $i);
40 | }
41 | return 'Error creating DNS record';
42 | }
43 |
44 | protected function update() : string
45 | {
46 | elog(__METHOD__);
47 |
48 | if (util::is_post()) {
49 | $in = $this->validate($this->in);
50 | if (!empty($in)) {
51 | $dom = util::enc($_POST['domain']);
52 | $in['created'] = $in['updated'];
53 | db::update($in, [['id', '=', $this->g->in['i']]]);
54 | $this->update_domains($in['domain_id'], $in['updated'] );
55 | util::log('Updated DNS record ID: ' . $this->g->in['i'] . ' for ' . $dom, 'success');
56 | }
57 | $i = intval(util::enc($_POST['did']));
58 | util::redirect( $this->g->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list&i=' . $i);
59 | }
60 | return 'Error updating DNS record';
61 | }
62 |
63 | protected function delete() : string
64 | {
65 | elog(__METHOD__);
66 |
67 | if (util::is_post()) {
68 | $dom = util::enc($_POST['domain']);
69 | $did = intval(util::enc($_POST['did']));
70 | $now = date('Y-m-d H:i:s');
71 |
72 | db::delete([['id', '=', $this->g->in['i']]]);
73 | $this->update_domains($did, $now);
74 | util::log('Deleted DNS record ID: ' . $this->g->in['i'] . ' from ' . $dom, 'success');
75 | $i = $did;
76 | util::redirect( $this->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list&i=' . $i);
77 | }
78 | return 'Error deleting DNS record';
79 | }
80 |
81 | protected function list() : string
82 | {
83 | elog(__METHOD__);
84 |
85 | if ($this->g->in['x'] === 'json') {
86 | $columns = [
87 | ['dt' => 0, 'db' => 'name'],
88 | ['dt' => 1, 'db' => 'content'],
89 | ['dt' => 2, 'db' => 'type'],
90 | ['dt' => 3, 'db' => 'prio'],
91 | ['dt' => 4, 'db' => 'ttl'],
92 | ['dt' => 5, 'db' => 'id', 'formatter' => function($d) {
93 | return '
94 |
95 |
96 |
97 | ';
98 | }],
99 | ['dt' => 6, 'db' => 'active'],
100 | ['dt' => 7, 'db' => 'did'],
101 | ['dt' => 8, 'db' => 'domain'],
102 | ['dt' => 9, 'db' => 'updated'],
103 | ];
104 | return json_encode(db::simple($_GET, 'records_view', 'id', $columns, 'did=' . $_GET['did']), JSON_PRETTY_PRINT);
105 | }
106 |
107 | $domain = db::qry("
108 | SELECT name FROM domains
109 | WHERE id = :did", ['did' => $this->g->in['i']], 'col'); // i = domain id at this point
110 |
111 | return $this->g->t->list(['domain' => $domain, 'did' => $this->g->in['i']]);
112 | }
113 |
114 | private function update_domains(int $did, string $now) : bool
115 | {
116 | elog(__METHOD__);
117 |
118 | if ($did && $now) {
119 | $sql = "
120 | SELECT content
121 | FROM records
122 | WHERE type='SOA'
123 | AND domain_id=:did";
124 |
125 | $soa = util::inc_soa(db::qry($sql, ['did' => $did], 'col'));
126 | $sql = "
127 | UPDATE records
128 | SET content=:content
129 | WHERE type='SOA'
130 | AND domain_id=:did";
131 |
132 | db::qry($sql, ['did' => $did, 'content' => $soa]);
133 | db::$tbl = 'domains';
134 | return db::update(['updated' => $now], [['id', '=', $did]]);
135 | }
136 | return false;
137 | }
138 |
139 | private function validate(array $in) : array
140 | {
141 | elog(__METHOD__);
142 |
143 | if (empty($in['content'])) {
144 | util::log('Content must not be empty');
145 | return [];
146 | } elseif (($in['type'] === 'A') && !filter_var($in['content'], FILTER_VALIDATE_IP)) {
147 | util::log('An "A" record must contain a legitimate IP');
148 | return [];
149 | } elseif ($in['type'] === 'CAA' && !preg_match('/^[a-zA-Z0-9"]+/', $in['content'])) {
150 | util::log('CAA record content must only contain letters and numbers');
151 | return [];
152 | } elseif ($in['name'] && $in['name'] !== '*' && !preg_match('/^[a-zA-Z0-9_-]+/', $in['name'])) {
153 | util::log('Record name must contain letters, numbers, _ - or only *');
154 | return [];
155 | }
156 |
157 | if ($in['type'] === 'TXT')
158 | $in['content'] = '"' . trim(htmlspecialchars_decode($in['content'], ENT_COMPAT), '"') . '"';
159 |
160 | if ($in['type'] === 'CAA')
161 | $in['content'] = htmlspecialchars_decode($in['content'], ENT_COMPAT);
162 |
163 | $domain = strtolower(util::enc($_POST['domain']));
164 | $in['name'] = strtolower(rtrim(str_replace($domain, '', $in['name']), '.'));
165 | $in['name'] = $in['name'] ? $in['name'] . '.' . $domain : $domain;
166 |
167 | $in['ttl'] = intval($in['ttl']);
168 | $in['prio'] = intval($in['prio']);
169 | $in['updated'] = date('Y-m-d H:i:s');
170 | $in['domain_id'] = intval(util::enc($_POST['did']));
171 |
172 | return $in;
173 | }
174 | }
175 |
176 | ?>
177 |
--------------------------------------------------------------------------------
/lib/php/plugins/sshm.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Plugins_Sshm extends Plugin
9 | {
10 | public array $inp = [
11 | 'name' => '',
12 | 'host' => '',
13 | 'port' => '22',
14 | 'user' => 'root',
15 | 'skey' => 'none',
16 | 'key_name' => '',
17 | 'key_cmnt' => '',
18 | 'key_pass' => '',
19 | ];
20 |
21 | public function create(): string
22 | {
23 | elog(__METHOD__);
24 |
25 | if (util::is_post()) {
26 | util::run('sshm create ' . implode(' ', $this->inp));
27 | util::relist();
28 | return null;
29 | }
30 |
31 | $output = util::run('sshm key_list');
32 | $this->inp['keys'] = $output ? explode("\n", $output) : [];
33 | return $this->g->t->create($this->inp);
34 | }
35 |
36 | public function update(): string
37 | {
38 | elog(__METHOD__);
39 |
40 | if (util::is_post()) {
41 | util::run('sshm create ' . implode(' ', $this->inp));
42 | util::relist();
43 | return null;
44 | }
45 |
46 | $output = util::run('sshm read ' . $this->inp['name']);
47 | $host_data = $output ? explode("\n", $output) : [];
48 | $inp = array_combine(
49 | array_keys($this->inp),
50 | array_map(fn($k, $i) => $host_data[$i] ?? '', array_keys($this->inp), array_keys($this->inp))
51 | );
52 | $keys_output = util::run('sshm key_list');
53 | $inp['keys'] = $keys_output ? explode("\n", $keys_output) : [];
54 | return $this->g->t->update($inp);
55 | }
56 |
57 | public function delete(): string
58 | {
59 | elog(__METHOD__);
60 |
61 | if (util::is_post()) {
62 | util::run('sshm delete ' . $this->inp['name']);
63 | util::relist();
64 | return null;
65 | }
66 | return $this->g->t->delete($this->inp);
67 | }
68 |
69 | public function list(): string
70 | {
71 | elog(__METHOD__);
72 |
73 | $output = util::run('sshm list');
74 | return $this->g->t->list(['ary' => $output ? explode("\n", $output) : []]);
75 | }
76 |
77 | public function help(): string
78 | {
79 | elog(__METHOD__);
80 |
81 | return $this->g->t->help(
82 | $this->inp['name'],
83 | util::run('sshm help ' . escapeshellarg($this->inp['name']))
84 | );
85 | }
86 |
87 | public function key_create(): ?string
88 | {
89 | elog(__METHOD__);
90 |
91 | if (util::is_post()) {
92 | util::run(
93 | 'sshm key_create ' .
94 | $this->inp['key_name'] . ' ' .
95 | $this->inp['key_cmnt'] . ' ' .
96 | $this->inp['key_pass']
97 | );
98 | util::relist('key_list');
99 | return null;
100 | }
101 | return $this->g->t->key_create($this->inp);
102 | }
103 |
104 | protected function key_read(): string
105 | {
106 | elog(__METHOD__);
107 |
108 | return $this->g->t->key_read(
109 | $this->inp['skey'],
110 | shell_exec('sshm key_read ' . $this->inp['skey'])
111 | );
112 | }
113 |
114 | public function key_delete(): ?string
115 | {
116 | elog(__METHOD__);
117 |
118 | if (util::is_post()) {
119 | util::run('sshm key_delete ' . $this->inp['key_name']);
120 | util::relist('key_list');
121 | return null;
122 | }
123 | return $this->g->t->key_delete($this->inp);
124 | }
125 |
126 | public function key_list(): string
127 | {
128 | elog(__METHOD__);
129 |
130 | $output = util::run('sshm key_list all');
131 | return $this->g->t->key_list(['ary' => $output ? explode("\n", $output) : [], 'err' => 0]);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/lib/php/plugins/vhosts.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_Vhosts extends Plugin
6 | {
7 | protected
8 | $tbl = 'vhosts',
9 | $in = [
10 | 'active' => 0,
11 | 'aid' => 0,
12 | 'aliases' => 10,
13 | 'diskquota' => 1000000000,
14 | 'domain' => '',
15 | 'gid' => 1000,
16 | 'mailboxes' => 1,
17 | 'mailquota' => 500000000,
18 | 'uid' => 1000,
19 | 'uname' => '',
20 | 'cms' => '',
21 | 'ssl' => '',
22 | 'ip' => '',
23 | 'uuser' => '',
24 | ];
25 |
26 | protected function create() : string
27 | {
28 | elog(__METHOD__);
29 |
30 | if (util::is_post()) {
31 | extract($this->in);
32 | // $active = $active ? 1 : 0;
33 |
34 | // if(!util::is_valid_plan($plan)){
35 | // util::log('Invalid plan ' . $plan);
36 | // util::redirect($this->g->cfg['self'] . '?o=vhosts');
37 | // }
38 |
39 | if (file_exists('/home/u/' . $domain)) {
40 | util::log('/home/u/' . $domain . ' already exists', 'warning');
41 | $_POST = []; return $this->g->t->create($this->in);
42 | }
43 |
44 | // if ($mailquota > $diskquota) {
45 | // util::log('Mailbox quota exceeds domain disk quota');
46 | // $_POST = []; return $this->g->t->create($this->in);
47 | // }
48 |
49 | $num_results = db::read('COUNT(id)', 'domain', $domain, '', 'col');
50 |
51 | if ($num_results != 0) {
52 | util::log('Domain already exists');
53 | $_POST = []; return $this->t->create($this->in);
54 | }
55 |
56 | $cms = ($cms === 'on') ? 'wp' : 'none';
57 | $ssl = ($ssl === 'on') ? 'self' : 'le';
58 | $vhost = $uuser ? $uuser . '@' . $domain : $domain;
59 |
60 | shell_exec("nohup sh -c 'sudo addvhost $vhost $cms $ssl $ip' > /tmp/addvhost.log 2>&1 &");
61 | util::log('Added ' . $domain . ', please wait another few minutes for the setup to complete', 'success');
62 | util::redirect($this->g->cfg['self'] . '?o=vhosts');
63 | }
64 | return $this->g->t->create($this->in);
65 | }
66 |
67 | protected function read() : string
68 | {
69 | elog(__METHOD__);
70 |
71 | return $this->g->t->update(db::read('*', 'id', $this->g->in['i'], '', 'one'));
72 | }
73 |
74 | protected function update() : string
75 | {
76 | elog(__METHOD__);
77 |
78 | if (util::is_post()) {
79 | extract($this->in);
80 | $diskquota *= 1000000;
81 | $mailquota *= 1000000;
82 | $active = $active ? 1 : 0;
83 |
84 | $domain = db::read('domain', 'id', $this->g->in['i'], '', 'col');
85 |
86 | if ($mailquota > $diskquota) {
87 | util::log('Mailbox quota exceeds disk quota');
88 | $_POST = []; return $this->read();
89 | }
90 |
91 | $sql = "
92 | UPDATE `vhosts` SET
93 | `active` = :active,
94 | `aliases` = :aliases,
95 | `diskquota` = :diskquota,
96 | `domain` = :domain,
97 | `mailboxes` = :mailboxes,
98 | `mailquota` = :mailquota,
99 | `updated` = :updated
100 | WHERE `id` = :id";
101 |
102 | $res = db::qry($sql, [
103 | 'id' => $this->g->in['i'],
104 | 'active' => $active,
105 | 'aliases' => $aliases,
106 | 'diskquota' => $diskquota,
107 | 'domain' => $domain,
108 | 'mailboxes' => $mailboxes,
109 | 'mailquota' => $mailquota,
110 | 'updated' => date('Y-m-d H:i:s'),
111 | ]);
112 |
113 | util::log('Vhost ID ' . $this->g->in['i'] . ' updated', 'success');
114 | util::redirect( $this->cfg['self'] . '?o=' . $this->g->in['o'] . '&m=list');
115 | } elseif ($this->g->in['i']) {
116 | return $this->read();
117 | } else return 'Error updating item';
118 | }
119 |
120 | protected function delete() : string
121 | {
122 | elog(__METHOD__);
123 |
124 | if (util::is_post() && $this->g->in['i']) {
125 | $domain = db::read('domain', 'id', $this->g->in['i'], '', 'col');
126 | if ($domain) {
127 | shell_exec("nohup sh -c 'sudo delvhost $domain' > /tmp/delvhost.log 2>&1 &");
128 | util::log('Removed ' . $domain, 'success');
129 | util::redirect($this->g->cfg['self'] . '?o=vhosts');
130 | } else util::log('ERROR: domain does not exist');
131 | }
132 | return 'Error deleting item';
133 | }
134 |
135 | protected function list() : string
136 | {
137 | elog(__METHOD__);
138 |
139 | if ($this->g->in['x'] === 'json') {
140 | $columns = [
141 | ['dt' => 0, 'db' => 'domain', 'formatter' => function($d, $row) {
142 | return '
143 |
144 | ' . $row['domain'] . ' ';
145 | }],
146 | ['dt' => 1, 'db' => 'num_aliases'],
147 | ['dt' => 2, 'db' => null, 'formatter' => function($d) { return '/'; } ],
148 | ['dt' => 3, 'db' => 'aliases'],
149 | ['dt' => 4, 'db' => 'num_mailboxes'],
150 | ['dt' => 5, 'db' => null, 'formatter' => function($d) { return '/'; } ],
151 | ['dt' => 6, 'db' => 'mailboxes'],
152 | ['dt' => 7, 'db' => 'size_mpath', 'formatter' => function($d) { return util::numfmt(intval($d)); }],
153 | ['dt' => 8, 'db' => null, 'formatter' => function($d) { return '/'; } ],
154 | ['dt' => 9, 'db' => 'mailquota', 'formatter' => function($d) { return util::numfmt(intval($d)); }],
155 | ['dt' => 10, 'db' => 'size_upath', 'formatter' => function($d) { return util::numfmt(intval($d)); }],
156 | ['dt' => 11, 'db' => null, 'formatter' => function($d) { return '/'; } ],
157 | ['dt' => 12, 'db' => 'diskquota', 'formatter' => function($d) { return util::numfmt(intval($d)); }],
158 | ['dt' => 13, 'db' => 'active', 'formatter' => function($d) {
159 | return ' ';
160 | }],
161 | ['dt' => 14, 'db' => 'id'],
162 | ['dt' => 15, 'db' => 'updated'],
163 | ];
164 | return json_encode(db::simple($_GET, 'vhosts_view', 'id', $columns), JSON_PRETTY_PRINT);
165 | }
166 | return $this->g->t->list([]);
167 | }
168 | }
169 |
170 | ?>
171 |
--------------------------------------------------------------------------------
/lib/php/plugins/vmails.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Plugins_Vmails extends Plugin
6 | {
7 | protected
8 | $tbl = 'vmails',
9 | $in = [
10 | 'newpw' => 0,
11 | 'password' => '',
12 | 'shpw' => 0,
13 | 'user' => '',
14 | ];
15 |
16 | protected function create() : string
17 | {
18 | elog(__METHOD__);
19 |
20 | if (util::is_post()) {
21 | if (!filter_var($this->in['user'], FILTER_VALIDATE_EMAIL)) {
22 | util::log('Email address (' . $this->in['user'] . ') is invalid');
23 | $_POST = []; return $this->read();
24 | }
25 | util::exe('addvmail ' . $this->in['user']);
26 | }
27 | util::relist();
28 | }
29 |
30 | protected function update() : string
31 | {
32 | elog(__METHOD__);
33 |
34 | extract($this->in);
35 |
36 | if ($shpw) {
37 | return util::run("shpw $user");
38 | } elseif ($newpw) {
39 | return util::run("newpw");
40 | } elseif (util::is_post()) {
41 | $password = html_entity_decode($password, ENT_QUOTES, 'UTF-8');
42 | if (util::chkpw($password)) {
43 | util::exe("chpw $user '$password'");
44 | }
45 | }
46 | util::relist();
47 | }
48 |
49 | protected function delete() : string
50 | {
51 | elog(__METHOD__);
52 |
53 | if (util::is_post()) {
54 | util::exe('delvmail ' . $this->in['user']);
55 | }
56 | util::relist();
57 | }
58 |
59 | protected function list() : string
60 | {
61 | elog(__METHOD__);
62 |
63 | if ($this->g->in['x'] === 'json') {
64 | $columns = [
65 | ['dt' => null, 'db' => 'id'],
66 | ['dt' => 0, 'db' => 'user', 'formatter' => function($d, $row) {
67 | return '' . $d . ' ';
68 | }],
69 | ['dt' => 1, 'db' => 'size_mail', 'formatter' => function($d) { return util::numfmt(intval($d)); }],
70 | ['dt' => 2, 'db' => 'num_total', 'formatter' => function($d) { return number_format(intval($d)); }],
71 | ['dt' => 3, 'db' => null, 'formatter' => function($d, $row) {
72 | return ' ';
73 | }],
74 | ['dt' => 4, 'db' => 'updated'],
75 | ];
76 | return json_encode(db::simple($_GET, 'vmails_view', 'id', $columns), JSON_PRETTY_PRINT);
77 | }
78 | return $this->g->t->list([]);
79 | }
80 | }
81 |
82 | ?>
83 |
--------------------------------------------------------------------------------
/lib/php/theme.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Theme
6 | {
7 | private string $buf;
8 | private array $in;
9 |
10 | public function __construct(
11 | public readonly object $g
12 | ) {
13 | elog(__METHOD__);
14 | $this->buf = '';
15 | $this->in = [];
16 | }
17 |
18 | protected function escape(string $value): string
19 | {
20 | return htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
21 | }
22 |
23 | public function __toString() : string
24 | {
25 | elog(__METHOD__);
26 | return $this->g->out['main'] ?? '';
27 | }
28 |
29 | public function log(): string
30 | {
31 | elog(__METHOD__);
32 |
33 | return implode("\n", array_map(
34 | fn(string $msg, string $lvl) => $msg
35 | ? sprintf('%s
',
36 | $this->escape($lvl),
37 | $this->escape($msg)
38 | )
39 | : '',
40 | util::log(),
41 | array_keys(util::log())
42 | ));
43 | }
44 |
45 | public function nav1(): string
46 | {
47 | elog(__METHOD__);
48 |
49 | $currentPath = '?o=' . $this->escape($this->g->in['o']);
50 |
51 | $links = array_map(
52 | function(array $nav) use ($currentPath): string {
53 | [$label, $path] = $nav;
54 | $activeClass = $currentPath === $path ? ' class="active"' : '';
55 | return sprintf(
56 | "\n %s ",
57 | $activeClass,
58 | $this->escape($path),
59 | $this->escape($label)
60 | );
61 | },
62 | $this->g->nav1
63 | );
64 |
65 | return "\n " . implode('', $links) . "\n ";
66 | }
67 |
68 | public function head(): string
69 | {
70 | elog(__METHOD__);
71 |
72 | return sprintf(
73 | "\n ",
74 | $this->escape($this->g->cfg['self']),
75 | $this->escape($this->g->out['head']),
76 | $this->g->out['nav1']
77 | );
78 | }
79 |
80 | public function main(): string
81 | {
82 | elog(__METHOD__);
83 |
84 | return sprintf(
85 | "\n %s%s\n ",
86 | $this->g->out['log'],
87 | $this->g->out['main']
88 | );
89 | }
90 |
91 | public function foot(): string
92 | {
93 | elog(__METHOD__);
94 |
95 | return sprintf(
96 | "\n ",
97 | $this->escape($this->g->out['foot'])
98 | );
99 | }
100 |
101 | public function end(): string
102 | {
103 | elog(__METHOD__);
104 |
105 | return sprintf(
106 | "\n %s\n ",
107 | $this->escape($this->g->out['end'])
108 | );
109 | }
110 |
111 | public function html(): string
112 | {
113 | elog(__METHOD__);
114 |
115 | $out = $this->g->out;
116 |
117 | return <<
119 |
120 |
121 |
122 |
123 |
124 |
125 | {$this->escape($out['doc'])}
126 | {$out['css']}
127 | {$out['js']}
128 |
129 |
130 | {$out['head']}
131 | {$out['main']}
132 | {$out['foot']}
133 | {$out['end']}
134 |
135 |
136 | HTML;
137 | }
138 |
139 | public static function dropdown(
140 | array $options,
141 | string $name,
142 | string $selected = '',
143 | ?string $label = null,
144 | ?string $class = null,
145 | ?string $extra = null
146 | ): string {
147 | elog(__METHOD__);
148 |
149 | $theme = new self((object)[]);
150 |
151 | $labelOption = $label
152 | ? sprintf("\n %s ",
153 | $theme->escape(ucfirst($label))
154 | )
155 | : '';
156 |
157 | $classAttr = $class
158 | ? sprintf(' class="%s"', $theme->escape($class))
159 | : '';
160 |
161 | $extraAttr = $extra
162 | ? ' ' . $theme->escape($extra)
163 | : '';
164 |
165 | $optionsHtml = array_map(
166 | function(array $option) use ($theme, $selected): string {
167 | [$label, $value] = $option;
168 | $value = str_replace('?t=', '', $value);
169 | $isSelected = $selected === $value ? ' selected' : '';
170 |
171 | return sprintf(
172 | "\n %s ",
173 | $theme->escape($value),
174 | $isSelected,
175 | $theme->escape($label)
176 | );
177 | },
178 | $options
179 | );
180 |
181 | return sprintf(
182 | "\n %s%s\n ",
183 | $theme->escape($name),
184 | $theme->escape($name),
185 | $classAttr,
186 | $extraAttr,
187 | $labelOption,
188 | implode('', $optionsHtml)
189 | );
190 | }
191 |
192 | public function __call(string $name, array $args): string
193 | {
194 | $message = sprintf(
195 | '%s() name = %s',
196 | __METHOD__,
197 | $this->escape($name)
198 | );
199 | elog($message);
200 |
201 | return sprintf(
202 | 'Theme::%s() not implemented',
203 | $this->escape($name)
204 | );
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/accounts.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Accounts extends Themes_Bootstrap4_Theme
6 | {
7 | public function create(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | return $this->editor($in);
12 | }
13 |
14 | public function read(array $in) : string
15 | {
16 | elog(__METHOD__);
17 |
18 | return $this->editor($in);
19 | }
20 |
21 | public function update(array $in) : string
22 | {
23 | elog(__METHOD__);
24 |
25 | return $this->editor($in);
26 | }
27 |
28 | public function list(array $in) : string
29 | {
30 | elog(__METHOD__.' '.var_export($in, true));
31 |
32 | extract($in);
33 | $aclgrp_buf = '';
34 |
35 | if (util::is_adm()) {
36 | $acl = $_SESSION['usr']['acl'];
37 | $grp = $_SESSION['usr']['grp'];
38 | $acl_ary = $grp_ary = [];
39 | foreach($this->g->acl as $k => $v) $acl_ary[] = [$v, $k];
40 | $acl_buf = $this->dropdown($acl_ary, 'acl', "$acl", '', 'custom-select');
41 | $res = db::qry("
42 | SELECT login,id
43 | FROM `accounts`
44 | WHERE acl = :0 OR acl = :1", ['0' => 0, '1' => 1]);
45 |
46 | foreach($res as $k => $v) $grp_ary[] = [$v['login'], $v['id']];
47 | $grp_buf = $this->dropdown($grp_ary, 'grp', "$grp", '', 'custom-select');
48 |
49 | $aclgrp_buf = '
50 |
51 |
52 |
53 | ACL ' . $acl_buf . '
54 |
55 |
56 |
57 |
58 | Group ' . $grp_buf . '
59 |
60 |
61 |
';
62 | }
63 |
64 | $createmodal = $this->modal([
65 | 'id' => 'createmodal',
66 | 'title' => 'Create New Account',
67 | 'action' => 'create',
68 | 'footer' => 'Create',
69 | 'body' => '
70 |
71 | Email ID
72 |
73 |
74 |
75 | First Name
76 |
77 |
78 |
79 | Last Name
80 |
81 |
82 |
83 | Alt Email
84 |
85 |
' . $aclgrp_buf,
86 | ]);
87 |
88 | return '
89 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | User ID
104 | First Name
105 | Last Name
106 | Alt Email
107 | ACL
108 | Grp
109 |
110 |
111 |
112 |
113 |
114 |
' . $createmodal . '
115 | ';
130 | }
131 |
132 | private function editor(array $in) : string
133 | {
134 | elog(__METHOD__);
135 |
136 | extract($in);
137 |
138 | $removemodal = $this->modal([
139 | 'id' => 'removemodal',
140 | 'title' => 'Remove User',
141 | 'action' => 'delete',
142 | 'footer' => 'Remove',
143 | 'hidden' => '
144 |
',
145 | 'body' => '
146 |
Are you sure you want to remove this user?' . $in['login'] . '
',
147 | ]);
148 |
149 | if ($this->g->in['m'] === 'create') {
150 | $header = 'Add Account';
151 | $switch = '';
152 | $submit = '
153 |
« Back
154 |
Add This Account ';
155 | } else {
156 | $header = 'Update Account';
157 | $switch = !util::is_usr($id) && (util::is_acl(0) || util::is_acl(1)) ? '
158 |
Switch to ' . $login . ' ' : '';
159 | $submit = '
160 |
« Back
161 |
Update ';
162 | }
163 |
164 | if (util::is_adm()) {
165 | $acl_ary = $grp_ary = [];
166 | foreach($this->g->acl as $k => $v) $acl_ary[] = [$v, $k];
167 | $acl_buf = $this->dropdown($acl_ary, 'acl', "$acl", '', 'custom-select');
168 | $res = db::qry("
169 | SELECT login,id
170 | FROM `accounts`
171 | WHERE acl = :0 OR acl = :1", ['0' => 0, '1' => 1]);
172 |
173 | foreach($res as $k => $v) $grp_ary[] = [$v['login'], $v['id']];
174 | $grp_buf = $this->dropdown($grp_ary, 'grp', "$grp", '', 'custom-select');
175 | $aclgrp_buf = '
176 |
177 | ACL ' . $acl_buf . '
178 |
179 |
180 | Group ' . $grp_buf . '
181 |
';
182 | } else {
183 | $aclgrp_buf = '';
184 | $anotes_buf = '';
185 | }
186 |
187 | return '
188 |
189 |
190 | Accounts
191 |
192 |
193 |
194 |
195 |
196 |
197 |
' . $removemodal;
236 | }
237 | }
238 |
239 | ?>
240 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/auth.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Auth extends Themes_Bootstrap4_Theme
6 | {
7 | // forgotpw (create new pw)
8 | public function create(array $in) : string
9 | {
10 | elog(__METHOD__);
11 |
12 | extract($in);
13 |
14 | return '
15 |
16 |
Forgot password
17 |
37 |
';
38 | }
39 |
40 | // signin (read current pw)
41 | public function list(array $in) : string
42 | {
43 | elog(__METHOD__);
44 | elog(var_export($in,true));
45 |
46 | extract($in);
47 |
48 | return '
49 |
';
82 | }
83 |
84 | // resetpw (update pw)
85 | public function update(array $in) : string
86 | {
87 | elog(__METHOD__);
88 | elog(var_export($in,true));
89 |
90 | extract($in);
91 |
92 | return '
93 |
94 |
Update Password
95 |
121 |
';
122 | }
123 | }
124 |
125 | ?>
126 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/dkim.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Dkim extends Themes_Bootstrap4_Theme
6 | {
7 | public function read(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | $remove = $this->modal([
12 | 'id' => 'removemodal',
13 | 'title' => 'Remove DKIM Record',
14 | 'action' => 'delete',
15 | 'footer' => 'Remove',
16 | 'hidden' => '
17 |
',
18 | 'body' => '
19 |
Are you sure you want to remove DKIM record for' . $in['domain'] . '
',
20 | ]);
21 |
22 | return '
23 |
24 |
25 | DKIM
26 |
27 |
28 |
29 |
30 |
31 |
32 |
' . $in['buf'] . '
33 |
34 |
35 | ' . $remove;
36 |
37 | }
38 |
39 | public function list(array $in) : string
40 | {
41 | elog(__METHOD__);
42 |
43 | $keybuf = $this->dropdown([
44 | ['1024', '1024'],
45 | ['2048', '2048'],
46 | ['4096', '4096'],
47 | ], 'keylen', '2048', '', 'custom-select');
48 |
49 | $create = $this->modal([
50 | 'id' => 'createmodal',
51 | 'title' => 'Create DKIM Record',
52 | 'action' => 'create',
53 | 'footer' => 'Create',
54 | 'body' => '
55 |
56 | Domain
57 |
58 |
59 |
60 |
61 |
62 | Selector
63 |
64 |
65 |
66 |
67 |
68 | Key Length ' . $keybuf . '
69 |
70 |
71 |
',
72 | ]);
73 |
74 | return '
75 |
76 |
77 | DKIM
78 |
79 |
80 |
81 |
82 |
83 |
84 |
' . $in['buf'] . '
85 |
86 |
87 | ' . $create;
88 | }
89 | }
90 |
91 | ?>
92 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/home.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Home extends Themes_Bootstrap4_Theme
6 | {
7 | public function list(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | return '
12 |
13 |
14 | NetServa HCP
15 |
16 |
17 | This is an ultra simple web based Hosting Control Panel for a
18 | lightweight Mail, Web and DNS server based on Ubuntu Bionic (18.04). It
19 | uses PowerDNS for DNS, Postfix/Dovecot + Spamprobe for SMTP and spam
20 | filtered IMAP email hosting along with nginx + PHP7 FPM + LetsEncrypt SSL
21 | for efficient and secure websites. It can use either SQLite or MySQL as
22 | database backends and the SQLite version only requires 60Mb of ram
23 | on a fresh install so it is ideal for lightweight 256Mb ram LXD containers
24 | or KVM/Xen cloud provisioning.
25 |
26 |
27 | Some of the features are...
28 |
29 |
30 | NetServa HCP does not reqire Python or Ruby, just PHP and Bash
31 | Fully functional Mail server with personalised Spam filtering
32 | Secure SSL enabled nginx web server with PHP FPM 7+
33 | Always based and tested on the latest release of *buntu
34 | Optional DNS server for local LAN or real-world DNS provisioning
35 | Built from the ground up using Bootstrap 4 and DataTables
36 |
37 |
38 | You can change the content of this page by creating a file called
39 | lib/php/home.tpl
and add any Bootstrap 4 based layout and
40 | text you care to. For example...
41 |
42 |
43 | <div class="col-12">
44 | <h1>Your Page Title</h1>
45 | <p>Lorem ipsum...</p>
46 | </div>
47 |
48 |
49 | Modifying the navigation menus above can be done by creating
50 | a lib/.ht_conf.php
file and copying the
51 |
52 | $nav1 array from index.php
into that optional config override file.
53 | Comments and pull requests are most welcome via the Issue Tracker link below.
54 |
55 |
56 |
57 | Project Page
58 |
59 | Issue Tracker
60 |
61 |
';
62 | }
63 | }
64 |
65 | ?>
66 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/infomail.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_InfoMail extends Themes_Bootstrap4_Theme
6 | {
7 | public function list(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | extract($in);
12 |
13 | return '
14 |
15 |
MailServer Info
16 |
17 |
26 |
27 |
28 |
29 |
Mail Queue
30 |
' . $mailq . '
31 |
32 |
33 |
34 |
35 |
' . $pflogs . '
36 |
37 |
38 |
';
39 | }
40 | }
41 | //
42 |
43 | ?>
44 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/infosys.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_InfoSys extends Themes_Bootstrap4_Theme
6 | {
7 | public function list(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | elog(var_export($in,true));
12 |
13 | extract($in);
14 |
15 | return '
16 |
17 |
System Info
18 |
19 |
28 |
29 |
30 |
31 |
32 |
RAM ' . $mem_used . ' / ' . $mem_total . ', ' . $mem_free . ' free
33 |
34 |
' . $mem_text . '
36 |
37 |
38 |
39 |
Disk ' . $dsk_used . ' / ' . $dsk_total . ', ' . $dsk_free . ' free
40 |
41 |
' . $dsk_text . '
43 |
44 |
45 |
46 |
CPU ' .$cpu_all . '
47 |
48 |
' . $cpu_text . '
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Hostname
60 | ' .$hostname . '
61 |
62 |
63 | Host IP
64 | ' . $host_ip . '
65 |
66 |
67 | Distro
68 | ' . $os_name . '
69 |
70 |
71 | Uptime
72 | ' . $uptime . '
73 |
74 |
75 | CPU Load
76 | ' . $loadav . ' (' . $cpu_num . ' cpus)
77 |
78 |
79 | CPU Model
80 | ' . $cpu_name . '
81 |
82 |
83 | Kernel Version
84 | ' . $kernel . '
85 |
86 |
87 |
88 |
89 |
90 |
';
91 | }
92 | }
93 |
94 | ?>
95 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/processes.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Processes extends Themes_Bootstrap4_Theme
6 | {
7 | public function list(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | return '
12 |
13 |
Processes
14 |
15 |
24 |
25 |
26 |
27 |
Process List (' . (count(explode("\n", $in['procs'])) - 1) . ')
28 |
' . $in['procs'] . '
29 |
30 |
31 |
';
32 | }
33 | }
34 |
35 | ?>
36 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/records.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Records extends Themes_Bootstrap4_Theme
6 | {
7 | private $types = [
8 | ['A', 'A'],
9 | ['MX', 'MX'],
10 | ['NS', 'NS'],
11 | ['TXT', 'TXT'],
12 | ['AAAA', 'AAAA'],
13 | ['CAA', 'CAA'],
14 | ['AFSDB', 'AFSDB'],
15 | ['CERT', 'CERT'],
16 | ['CNAME', 'CNAME'],
17 | ['DHCID', 'DHCID'],
18 | ['DLV', 'DLV'],
19 | ['DNSKEY', 'DNSKEY'],
20 | ['DS', 'DS'],
21 | ['EUI48', 'EUI48'],
22 | ['EUI64', 'EUI64'],
23 | ['HINFO', 'HINFO'],
24 | ['IPSECKEY', 'IPSECKEY'],
25 | ['KEY', 'KEY'],
26 | ['KX', 'KX'],
27 | ['LOC', 'LOC'],
28 | ['MINFO', 'MINFO'],
29 | ['MR', 'MR'],
30 | ['NAPTR', 'NAPTR'],
31 | ['NSEC', 'NSEC'],
32 | ['NSEC3', 'NSEC3'],
33 | ['NSEC3PARAM', 'NSEC3PARAM'],
34 | ['OPT', 'OPT'],
35 | ['PTR', 'PTR'],
36 | ['RKEY', 'RKEY'],
37 | ['RP', 'RP'],
38 | ['RRSIG', 'RRSIG'],
39 | ['SPF', 'SPF'],
40 | ['SRV', 'SRV'],
41 | ['SSHFP', 'SSHFP'],
42 | ['TLSA', 'TLSA'],
43 | ['TSIG', 'TSIG'],
44 | ['WKS', 'WKS'],
45 | ];
46 |
47 | public function list(array $in) : string
48 | {
49 | elog(__METHOD__);
50 |
51 | elog('in='.var_export($in, true));
52 |
53 | return '
54 |
55 |
56 | ' . $in['domain'] . '
57 |
58 |
59 |
60 |
61 |
62 |
102 |
103 |
104 |
105 |
106 |
107 | Name
108 | Content
109 | Type
110 | Priority
111 | TTL
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 | ';
184 | }
185 | }
186 |
187 | ?>
188 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/theme.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Theme extends Theme
6 | {
7 | public function css() : string
8 | {
9 | elog(__METHOD__);
10 |
11 | return '
12 |
13 |
14 |
15 |
16 | ';
80 | }
81 |
82 | public function log() : string
83 | {
84 | elog(__METHOD__);
85 |
86 | $alts = '';
87 | foreach (util::log() as $lvl => $msg) {
88 | $alts .= $msg ? '
89 |
90 |
91 |
92 | ×
93 | ' . $msg . '
94 |
95 |
' : '';
96 | }
97 | return $alts;
98 | }
99 |
100 | public function head() : string
101 | {
102 | elog(__METHOD__);
103 |
104 | return '
105 |
106 |
120 | ';
121 | }
122 |
123 | public function nav1(array $a = []) : string
124 | {
125 | elog(__METHOD__);
126 |
127 | $a = isset($a[0]) ? $a : util::get_nav($this->g->nav1);
128 | $o = '?o=' . $this->g->in['o'];
129 | $t = '?t=' . util::ses('t');
130 | return join('', array_map(function ($n) use ($o, $t) {
131 | if (is_array($n[1])) return $this->nav_dropdown($n);
132 | $c = $o === $n[1] || $t === $n[1] ? ' active' : '';
133 | $i = isset($n[2]) ? '
' : '';
134 | return '
135 |
' . $i . $n[0] . ' ';
136 | }, $a));
137 | }
138 |
139 | public function nav2() : string
140 | {
141 | elog(__METHOD__);
142 |
143 | return $this->nav_dropdown(['Theme', $this->g->nav2, 'fa fa-th fa-fw']);
144 | }
145 |
146 | public function nav3() : string
147 | {
148 | elog(__METHOD__);
149 |
150 | if (util::is_usr()) {
151 | $usr[] = ['Change Profile', '?o=accounts&m=read&i=' . $_SESSION['usr']['id'], 'fas fa-user fa-fw'];
152 | $usr[] = ['Change Password', '?o=auth&m=update&i=' . $_SESSION['usr']['id'], 'fas fa-key fa-fw'];
153 | $usr[] = ['Sign out', '?o=auth&m=delete', 'fas fa-sign-out-alt fa-fw'];
154 |
155 | if (util::is_adm() && !util::is_acl(0)) $usr[] =
156 | ['Switch to sysadm', '?o=accounts&m=switch_user&i=' . $_SESSION['adm'], 'fas fa-user fa-fw'];
157 |
158 | return $this->nav_dropdown([$_SESSION['usr']['login'], $usr, 'fas fa-user fa-fw']);
159 | } else return '';
160 | }
161 |
162 | public function nav_dropdown(array $a = []) : string
163 | {
164 | elog(__METHOD__);
165 |
166 | $o = '?o=' . $this->g->in['o'];
167 | $i = isset($a[2]) ? '
' : '';
168 | return '
169 |
170 | ' . $i . $a[0] . '
171 |
178 | ';
179 | }
180 |
181 | public function main() : string
182 | {
183 | elog(__METHOD__);
184 |
185 | return '
186 |
187 | ' . $this->g->out['log'] . $this->g->out['main'] . '
188 |
189 | ';
190 | }
191 |
192 | public function js() : string
193 | {
194 | elog(__METHOD__);
195 |
196 | return '
197 |
198 |
199 |
200 |
201 |
202 |
203 | ';
204 | }
205 |
206 | protected function modal(array $ary) : string
207 | {
208 | elog(__METHOD__);
209 |
210 | extract($ary);
211 | $hidden = isset($hidden) && $hidden ? $hidden : '';
212 | $footer = $footer ? '
213 | ' : '';
217 |
218 | return '
219 |
';
239 | }
240 |
241 | }
242 |
243 | ?>
244 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/valias.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Valias extends Themes_Bootstrap4_Theme
6 | {
7 | public function create(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | return $this->editor($in);
12 | }
13 |
14 | public function update(array $in) : string
15 | {
16 | elog(__METHOD__);
17 |
18 | return $this->editor($in);
19 | }
20 |
21 | public function list(array $in) : string
22 | {
23 | elog(__METHOD__);
24 |
25 | return '
26 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | Alias
41 | Target Address
42 | Domain
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | ';
67 | }
68 |
69 | private function editor(array $in) : string
70 | {
71 | elog(__METHOD__);
72 |
73 | extract($in);
74 |
75 | $active = $active ? 1 : 0;
76 | $actbuf = $active ? ' checked' : '';
77 | $header = $this->g->in['m'] === 'create' ? 'Add new Alias' : 'Aliases
78 |
79 | ';
80 | $tolist = '
81 |
« Back ';
82 | $submit = $this->g->in['m'] === 'create' ? $tolist . '
83 |
Add this Alias ' : $tolist . '
84 |
Save ';
85 | $remove = $this->g->in['m'] === 'create' ? '' : $this->modal([
86 | 'id' => 'removemodal',
87 | 'title' => 'Remove Alias',
88 | 'action' => 'delete',
89 | 'footer' => 'Remove',
90 | 'hidden' => '
91 |
',
92 | 'body' => '
93 |
Are you sure you want to remove this alias?' . $source . '
',
94 | ]);
95 |
96 | return '
97 |
98 |
99 | ' . $header . '
100 |
101 |
102 |
103 |
104 |
105 |
Note: If your chosen destination address is an external mailbox, the receiving mailserver may reject your message due to an SPF failure.
106 |
137 |
' . $remove;
138 | }
139 | }
140 |
141 | ?>
142 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/vhosts.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Vhosts extends Themes_Bootstrap4_Theme
6 | {
7 | public function update(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | $remove = $this->modal([
12 | 'id' => 'removemodal',
13 | 'title' => 'Remove Vhost',
14 | 'action' => 'delete',
15 | 'footer' => 'Remove',
16 | // 'hidden' => '
17 | //
',
18 | 'body' => '
19 |
Are you sure you want to remove this Vhost?' . $in['domain'] . '
',
20 | ]);
21 |
22 | return '
23 |
24 |
25 | Vhosts
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
59 |
75 |
76 |
' . $remove;
77 | }
78 |
79 | public function list(array $in) : string
80 | {
81 | elog(__METHOD__);
82 |
83 | $create = $this->modal([
84 | 'id' => 'createmodal',
85 | 'title' => 'Create New Vhost',
86 | 'action' => 'create',
87 | 'footer' => 'Create',
88 | 'body' => '
89 |
90 | Vhost
91 |
92 |
93 |
111 |
112 |
113 |
114 | IP (optional)
115 |
116 |
117 |
118 |
119 |
120 | Custom User
121 |
122 |
123 |
124 |
',
125 | ]);
126 |
127 | return '
128 |
129 |
130 | Vhosts
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | Domain
142 | Alias
143 |
144 |
145 | Mbox
146 |
147 |
148 | Mail
149 |
150 |
151 | Disk
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
' . $create . '
161 | ';
190 | }
191 | }
192 |
193 | ?>
194 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap4/vmails.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
4 |
5 | class Themes_Bootstrap4_Vmails extends Themes_Bootstrap4_Theme
6 | {
7 | public function list(array $in) : string
8 | {
9 | elog(__METHOD__);
10 |
11 | $create = $this->modal([
12 | 'id' => 'createmodal',
13 | 'title' => 'Create New Mailbox',
14 | 'action' => 'create',
15 | 'footer' => 'Create',
16 | 'body' => '
17 |
18 | Email Address
19 |
20 |
',
21 | ]);
22 |
23 | $remove = $this->modal([
24 | 'id' => 'removemodal',
25 | 'title' => 'Remove Mailbox',
26 | 'action' => 'delete',
27 | 'footer' => 'Remove',
28 | 'body' => '
29 |
30 |
Are you sure you want to remove this mailbox?
',
31 | ]);
32 |
33 | $update = $this->modal([
34 | 'id' => 'updatemodal',
35 | 'title' => 'Change Password',
36 | 'action' => 'update',
37 | 'footer' => 'Update',
38 | 'body' => '
39 |
40 |
',
49 | ]);
50 |
51 | return '
52 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Email
67 | Usage
68 | Messages
69 |
70 |
71 |
72 |
73 |
74 |
75 |
' . $create . $remove . $update .'
76 | ';
125 | }
126 | }
127 |
128 | ?>
129 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/accounts.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_Accounts extends Themes_Bootstrap5_Theme
9 | {
10 | public function create(array $in): string
11 | {
12 | elog(__METHOD__);
13 |
14 | return $this->modal_content([
15 | 'title' => 'Create new user',
16 | 'action' => 'create',
17 | 'lhs_cmd' => '',
18 | 'rhs_cmd' => 'Create',
19 | 'body' => $this->modal_body($in)
20 | ]);
21 | }
22 |
23 | public function read(array $in): string
24 | {
25 | elog(__METHOD__);
26 |
27 | return $this->modal_content([
28 | 'title' => 'Update user',
29 | 'action' => 'update',
30 | 'lhs_cmd' => 'Delete',
31 | 'rhs_cmd' => 'Update',
32 | 'body' => $this->modal_body($in)
33 | ]);
34 | }
35 |
36 | public function delete(): ?string
37 | {
38 | elog(__METHOD__);
39 |
40 | $usr = db::read('login', 'id', $this->g->in['i'], '', 'one');
41 |
42 | return $this->modal_content([
43 | 'title' => 'Remove User',
44 | 'action' => 'delete',
45 | 'lhs_cmd' => '',
46 | 'rhs_cmd' => 'Remove',
47 | 'hidden' => sprintf('
', $this->g->in['i']),
48 | 'body' => sprintf('
Are you sure you want to remove this user?%s
', $usr['login']),
49 | ]);
50 | }
51 |
52 | public function list(array $in): string
53 | {
54 | elog(__METHOD__);
55 |
56 | return <<
58 |
59 | Accounts
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | User ID
70 | First Name
71 | Last Name
72 | Alt Email
73 | ACL
74 | Grp
75 |
76 |
77 |
78 |
79 |
80 |
81 |
85 |
89 |
93 |
116 | HTML;
117 | }
118 |
119 | private function modal_body(array $in): string
120 | {
121 | elog(__METHOD__);
122 |
123 | $acl = $_SESSION['usr']['acl'];
124 | $grp = $_SESSION['usr']['grp'];
125 |
126 | $acl_ary = array_map(fn($k, $v) => [$v, $k], array_keys($this->g->acl), $this->g->acl);
127 | $acl_buf = $this->dropdown($acl_ary, 'acl', "{$acl}", '', 'form-select');
128 |
129 | $res = db::qry('SELECT login, id FROM `accounts` WHERE acl IN (0, 1)');
130 | $grp_ary = array_map(fn($row) => [$row['login'], $row['id']], $res);
131 | $grp_buf = $this->dropdown($grp_ary, 'grp', "{$grp}", '', 'form-select');
132 |
133 | $aclgrp_buf = <<
135 |
136 | ACL $acl_buf
137 |
138 |
139 | Group $grp_buf
140 |
141 |
142 | HTML;
143 |
144 | return <<
146 |
147 | Email ID
148 |
149 |
150 |
151 | Alt Email
152 |
153 |
154 |
155 |
165 | $aclgrp_buf
166 | HTML;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/auth.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_Auth extends Themes_Bootstrap5_Theme
9 | {
10 | public function create(array $in): string
11 | {
12 | elog(__METHOD__);
13 |
14 | $login = $in['login'] ?? '';
15 | return <<
17 | Forgot password
18 |
19 |
20 |
21 |
27 |
28 | You will receive an email with further instructions and please note that this only resets the password for this website interface.
29 |
30 |
36 |
37 |
38 | HTML;
39 | }
40 |
41 | public function list(array $in): string
42 | {
43 | elog(__METHOD__);
44 |
45 | $login = $in['login'] ?? '';
46 | return <<
48 | Sign in
49 |
50 |
51 |
52 |
53 |
Username
54 |
55 |
56 |
57 |
58 |
59 |
60 |
Password
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Remember me on this computer
70 |
71 |
72 |
78 |
79 |
80 | HTML;
81 | }
82 |
83 | public function update(array $in): string
84 | {
85 | elog(__METHOD__);
86 |
87 | $id = $in['id'] ?? '';
88 | $login = $in['login'] ?? '';
89 | return <<
91 | Update Password
92 |
93 |
94 |
95 |
96 |
97 | For {$login}
98 | New Password
99 |
105 | Confirm Password
106 |
112 |
113 |
114 | Update my password
115 |
116 |
117 |
118 |
119 | HTML;
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/dkim.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_Dkim extends Themes_Bootstrap5_Theme
9 | {
10 | public function create(): string
11 | {
12 | elog(__METHOD__);
13 |
14 | $keybuf = $this->dropdown([
15 | ['1024', '1024'],
16 | ['2048', '2048'],
17 | ['4096', '4096'],
18 | ], 'keylen', '2048', '', 'form-select');
19 |
20 | return $this->modal([
21 | 'id' => 'createmodal',
22 | 'title' => 'Create SSH Host',
23 | 'action' => 'create',
24 | 'lhs_cmd' => '',
25 | 'rhs_cmd' => 'Create',
26 | 'body' => <<
28 | Domain
29 |
30 |
31 |
32 |
33 | Selector
34 |
35 |
36 |
37 | Key Length
38 | $keybuf
39 |
40 |
41 | HTML,
42 | ]);
43 | }
44 |
45 | public function read(array $in): string
46 | {
47 | elog(__METHOD__);
48 |
49 | return <<
51 |
52 | DKIM
53 |
54 |
55 |
56 |
57 |
58 | {$in['buf']}
59 | {$this->delete($in)}
60 | HTML;
61 | }
62 |
63 | public function delete(array $in): string
64 | {
65 | elog(__METHOD__);
66 |
67 | return $this->modal([
68 | 'id' => 'removemodal',
69 | 'title' => 'Remove DKIM Record',
70 | 'action' => 'delete',
71 | 'lhs_cmd' => '',
72 | 'rhs_cmd' => 'Remove',
73 | 'hidden' => sprintf(' ', $in['domain']),
74 | 'body' => sprintf('Are you sure you want to remove DKIM record for%s
', $in['domain']),
75 | ]);
76 | }
77 |
78 | public function list(array $in): string
79 | {
80 | elog(__METHOD__);
81 |
82 | return <<
84 |
85 | DKIM
86 |
87 |
88 |
89 |
90 |
91 | {$in['buf']}
92 | {$this->create()}
93 | HTML;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/home.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_Home extends Themes_Bootstrap5_Theme
9 | {
10 | public function list(array $in): string
11 | {
12 | elog(__METHOD__);
13 |
14 | return <<
16 | NetServa HCP
17 |
18 | This is a lightweight Web, Mail and DNS server with a PHP based
19 | Hosting Control Panel for servicing multiple virtually
20 | hosted domains. The operating system is based on the latest
21 | Debian or Ubuntu packages and can use either SQLite or MySQL as
22 | a backend database. The entire server can run in as little as 256
23 | MB of ram when paired with SQLite and still serve a dozen lightly
24 | loaded hosts so it is ideal for LXD and Proxmox virtual machines
25 | and containers.
26 |
27 |
28 | Project Page
29 |
30 |
31 | Issue Tracker
32 |
33 |
34 |
35 |
36 |
37 |
Features
38 |
39 | NetServa HCP does not require Python or Ruby, just PHP and Bash
40 | Fully functional Mail server with personalised Spam filtering
41 | Secure SSL enabled nginx web server with PHP FPM 8+
42 | Always based and tested on the latest release on Ubuntu and Debian
43 | Optional DNS server for local LAN or real-world DNS provisioning
44 | Built from the ground up using Bootstrap 5 and DataTables
45 |
46 |
Software
47 |
48 | nginx and PHP8+ FPM for web services
49 | Postfix for SMTP email delivery
50 | Dovecot and Spamprobe spam filtered IMAP email
51 | Acme.sh and LetsEncrypt SSL for SSL certificates
52 | PowerDNS for DNS
53 | WordPress when paired with Mysql/Mariadb
54 |
55 |
56 |
57 |
58 |
59 |
Notes
60 |
61 | You can change the content of this page by creating a file
62 | called lib/php/home.tpl
and add any Bootstrap 5
63 | based layout and text you care to. Modifying the navigation
64 | menus above can be done by creating a lib/.ht_conf.php
65 | file and copying the
66 | \$nav1 array
67 | from index.php
into that optional config override file.
68 |
69 |
70 |
71 |
72 | HTML;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/infomail.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_InfoMail extends Themes_Bootstrap5_Theme
9 | {
10 | public function list(array $in): string
11 | {
12 | elog(__METHOD__);
13 |
14 | $csrfToken = $_SESSION['c'] ?? '';
15 | $mailq = htmlspecialchars($in['mailq'] ?? '');
16 | $pflogs = htmlspecialchars($in['pflogs'] ?? '');
17 |
18 | return <<
20 | Mailserver Info
21 |
22 |
23 |
24 |
25 | Refresh
26 |
27 |
28 |
29 |
30 |
31 |
Mail Queue
32 |
{$mailq}
33 |
34 |
35 |
40 | HTML;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/infosys.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_InfoSys extends Themes_Bootstrap5_Theme
9 | {
10 | public function list(array $in): string
11 | {
12 | elog(__METHOD__);
13 |
14 | $data = $this->prepareData($in);
15 | return $this->generateInfoSysContent($data);
16 | }
17 |
18 | private function prepareData(array $in): array
19 | {
20 | elog(__METHOD__);
21 |
22 | return [
23 | 'csrfToken' => $_SESSION['c'] ?? '',
24 | 'hostname' => $in['hostname'] ?? '',
25 | 'host_ip' => $in['host_ip'] ?? '',
26 | 'os_name' => $in['os_name'] ?? '',
27 | 'uptime' => $in['uptime'] ?? '',
28 | 'loadav' => $in['loadav'] ?? '',
29 | 'cpu_num' => $in['cpu_num'] ?? '',
30 | 'cpu_name' => $in['cpu_name'] ?? '',
31 | 'kernel' => $in['kernel'] ?? '',
32 | 'mem_used' => $in['mem_used'] ?? '',
33 | 'mem_total' => $in['mem_total'] ?? '',
34 | 'mem_free' => $in['mem_free'] ?? '',
35 | 'mem_pcnt' => $in['mem_pcnt'] ?? '',
36 | 'mem_color' => $in['mem_color'] ?? '',
37 | 'mem_text' => $in['mem_text'] ?? '',
38 | 'dsk_used' => $in['dsk_used'] ?? '',
39 | 'dsk_total' => $in['dsk_total'] ?? '',
40 | 'dsk_free' => $in['dsk_free'] ?? '',
41 | 'dsk_pcnt' => $in['dsk_pcnt'] ?? '',
42 | 'dsk_color' => $in['dsk_color'] ?? '',
43 | 'dsk_text' => $in['dsk_text'] ?? '',
44 | 'cpu_all' => $in['cpu_all'] ?? '',
45 | 'cpu_pcnt' => $in['cpu_pcnt'] ?? '',
46 | 'cpu_color' => $in['cpu_color'] ?? '',
47 | 'cpu_text' => $in['cpu_text'] ?? '',
48 | ];
49 | }
50 |
51 | private function generateInfoSysContent(array $data): string
52 | {
53 | elog(__METHOD__);
54 |
55 | $progressBar = fn($label, $used, $total, $free, $pcnt, $color, $text) => <<{$label} Used: {$used} - Total: {$total} - Free: {$free}
57 |
62 | HTML;
63 | $progressBar2 = fn($label, $used, $total, $free, $pcnt, $color, $text) => <<{$label} {$used}
65 |
70 | HTML;
71 |
72 | return <<
74 | System Info
75 |
76 |
77 |
78 |
79 | Refresh
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Hostname {$data['hostname']}
89 | Host IP {$data['host_ip']}
90 | Distro {$data['os_name']}
91 | Uptime {$data['uptime']}
92 | CPU Load {$data['loadav']} - {$data['cpu_num']} cpus
93 | CPU Model {$data['cpu_name']}
94 | Kernel Version {$data['kernel']}
95 |
96 |
97 |
98 |
99 |
100 |
101 | {$progressBar('RAM', $data['mem_used'], $data['mem_total'], $data['mem_free'], $data['mem_pcnt'], $data['mem_color'], $data['mem_text'])}
102 | {$progressBar('DISK', $data['dsk_used'], $data['dsk_total'], $data['dsk_free'], $data['dsk_pcnt'], $data['dsk_color'], $data['dsk_text'])}
103 | {$progressBar2('CPU', $data['cpu_all'], '', '', $data['cpu_pcnt'], $data['cpu_color'], $data['cpu_text'])}
104 |
105 |
106 |
107 | HTML;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/processes.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_Processes extends Themes_Bootstrap5_Theme
9 | {
10 | public function list(array $in): string
11 | {
12 | elog(__METHOD__);
13 |
14 | $csrfToken = $_SESSION['c'] ?? '';
15 | $procs = htmlspecialchars($in['procs'] ?? '');
16 | $processCount = count(explode("\n", $in['procs'] ?? '')) - 1;
17 |
18 | return <<
20 |
21 | Processes ({$processCount})
22 |
23 |
24 |
25 |
26 |
27 |
28 | Refresh
29 |
30 |
31 |
32 |
33 |
38 | HTML;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/records.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_Records extends Themes_Bootstrap5_Theme
9 | {
10 | private const TYPES = [
11 | 'A', 'MX', 'NS', 'TXT', 'AAAA', 'CAA', 'AFSDB', 'CERT', 'CNAME', 'DHCID',
12 | 'DLV', 'DNSKEY', 'DS', 'EUI48', 'EUI64', 'HINFO', 'IPSECKEY', 'KEY', 'KX',
13 | 'LOC', 'MINFO', 'MR', 'NAPTR', 'NSEC', 'NSEC3', 'NSEC3PARAM', 'OPT', 'PTR',
14 | 'RKEY', 'RP', 'RRSIG', 'SPF', 'SRV', 'SSHFP', 'TLSA', 'TSIG', 'WKS'
15 | ];
16 |
17 | public function list(array $in): string
18 | {
19 | elog(__METHOD__);
20 |
21 | return $this->generateHtml($in) . $this->generateJavaScript($in);
22 | }
23 |
24 | private function generateHtml(array $in): string
25 | {
26 | elog(__METHOD__);
27 |
28 | $csrfToken = $_SESSION['c'] ?? '';
29 | $currentObject = $this->g->in['o'] ?? '';
30 | $domainId = $in['did'] ?? '';
31 | $domain = htmlspecialchars($in['domain'] ?? '');
32 | $typeDropdown = $this->dropdown(array_map(fn($type) => [$type, $type], self::TYPES), 'type', 'A', '', 'form-select');
33 |
34 | return <<
36 |
37 | {$domain}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
57 |
62 |
63 |
{$typeDropdown}
64 |
65 |
70 |
75 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | Name
89 | Content
90 | Type
91 | Priority
92 | TTL
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | HTML;
101 | }
102 |
103 | private function generateJavaScript(array $in): string
104 | {
105 | elog(__METHOD__);
106 |
107 | $domainId = $in['did'] ?? '';
108 | return <<
110 | $(document).ready(function() {
111 | $("#records").DataTable({
112 | "processing": true,
113 | "serverSide": true,
114 | "ajax": "?x=json&o=records&m=list&did={$domainId}",
115 | "order": [[ 9, "desc" ]],
116 | "scrollX": true,
117 | "columnDefs": [
118 | {"targets":0, "width":"30%"},
119 | {"targets":1, "width":"40%",
120 | render: function ( data, type, row ) {
121 | return type === "display" && data.length > 40 ? data.substr( 0, 40 ) + "…" : data; }},
122 | {"targets":2, "width":"3rem"},
123 | {"targets":3, "width":"3rem"},
124 | {"targets":4, "width":"3rem"},
125 | {"targets":5, "width":"2rem", "className":"text-end", "sortable": false},
126 | {"targets":6, "visible":false},
127 | {"targets":7, "visible":false},
128 | {"targets":8, "visible":false},
129 | {"targets":9, "visible":false},
130 | ],
131 | });
132 |
133 | $(document).on("click", ".create", function(e) {
134 | e.preventDefault();
135 | $("#m").val("Create").attr("class", "btn btn-success").removeAttr("disabled");
136 | $("#name, #content").val("");
137 | });
138 |
139 | $(document).on("click", ".delete", function(e) {
140 | e.preventDefault();
141 | $("#m").val("Delete").attr("class", "btn btn-danger").removeAttr("disabled");
142 | });
143 |
144 | $(document).on("click", ".update", function(e) {
145 | e.preventDefault();
146 | $("#m").val("Update").attr("class", "btn btn-primary").removeAttr("disabled");
147 | });
148 |
149 | $(document).on("click", ".update,.delete", function(e) {
150 | e.preventDefault();
151 | var row = $(this).closest("tr");
152 | $("#i").val($(this).attr("data-rowid"));
153 | $("#name").val(row.find("td:eq(0)").text());
154 | $("#content").val(row.find("td:eq(1)").text());
155 | $("#type").val(row.find("td:eq(2)").text());
156 | $("#prio").val(row.find("td:eq(3)").text());
157 | $("#ttl").val(row.find("td:eq(4)").text());
158 | });
159 | });
160 |
161 | JavaScript;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/lib/php/themes/bootstrap5/valias.php:
--------------------------------------------------------------------------------
1 | (AGPL-3.0)
7 |
8 | class Themes_Bootstrap5_Valias extends Themes_Bootstrap5_Theme
9 | {
10 | public function create(array $in): string
11 | {
12 | elog(__METHOD__);
13 |
14 | return $this->modalContent(
15 | 'Create New Alias',
16 | 'create',
17 | '',
18 | 'Create',
19 | $this->modalBody($in)
20 | );
21 | }
22 |
23 | public function update(array $in): string
24 | {
25 | elog(__METHOD__);
26 |
27 | return $this->modalContent(
28 | 'Update Alias',
29 | 'update',
30 | 'Delete',
31 | 'Update',
32 | $this->modalBody($in)
33 | );
34 | }
35 |
36 | public function delete(): string
37 | {
38 | elog(__METHOD__);
39 |
40 | $source = db::read('source', 'id', $this->g->in['i'], '', 'one');
41 |
42 | return $this->modalContent(
43 | 'Remove Alias',
44 | 'delete',
45 | '',
46 | 'Remove',
47 | $this->deleteModalBody($source['source'] ?? '')
48 | );
49 | }
50 |
51 | public function list(array $in): string
52 | {
53 | elog(__METHOD__);
54 |
55 | return $this->generateListHTML();
56 | }
57 |
58 | private function modalContent(string $title, string $action, string $lhsCmd, string $rhsCmd, string $body): string
59 | {
60 | elog(__METHOD__);
61 |
62 | return <<
64 |
68 |
69 |
70 | $body
71 |
72 |
75 |
76 |
77 | HTML;
78 | }
79 |
80 | private function modalBody(array $in): string
81 | {
82 | elog(__METHOD__);
83 |
84 | $activeChecked = ($in['active'] ?? false) ? ' checked' : '';
85 | return <<
87 | Alias Address(es)
88 | {$in['source']}
89 | Full email address/es or @example.com, to catch all messages for a domain (comma-separated). Locally hosted domains only .
90 |
91 |
92 |
Target Address(es)
93 |
{$in['target']}
94 |
Full email address/es (comma-separated).
95 |
96 |
97 |
98 | Active
99 |
100 | HTML;
101 | }
102 |
103 | private function deleteModalBody(string $source): string
104 | {
105 | elog(__METHOD__);
106 |
107 | $id = htmlspecialchars($this->g->in['i'], ENT_QUOTES, 'UTF-8');
108 | return <<Are you sure you want to remove this alias?$source
110 |
111 | HTML;
112 | }
113 |
114 | private function modalFooter(string $lhsCmd, string $rhsCmd): string
115 | {
116 | elog(__METHOD__);
117 |
118 | $lhsButton = $lhsCmd ? "$lhsCmd " : '';
119 | return <<Cancel
122 | $rhsCmd
123 | HTML;
124 | }
125 |
126 | private function generateListHTML(): string
127 | {
128 | elog(__METHOD__);
129 |
130 | return <<
132 |
133 | Aliases
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | Alias
144 | Target Address
145 | Domain
146 |
147 |
148 |
149 |
150 |
151 |
154 |
157 |
160 |
192 | HTML;
193 | }
194 | }
195 |
--------------------------------------------------------------------------------