Compare commits
8 Commits
8b1c2f1822
...
67a35cdf3e
| Author | SHA1 | Date | |
|---|---|---|---|
| 67a35cdf3e | |||
| bc21598ba9 | |||
| 606a3fad8e | |||
| 5ba0911d7e | |||
| 759afbdd49 | |||
| aa44215eb1 | |||
| 6cc9953a68 | |||
| e90b8e7388 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@ app/config/.env.cfg
|
|||||||
app/.env.cfg
|
app/.env.cfg
|
||||||
app/config/tp_servicedesk.sql
|
app/config/tp_servicedesk.sql
|
||||||
node_modules
|
node_modules
|
||||||
|
schemafile.sql
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"python-envs.defaultEnvManager": "ms-python.python:system",
|
||||||
|
"python-envs.pythonProjects": []
|
||||||
|
}
|
||||||
9995
_codebase_output.txt
Normal file
9995
_codebase_output.txt
Normal file
File diff suppressed because it is too large
Load Diff
362
_codebase_schemafile.sql
Normal file
362
_codebase_schemafile.sql
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
-- MariaDB dump 10.19 Distrib 10.4.32-MariaDB, for Win64 (AMD64)
|
||||||
|
--
|
||||||
|
-- Host: localhost Database: tp_servicedesk
|
||||||
|
-- ------------------------------------------------------
|
||||||
|
-- Server version 10.4.32-MariaDB
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `attachments`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `attachments`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `attachments` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`path` text NOT NULL,
|
||||||
|
`file_name` text NOT NULL,
|
||||||
|
`ticket_id` int(11) DEFAULT NULL,
|
||||||
|
`kb_id` int(11) DEFAULT NULL,
|
||||||
|
`version_number` int(11) NOT NULL,
|
||||||
|
`uploaded_by` int(11) NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `kb`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `kb`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `kb` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`title` text NOT NULL,
|
||||||
|
`content` text NOT NULL,
|
||||||
|
`created_by` int(11) NOT NULL,
|
||||||
|
`updated_by` int(11) DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `kb_tags`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `kb_tags`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `kb_tags` (
|
||||||
|
`kb_id` int(11) NOT NULL,
|
||||||
|
`tag_id` int(11) NOT NULL,
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `project_events`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `project_events`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `project_events` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`project_id` int(11) NOT NULL,
|
||||||
|
`event_date` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
|
||||||
|
`description` text NOT NULL,
|
||||||
|
`created_by` int(11) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `project_links`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `project_links`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `project_links` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`project_id` int(11) NOT NULL,
|
||||||
|
`url` text NOT NULL,
|
||||||
|
`description` text NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `project_tasks`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `project_tasks`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `project_tasks` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`project_id` int(11) NOT NULL,
|
||||||
|
`title` varchar(255) NOT NULL,
|
||||||
|
`description` text NOT NULL,
|
||||||
|
`status` enum('pending','in_progress','done','') NOT NULL,
|
||||||
|
`start_date` timestamp NULL DEFAULT NULL,
|
||||||
|
`end_date` timestamp NULL DEFAULT NULL,
|
||||||
|
`created_by` int(11) NOT NULL,
|
||||||
|
`created_at` int(11) NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `projects`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `projects`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `projects` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`title` varchar(255) NOT NULL,
|
||||||
|
`description` text NOT NULL,
|
||||||
|
`requester` varchar(255) NOT NULL,
|
||||||
|
`created_by` int(11) NOT NULL,
|
||||||
|
`start_date` timestamp NULL DEFAULT NULL,
|
||||||
|
`end_date` timestamp NULL DEFAULT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `roles`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `roles`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `roles` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`role` varchar(50) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `sessions`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `sessions`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `sessions` (
|
||||||
|
`session_id` varchar(255) NOT NULL,
|
||||||
|
`data` text DEFAULT NULL,
|
||||||
|
`ip` varchar(45) DEFAULT NULL,
|
||||||
|
`agent` varchar(300) DEFAULT NULL,
|
||||||
|
`stamp` int(11) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`session_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `tags`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `tags`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `tags` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`color` text NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ticket_comments`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ticket_comments`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ticket_comments` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`ticket_id` int(11) NOT NULL,
|
||||||
|
`comment` text NOT NULL,
|
||||||
|
`created_by` int(11) DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
`deleted` int(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `ticket_id` (`ticket_id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ticket_meta`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ticket_meta`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ticket_meta` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`ticket_id` int(11) NOT NULL,
|
||||||
|
`meta_key` text NOT NULL,
|
||||||
|
`meta_value` text NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ticket_priorities`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ticket_priorities`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ticket_priorities` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(100) NOT NULL,
|
||||||
|
`sort_order` int(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ticket_relations`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ticket_relations`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ticket_relations` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`parent_ticket_id` int(11) NOT NULL,
|
||||||
|
`child_ticket_id` int(11) NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ticket_statuses`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ticket_statuses`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ticket_statuses` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(100) NOT NULL,
|
||||||
|
`sort_order` int(11) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ticket_tags`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ticket_tags`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ticket_tags` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`ticket_id` int(11) NOT NULL,
|
||||||
|
`tag_id` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `ticket_updates`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `ticket_updates`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `ticket_updates` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`ticket_id` int(11) NOT NULL,
|
||||||
|
`comment` text NOT NULL,
|
||||||
|
`updated_by` int(11) NOT NULL,
|
||||||
|
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `tickets`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `tickets`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `tickets` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`title` text NOT NULL,
|
||||||
|
`description` text NOT NULL,
|
||||||
|
`status_id` int(11) NOT NULL,
|
||||||
|
`priority_id` int(11) NOT NULL,
|
||||||
|
`created_by` int(11) NOT NULL,
|
||||||
|
`assigned_to` int(11) DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT current_timestamp(),
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_by` int(11) DEFAULT NULL,
|
||||||
|
`project_id` int(11) DEFAULT NULL,
|
||||||
|
`recycled` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `users`
|
||||||
|
--
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS `users`;
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`username` text NOT NULL,
|
||||||
|
`password` text NOT NULL,
|
||||||
|
`email` text NOT NULL,
|
||||||
|
`display_name` text NOT NULL,
|
||||||
|
`role` int(11) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`is_admin` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `username` (`username`) USING HASH
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
|
-- Dump completed on 2025-04-30 0:35:57
|
||||||
@ -7,7 +7,7 @@ class TicketOptionsController extends \BaseController
|
|||||||
public function listPriorities()
|
public function listPriorities()
|
||||||
{
|
{
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
// TODO: check admin
|
$this->requireAdmin(); // Added admin check
|
||||||
|
|
||||||
$model = new \TicketPriority($this->getDB());
|
$model = new \TicketPriority($this->getDB());
|
||||||
$priorities = $model->findAll();
|
$priorities = $model->findAll();
|
||||||
@ -20,17 +20,83 @@ class TicketOptionsController extends \BaseController
|
|||||||
public function createPriorityForm()
|
public function createPriorityForm()
|
||||||
{
|
{
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
|
$this->requireAdmin(); // Added admin check
|
||||||
$this->renderView('/ui/views/admin/priorities/create.html');
|
$this->renderView('/ui/views/admin/priorities/create.html');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPriority()
|
public function createPriority()
|
||||||
{
|
{
|
||||||
$this->requireLogin();
|
$this->requireLogin();
|
||||||
|
$this->requireAdmin(); // Added admin check
|
||||||
$p = new \TicketPriority($this->getDB());
|
$p = new \TicketPriority($this->getDB());
|
||||||
$p->name = $this->f3->get('POST.name');
|
$p->name = $this->f3->get('POST.name');
|
||||||
$p->sort_order = $this->f3->get('POST.sort_order');
|
$p->sort_order = $this->f3->get('POST.sort_order');
|
||||||
$p->save();
|
$p->save();
|
||||||
|
|
||||||
|
// Redirect after save
|
||||||
|
$this->f3->reroute('/admin/priorities');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editPriorityForm($f3, $params)
|
||||||
|
{
|
||||||
|
$this->requireLogin();
|
||||||
|
$this->requireAdmin();
|
||||||
|
$priorityId = $params['id'];
|
||||||
|
|
||||||
|
$model = new \TicketPriority($this->getDB());
|
||||||
|
$priority = $model->load(['id = ?', $priorityId]);
|
||||||
|
|
||||||
|
if (!$priority) {
|
||||||
|
$f3->error(404, 'Priority not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->renderView('/ui/views/admin/priorities/edit.html', [
|
||||||
|
'priority' => $priority
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updatePriority($f3, $params)
|
||||||
|
{
|
||||||
|
$this->requireLogin();
|
||||||
|
$this->requireAdmin();
|
||||||
|
$priorityId = $params['id'];
|
||||||
|
|
||||||
|
$model = new \TicketPriority($this->getDB());
|
||||||
|
$priority = $model->load(['id = ?', $priorityId]);
|
||||||
|
|
||||||
|
if (!$priority) {
|
||||||
|
$f3->error(404, 'Priority not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$priority->name = $this->f3->get('POST.name');
|
||||||
|
$priority->sort_order = $this->f3->get('POST.sort_order');
|
||||||
|
$priority->save();
|
||||||
|
|
||||||
|
// Redirect after update
|
||||||
|
$this->f3->reroute('/admin/priorities');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deletePriority($f3, $params)
|
||||||
|
{
|
||||||
|
$this->requireLogin();
|
||||||
|
$this->requireAdmin();
|
||||||
|
$priorityId = $params['id'];
|
||||||
|
|
||||||
|
$model = new \TicketPriority($this->getDB());
|
||||||
|
$priority = $model->load(['id = ?', $priorityId]);
|
||||||
|
|
||||||
|
if (!$priority) {
|
||||||
|
// Optionally show an error message or just redirect
|
||||||
|
$this->f3->reroute('/admin/priorities');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$priority->erase();
|
||||||
|
|
||||||
|
// Redirect after delete
|
||||||
|
$this->f3->reroute('/admin/priorities');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: editPriorityForm(), updatePriorityForm(), deletePriorityForm()
|
|
||||||
}
|
}
|
||||||
39
app/controllers/Admin/UserController.php
Normal file
39
app/controllers/Admin/UserController.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Admin;
|
||||||
|
|
||||||
|
class UserController extends \BaseController implements \CRUD
|
||||||
|
{
|
||||||
|
public function index($f3)
|
||||||
|
{
|
||||||
|
// TODO: Implement index() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createForm($f3)
|
||||||
|
{
|
||||||
|
// TODO: Implement createForm() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create($f3)
|
||||||
|
{
|
||||||
|
// TODO: Implement create() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function view($id)
|
||||||
|
{
|
||||||
|
// TODO: Implement view() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function editForm($id)
|
||||||
|
{
|
||||||
|
// TODO: Implement editForm() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update($id)
|
||||||
|
{
|
||||||
|
// TODO: Implement update() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -22,7 +22,7 @@ class AuthController {
|
|||||||
$db = $f3->get('DB');
|
$db = $f3->get('DB');
|
||||||
// query for user
|
// query for user
|
||||||
$result = $db->exec(
|
$result = $db->exec(
|
||||||
'SELECT u.id, u.username, u.password, u.role, r.role as role_name
|
'SELECT u.id, u.username, u.password, u.role, u.is_admin, r.role as role_name
|
||||||
FROM users u
|
FROM users u
|
||||||
LEFT JOIN roles r ON r.id = u.role
|
LEFT JOIN roles r ON r.id = u.role
|
||||||
WHERE username =?
|
WHERE username =?
|
||||||
@ -38,7 +38,8 @@ class AuthController {
|
|||||||
'id'=> $user['id'],
|
'id'=> $user['id'],
|
||||||
'username' => $user['username'],
|
'username' => $user['username'],
|
||||||
'role' => $user['role'],
|
'role' => $user['role'],
|
||||||
'role_name' => $user['role_name']
|
'role_name' => $user['role_name'],
|
||||||
|
'is_admin' => $user['is_admin']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if($f3->exists('SESSION.redirect')){
|
if($f3->exists('SESSION.redirect')){
|
||||||
|
|||||||
@ -10,6 +10,7 @@ abstract class BaseController
|
|||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->f3 = \Base::instance();
|
$this->f3 = \Base::instance();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -36,6 +37,21 @@ abstract class BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enforce that the user is logged in AND is an admin before proceeding.
|
||||||
|
*/
|
||||||
|
protected function requireAdmin()
|
||||||
|
{
|
||||||
|
$this->requireLogin(); // First, ensure the user is logged in
|
||||||
|
|
||||||
|
// Check if the user is an admin (assuming 'is_admin' property in session)
|
||||||
|
if (!$this->f3->get('SESSION.user.is_admin')) {
|
||||||
|
// Optionally set an error message
|
||||||
|
$this->f3->set('SESSION.error', 'Admin access required.');
|
||||||
|
$this->f3->reroute('/'); // Redirect non-admins to home page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up a main layout template and inject the specified view path
|
* Set up a main layout template and inject the specified view path
|
||||||
* optional $data to pass variables down to template
|
* optional $data to pass variables down to template
|
||||||
@ -45,7 +61,7 @@ abstract class BaseController
|
|||||||
foreach($data as $key => $value){
|
foreach($data as $key => $value){
|
||||||
$this->f3->set($key, $value);
|
$this->f3->set($key, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set {{content}}
|
// set {{content}}
|
||||||
$this->f3->set('content', $viewPath);
|
$this->f3->set('content', $viewPath);
|
||||||
|
|
||||||
|
|||||||
@ -2,22 +2,25 @@
|
|||||||
|
|
||||||
class ParsedownHelper extends \Prefab {
|
class ParsedownHelper extends \Prefab {
|
||||||
|
|
||||||
static public function render($args) {
|
static public function render($args)
|
||||||
|
{
|
||||||
|
|
||||||
|
# inline <parsedown inline="true">#markdown here</parsedown>
|
||||||
|
if(isset($args['@attrib']) && isset($args['@attrib']['inline']) &&
|
||||||
|
$args['@attrib']['inline'] === 'true'){
|
||||||
|
|
||||||
if(isset($args['@attrib']) && $args['@attrib']['inline'] === 'true'){
|
return self::instance()->inline($args);
|
||||||
|
|
||||||
$return = \Parsedown::instance()->text($args[0]);
|
|
||||||
|
|
||||||
return '<!-- tableextension -->
|
|
||||||
<div class="content">
|
|
||||||
<parsedown_rendered>'.$return.'</parsedown_rendered>
|
|
||||||
</div>
|
|
||||||
';
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// return '<pre>'.print_r($args,1).'</pre>';
|
# href <parsedown href="filename"></parsedown>
|
||||||
|
if(isset($args['@attrib']) && isset($args['@attrib']['href']))
|
||||||
|
{
|
||||||
|
return self::instance()->load_href($args);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# token <parsedown>{@variable}</parsedown>
|
||||||
$content = $args[0];
|
$content = $args[0];
|
||||||
$content_token = \Template::instance()->token($content);
|
$content_token = \Template::instance()->token($content);
|
||||||
|
|
||||||
@ -30,6 +33,52 @@ class ParsedownHelper extends \Prefab {
|
|||||||
function build($content){
|
function build($content){
|
||||||
return \ParsedownTableExtension::instance()->text($content);
|
return \ParsedownTableExtension::instance()->text($content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static private function inline($args){
|
||||||
|
$return = \Parsedown::instance()->text($args[0]);
|
||||||
|
|
||||||
|
return '<!-- tableextension -->
|
||||||
|
<div class="content">
|
||||||
|
<parsedown_rendered>'.$return.'</parsedown_rendered>
|
||||||
|
</div>
|
||||||
|
';
|
||||||
|
}
|
||||||
|
|
||||||
|
static private function load_href($args){
|
||||||
|
$href= $args['@attrib']['href'] ?? '';
|
||||||
|
if(!$href){
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$ui = Base::instance()->get('UI');
|
||||||
|
$dirs = preg_split('#[;]+#', $ui, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
|
||||||
|
// look for the file in each UI dir
|
||||||
|
$file = '';
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
// normalize trailing slash
|
||||||
|
$base = rtrim($dir, '/').'/';
|
||||||
|
// resolve relative paths
|
||||||
|
$candidate = realpath($base . $href) ?: $base . $href;
|
||||||
|
// print_r("<p>".$candidate . "</p>");
|
||||||
|
if (is_readable($candidate)) {
|
||||||
|
$file = $candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!$file) {
|
||||||
|
return "<p><em>File not found: {$href}</em></p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = file_get_contents($file);
|
||||||
|
$md = \Parsedown::instance()->text($text);
|
||||||
|
|
||||||
|
return '
|
||||||
|
<parsedown_rendered class="content">
|
||||||
|
'.$md.'
|
||||||
|
</parsedown_rendered>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
\Template::instance()->extend('parsedown', 'ParsedownHelper::render');
|
\Template::instance()->extend('parsedown', 'ParsedownHelper::render');
|
||||||
487
codebase-to-text.py
Normal file
487
codebase-to-text.py
Normal file
@ -0,0 +1,487 @@
|
|||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
import subprocess
|
||||||
|
import git # Still needed for potential future use or for checking if it's a repo, even if not cloning
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
from docx import Document
|
||||||
|
from pathspec import PathSpec
|
||||||
|
from pathspec.patterns import GitWildMatchPattern # Explicit import often good practice
|
||||||
|
|
||||||
|
class CodebaseToText:
|
||||||
|
def __init__(self, input_path, output_path, output_type, verbose, exclude_hidden, ignored_paths=None):
|
||||||
|
# Normalize input path early
|
||||||
|
self.input_path = os.path.abspath(input_path) # Use absolute path for consistency
|
||||||
|
self.output_path = output_path
|
||||||
|
self.output_type = output_type
|
||||||
|
self.verbose = verbose
|
||||||
|
self.exclude_hidden = exclude_hidden
|
||||||
|
self.ignored_paths = ignored_paths if ignored_paths else [] # Store custom ignores
|
||||||
|
|
||||||
|
self.temp_folder_path = None # Used only if cloning
|
||||||
|
self.is_cloned_repo = False # Flag to track if we cloned
|
||||||
|
|
||||||
|
self.git_ignore_spec = None
|
||||||
|
self.custom_ignore_spec = None
|
||||||
|
|
||||||
|
self._initialize_ignores() # Load ignores after setting input_path
|
||||||
|
|
||||||
|
def _initialize_ignores(self):
|
||||||
|
"""Loads .gitignore and initializes custom ignore spec."""
|
||||||
|
# Load .gitignore relative to the current input_path
|
||||||
|
gitignore_path = os.path.join(self.input_path, ".gitignore")
|
||||||
|
if os.path.exists(gitignore_path):
|
||||||
|
try:
|
||||||
|
with open(gitignore_path, 'r', encoding='utf-8') as f: # Specify encoding
|
||||||
|
lines = f.read().splitlines()
|
||||||
|
# Filter out empty lines and comments
|
||||||
|
lines = [line for line in lines if line.strip() and not line.strip().startswith('#')]
|
||||||
|
if lines:
|
||||||
|
self.git_ignore_spec = PathSpec.from_lines(GitWildMatchPattern, lines)
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Loaded .gitignore rules from: {gitignore_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not read .gitignore file at {gitignore_path}: {e}")
|
||||||
|
elif self.verbose:
|
||||||
|
print(f"No .gitignore file found at: {gitignore_path}")
|
||||||
|
|
||||||
|
# Create PathSpec for custom ignored paths
|
||||||
|
if self.ignored_paths:
|
||||||
|
# Filter out empty lines/patterns just in case
|
||||||
|
valid_custom_paths = [p for p in self.ignored_paths if p.strip()]
|
||||||
|
if valid_custom_paths:
|
||||||
|
self.custom_ignore_spec = PathSpec.from_lines(GitWildMatchPattern, valid_custom_paths)
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Using custom ignore rules: {valid_custom_paths}")
|
||||||
|
else:
|
||||||
|
self.ignored_paths = [] # Clear if only contained empty strings
|
||||||
|
|
||||||
|
|
||||||
|
def _is_path_ignored(self, file_or_dir_path):
|
||||||
|
"""Checks if a given path should be ignored based on all rules."""
|
||||||
|
try:
|
||||||
|
# Calculate relative path from the project root (self.input_path)
|
||||||
|
# Use pathlib for robustness
|
||||||
|
base_path = Path(self.input_path)
|
||||||
|
target_path = Path(file_or_dir_path)
|
||||||
|
# Use absolute paths temporarily to ensure correct relative calculation
|
||||||
|
rel_path = target_path.relative_to(base_path).as_posix() # Use POSIX paths for pathspec
|
||||||
|
except ValueError:
|
||||||
|
# If the path is not relative to input_path (shouldn't normally happen with os.walk)
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Warning: Path {file_or_dir_path} is not relative to {self.input_path}. Skipping ignore checks for it.")
|
||||||
|
return False # Or decide how to handle this case
|
||||||
|
|
||||||
|
# Check .gitignore rules
|
||||||
|
if self.git_ignore_spec and self.git_ignore_spec.match_file(rel_path):
|
||||||
|
if self.verbose > 1: # More detailed verbose logging if needed
|
||||||
|
print(f"Ignoring '{rel_path}' (gitignore)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check custom ignore rules
|
||||||
|
if self.custom_ignore_spec and self.custom_ignore_spec.match_file(rel_path):
|
||||||
|
if self.verbose > 1:
|
||||||
|
print(f"Ignoring '{rel_path}' (custom)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if hidden files/dirs should be excluded
|
||||||
|
# Note: PathSpec patterns can also match hidden files (e.g., '.*'),
|
||||||
|
# so this check is primarily for the simple dot/underscore prefix rule.
|
||||||
|
if self.exclude_hidden and self._is_hidden_path_component(target_path):
|
||||||
|
if self.verbose > 1:
|
||||||
|
print(f"Ignoring '{rel_path}' (hidden)")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_hidden_path_component(self, path_obj: Path):
|
||||||
|
"""Checks if any component of the path starts with '.' or '__'."""
|
||||||
|
# Check the name itself and its parents relative to the base input path
|
||||||
|
relative_parts = path_obj.relative_to(self.input_path).parts
|
||||||
|
return any(part.startswith(('.', '__')) for part in relative_parts if part != '.')
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_folder(self, folder_path):
|
||||||
|
"""Generates the directory tree string, respecting ignore rules."""
|
||||||
|
tree = ""
|
||||||
|
base_level = folder_path.count(os.sep)
|
||||||
|
|
||||||
|
# Ensure folder_path is absolute for consistent relative path calculations
|
||||||
|
abs_folder_path = Path(folder_path).resolve()
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(abs_folder_path, topdown=True):
|
||||||
|
abs_root_path = Path(root).resolve()
|
||||||
|
|
||||||
|
# --- Directory Ignore Logic ---
|
||||||
|
# Filter directories *before* recursing into them
|
||||||
|
# Keep track of original dirs list to modify dirs[:]
|
||||||
|
original_dirs = list(dirs)
|
||||||
|
dirs[:] = [] # Clear dirs list, we will re-add ones we want to keep
|
||||||
|
|
||||||
|
for d in original_dirs:
|
||||||
|
dir_path = abs_root_path / d
|
||||||
|
# Skip .git directory explicitly (essential)
|
||||||
|
if d == ".git":
|
||||||
|
if self.verbose > 1: print(f"Skipping .git directory: {dir_path}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self._is_path_ignored(str(dir_path)):
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Ignoring directory: {dir_path.relative_to(self.input_path)}")
|
||||||
|
# Don't add 'd' back to dirs[:], effectively pruning the walk
|
||||||
|
else:
|
||||||
|
dirs.append(d) # Keep this directory for recursion
|
||||||
|
|
||||||
|
# --- Calculate Tree Indentation ---
|
||||||
|
try:
|
||||||
|
# Calculate level relative to the *initial* input path for correct indentation
|
||||||
|
rel_root = abs_root_path.relative_to(self.input_path)
|
||||||
|
level = len(rel_root.parts) if rel_root.parts != ('.',) else 0
|
||||||
|
except ValueError:
|
||||||
|
# Should not happen if os.walk starts within input_path
|
||||||
|
print(f"Warning: Cannot determine relative path for {abs_root_path}. Using level 0.")
|
||||||
|
level = 0
|
||||||
|
|
||||||
|
indent = ' ' * level # 4 spaces per level
|
||||||
|
# Add directory entry to tree (only if it's not the root itself processed initially)
|
||||||
|
if abs_root_path != Path(self.input_path).resolve(): # Don't print root '/'
|
||||||
|
tree += f"{indent}{abs_root_path.name}/\n"
|
||||||
|
elif level == 0 and not tree: # Print root marker only once at the start
|
||||||
|
tree += f"{Path(self.input_path).name}/\n"
|
||||||
|
|
||||||
|
|
||||||
|
# --- File Listing ---
|
||||||
|
subindent = ' ' * (level + 1)
|
||||||
|
sorted_files = sorted(files) # Sort files for consistent output
|
||||||
|
|
||||||
|
for f in sorted_files:
|
||||||
|
file_path = abs_root_path / f
|
||||||
|
# Check if file is ignored
|
||||||
|
if not self._is_path_ignored(str(file_path)):
|
||||||
|
tree += f"{subindent}{f}\n"
|
||||||
|
elif self.verbose:
|
||||||
|
# Note: _is_path_ignored already prints detailed reasons if verbose > 1
|
||||||
|
print(f"Ignoring file (in tree): {file_path.relative_to(self.input_path)}")
|
||||||
|
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
print(f"\n--- Generated File Tree ---\n{tree}")
|
||||||
|
print("--- End File Tree ---\n")
|
||||||
|
|
||||||
|
return tree
|
||||||
|
|
||||||
|
def _get_file_contents(self, file_path):
|
||||||
|
"""Reads file content, handling potential encoding issues."""
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as file:
|
||||||
|
return file.read()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
try:
|
||||||
|
# Try a fallback encoding (e.g., latin-1 or detected encoding)
|
||||||
|
with open(file_path, 'r', encoding='latin-1') as file:
|
||||||
|
if self.verbose: print(f"Warning: Used fallback encoding 'latin-1' for {file_path}")
|
||||||
|
return file.read()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: Could not read file {file_path} with utf-8 or latin-1: {e}")
|
||||||
|
return f"Error reading file: {e}" # Include error message in output
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading file {file_path}: {e}")
|
||||||
|
return f"Error reading file: {e}"
|
||||||
|
|
||||||
|
|
||||||
|
def _process_files(self, path_to_walk):
|
||||||
|
"""Walks through files and concatenates their content, respecting ignores."""
|
||||||
|
content = ""
|
||||||
|
base_path = Path(self.input_path).resolve() # Use resolved base path
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(path_to_walk, topdown=True):
|
||||||
|
abs_root_path = Path(root).resolve()
|
||||||
|
|
||||||
|
# --- Directory Pruning (same logic as in _parse_folder) ---
|
||||||
|
original_dirs = list(dirs)
|
||||||
|
dirs[:] = []
|
||||||
|
for d in original_dirs:
|
||||||
|
dir_path = abs_root_path / d
|
||||||
|
if d == ".git" or self._is_path_ignored(str(dir_path)):
|
||||||
|
continue # Skip ignored or .git dirs
|
||||||
|
else:
|
||||||
|
dirs.append(d)
|
||||||
|
|
||||||
|
# --- Process Files in Current Directory ---
|
||||||
|
sorted_files = sorted(files)
|
||||||
|
for file_name in sorted_files:
|
||||||
|
file_path = abs_root_path / file_name
|
||||||
|
str_file_path = str(file_path)
|
||||||
|
|
||||||
|
# Skip ignored files
|
||||||
|
if self._is_path_ignored(str_file_path):
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Ignoring file (content): {file_path.relative_to(base_path)}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Try to get content
|
||||||
|
try:
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Processing: {file_path.relative_to(base_path)}")
|
||||||
|
|
||||||
|
file_content = self._get_file_contents(str_file_path)
|
||||||
|
rel_file_path_display = file_path.relative_to(base_path).as_posix() # Display relative path
|
||||||
|
|
||||||
|
content += f"\n\n--- File: {rel_file_path_display} ---\n"
|
||||||
|
# Optional: Add file type hint
|
||||||
|
# content += f"File type: {os.path.splitext(file_name)[1]}\n\n"
|
||||||
|
content += file_content
|
||||||
|
# Use a clear end marker
|
||||||
|
content += f"\n--- End File: {rel_file_path_display} ---\n"
|
||||||
|
|
||||||
|
except Exception as e: # Catch potential errors during processing
|
||||||
|
print(f"Couldn't process {file_path.relative_to(base_path)}: {e}")
|
||||||
|
content += f"\n\n--- Error processing file: {file_path.relative_to(base_path)} --- \n {e} \n--- End Error ---\n"
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def get_text(self):
|
||||||
|
"""Generates the final combined text output."""
|
||||||
|
# --- Decide whether to clone or use local path ---
|
||||||
|
process_path = self.input_path # Default to local path
|
||||||
|
if self.is_github_repo():
|
||||||
|
success = self._clone_github_repo()
|
||||||
|
if success:
|
||||||
|
process_path = self.temp_folder_path
|
||||||
|
self.is_cloned_repo = True
|
||||||
|
# Re-initialize ignores for the cloned repo location
|
||||||
|
self.input_path = process_path # Temporarily change base for ignore checks
|
||||||
|
self._initialize_ignores()
|
||||||
|
print(f"Processing cloned repo at: {process_path}")
|
||||||
|
else:
|
||||||
|
print("Error: Failed to clone GitHub repository. Aborting.")
|
||||||
|
# Reset input_path if cloning failed and we modified it
|
||||||
|
if self.is_cloned_repo: self.input_path = os.path.dirname(self.temp_folder_path) # Hacky way to get original path back conceptually
|
||||||
|
return "Error: Could not clone repository." # Return error message
|
||||||
|
else:
|
||||||
|
print(f"Processing local path: {process_path}")
|
||||||
|
# Ensure ignores are initialized for the local path (done in __init__)
|
||||||
|
|
||||||
|
|
||||||
|
# --- Generate Structure and Content ---
|
||||||
|
folder_structure = self._parse_folder(process_path)
|
||||||
|
file_contents = self._process_files(process_path)
|
||||||
|
|
||||||
|
# --- Assemble Final Output ---
|
||||||
|
folder_structure_header = "--- Folder Structure ---"
|
||||||
|
file_contents_header = "--- File Contents ---"
|
||||||
|
delimiter = "=" * 60 # Use a more prominent delimiter
|
||||||
|
|
||||||
|
# Restore original input_path if it was changed for cloning
|
||||||
|
if self.is_cloned_repo:
|
||||||
|
# This assumes the original input_path wasn't needed after _initialize_ignores
|
||||||
|
# A cleaner way might be to pass the base path explicitly to ignore checkers
|
||||||
|
pass # No need to restore if input_path wasn't critical after cloning
|
||||||
|
|
||||||
|
return (
|
||||||
|
f"{folder_structure_header}\n{folder_structure}\n{delimiter}\n\n"
|
||||||
|
f"{file_contents_header}\n{file_contents}\n{delimiter}\nEnd of Codebase\n{delimiter}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_file(self):
|
||||||
|
"""Gets the text and saves it to the specified output file."""
|
||||||
|
text_content = self.get_text()
|
||||||
|
|
||||||
|
# Check for error during get_text (e.g., cloning failure)
|
||||||
|
if text_content.startswith("Error:"):
|
||||||
|
print(text_content) # Print the error
|
||||||
|
# Optionally, clean up temp folder even on error
|
||||||
|
self.clean_up_temp_folder()
|
||||||
|
return # Exit without writing file
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Ensure output directory exists
|
||||||
|
output_dir = os.path.dirname(self.output_path)
|
||||||
|
if output_dir: # Handle case where output is in current dir
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
if self.output_type == "txt":
|
||||||
|
with open(self.output_path, "w", encoding='utf-8') as file:
|
||||||
|
file.write(text_content)
|
||||||
|
elif self.output_type == "docx":
|
||||||
|
doc = Document()
|
||||||
|
# Add text respecting paragraphs (simple split, might need refinement)
|
||||||
|
# Consider adding as preformatted text run if python-docx supports it well
|
||||||
|
for paragraph in text_content.split('\n'):
|
||||||
|
doc.add_paragraph(paragraph)
|
||||||
|
doc.save(self.output_path)
|
||||||
|
else:
|
||||||
|
# Should be caught by argparse choices usually, but good to have
|
||||||
|
raise ValueError(f"Invalid output type '{self.output_type}'. Supported types: txt, docx")
|
||||||
|
|
||||||
|
print(f"\nSuccessfully generated {self.output_type} file: {self.output_path}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError writing output file {self.output_path}: {e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Clean up temp folder regardless of writing success/failure
|
||||||
|
self.clean_up_temp_folder()
|
||||||
|
|
||||||
|
|
||||||
|
#### GitHub ####
|
||||||
|
def _clone_github_repo(self):
|
||||||
|
"""Clones the repo to a temporary directory."""
|
||||||
|
try:
|
||||||
|
# Create temp dir *before* cloning into it
|
||||||
|
# Use a more descriptive prefix/suffix if desired
|
||||||
|
self.temp_folder_path = tempfile.mkdtemp(prefix="cbt_repo_")
|
||||||
|
print(f"Cloning {self.input_path} into temporary folder {self.temp_folder_path}...")
|
||||||
|
git.Repo.clone_from(self.input_path, self.temp_folder_path)
|
||||||
|
|
||||||
|
# Important: Update self.input_path to the temp folder *for processing*
|
||||||
|
# self.input_path = self.temp_folder_path # Now done within get_text
|
||||||
|
if self.verbose:
|
||||||
|
print("GitHub repository cloned successfully.")
|
||||||
|
return True # Indicate success
|
||||||
|
except git.GitCommandError as e:
|
||||||
|
print(f"Error cloning GitHub repository: {e}")
|
||||||
|
# Clean up failed clone attempt
|
||||||
|
self.clean_up_temp_folder() # Ensure cleanup even on clone failure
|
||||||
|
self.temp_folder_path = None # Reset path
|
||||||
|
return False # Indicate failure
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred during cloning: {e}")
|
||||||
|
self.clean_up_temp_folder() # Ensure cleanup
|
||||||
|
self.temp_folder_path = None # Reset path
|
||||||
|
return False # Indicate failure
|
||||||
|
|
||||||
|
def is_github_repo(self):
|
||||||
|
"""Checks if the input path looks like a common Git repo URL."""
|
||||||
|
# Keep it simple, add more patterns if needed
|
||||||
|
return self.input_path.startswith(("https://github.com/", "git@github.com:", "https://gitlab.com/", "git@gitlab.com:", "https://bitbucket.org/", "git@bitbucket.org:")) \
|
||||||
|
or self.input_path.endswith(".git") # Common convention for clone URLs
|
||||||
|
|
||||||
|
def clean_up_temp_folder(self):
|
||||||
|
"""Removes the temporary folder if it was created."""
|
||||||
|
if self.temp_folder_path and os.path.exists(self.temp_folder_path):
|
||||||
|
try:
|
||||||
|
shutil.rmtree(self.temp_folder_path)
|
||||||
|
if self.verbose:
|
||||||
|
print(f"Cleaned up temporary folder: {self.temp_folder_path}")
|
||||||
|
self.temp_folder_path = None # Reset path after successful removal
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning: Could not remove temporary folder {self.temp_folder_path}: {e}")
|
||||||
|
|
||||||
|
# --- Main Execution ---
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Generate a single text or docx file from a codebase, respecting .gitignore and custom ignore rules.",
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter # Show defaults
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"input",
|
||||||
|
help="Input path (local folder path or Git repository URL)."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-o", "--output",
|
||||||
|
required=True,
|
||||||
|
help="Output file path (e.g., my_codebase.txt or output/report.docx)."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t", "--output_type",
|
||||||
|
choices=["txt", "docx"],
|
||||||
|
default="txt",
|
||||||
|
help="Output file type."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--ignore",
|
||||||
|
nargs='*', default=[], # Accept zero or more ignore patterns
|
||||||
|
help="List of additional paths/patterns to ignore (e.g., 'dist/' '/node_modules' '*.log' 'config. Maches relative to the input path root."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--exclude_hidden",
|
||||||
|
action="store_true",
|
||||||
|
help="Exclude files and folders starting with '.' or '__'. Note that .gitignore or custom ignores might already cover these."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-v", "--verbose",
|
||||||
|
action="count", default=0, # Use count for verbosity levels (0, 1, 2)
|
||||||
|
help="Increase output verbosity. -v for basic info, -vv for detailed ignore reasons."
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Basic validation
|
||||||
|
if not args.input:
|
||||||
|
parser.error("Input path cannot be empty.")
|
||||||
|
if not args.output:
|
||||||
|
parser.error("Output path cannot be empty.")
|
||||||
|
|
||||||
|
|
||||||
|
code_to_text = None # Ensure it's defined for finally block
|
||||||
|
try:
|
||||||
|
code_to_text = CodebaseToText(
|
||||||
|
input_path=args.input,
|
||||||
|
output_path=args.output,
|
||||||
|
output_type=args.output_type,
|
||||||
|
verbose=args.verbose,
|
||||||
|
exclude_hidden=args.exclude_hidden,
|
||||||
|
ignored_paths=args.ignore # Pass the list here
|
||||||
|
)
|
||||||
|
code_to_text.get_file()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nAn unexpected error occurred: {e}")
|
||||||
|
# Attempt cleanup even if initialization failed partially
|
||||||
|
if code_to_text:
|
||||||
|
code_to_text.clean_up_temp_folder()
|
||||||
|
# No finally block needed here as get_file() now handles cleanup
|
||||||
|
|
||||||
|
def _sql_dump(database, export_file):
|
||||||
|
command = [
|
||||||
|
r'd:\xampp\mysql\bin\mysqldump.exe',
|
||||||
|
'-u', 'root',
|
||||||
|
'--no-data',
|
||||||
|
database
|
||||||
|
]
|
||||||
|
|
||||||
|
with open(export_file, 'w') as output_file:
|
||||||
|
result = subprocess.run(command, stdout=output_file, stderr=subprocess.PIPE)
|
||||||
|
|
||||||
|
if(result.returncode == 0):
|
||||||
|
print(f"Schema dump successful: {export_file}")
|
||||||
|
else:
|
||||||
|
print("Error occurred:", result.stderr.decode())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# --- Example Usage (replace with main() for CLI) ---
|
||||||
|
|
||||||
|
# To run from command line, save the script (e.g., codebase_to_text.py) and run:
|
||||||
|
# python codebase_to_text.py . -o my_project.txt --ignore "dist/" "*.tmp" "/tests/data/" --exclude_hidden -v
|
||||||
|
# python codebase_to_text.py https://github.com/user/repo.git -o repo_code.docx -t docx -vv
|
||||||
|
|
||||||
|
# --- Direct call example (useful for testing) ---
|
||||||
|
try:
|
||||||
|
print("Running direct example...")
|
||||||
|
# Example: Process current directory, output to output.txt, ignore 'venv' folder and all '.log' files
|
||||||
|
example_ignores = ["venv/", "*.log", "/output.txt", ".git/", "__pycache__/"] # Add common ignores
|
||||||
|
example_ignores.append("public/css/")
|
||||||
|
example_ignores.append("codebase-to-text.py")
|
||||||
|
converter = CodebaseToText(
|
||||||
|
input_path=".",
|
||||||
|
output_path="_codebase_output.txt",
|
||||||
|
output_type="txt",
|
||||||
|
verbose=0, # Set verbosity level (0, 1, or 2)
|
||||||
|
exclude_hidden=True,
|
||||||
|
ignored_paths=example_ignores
|
||||||
|
)
|
||||||
|
converter.get_file()
|
||||||
|
print("Direct example finished.")
|
||||||
|
|
||||||
|
_sql_dump('tp_servicedesk', '_codebase_schemafile.sql')
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error running direct example: {e}")
|
||||||
|
|
||||||
|
# Uncomment the line below to enable command-line argument parsing when running the script directly
|
||||||
|
# main()
|
||||||
|
|
||||||
|
# to get a sql dump use somthing similar to
|
||||||
|
# d:\xampp\mysql\bin\mysqldump.exe -u root --no-data tp_servicedesk > _codebase_schemafile.sql
|
||||||
|
|
||||||
114
issues.md
Normal file
114
issues.md
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
# MVP Issues
|
||||||
|
|
||||||
|
This issue list defines the work required to bring the `tp_servicedesk` Fat-Free PHP application to a beta-ready MVP for personal workload/project tracking.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧩 Ticketing System
|
||||||
|
- [ ] Display assigned user in ticket view
|
||||||
|
- [ ] Add user assignment capability in ticket create/edit forms
|
||||||
|
- [ ] Implement ticket filtering (by status, assignee, project)
|
||||||
|
- [ ] Improve UI feedback for ticket soft-delete
|
||||||
|
- [ ] Ensure metadata, attachments, and comments update properly on edit
|
||||||
|
- [ ] Add tag support for tickets (UI and DB updates)
|
||||||
|
- [ ] Implement ticket history for status/priority changes
|
||||||
|
- [ ] Add comment thread display on ticket edit view
|
||||||
|
- [ ] Update "new ticket" template to match the edit form
|
||||||
|
- [ ] Enable linking tickets using markdown shortcodes (e.g. `#ticket-id` autocomplete)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Project Management
|
||||||
|
- [ ] Implement `ProjectController::create()` and `update()` logic
|
||||||
|
- [ ] Add CRUD for:
|
||||||
|
- [ ] `project_tasks` (task list under project)
|
||||||
|
- [ ] `project_links` (related links/resources)
|
||||||
|
- [ ] `project_events` (project timeline/log)
|
||||||
|
- [ ] Show tickets related to a project in the project view page
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Knowledge Base (KB)
|
||||||
|
- [ ] Display and edit tags for each article
|
||||||
|
- [ ] Show article creator and last updated info
|
||||||
|
- [ ] Add search suggestions or recent articles on main KB page
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 👤 User Management
|
||||||
|
- [ ] Implement full CRUD in `Admin\UserController`
|
||||||
|
- [ ] Add role selection to user creation/edit forms
|
||||||
|
- [ ] Restrict sensitive actions (ticket edit/delete) to owner or admin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Admin Panel
|
||||||
|
- [ ] Complete admin routes and views for ticket status management
|
||||||
|
- [ ] Add dashboard widgets (open tickets count, recent updates, etc.)
|
||||||
|
- [ ] Create settings/config view to manage global app options
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 UI & UX Polish
|
||||||
|
- [ ] Show success/error alerts for all user actions
|
||||||
|
- [ ] Apply consistent Bulma styling to forms, buttons, and messages
|
||||||
|
- [ ] Add loading indicators or skeletons on longer actions
|
||||||
|
- [ ] Embed `/public/test.md.php` JS markdown editor into edit/create ticket templates
|
||||||
|
- [ ] Add smart shortcuts to markdown editor:
|
||||||
|
- [ ] `#` opens ticket linking modal
|
||||||
|
- [ ] `@` opens user tagging modal
|
||||||
|
- [ ] `!a` opens hyperlink creation modal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Quality Assurance
|
||||||
|
- [ ] Create fake/test data seeder
|
||||||
|
- [ ] Add DB integrity checks for tickets, users, and project FK links
|
||||||
|
- [ ] Manually test:
|
||||||
|
- [ ] Login/logout
|
||||||
|
- [ ] Ticket create/edit/delete
|
||||||
|
- [ ] KB CRUD
|
||||||
|
- [ ] Attachment upload/download
|
||||||
|
- [ ] Audit all routes and controllers for authentication and access checks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Search & Indexing
|
||||||
|
- [ ] Improve full-text search to be smarter than `LIKE %{query}%`
|
||||||
|
- [ ] Add partial, fuzzy, and prioritised relevance to results (e.g. `title > tags > content`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📅 Timeline & Activity Views
|
||||||
|
- [ ] Create timeline view per ticket using:
|
||||||
|
- [ ] status/priority history
|
||||||
|
- [ ] comments
|
||||||
|
- [ ] attachments and other actions
|
||||||
|
- [ ] Build global timeline dashboard to see activity across system
|
||||||
|
- [ ] Implement calendar heatmap (e.g. GitHub-style) for activity tracking
|
||||||
|
- [ ] Add drilldown support from calendar to view specific actions on each day
|
||||||
|
- [ ] Add detailed log/summary of:
|
||||||
|
- [ ] ticket creation/updates
|
||||||
|
- [ ] project creation/updates
|
||||||
|
- [ ] assignments
|
||||||
|
- [ ] comments
|
||||||
|
- [ ] closures
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Security & Session
|
||||||
|
- [ ] Add CSRF protection for all POST forms
|
||||||
|
- [ ] Enforce permission checks on:
|
||||||
|
- [ ] Ticket edits
|
||||||
|
- [ ] Comment deletion
|
||||||
|
- [ ] Attachment deletion
|
||||||
|
- [ ] Add password reset flow or admin-reset function
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 Post-MVP (Optional Enhancements)
|
||||||
|
- [ ] Ticket due dates and reminders
|
||||||
|
- [ ] Kanban or Gantt-style project view
|
||||||
|
- [ ] REST API endpoints
|
||||||
|
|
||||||
|
---
|
||||||
@ -10,6 +10,8 @@ i.fa { font-weight: 100 !important ; }
|
|||||||
|
|
||||||
#ticket_list .g-flex-item { border-bottom: 1px solid var(--bulma-text-soft); }
|
#ticket_list .g-flex-item { border-bottom: 1px solid var(--bulma-text-soft); }
|
||||||
|
|
||||||
|
a { word-break: break-word; }
|
||||||
|
|
||||||
/* parsedown check-checkbox */
|
/* parsedown check-checkbox */
|
||||||
li.parsedown-task-list {
|
li.parsedown-task-list {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
|||||||
@ -48,6 +48,9 @@
|
|||||||
<a class="navbar-item" href="/projects">Projects</a>
|
<a class="navbar-item" href="/projects">Projects</a>
|
||||||
<a class="navbar-item" href="/tickets">Tickets</a>
|
<a class="navbar-item" href="/tickets">Tickets</a>
|
||||||
<a class="navbar-item" href="/tags">Tags</a>
|
<a class="navbar-item" href="/tags">Tags</a>
|
||||||
|
<check if="{{ isset(@SESSION.user) && @SESSION.user.is_admin }}">
|
||||||
|
<a class="navbar-item" href="/admin">Admin</a>
|
||||||
|
</check>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
@ -88,6 +91,9 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- Main Content Area -->
|
<!-- Main Content Area -->
|
||||||
|
<div class="container">
|
||||||
|
<include href="ui/session/error.html">
|
||||||
|
</div>
|
||||||
<main class="section" id="page">
|
<main class="section" id="page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<!-- Fat-Free Framework content injection -->
|
<!-- Fat-Free Framework content injection -->
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<h1 class="title">Admin</h1>
|
<h1 class="title">Admin</h1>
|
||||||
<include href="/ui/session/error.html">
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<h1 class="title">Admin: Ticket Priorities</h1>
|
<h1 class="title">Admin: Ticket Priorities</h1>
|
||||||
<include href="/ui/session/error.html">
|
|
||||||
<p><a class="button" href="/admin/priority/create">create priority</a></p>
|
<p><a class="button" href="/admin/priority/create">create priority</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|||||||
@ -1 +1,5 @@
|
|||||||
<h1 class="title">Dashboard</h1>
|
<h1 class="title">Dashboard</h1>
|
||||||
|
|
||||||
|
<parsedown href="issues.md"></parsedown>
|
||||||
|
|
||||||
|
<p>109</p>
|
||||||
@ -1,5 +1,4 @@
|
|||||||
<h1 class="title">Knowledge Base</h1>
|
<h1 class="title">Knowledge Base</h1>
|
||||||
<include href="/ui/session/error.html"></include>
|
|
||||||
<p><a class="button" href="/kb/create">create kb article</a></p>
|
<p><a class="button" href="/kb/create">create kb article</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<h1 class="title">Projects</h1>
|
<h1 class="title">Projects</h1>
|
||||||
<include href="/ui/session/error.html"></include>
|
|
||||||
<p><a class="button" href="/project/create">create project</a></p>
|
<p><a class="button" href="/project/create">create project</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<h1 class="title">Tags</h1>
|
<h1 class="title">Tags</h1>
|
||||||
<include href="/ui/session/error.html"></include>
|
|
||||||
<p><a class="button" href="/tag/create">create tag</a></p>
|
<p><a class="button" href="/tag/create">create tag</a></p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
<h1 class="title">Tickets</h1>
|
<h1 class="title">Tickets</h1>
|
||||||
<include href="/ui/session/error.html"></include>
|
|
||||||
</include>
|
|
||||||
<!-- updated design -- inspiration gitea -->
|
<!-- updated design -- inspiration gitea -->
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user