Compare commits
13 Commits
1026ef34e1
...
b2ff261e74
| Author | SHA1 | Date | |
|---|---|---|---|
| b2ff261e74 | |||
| 80d0174d96 | |||
| 0de56b2a4c | |||
| 42fbb89048 | |||
| da5c6e5156 | |||
| 37026c4a8a | |||
| d3f31d0dc5 | |||
| e4bed85f7d | |||
| 7960cb6cdb | |||
| df85246db0 | |||
| ef77013ac5 | |||
| 368cb5fff3 | |||
| e8b13ad9f5 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
/lib/
|
||||
/downloads/
|
||||
/downloads/
|
||||
/app/.env.cfg
|
||||
/public/tmp/
|
||||
@ -7,4 +7,9 @@ Used to keep track of ongoing projects/tasks, allow to view and search historic
|
||||
## Plesk - quest notes
|
||||
|
||||
- plesk ext composer --application -register -domain desk.tinylink.uk -path desk.tinylink.uk/tp_servicedesk
|
||||
~ https://www.plesk.com/kb/support/how-to-change-in-the-php-composer-extension-the-path-of-the-composer-json-file/
|
||||
~ https://www.plesk.com/kb/support/how-to-change-in-the-php-composer-extension-the-path-of-the-composer-json-file/
|
||||
|
||||
## Milestones
|
||||
- Database created locally
|
||||
- .gitignore added
|
||||
- added AuthController - login and logout process
|
||||
54
app/controllers/AuthController.php
Normal file
54
app/controllers/AuthController.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class AuthController {
|
||||
|
||||
|
||||
public function showLoginForm($f3){
|
||||
// store session errors or messages, then clear
|
||||
$f3->set('error', $f3->get('SESSION.login_error'));
|
||||
$f3->clear('SESSION.login_error');
|
||||
|
||||
// this can be in our controller base
|
||||
$f3->set('content', '../ui/views/login.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
$f3->clear('error');
|
||||
}
|
||||
|
||||
public function login($f3){
|
||||
$username = $f3->get('POST.username');
|
||||
$password = $f3->get('POST.password');
|
||||
|
||||
$db = $f3->get('DB');
|
||||
// query for user
|
||||
$result = $db->exec(
|
||||
'SELECT id, username, password, role FROM users WHERE username =? LIMIT 1', $username
|
||||
);
|
||||
|
||||
// verifiy password
|
||||
if($result){
|
||||
$user = $result[0]; // first row
|
||||
if(password_verify($password, $user['password'])){
|
||||
// valid
|
||||
$f3->set('SESSION.user', [
|
||||
'id'=> $user['id'],
|
||||
'username' => $user['username']
|
||||
]);
|
||||
|
||||
$f3->reroute('/dashboard');
|
||||
} else {
|
||||
$f3->set('SESSION.login_error', 'Invalid password');
|
||||
}
|
||||
} else {
|
||||
// if here, login failed.
|
||||
$f3->set('SESSION.login_error', 'Invalid username');
|
||||
}
|
||||
$f3->reroute('/login');
|
||||
|
||||
}
|
||||
|
||||
public function logout($f3){
|
||||
$f3->clear('SESSION');
|
||||
$f3->reroute('/');
|
||||
}
|
||||
|
||||
}
|
||||
9
app/controllers/DashboardController.php
Normal file
9
app/controllers/DashboardController.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class DashboardController {
|
||||
|
||||
function index($f3){
|
||||
$f3->set('content', '../ui/views/dashboard.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
}
|
||||
}
|
||||
15
app/controllers/HomeController.php
Normal file
15
app/controllers/HomeController.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
class HomeController {
|
||||
|
||||
public function display($f3){
|
||||
// $db = $f3->get('DB');
|
||||
// echo \Template::instance()->render('../ui/views/home.html');
|
||||
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
|
||||
// Query
|
||||
// View
|
||||
}
|
||||
// ...
|
||||
}
|
||||
131
app/controllers/KBController.php
Normal file
131
app/controllers/KBController.php
Normal file
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
class KBController {
|
||||
|
||||
protected function check_access($f3){
|
||||
if(!$f3->exists('SESSION.user')){
|
||||
// $f3->set('SESSION.error', 'You don\'t have permission for this ticket.');
|
||||
$f3->reroute('/login');
|
||||
}
|
||||
}
|
||||
|
||||
public function index($f3){
|
||||
|
||||
$this->check_access($f3);
|
||||
|
||||
$db = $f3->get('DB');
|
||||
$search_term = $f3->get('GET.search');
|
||||
$tag_param = $f3->get('GET.tag');
|
||||
|
||||
// base query
|
||||
$sql = 'SELECT a.* FROM kb a';
|
||||
$args = [];
|
||||
|
||||
if($tag_param){
|
||||
$sql .= '
|
||||
JOIN kb_tags AS at ON a.id = at.article_id
|
||||
JOIN tags t ON at.tag_id = t.id
|
||||
WHERE t.name = ?
|
||||
';
|
||||
$args[] = $tag_param;
|
||||
|
||||
if($search_term){
|
||||
$sql .= ' AND a.title LIKE ?';
|
||||
$args[] = '%' . $search_term . '%';
|
||||
}
|
||||
} else if ($search_term){
|
||||
$sql .= ' WHERE a.title LIKE ?';
|
||||
$args[] = '%' . $search_term . '%';
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY a.created_at DESC';
|
||||
|
||||
$articles = $db->exec($sql, $args);
|
||||
|
||||
// render
|
||||
$f3->set('articles', $articles);
|
||||
$f3->set('content', '../ui/views/kb/index.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
$f3->clear('SESSION.error');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to create new article
|
||||
*/
|
||||
public function createForm($f3){
|
||||
$this->check_access($f3);
|
||||
|
||||
$db = $f3->get('DB');
|
||||
$all_tags = $db->exec('SELECT * FROM tags ORDER BY name ASC');
|
||||
$f3->set('all_tags', $all_tags);
|
||||
|
||||
// render
|
||||
$f3->set('content', '../ui/views/kb/create.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
$f3->clear('SESSION.error');
|
||||
|
||||
}
|
||||
|
||||
// handle POST
|
||||
public function create($f3){
|
||||
$this->check_access($f3);
|
||||
|
||||
$title = $f3->get('POST.title');
|
||||
$content = $f3->get('POST.content');
|
||||
$created_by = $f3->get('SESSION.user.id');
|
||||
|
||||
$db = $f3->get('DB');
|
||||
|
||||
// insert
|
||||
|
||||
$db->exec(
|
||||
'INSERT INTO kb (title, content, created_by, updated_by, created_at, updated_at)
|
||||
VALUES (?,?,?,?, NOW(), NOW())',
|
||||
[$title, $content, $created_by]
|
||||
);
|
||||
|
||||
$article_id = $db->lastInsertId();
|
||||
|
||||
// TODO: tags
|
||||
|
||||
$f3->reroute('/kb');
|
||||
}
|
||||
|
||||
// view a single
|
||||
public function view($f3){
|
||||
$this->check_access($f3);
|
||||
$article_id = $f3->get('PARAMS.id');
|
||||
$db = $f3->get('DB');
|
||||
|
||||
$articles = $db->exec(
|
||||
'SELECT a.*, u.username AS created_by_name
|
||||
FROM kb AS a
|
||||
LEFT JOIN users AS u ON a.created_by = u.id
|
||||
WHERE a.id = ? LIMIT 1',
|
||||
[$article_id]
|
||||
);
|
||||
|
||||
if(!$articles){
|
||||
$f3->set('SESSION.error', 'Article not found');
|
||||
$f3->reroute('/kb');
|
||||
}
|
||||
|
||||
$article = $articles[0];
|
||||
$f3->set('article', $article);
|
||||
|
||||
// TODO: tags
|
||||
$tags = $db->exec(
|
||||
'SELECT t.* FROM tags AS t
|
||||
JOIN kb_tags AS at ON t.id = at.tag_id
|
||||
WHERE at.article_id = ?',
|
||||
[$article_id]
|
||||
);
|
||||
|
||||
// render
|
||||
$f3->set('content', '../ui/views/kb/view.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
$f3->clear('SESSION.error');
|
||||
}
|
||||
|
||||
}
|
||||
161
app/controllers/TicketController.php
Normal file
161
app/controllers/TicketController.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
class TicketController {
|
||||
|
||||
protected function check_access($f3){
|
||||
if(!$f3->exists('SESSION.user')){
|
||||
// $f3->set('SESSION.error', 'You don\'t have permission for this ticket.');
|
||||
$f3->reroute('/login');
|
||||
}
|
||||
}
|
||||
|
||||
// list all tickts
|
||||
public function index($f3){
|
||||
$this->check_access($f3);
|
||||
|
||||
$db = $f3->get('DB');
|
||||
|
||||
// retrieve tickets
|
||||
$tickets = $db->exec('SELECT * FROM tickets ORDER BY created_at DESC');
|
||||
|
||||
// pass data to template
|
||||
$f3->set('tickets', $tickets);
|
||||
|
||||
// render
|
||||
$f3->set('content', '../ui/views/ticket/index.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
|
||||
$f3->clear('SESSION.error');
|
||||
}
|
||||
|
||||
// view a single ticket
|
||||
public function view($f3){
|
||||
$this->check_access($f3);
|
||||
|
||||
$ticket_id = $f3->get('PARAMS.id');
|
||||
$db = $f3->get('DB');
|
||||
|
||||
$result = $db->exec(
|
||||
'SELECT t.*, u.username as created_by_name
|
||||
FROM tickets t
|
||||
LEFT JOIN users u ON t.created_by = u.id
|
||||
WHERE t.id =? LIMIT 1',
|
||||
[$ticket_id]
|
||||
);
|
||||
|
||||
if(!$result){
|
||||
// no record
|
||||
$f3->set('SESSION.error', 'Ticket not found.');
|
||||
$f3->reroute('/tickets');
|
||||
}
|
||||
|
||||
$ticket = $result[0];
|
||||
$f3->set('ticket', $ticket);
|
||||
|
||||
// render
|
||||
$f3->set('content', '../ui/views/ticket/view.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
|
||||
}
|
||||
|
||||
// show create form
|
||||
public function createForm($f3){
|
||||
$this->check_access($f3);
|
||||
$f3->set('content', '../ui/views/ticket/create.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
}
|
||||
|
||||
// handle POST
|
||||
public function create($f3){
|
||||
$this->check_access($f3);
|
||||
|
||||
$title = $f3->get('POST.title');
|
||||
$description = $f3->get('POST.description');
|
||||
$priority = $f3->get('POST.priority'); // eg - low, medium, high
|
||||
$status = $f3->get('POST.status'); // eg - new, in_progress
|
||||
$created_by = $f3->get('SESSION.user.id'); // current logged in user
|
||||
|
||||
$db = $f3->get('DB');
|
||||
|
||||
$db->exec(
|
||||
'INSERT
|
||||
INTO tickets (title, description, priority, status, created_by, created_at, updated_at)
|
||||
VALUES (?,?,?,?,?,NOW(), NOW())',
|
||||
[$title, $description, $priority, $status, $created_by]
|
||||
);
|
||||
|
||||
$f3->reroute('/tickets');
|
||||
}
|
||||
|
||||
protected function get_ticket_check_edit_permission($f3){
|
||||
|
||||
$db = $f3->get('DB');
|
||||
|
||||
$ticket_id = $f3->get('PARAMS.id');
|
||||
$result = $db->exec('SELECT * FROM tickets WHERE id = ? LIMIT 1', [$ticket_id]);
|
||||
|
||||
if(!$result){
|
||||
$f3->set('SESSION.error', 'Ticket not found.');
|
||||
$f3->reroute('/tickets');
|
||||
}
|
||||
|
||||
$ticket = $result[0];
|
||||
|
||||
// TODO: refine
|
||||
$current_user = $f3->get('SESSION.user');
|
||||
$is_admin = (isset($current_user['role']) && $current_user['role'] == 'admin');
|
||||
$is_assigned = ($ticket['assigned_to'] == $current_user['id']);
|
||||
|
||||
if(!$is_admin && !$is_assigned){ // should this be ||
|
||||
// if not assigned and not admin, disallow edit
|
||||
$f3->set('SESSION.error', 'You do not have permission to edit this ticket.');
|
||||
$f3->reroute('/tickets');
|
||||
}
|
||||
|
||||
return $ticket;
|
||||
|
||||
}
|
||||
|
||||
// show edit form
|
||||
public function editForm($f3){
|
||||
$this->check_access($f3);
|
||||
|
||||
$ticket_id = $f3->get('PARAMS.id');
|
||||
$db = $f3->get('DB');
|
||||
|
||||
|
||||
$ticket = $this->get_ticket_check_edit_permission($f3);
|
||||
$f3->set('ticket', $ticket);
|
||||
|
||||
$f3->set('ticket', $ticket);
|
||||
$f3->set('content', '../ui/views/ticket/edit.html');
|
||||
echo \Template::instance()->render('../ui/templates/layout.html');
|
||||
}
|
||||
|
||||
// process edit POST TODO: if assigned or admin
|
||||
public function update($f3){
|
||||
$this->check_access($f3);
|
||||
$ticket = $this->get_ticket_check_edit_permission($f3);
|
||||
$ticket_id = $ticket['id'];
|
||||
$db = $f3->get('DB');
|
||||
|
||||
// get updated fields from post
|
||||
$title = $f3->get('POST.title');
|
||||
$description = $f3->get('POST.description');
|
||||
$priority = $f3->get('POST.priority'); // eg - low, medium, high
|
||||
$status = $f3->get('POST.status'); // eg - new, in_progress
|
||||
$updated_by = $f3->get('SESSION.user.id'); // current logged in user
|
||||
|
||||
// TODO: if you want to update assignment, should be added here.
|
||||
|
||||
$db->exec(
|
||||
'UPDATE tickets
|
||||
SET title=?, description=?, priority=?, status=?, updated_by=?, updated_at=?
|
||||
WHERE id=?',
|
||||
[$title, $description, $priority, $status, $updated_by, 'NOW()', $ticket_id]
|
||||
);
|
||||
|
||||
$f3->reroute('/ticket/' . $ticket_id);
|
||||
}
|
||||
|
||||
}
|
||||
83
app/model/BulmaForm.php
Normal file
83
app/model/BulmaForm.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
// this isn't the way to do it, but nevermind!
|
||||
|
||||
class BulmaForm {
|
||||
|
||||
public static function horizontal_field_input($label = "%label%", $name = "%name%", $value=""){
|
||||
$string = '
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label>%label%</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<input class="input" type="text" name="%name%" value="%value%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$string = str_replace('%label%', $label, $string);
|
||||
$string = str_replace('%name%', $name, $string);
|
||||
$string = str_replace('%value%', $value, $string);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function horizontal_field_textarea($label = "%label%", $name = "%name%", $value=""){
|
||||
$string = '
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label>%label%</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<textarea class="textarea" type="text" name="%name%">%value%</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$string = str_replace('%label%', $label, $string);
|
||||
$string = str_replace('%name%', $name, $string);
|
||||
$string = str_replace('%value%', $value, $string);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public static function horizontal_field_select($label="%label%", $name="%name%", $options=[], $selected=0){
|
||||
$string = '
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label>%label%</label>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
<div class="select">
|
||||
<select name="%name%">
|
||||
%options%
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$string = str_replace('%label%', $label, $string);
|
||||
$string = str_replace('%name%', $name, $string);
|
||||
$opts_str = ''; $i=0;
|
||||
foreach($options as $v){
|
||||
$opts_str .= '<option'.($i==$selected ? ' selected="selected" ' : '').'>'.$v.'</option>';
|
||||
$i++;
|
||||
}
|
||||
$string = str_replace('%options%', $opts_str, $string);
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
"vendor-dir": "lib"
|
||||
},
|
||||
"require": {
|
||||
"bcosca/fatfree-core": "^3.9"
|
||||
"bcosca/fatfree-core": "^3.9",
|
||||
"erusev/parsedown": "^1.7"
|
||||
}
|
||||
}
|
||||
9
public/.htaccess
Normal file
9
public/.htaccess
Normal file
@ -0,0 +1,9 @@
|
||||
RewriteEngine On
|
||||
|
||||
RewriteRule ^(app|dict|ns|tmp)\/|\.ini$ - [R=404]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-l
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule .* index.php [L,QSA]
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]
|
||||
62
public/index.php
Normal file
62
public/index.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
require '../lib/vendor/autoload.php';
|
||||
|
||||
$f3 = \Base::instance();
|
||||
$f3->set('DEBUG', 3); // development debug
|
||||
$f3->config('../app/.env.cfg');
|
||||
|
||||
$f3->set('DB', new \DB\SQL(
|
||||
'mysql:host=localhost;port=3306;dbname=' . $f3->get('database.db_name'),
|
||||
$f3->get('database.username'),
|
||||
$f3->get('database.password')
|
||||
));
|
||||
|
||||
new \DB\SQL\Session($f3->get('DB'));
|
||||
$f3->set('SESSION.status', 'running');
|
||||
|
||||
// Routing and Controller Setup
|
||||
|
||||
// home
|
||||
$f3->route('GET /', 'HomeController->display');
|
||||
|
||||
// auth
|
||||
$f3->route('GET /login', 'AuthController->showLoginForm');
|
||||
$f3->route('POST /login', 'AuthController->login');
|
||||
$f3->route('GET /logout', 'AuthController->logout');
|
||||
|
||||
// Example protected route
|
||||
$f3->route('GET /dashboard', function($f3){
|
||||
if(!$f3->exists('SESSION.user')){
|
||||
$f3->reroute('/login');
|
||||
}
|
||||
echo 'Welcome to the dashboard' . $f3->get('SESSION.username');
|
||||
echo '<a href="/logout">logout</a>';
|
||||
});
|
||||
|
||||
// tickets - CRUD (CREATE, READ, UPDATE, DELETE)
|
||||
$f3->route('GET /tickets', 'TicketController->index'); // view all tickets
|
||||
$f3->route('GET /ticket/@id', 'TicketController->view'); // view ticket details
|
||||
$f3->route('GET /ticket/create', 'TicketController->createForm'); // show form to create
|
||||
$f3->route('POST /ticket/create', 'TicketController->create'); // save
|
||||
$f3->route('GET /ticket/@id/edit', 'TicketController->editForm'); // edit ticket
|
||||
$f3->route('POST /ticket/@id/update', 'TicketController->update'); //
|
||||
|
||||
// knowledgebase
|
||||
$f3->route('GET /kb', 'KBController->index');
|
||||
$f3->route('GET /kb/create', 'KBController->createForm');
|
||||
$f3->route('POST /kb/create', 'KBController->create');
|
||||
$f3->route('GET /kb/@id', 'KBController->view');
|
||||
$f3->route('GET /kb/@id/edit', 'KBController->editForm');
|
||||
$f3->route('POST /kb/@id/edit', 'KBControllerKB->edit'); // should this be update - "crud"?
|
||||
|
||||
// tags
|
||||
$f3->route('GET /tags', 'TagController->index');
|
||||
$f3->route('GET /tag/create', 'Tag->createForm');
|
||||
$f3->route('POST /tag/create', 'Tag->create');
|
||||
|
||||
|
||||
// dashboard
|
||||
$f3->route('GET /dashboard', 'DashboardController->index');
|
||||
|
||||
$f3->run();
|
||||
1
public/logo.svg
Normal file
1
public/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000" preserveAspectRatio="xMidYMid meet"> <g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)" fill="#03045e" stroke="none"> <path d="M465 2639 c-88 -12 -236 -51 -312 -83 l-58 -24 0 -1054 c0 -880 2 -1053 14 -1050 177 37 258 45 461 46 187 1 230 -2 328 -22 145 -29 257 -69 414 -148 70 -35 130 -64 133 -64 3 0 5 470 5 1044 l0 1044 -26 31 c-14 17 -55 50 -92 74 -256 170 -569 244 -867 206z m397 -313 c85 -17 147 -37 241 -79 l67 -30 0 -318 c0 -176 -2 -319 -5 -319 -4 0 -46 14 -95 31 l-88 31 -4 97 c-3 90 -5 101 -32 135 -16 22 -50 48 -80 62 -33 16 -43 24 -29 24 12 0 37 15 56 33 46 43 56 98 29 161 -26 59 -66 80 -139 74 -77 -7 -114 -45 -121 -122 -6 -68 21 -119 74 -138 l34 -12 -51 -24 c-76 -36 -103 -84 -108 -189 -3 -82 -3 -83 -30 -84 -14 0 -65 -10 -114 -23 -48 -12 -90 -20 -92 -17 -3 2 -5 156 -5 342 l0 339 23 5 c148 36 343 45 469 21z m26 -990 c99 -22 209 -59 285 -97 34 -17 64 -33 66 -35 1 -1 -7 -20 -19 -42 l-22 -40 -86 40 c-244 113 -543 132 -803 52 -12 -3 -19 6 -27 38 -6 24 -8 46 -4 49 8 8 123 35 202 49 78 13 322 5 408 -14z m-5 -281 c110 -23 321 -100 355 -130 2 -2 -6 -22 -18 -44 l-20 -41 -48 24 c-74 37 -213 83 -302 100 -154 30 -381 17 -536 -29 -18 -6 -23 -1 -32 37 -6 23 -8 46 -4 50 7 7 175 43 242 52 67 8 285 -3 363 -19z m-33 -286 c88 -13 219 -54 313 -96 42 -20 77 -38 77 -41 0 -11 -44 -82 -49 -79 -105 58 -268 111 -397 129 l-94 12 0 41 c0 23 3 44 6 48 6 6 30 3 144 -14z m-334 -23 c3 -19 4 -40 2 -47 -2 -7 -42 -20 -89 -29 -46 -10 -96 -21 -110 -25 -23 -6 -27 -3 -37 33 -7 22 -12 42 -12 45 0 7 62 25 145 43 92 19 93 18 101 -20z"/> <path d="M2180 2634 c-218 -35 -517 -171 -605 -275 l-25 -31 0 -1044 c0 -574 2 -1044 5 -1044 3 0 63 29 133 64 157 79 269 119 414 148 98 20 141 23 328 22 203 -1 284 -9 461 -46 12 -3 14 170 17 1049 l2 1052 -47 21 c-188 83 -472 118 -683 84z m118 -246 l3 -48 -34 0 c-92 0 -268 -47 -393 -105 l-72 -33 -22 40 c-12 22 -20 41 -19 42 9 8 110 55 159 74 81 31 165 53 255 68 118 18 119 18 123 -38z m352 15 c36 -9 68 -19 72 -22 11 -10 -13 -93 -26 -88 -10 4 -81 19 -193 43 -24 5 -29 26 -16 72 5 22 9 23 52 17 25 -4 75 -14 111 -22z m-180 -253 c93 -10 231 -38 250 -50 9 -6 9 -17 -1 -49 -7 -22 -13 -41 -15 -41 -1 0 -42 10 -91 22 -75 19 -114 23 -263 22 -156 0 -186 -3 -272 -27 -53 -14 -136 -44 -184 -66 -48 -23 -88 -41 -90 -41 -2 0 -13 18 -24 39 l-19 39 22 14 c47 31 197 88 279 108 155 37 274 45 408 30z m-15 -280 c94 -8 259 -41 272 -54 3 -2 -1 -23 -8 -46 -11 -38 -15 -41 -38 -36 -14 3 -55 13 -91 21 -103 25 -298 30 -411 11 -100 -16 -239 -61 -321 -102 -26 -13 -50 -24 -52 -24 -3 0 -15 18 -26 40 l-20 40 57 29 c129 64 282 108 428 121 44 4 85 8 90 8 6 1 60 -3 120 -8z m130 -398 l40 -7 3 -340 c1 -187 0 -345 -2 -351 -3 -8 -22 -7 -68 2 -168 36 -398 27 -581 -22 -51 -13 -104 -27 -120 -30 l-27 -7 2 328 3 328 95 34 c103 37 190 60 285 73 65 10 305 4 370 -8z"/> <path d="M2174 1248 c-15 -29 -27 -54 -29 -56 -1 -1 -30 -7 -64 -13 l-63 -11 47 -48 47 -47 -11 -58 c-6 -32 -11 -63 -11 -68 0 -4 25 6 56 24 l57 32 59 -33 c51 -28 59 -30 55 -14 -3 11 -9 41 -13 67 -6 48 -6 49 41 97 l48 48 -49 8 c-77 11 -89 19 -113 73 -12 28 -24 51 -26 51 -3 0 -17 -24 -31 -52z"/> </g> </svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
6
public/style.css
Normal file
6
public/style.css
Normal file
@ -0,0 +1,6 @@
|
||||
html, body {padding:0; margin:0;}
|
||||
html, body, #sidebar, #page,#base_body {
|
||||
min-height: 100%
|
||||
}
|
||||
|
||||
#page { min-height: calc(100vh - 170px - 52px) }
|
||||
6
public/test.php
Normal file
6
public/test.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
|
||||
$password = "pass!local";
|
||||
|
||||
echo password_hash($password, PASSWORD_DEFAULT);
|
||||
|
||||
94
ui/templates/layout.html
Normal file
94
ui/templates/layout.html
Normal file
@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Desk - Work Streams</title>
|
||||
<!-- bulma.io-->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css">
|
||||
<!-- bulma helpers -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma-helpers/0.4.3/css/bulma-helpers.min.css"
|
||||
integrity="sha512-U6ELnUi7oqVEjkLmFw5r5UR5LEtvpImS/jUykBKneVhD0lxZxfJZ3k3pe003ktrtNZYungd9u3Urp2X09wKwXg=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<!-- <link rel="stylesheet" href="style.css"> -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkradio@2.1/dist/css/bulma-checkradio.min.css">
|
||||
<!-- font awesome -->
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
|
||||
integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Navigation Bar -->
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<!-- Your logo or app name -->
|
||||
<img src="/logo.svg" alt="App Logo">
|
||||
</a>
|
||||
<!-- Burger menu for mobile -->
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="mainNavbar">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="mainNavbar" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" href="/dashboard">Dashboard</a>
|
||||
<a class="navbar-item" href="/tickets">Tickets</a>
|
||||
<a class="navbar-item" href="/projects">Projects</a>
|
||||
<a class="navbar-item" href="/kb">Knowledge Base</a>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<check if="{{ isset(@SESSION.user) }}">
|
||||
<true>
|
||||
<a class="button is-primary" href="/logout">Log Out</a>
|
||||
</true>
|
||||
<false>
|
||||
<a class="button is-primary" href="/login">Log In</a>
|
||||
</false>
|
||||
</check>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="section" id="page">
|
||||
<div class="container">
|
||||
<!-- Fat-Free Framework content injection -->
|
||||
<include href="{{@content}}" />
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p>© <?php echo date('Y'); ?> Terry Probert</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript for Bulma navbar burger (mobile) -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const burgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
if (burgers.length > 0) {
|
||||
burgers.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = document.getElementById(el.dataset.target);
|
||||
el.classList.toggle('is-active');
|
||||
target.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
1
ui/views/dashboard.html
Normal file
1
ui/views/dashboard.html
Normal file
@ -0,0 +1 @@
|
||||
<h1 class="title">Dashboard</h1>
|
||||
77
ui/views/home.html
Normal file
77
ui/views/home.html
Normal file
@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bulma Dashboard</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkradio@2.1/dist/css/bulma-checkradio.min.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Navigation Bar -->
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<!-- Your logo or app name -->
|
||||
<img src="logo.svg" alt="App Logo">
|
||||
</a>
|
||||
<!-- Burger menu for mobile -->
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="mainNavbar">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="mainNavbar" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" href="/dashboard">Dashboard</a>
|
||||
<a class="navbar-item" href="/tickets">Tickets</a>
|
||||
<a class="navbar-item" href="/projects">Projects</a>
|
||||
<a class="navbar-item" href="/knowledge">Knowledge Base</a>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<a class="button is-primary" href="/login">Log in</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main class="section" id="page">
|
||||
<div class="container">
|
||||
<!-- Fat-Free Framework content injection -->
|
||||
{{@content}}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
<p>© <?php echo date('Y'); ?> Terry Probert</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript for Bulma navbar burger (mobile) -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const burgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
|
||||
if (burgers.length > 0) {
|
||||
burgers.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const target = document.getElementById(el.dataset.target);
|
||||
el.classList.toggle('is-active');
|
||||
target.classList.toggle('is-active');
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
16
ui/views/kb/create.html
Normal file
16
ui/views/kb/create.html
Normal file
@ -0,0 +1,16 @@
|
||||
<h1 class="title">Create Knowledge Base Article</h1>
|
||||
|
||||
|
||||
<form action="/kb/create" method="POST">
|
||||
|
||||
{{ BulmaForm::horizontal_field_input('Title:', 'title') }}
|
||||
|
||||
{{ BulmaForm::horizontal_field_textarea('Description:', 'description') }}
|
||||
|
||||
{{ BulmaForm::horizontal_field_select('Priority:', 'priority', ['Low', 'Medium', 'High'])}}
|
||||
|
||||
{{ BulmaForm::horizontal_field_select('Status:', 'status', ['New', 'In Progress', 'On Hold', 'Completed'])}}
|
||||
|
||||
<button class="button is-primary" type="submit">Create Ticket</button>
|
||||
</div>
|
||||
</form>
|
||||
37
ui/views/kb/index.html
Normal file
37
ui/views/kb/index.html
Normal file
@ -0,0 +1,37 @@
|
||||
<h1 class="title">Knowledge Base</h1>
|
||||
|
||||
<check if="{{isset(@SESSION.error)}}">
|
||||
<div class="notification is-warning">
|
||||
{{ @SESSION.error }}
|
||||
</div>
|
||||
</check>
|
||||
|
||||
<p><a href="/kb/create">create kb article</a></p>
|
||||
<hr>
|
||||
|
||||
<table class="table is-fullwidth is-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th><th>title</th><th>description</th>
|
||||
<th>status</th><th>priority</th><th>created_at</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<repeat group="{{@tickets}}" value="{{@ticket}}">
|
||||
<tr>
|
||||
<td>{{@ticket.id}}</td>
|
||||
<td>{{@ticket.title}}</td>
|
||||
<td>{{@ticket.description}}</td>
|
||||
<td>{{@ticket.status}}</td>
|
||||
<td>{{@ticket.priority}}</td>
|
||||
<td>{{@ticket.created_at}}</td>
|
||||
<td>
|
||||
<a href="/ticket/{{@ticket.id}}"><i class="fa fa-eye"></i></a>
|
||||
<a href="/ticket/{{@ticket.id}}/edit"><i class="fa fa-edit"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</repeat>
|
||||
</tbody>
|
||||
</table>
|
||||
18
ui/views/kb/view.html
Normal file
18
ui/views/kb/view.html
Normal file
@ -0,0 +1,18 @@
|
||||
<h1 class="title">Knowledge Article</h1>
|
||||
|
||||
<div class="box">
|
||||
|
||||
<h2 class="title">{{@article.title}}</h2>
|
||||
|
||||
<table class="table is-bordered is-fullwidth">
|
||||
<thead>
|
||||
<tr><th class="has-width-200">Property</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<repeat group="{{ @ticket }}" key="{{ @key }}" value="{{ @value }}">
|
||||
<tr><td>{{@key}}</td> <td>{{@value}}</td></tr>
|
||||
</repeat>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
35
ui/views/login.html
Normal file
35
ui/views/login.html
Normal file
@ -0,0 +1,35 @@
|
||||
<h1 class="title">Please Log In</h1>
|
||||
|
||||
<check if="{{ @error}}">
|
||||
<div class="notification is-danger is-light">
|
||||
<p style="color: red;">{{ @error }}</p>
|
||||
</div>
|
||||
</check>
|
||||
|
||||
<form action="/login" method="POST">
|
||||
|
||||
<div class="field">
|
||||
<p class="control has-icons-left has-icons-right">
|
||||
<input name="username" class="input" type="text" placeholder="Username">
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-user"></i>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control has-icons-left">
|
||||
<input name="password" class="input" type="password" placeholder="Password">
|
||||
<span class="icon is-small is-left">
|
||||
<i class="fas fa-lock"></i>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<button class="button is-success">
|
||||
Login
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
16
ui/views/ticket/create.html
Normal file
16
ui/views/ticket/create.html
Normal file
@ -0,0 +1,16 @@
|
||||
<h1 class="title">Create Ticket Form</h1>
|
||||
|
||||
|
||||
<form action="/ticket/create" method="POST">
|
||||
|
||||
{{ BulmaForm::horizontal_field_input('Title:', 'title') }}
|
||||
|
||||
{{ BulmaForm::horizontal_field_textarea('Description:', 'description') }}
|
||||
|
||||
{{ BulmaForm::horizontal_field_select('Priority:', 'priority', ['Low', 'Medium', 'High'])}}
|
||||
|
||||
{{ BulmaForm::horizontal_field_select('Status:', 'status', ['New', 'In Progress', 'On Hold', 'Completed'])}}
|
||||
|
||||
<button class="button is-primary" type="submit">Create Ticket</button>
|
||||
</div>
|
||||
</form>
|
||||
15
ui/views/ticket/edit.html
Normal file
15
ui/views/ticket/edit.html
Normal file
@ -0,0 +1,15 @@
|
||||
<h1 class="title">Edit Ticket Form</h1>
|
||||
|
||||
<form action="/ticket/{{ @PARAMS.id }}/update" method="POST">
|
||||
|
||||
{{ BulmaForm::horizontal_field_input('Title:', 'title', @ticket.title) }}
|
||||
|
||||
{{ BulmaForm::horizontal_field_textarea('Description:', 'description', @ticket.description) }}
|
||||
|
||||
{{ BulmaForm::horizontal_field_select('Priority:', 'priority', ['Low', 'Medium', 'High'])}}
|
||||
|
||||
{{ BulmaForm::horizontal_field_select('Status:', 'status', ['New', 'In Progress', 'On Hold', 'Completed'])}}
|
||||
|
||||
<button class="button is-primary" type="submit">Edit Ticket</button>
|
||||
</div>
|
||||
</form>
|
||||
36
ui/views/ticket/index.html
Normal file
36
ui/views/ticket/index.html
Normal file
@ -0,0 +1,36 @@
|
||||
<h1 class="title">View Tickets</h1>
|
||||
|
||||
<check if="{{isset(@SESSION.error)}}">
|
||||
<div class="notification is-warning">
|
||||
{{ @SESSION.error }}
|
||||
</div>
|
||||
</check>
|
||||
|
||||
<p><a href="/ticket/create">create ticket</a></p>
|
||||
<hr>
|
||||
|
||||
<table class="table is-fullwidth is-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>id</th><th>title</th><th>description</th>
|
||||
<th>status</th><th>priority</th><th>created_at</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<repeat group="{{@tickets}}" value="{{@ticket}}">
|
||||
<tr>
|
||||
<td>{{@ticket.id}}</td>
|
||||
<td>{{@ticket.title}}</td>
|
||||
<td>{{@ticket.status}}</td>
|
||||
<td>{{@ticket.priority}}</td>
|
||||
<td>{{@ticket.created_at}}</td>
|
||||
<td>
|
||||
<a href="/ticket/{{@ticket.id}}"><i class="fa fa-eye"></i></a>
|
||||
<a href="/ticket/{{@ticket.id}}/edit"><i class="fa fa-edit"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
</repeat>
|
||||
</tbody>
|
||||
</table>
|
||||
16
ui/views/ticket/view.html
Normal file
16
ui/views/ticket/view.html
Normal file
@ -0,0 +1,16 @@
|
||||
<h1 class="title">Ticket - View</h1>
|
||||
|
||||
<div class="box">
|
||||
|
||||
<table class="table is-bordered is-fullwidth">
|
||||
<thead>
|
||||
<tr><th class="has-width-200">Property</th><th>Value</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<repeat group="{{ @ticket }}" key="{{ @key }}" value="{{ @value }}">
|
||||
<tr><td>{{@key}}</td> <td>{{@value}}</td></tr>
|
||||
</repeat>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
Loading…
x
Reference in New Issue
Block a user