Compare commits
7 Commits
18d4d27183
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8444e1f23a | ||
|
|
5df09b1f2f | ||
|
|
b568a283ac | ||
|
|
ffc04634ff | ||
| 0b90913b5b | |||
|
|
b9e362497a | ||
|
|
7d5904e925 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
database/default-content.sql
|
database/default-content.sql
|
||||||
vendor
|
vendor
|
||||||
node_modules
|
node_modules
|
||||||
database/origo.db
|
database/origo.db
|
||||||
scripts/default-content.sh
|
scripts/default-content.sh
|
||||||
|
docker-compose.override.yml
|
||||||
|
|||||||
15
Dockerfile
15
Dockerfile
@@ -1,15 +0,0 @@
|
|||||||
FROM php:8.4-fpm
|
|
||||||
|
|
||||||
# Install SQLite extension and Node.js
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
sqlite3 \
|
|
||||||
libsqlite3-dev \
|
|
||||||
curl \
|
|
||||||
&& docker-php-ext-install pdo_sqlite \
|
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
|
|
||||||
&& apt-get install -y nodejs \
|
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
|
||||||
COPY ./src .
|
|
||||||
37
TODO.md
37
TODO.md
@@ -1,37 +0,0 @@
|
|||||||
# To Do
|
|
||||||
|
|
||||||
- [ ] Need to create an agent to send data
|
|
||||||
- [ ] Need to create api endpoint to handle request
|
|
||||||
- [ ] Need a new table for accounts
|
|
||||||
- Users can belong to accounts
|
|
||||||
|
|
||||||
## How will authentication work?
|
|
||||||
JWT or Oauth based tokens
|
|
||||||
|
|
||||||
## Lightweight agent
|
|
||||||
What data will the agent need to send:
|
|
||||||
1. Gather user data
|
|
||||||
2. Account id
|
|
||||||
3. Site id
|
|
||||||
|
|
||||||
The agent should be availableas a simple script.
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
```
|
|
||||||
/api/webhook
|
|
||||||
```
|
|
||||||
This will receive a JSON payload from the agent
|
|
||||||
It will parse the payload to store the data for later display
|
|
||||||
|
|
||||||
```
|
|
||||||
/api/dataset/{account_id}/{site_id}
|
|
||||||
```
|
|
||||||
This will provide all metrics to allow displaying
|
|
||||||
information about the account.:w
|
|
||||||
|
|
||||||
```
|
|
||||||
/api/site-list/{account_id}
|
|
||||||
```
|
|
||||||
This will provide a list of sites for a given account.
|
|
||||||
Each account can have unlimited sites. Each site has an id.
|
|
||||||
@@ -16,8 +16,6 @@
|
|||||||
"guzzlehttp/guzzle": "^7.10"
|
"guzzlehttp/guzzle": "^7.10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "sh scripts/start.sh",
|
|
||||||
"rebuild": "sh scripts/rebuild.sh",
|
|
||||||
"theme": "sh scripts/theme.sh",
|
"theme": "sh scripts/theme.sh",
|
||||||
"db-reset": "sh scripts/db-setup.sh"
|
"db-reset": "sh scripts/db-setup.sh"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,9 +55,13 @@ CREATE TABLE IF NOT EXISTS user_accounts (
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS pageviews (
|
CREATE TABLE IF NOT EXISTS pageviews (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
account_id INTEGER NOT NULL,
|
account_id TEXT NOT NULL,
|
||||||
page TEXT NOT NULL,
|
page TEXT NOT NULL,
|
||||||
|
title TEXT,
|
||||||
user_agent TEXT,
|
user_agent TEXT,
|
||||||
|
screen_res TEXT,
|
||||||
|
language TEXT,
|
||||||
|
timestamp TEXT,
|
||||||
referrer TEXT,
|
referrer TEXT,
|
||||||
ip TEXT,
|
ip TEXT,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
services:
|
services:
|
||||||
app:
|
php:
|
||||||
build: .
|
image: dchadwick/planet-express:1.0.1
|
||||||
|
working_dir: /app
|
||||||
|
command: php-fpm -F
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:9000:9000"
|
- "127.0.0.1:9000:9000"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
- ./database:/app/database # Persists your SQLite file
|
- ./database:/app/database
|
||||||
networks:
|
networks:
|
||||||
- app-network
|
- app-network
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ if (!$router->routeExists($path)) {
|
|||||||
// now we dont have to do this in every controller.
|
// now we dont have to do this in every controller.
|
||||||
// Still allow login on homepage.
|
// Still allow login on homepage.
|
||||||
$request = new Request();
|
$request = new Request();
|
||||||
$allowed_paths = ['/', '/user-login'];
|
$allowed_paths = [
|
||||||
|
'/',
|
||||||
|
'/user-login',
|
||||||
|
'/api/v1/webhook'
|
||||||
|
];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!$request->getCookie('ORIGOSESS') &&
|
!$request->getCookie('ORIGOSESS') &&
|
||||||
!in_array($path, $allowed_paths)
|
!in_array($path, $allowed_paths)
|
||||||
|
|||||||
32
nginx.conf
Normal file
32
nginx.conf
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# This MUST match the working_dir and volume path from your docker-compose.yml
|
||||||
|
root /app;
|
||||||
|
index index.php index.html;
|
||||||
|
|
||||||
|
# Using underscores for log files
|
||||||
|
access_log /var/log/nginx/access_log.log;
|
||||||
|
error_log /var/log/nginx/error_log.log;
|
||||||
|
|
||||||
|
# Route everything to index.php if the file doesn't explicitly exist
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Pass all .php files to the PHP-FPM container
|
||||||
|
location ~ \.php$ {
|
||||||
|
try_files $uri =404;
|
||||||
|
fastcgi_split_path_info ^(.+\.php)(/.+)$;
|
||||||
|
|
||||||
|
# 'php' is the name of the service in your docker-compose.yml
|
||||||
|
# 9000 is the default port PHP-FPM listens on
|
||||||
|
fastcgi_pass php:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
PASSWORD=$1
|
||||||
|
echo -n $1 | shasum -a 256
|
||||||
openssl rand -hex 16
|
openssl rand -hex 16
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
docker build -t origo .
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
docker run --rm -p 8000:8000 -v $(pwd):/app origo php -S 0.0.0.0:8000
|
|
||||||
@@ -30,30 +30,48 @@ class ApiWebhookController extends ControllerBase implements ControllerInterface
|
|||||||
* The response.
|
* The response.
|
||||||
*/
|
*/
|
||||||
public function getResponse(): string {
|
public function getResponse(): string {
|
||||||
header('Access-Control-Allow-Origin: *'); // Allow all origins.
|
// @todo: Create Enum.
|
||||||
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
|
$allowed_origins = [
|
||||||
header('Access-Control-Allow-Headers: Content-Type, Authorization');
|
'http://127.0.0.1:8088',
|
||||||
|
'http://localhost:8088',
|
||||||
|
'https://code.danchadwick.dev',
|
||||||
|
];
|
||||||
|
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
|
||||||
|
// Check if the requester is in our whitelist
|
||||||
|
if (in_array($origin, $allowed_origins)) {
|
||||||
|
header("Access-Control-Allow-Origin: $origin");
|
||||||
|
header("Access-Control-Allow-Credentials: true");
|
||||||
|
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
|
||||||
|
header("Access-Control-Allow-Headers: Content-Type, Authorization");
|
||||||
|
}
|
||||||
|
|
||||||
// Handle preflight OPTIONS request
|
// Handle preflight OPTIONS request
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
http_response_code(200);
|
http_response_code(200);
|
||||||
exit(); // Stop here - don't process the request
|
exit(); // Stop here - don't process the request
|
||||||
}
|
}
|
||||||
// Make sure we have a path and account before proceeding.
|
|
||||||
$path = $this->request->post('path');
|
$raw_input = file_get_contents('php://input');
|
||||||
$account_id = $this->request->post('account');
|
$json_data = json_decode($raw_input, true) ?? [];
|
||||||
if (
|
$path = $json_data['path'] ?? $this->request->post('path');
|
||||||
!$path ||
|
$account_id = $json_data['account'] ?? $this->request->post('account');
|
||||||
!$account_id
|
|
||||||
) {
|
if (!$path || !$account_id) {
|
||||||
|
// Log what actually arrived to help debugging
|
||||||
|
error_log("Missing Data - Path: $path, Account: $account_id. Raw: $raw_input");
|
||||||
header("HTTP/1.1 422 Unprocessable Entity");
|
header("HTTP/1.1 422 Unprocessable Entity");
|
||||||
die();
|
die("Missing required fields");
|
||||||
}
|
}
|
||||||
// @todo: validate the account id.
|
// @todo: validate the account id.
|
||||||
$entry = [
|
$entry = [
|
||||||
'page' => $path,
|
'page' => $path,
|
||||||
'account_id' => $account_id,
|
'account_id' => $account_id,
|
||||||
'user_agent' => $this->request->post('user_agent'),
|
'title' => $json_data['title'] ?? null,
|
||||||
'referrer' => $this->request->post('referrer'),
|
'user_agent' => $json_data['user_agent'] ?? null,
|
||||||
|
'screen_res' => $json_data['screen_res'] ?? null,
|
||||||
|
'language' => $json_data['lang'] ?? null,
|
||||||
|
'timestamp' => $json_data['ts'] ?? null,
|
||||||
|
'referrer' => $json_data['referrer'] ?? null,
|
||||||
'ip' => $this->request->ip(),
|
'ip' => $this->request->ip(),
|
||||||
];
|
];
|
||||||
$clean_entry = array_filter($entry);
|
$clean_entry = array_filter($entry);
|
||||||
|
|||||||
@@ -3,20 +3,59 @@
|
|||||||
namespace Origo\Controller;
|
namespace Origo\Controller;
|
||||||
|
|
||||||
use Origo\Entity\User;
|
use Origo\Entity\User;
|
||||||
|
use Origo\Controller\ControllerBase;
|
||||||
use Origo\Controller\ControllerInterface;
|
use Origo\Controller\ControllerInterface;
|
||||||
use Origo\Services\Template;
|
use Origo\Services\Template;
|
||||||
use Origo\Services\Request;
|
use Origo\Services\Request;
|
||||||
use Origo\Services\Renderer;
|
|
||||||
|
|
||||||
class UserDashboardController implements ControllerInterface {
|
class UserDashboardController extends ControllerBase implements ControllerInterface {
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the response for the dashboard.
|
|
||||||
*/
|
|
||||||
public function getResponse(): string {
|
public function getResponse(): string {
|
||||||
$template = new Template('dashboard.html');
|
$user = new User();
|
||||||
|
$user->loadUserFromSession($this->request->getCookie('ORIGOSESS'));
|
||||||
|
$user_id = $user->get('id');
|
||||||
|
|
||||||
return $template->render();
|
$query = $this->database->query("
|
||||||
|
SELECT pv.page, pv.title, pv.referrer, pv.ip, pv.language, pv.screen_res, pv.timestamp
|
||||||
|
FROM pageviews pv
|
||||||
|
JOIN user_accounts ua ON pv.account_id = ua.account_id
|
||||||
|
WHERE ua.user_id = :user_id
|
||||||
|
ORDER BY pv.timestamp DESC
|
||||||
|
");
|
||||||
|
$query->bindParam(':user_id', $user_id);
|
||||||
|
$query->execute();
|
||||||
|
$results = $query->fetchAll();
|
||||||
|
|
||||||
|
$rows = '';
|
||||||
|
if ($results) {
|
||||||
|
foreach ($results as $row) {
|
||||||
|
$page = htmlspecialchars($row['page']);
|
||||||
|
$title = htmlspecialchars($row['title']);
|
||||||
|
$referrer = htmlspecialchars($row['referrer']);
|
||||||
|
$ip = htmlspecialchars($row['ip']);
|
||||||
|
$language = htmlspecialchars($row['language']);
|
||||||
|
$screen_res = htmlspecialchars($row['screen_res']);
|
||||||
|
$timestamp = htmlspecialchars($row['timestamp']);
|
||||||
|
|
||||||
|
$rows .= "
|
||||||
|
<tr>
|
||||||
|
<td>{$title}</td>
|
||||||
|
<td>{$page}</td>
|
||||||
|
<td>{$referrer}</td>
|
||||||
|
<td>{$ip}</td>
|
||||||
|
<td>{$language}</td>
|
||||||
|
<td>{$screen_res}</td>
|
||||||
|
<td>{$timestamp}</td>
|
||||||
|
</tr>";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$rows = '<tr><td colspan="7">No pageviews yet.</td></tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$template = new Template('dashboard.html');
|
||||||
|
return $template->render([
|
||||||
|
'pageview_rows' => $rows,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,30 @@
|
|||||||
<h4>Actions</h4>
|
<h4>Actions</h4>
|
||||||
<ul id="dash-menu">
|
<ul id="dash-menu">
|
||||||
<li><a href="/planner" class="btn btn-primary"><span class="material-symbols-outlined">edit_calendar</span>Planner</a></li>
|
<li><a href="/planner" class="btn btn-primary"><span class="material-symbols-outlined">edit_calendar</span>Planner</a></li>
|
||||||
<li><a href="/analytics" class="btn btn-primary"><span class="material-symbols-outlined">area_chart</span>Analytics</a></li>
|
<li><a href="/user-logout" class="btn btn-accent"><span class="material-symbols-outlined">logout</span>Logout</a></li>
|
||||||
<li><a href="/user-logout" class="btn btn-accent"><span class="material-symbols-outlined"> logout </span>Logout</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="pageviews">
|
||||||
|
<h2>Pageviews</h2>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Page</th>
|
||||||
|
<th>Referrer</th>
|
||||||
|
<th>IP</th>
|
||||||
|
<th>Language</th>
|
||||||
|
<th>Screen</th>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ pageview_rows }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#dashboard {
|
#dashboard {
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
max-width: 800px;
|
max-width: 1200px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding-bottom: 100px;
|
padding: 0 24px 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1.page-title {
|
h1.page-title {
|
||||||
@@ -15,11 +15,71 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pageviews {
|
||||||
|
margin-top: 2rem;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--color-light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
thead tr {
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: var(--color-soft-cream);
|
||||||
|
|
||||||
|
th {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
border-bottom: 1px solid var(--color-light-gray);
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: var(--color-light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: var(--color-soft-cream);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -482,9 +482,9 @@ img {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#dashboard .dashboard-container {
|
#dashboard .dashboard-container {
|
||||||
max-width: 800px;
|
max-width: 1200px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
padding-bottom: 100px;
|
padding: 0 24px 100px;
|
||||||
}
|
}
|
||||||
#dashboard h1.page-title {
|
#dashboard h1.page-title {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -501,6 +501,54 @@ img {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
#dashboard #pageviews {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
#dashboard #pageviews h2 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
#dashboard #pageviews .table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--color-light-gray);
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table thead tr {
|
||||||
|
background: var(--color-primary);
|
||||||
|
color: var(--color-soft-cream);
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table thead tr th {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table tbody tr {
|
||||||
|
border-bottom: 1px solid var(--color-light-gray);
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table tbody tr:nth-child(even) {
|
||||||
|
background: var(--color-light-gray);
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table tbody tr:nth-child(odd) {
|
||||||
|
background: var(--color-soft-cream);
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table tbody tr:hover {
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
#dashboard #pageviews table.data-table tbody tr td {
|
||||||
|
padding: 0.65rem 1rem;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
max-width: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
#task-list .container {
|
#task-list .container {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"version":3,"sourceRoot":"","sources":["partials/reset.scss","partials/base.scss","partials/header.scss","partials/animations.scss","partials/login-form.scss","partials/403.scss","partials/dashboard.scss","partials/task-list.scss","style.scss"],"names":[],"mappings":"AAAA;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;IACE;;;AAIJ;AACE;EACA;AACA;EACA;;;AAGF;AACA;EACE;EACA;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAEF;EACE;;;AAGF;AAAA;AAAA;AAGA;EACE;;;ACpDF;AACE;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;AAEA;EACA;AAEA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;AAEA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;;AC9EN;EACE;EACA;EACA;;;AAIA;EACE;EACA;EACA;;;AAIJ;EACE;;;ACfF;EACE;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;;ACnBF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;AAGF;EACE;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;;;AC9IN;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;IAAW;;EACX;IAAM;;;AAGR;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;IAAW;;EACX;IAAM;;EACN;IAAM;;;AAGR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;IAAK;;;AAGP;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;;;AC5MJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;ACpBJ;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;;;ACrDF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA","file":"style.css"}
|
{"version":3,"sourceRoot":"","sources":["partials/reset.scss","partials/base.scss","partials/header.scss","partials/animations.scss","partials/login-form.scss","partials/403.scss","partials/dashboard.scss","partials/task-list.scss","style.scss"],"names":[],"mappings":"AAAA;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;IACE;;;AAIJ;AACE;EACA;AACA;EACA;;;AAGF;AACA;EACE;EACA;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAGF;AACA;EACE;;;AAEF;EACE;;;AAGF;AAAA;AAAA;AAGA;EACE;;;ACpDF;AACE;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;AAEA;EACA;AAEA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;EACA;EACA;AAEA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;AAEA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;AAIJ;EACE;;AAEA;EACE;;;AC9EN;EACE;EACA;EACA;;;AAIA;EACE;EACA;EACA;;;AAIJ;EACE;;;ACfF;EACE;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;EAEF;IACE;;;ACnBF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;AAAA;EAEE;EACA;EACA;EACA;;AAGF;EACE;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;;;AC9IN;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;IAAW;;EACX;IAAM;;;AAGR;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;IAAW;;EACX;IAAM;;EACN;IAAM;;;AAGR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;IAAK;;;AAGP;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;IACE;;EAGF;IACE;;EAGF;IACE;;EAGF;IACE;IACA;;EAGF;IACE;;;;AC5MJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;AACA;EACE;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAKF;EACE;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AC7EV;EACE;EACA;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;EACA;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;;;AAGF;AACA;EACE;EACA;EACA;;;ACrDF;EACE;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAGF;EACE;EACA","file":"style.css"}
|
||||||
Reference in New Issue
Block a user