Compare commits

..
5 Commits
Author SHA1 Message Date
nikola 937c06ef8d Login Done 2026-09-12 00:08:24 +02:00
nikola fb09048e45 Work on login 2026-09-11 22:23:00 +02:00
nikola abf00ac401 Working on UI 2026-09-11 16:06:49 +02:00
nikola fcd4d4743b Theme switcher and BULMA 2026-09-11 13:30:02 +02:00
nikola fb152bd023 small changes 2026-09-11 10:34:44 +02:00
32 changed files with 585 additions and 118 deletions
+1 -2
View File
@@ -4,5 +4,4 @@
session.use_strict_mode=1 session.use_strict_mode=1
``` ```
https://github.com/ldaptools/ldaptools https://bulma.io/
+34 -4
View File
@@ -6,7 +6,7 @@
"name": "portal-vue", "name": "portal-vue",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "7.3.1", "@fortawesome/fontawesome-free": "7.3.1",
"bootstrap": "5.3.8", "bulma": "^1.0.4",
"vue": "^3.5.40", "vue": "^3.5.40",
"vue-i18n": "^11.4.10", "vue-i18n": "^11.4.10",
"vue-multiselect": "^3.5.0", "vue-multiselect": "^3.5.0",
@@ -150,7 +150,31 @@
"@oxc-project/types": ["@oxc-project/types@0.148.0", "", {}, "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A=="], "@oxc-project/types": ["@oxc-project/types@0.148.0", "", {}, "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A=="],
"@popperjs/core": ["@popperjs/core@2.11.8", "", {}, "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="], "@parcel/watcher": ["@parcel/watcher@2.6.0", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.4" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.6.0", "@parcel/watcher-darwin-arm64": "2.6.0", "@parcel/watcher-darwin-x64": "2.6.0", "@parcel/watcher-freebsd-x64": "2.6.0", "@parcel/watcher-linux-arm-glibc": "2.6.0", "@parcel/watcher-linux-arm-musl": "2.6.0", "@parcel/watcher-linux-arm64-glibc": "2.6.0", "@parcel/watcher-linux-arm64-musl": "2.6.0", "@parcel/watcher-linux-x64-glibc": "2.6.0", "@parcel/watcher-linux-x64-musl": "2.6.0", "@parcel/watcher-win32-arm64": "2.6.0", "@parcel/watcher-win32-x64": "2.6.0" } }, "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w=="],
"@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.6.0", "", { "os": "android", "cpu": "arm64" }, "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA=="],
"@parcel/watcher-darwin-arm64": ["@parcel/watcher-darwin-arm64@2.6.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw=="],
"@parcel/watcher-darwin-x64": ["@parcel/watcher-darwin-x64@2.6.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw=="],
"@parcel/watcher-freebsd-x64": ["@parcel/watcher-freebsd-x64@2.6.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ=="],
"@parcel/watcher-linux-arm-glibc": ["@parcel/watcher-linux-arm-glibc@2.6.0", "", { "os": "linux", "cpu": "arm" }, "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg=="],
"@parcel/watcher-linux-arm-musl": ["@parcel/watcher-linux-arm-musl@2.6.0", "", { "os": "linux", "cpu": "arm" }, "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw=="],
"@parcel/watcher-linux-arm64-glibc": ["@parcel/watcher-linux-arm64-glibc@2.6.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g=="],
"@parcel/watcher-linux-arm64-musl": ["@parcel/watcher-linux-arm64-musl@2.6.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA=="],
"@parcel/watcher-linux-x64-glibc": ["@parcel/watcher-linux-x64-glibc@2.6.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A=="],
"@parcel/watcher-linux-x64-musl": ["@parcel/watcher-linux-x64-musl@2.6.0", "", { "os": "linux", "cpu": "x64" }, "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw=="],
"@parcel/watcher-win32-arm64": ["@parcel/watcher-win32-arm64@2.6.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ=="],
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.6.0", "", { "os": "win32", "cpu": "x64" }, "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A=="],
"@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.7", "", { "os": "android", "cpu": "arm" }, "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ=="], "@rolldown/binding-android-arm-eabi": ["@rolldown/binding-android-arm-eabi@1.2.7", "", { "os": "android", "cpu": "arm" }, "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ=="],
@@ -250,12 +274,12 @@
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="], "birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
"bootstrap": ["bootstrap@5.3.8", "", { "peerDependencies": { "@popperjs/core": "^2.11.8" } }, "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg=="],
"brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="], "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"bulma": ["bulma@1.0.4", "", {}, "sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g=="],
"cacheable": ["cacheable@2.5.0", "", { "dependencies": { "@cacheable/memory": "^2.2.0", "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" } }, "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g=="], "cacheable": ["cacheable@2.5.0", "", { "dependencies": { "@cacheable/memory": "^2.2.0", "@cacheable/utils": "^2.5.0", "hookified": "^1.15.0", "keyv": "^5.6.0", "qified": "^0.10.1" } }, "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
@@ -352,6 +376,8 @@
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"immutable": ["immutable@5.1.9", "", {}, "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg=="],
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
@@ -420,6 +446,8 @@
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="], "nostics": ["nostics@1.2.0", "", {}, "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg=="],
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
@@ -464,6 +492,8 @@
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"sass": ["sass@1.104.0", "", { "dependencies": { "chokidar": "^5.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-btHMApW2bgolvClhRW8AlQJzgI9lUB3pPSofBQQT+E46GWvf9o0TVQ13SYv5riWZVFyPN+JNz3TKW9XhBlc10w=="],
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="], "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
"semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+2 -1
View File
@@ -4,7 +4,8 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NASLOV</title> <title>CMMS</title>
<link rel="icon" type="image/x-icon" href="/vue/favicon.ico">
<script defer type="module" src="/vue/main.js"></script> <script defer type="module" src="/vue/main.js"></script>
</head> </head>
+3 -2
View File
@@ -4,11 +4,12 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "vite build && cp -R php dist/php && cp -R vendor dist/vendor", "build": "vite build && cp -R php dist/php && cp -R vendor dist/vendor",
"serve": "concurrently \"vite\" \"php -S localhost:8000\" \"mailpit\" \"caddy run --config Caddyfile\"" "serve": "concurrently \"vite\" \"php -S localhost:8000\" \"mailpit\" \"caddy run --config Caddyfile\"",
"php": "concurrently \"php -t dist -S localhost:8000\" \"mailpit\" \"caddy reverse-proxy -t localhost:8000\""
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "7.3.1", "@fortawesome/fontawesome-free": "7.3.1",
"bootstrap": "5.3.8", "bulma": "^1.0.4",
"vue": "^3.5.40", "vue": "^3.5.40",
"vue-i18n": "^11.4.10", "vue-i18n": "^11.4.10",
"vue-multiselect": "^3.5.0", "vue-multiselect": "^3.5.0",
+10
View File
@@ -8,3 +8,13 @@ $dotenv->load();
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/connection.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/connection.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/user.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/user.php';
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if (str_contains($contentType, 'application/json') || str_contains($contentType, 'text/plain')) {
$input = json_decode(file_get_contents("php://input"), true);
if (is_array($input)) {
$_POST = [...$_POST, ...$input];
}
}
}
+6 -5
View File
@@ -1,8 +1,8 @@
<?php <?php
$servername = getenv('DB_HOSTNAME'); $servername = $_ENV['DB_HOSTNAME'];
$username = getenv('DB_USERNAME'); $username = $_ENV['DB_USERNAME'];
$password = getenv('DB_PASS'); $password = $_ENV['DB_PASS'];
$dbname = getenv('DB_NAME'); $dbname = $_ENV['DB_NAME'];
try { try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password); $conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
@@ -11,7 +11,8 @@ try {
$conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); $conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch(PDOException $e) { } catch(PDOException $e) {
echo "Connection failed: " . $e->getMessage(); echo json_encode(["error" => "error.connect.faild", "exc" => $e->getMessage()]);
exit;
} }
+7 -3
View File
@@ -1,11 +1,12 @@
<?php <?php
function hasPermission(string $resourceName, string $actionName): bool function hasPermission(string $resourceName, string $actionName)
{ {
global $conn; global $conn;
if(!isset($_SESSION['id'])){ if(!isset($_SESSION['id'])){
return false; http_response_code(401);
exit;
} }
$sql = " $sql = "
@@ -28,5 +29,8 @@ function hasPermission(string $resourceName, string $actionName): bool
$result = $stmt->fetch(); $result = $stmt->fetch();
return ((int) $result['permission_count']) > 0; if(((int) $result['permission_count']) <= 0){
http_response_code(403);
exit;
}
} }
+13
View File
@@ -0,0 +1,13 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/auto.php';
if (!isset($_SESSION['id'])) {
http_response_code(401);
exit;
}
http_response_code(200);
echo json_encode([
"res" => "res.user.login.true",
"displayname" => $_SESSION['displayname']
]);
+50 -37
View File
@@ -2,37 +2,64 @@
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/auto.php'; require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/auto.php';
use LdapRecord\Connection; if (isset($_ENV['DEV_LOGIN'])) {
$nimrodLDAP = new Connection([ $email = $_POST['username'];
'hosts' => [getenv('LDAP_HOST')], $find = $conn->prepare("SELECT user_id, username, display_name FROM users WHERE email = :email");
'port' => 389, $find->execute([':email' => $email]);
'base_dn' => getenv('LDAP_BASEDN'), $obstojeciUporabnik = $find->fetch();
'username' => getenv('LDAP_USER'),
'password' => getenv('LDAP_SECRET')
]);
if (!isset($_POST['uporabnik']) || !isset($_POST['geslo'])) { $_SESSION['id'] = $obstojeciUporabnik['user_id'];
echo json_encode(["napaka" => "manjkajo podatki"]); $_SESSION['username'] = $obstojeciUporabnik['username'];
$_SESSION['displayname'] = $obstojeciUporabnik['display_name'];
$_SESSION['email'] = $email;
echo json_encode([
"res" => "res.user.login.true",
"displayname" => $_SESSION['displayname']
]);
exit; exit;
} }
$username = $_POST['uporabnik']; use LdapRecord\Connection;
$geslo = $_POST['geslo']; $nimrodLDAP = new Connection([
'hosts' => [$_ENV['LDAP_HOST']],
'port' => 389,
'base_dn' => $_ENV['LDAP_BASEDN'],
'username' => $_ENV['LDAP_USER'],
'password' => $_ENV['LDAP_SECRET']
]);
if (!isset($_POST['username']) || !isset($_POST['pass'])) {
http_response_code(401);
echo json_encode(["error" => "error.user.login.missing"]);
exit;
}
$username = $_POST['username'];
$geslo = $_POST['pass'];
try { try {
$nimrodLDAP->connect(); $nimrodLDAP->connect();
} catch (Exception $e) { } catch (Exception $e) {
echo 'napaka pri povezavi na LDap'; http_response_code(401);
echo $e; echo json_encode(["error" => "error.user.login.connect"]);
exit; exit;
} }
$LUser = $nimrodLDAP->query()->findBy('samaccountname', $username); $LUser = $nimrodLDAP->query()->findBy('samaccountname', $username);
if (empty($LUser)) { if (empty($LUser)) {
echo json_encode(["napaka" => "uporabnik ni najden"]); http_response_code(401);
echo json_encode(["error" => "error.user.login.notFound"]);
exit; exit;
} }
$emailUserja = $LUser['userprincipalname'][0];
$user = $LUser['distinguishedname'][0];
if (!$nimrodLDAP->auth()->attempt($user, $geslo, $stayAuthenticated = true)) {
http_response_code(401);
echo json_encode(["error" => "error.user.login.wrongPassword"]);
exit;
}
$vGrupah = $LUser['memberof']; $vGrupah = $LUser['memberof'];
$dGrupe = [ $dGrupe = [
@@ -44,14 +71,8 @@ $razlikaGrup = array_intersect(
array_map('strtolower', $dGrupe) array_map('strtolower', $dGrupe)
); );
if (count($razlikaGrup) <= 0) { if (count($razlikaGrup) <= 0) {
echo json_encode(["napaka" => "nimaš pravic skupinskih"]); http_response_code(401);
exit; echo json_encode(["error" => "error.user.login.groupRight"]);
}
$emailUserja = $LUser['userprincipalname'][0];
$user = $LUser['distinguishedname'][0];
if (!$nimrodLDAP->auth()->attempt($user, $geslo, $stayAuthenticated = true)) {
echo json_encode(["napaka" => "napacno geslo"]);
exit; exit;
} }
@@ -63,8 +84,8 @@ $find->execute([':email' => $email]);
$obstojeciUporabnik = $find->fetch(); $obstojeciUporabnik = $find->fetch();
if ($obstojeciUporabnik) { if ($obstojeciUporabnik) {
$update = $conn->prepare("UPDATE users SET display_name = :display_name WHERE user_id = :user_id"); //$update = $conn->prepare("UPDATE users SET display_name = :display_name WHERE user_id = :user_id");
$update->execute([':display_name' => $uporabnik, ':user_id' => $obstojeciUporabnik['user_id']]); //$update->execute([':display_name' => $uporabnik, ':user_id' => $obstojeciUporabnik['user_id']]);
$_SESSION['id'] = $obstojeciUporabnik['user_id']; $_SESSION['id'] = $obstojeciUporabnik['user_id'];
$_SESSION['username'] = $obstojeciUporabnik['username']; $_SESSION['username'] = $obstojeciUporabnik['username'];
@@ -78,15 +99,7 @@ if ($obstojeciUporabnik) {
$_SESSION['displayname'] = $uporabnik; $_SESSION['displayname'] = $uporabnik;
$_SESSION['email'] = $email; $_SESSION['email'] = $email;
echo json_encode(["res" => "uspesno"]); echo json_encode([
"res" => "res.user.login.true",
"displayname" => $_SESSION['displayname']
]);
+10
View File
@@ -0,0 +1,10 @@
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/auto/auto.php';
$sql = "SELECT username, email, group_id FROM users;";
$res = $conn->query($sql);
echo json_encode($res);
+2
View File
@@ -1,2 +1,4 @@
<?php <?php
session_start();
session_destroy(); session_destroy();
echo json_encode(["res" => "res.user.loginout.true"]);
+83 -5
View File
@@ -38,9 +38,45 @@ declare module 'vue-router/auto-routes' {
Record<never, never>, Record<never, never>,
| never | never
>, >,
'/test': RouteRecordInfo< '/login': RouteRecordInfo<
'/test', '/login',
'/test', '/login',
Record<never, never>,
Record<never, never>,
| never
>,
'/logout': RouteRecordInfo<
'/logout',
'/logout',
Record<never, never>,
Record<never, never>,
| never
>,
'/settings': RouteRecordInfo<
'/settings',
'/settings',
Record<never, never>,
Record<never, never>,
| '/settings/groups'
| '/settings/users'
>,
'/settings/groups': RouteRecordInfo<
'/settings/groups',
'/settings/groups',
Record<never, never>,
Record<never, never>,
| never
>,
'/settings/users': RouteRecordInfo<
'/settings/users',
'/settings/users',
Record<never, never>,
Record<never, never>,
| never
>,
'/user_settings': RouteRecordInfo<
'/user_settings',
'/user_settings',
Record<never, never>, Record<never, never>,
Record<never, never>, Record<never, never>,
| never | never
@@ -66,9 +102,51 @@ declare module 'vue-router/auto-routes' {
pathParamNames: pathParamNames:
| never | never
} }
'vue/routes/test.vue': { 'vue/routes/login.vue': {
routes: routes:
| '/test' | '/login'
views:
| never
pathParamNames:
| never
}
'vue/routes/logout.vue': {
routes:
| '/logout'
views:
| never
pathParamNames:
| never
}
'vue/routes/settings.vue': {
routes:
| '/settings'
| '/settings/groups'
| '/settings/users'
views:
| 'default'
pathParamNames:
| never
}
'vue/routes/settings/groups.vue': {
routes:
| '/settings/groups'
views:
| never
pathParamNames:
| never
}
'vue/routes/settings/users.vue': {
routes:
| '/settings/users'
views:
| never
pathParamNames:
| never
}
'vue/routes/user_settings.vue': {
routes:
| '/user_settings'
views: views:
| never | never
pathParamNames: pathParamNames:
+1
View File
@@ -12,6 +12,7 @@ export default defineConfig(() => ({
VueRouter({ VueRouter({
routesFolder: 'vue/routes', routesFolder: 'vue/routes',
importMode: 'sync', importMode: 'sync',
exclude: ['vue/routes/**/comp/**'],
}), }),
vue(), vue(),
], ],
+11 -9
View File
@@ -1,15 +1,17 @@
<template> <template>
<LanguageSwitcher /> <div class="is-flex">
<p> <Sidebar v-if="isLogedIn"/>
{{ t('app.hello') }} <main class="p-5 is-flex-grow-1">
</p> <RouterView />
</main>
<RouterView /> </div>
</template> </template>
<script setup> <script setup>
import { useI18n } from 'vue-i18n' import { applyTheme } from '@/components/theme';
import LanguageSwitcher from '@/components/LanguageSwitcher.vue' import Sidebar from '@/components/Sidebar.vue';
import { isLogedIn } from './global';
applyTheme();
const { t } = useI18n();
</script> </script>
+13 -9
View File
@@ -1,13 +1,17 @@
<template> <template>
<div class="dropdown"> <div class="dropdown is-hoverable">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown"> <div class="dropdown-trigger">
{{ locale.toUpperCase() }} <button class="button">
</button> <span>{{ locale.toUpperCase() }}</span>
<ul class="dropdown-menu"> </button>
<li v-for="loc in SUPPORT_LOCALES" :key="loc"> </div>
<a class="dropdown-item" href="#" @click.prevent="setLocale(loc)">{{ loc.toUpperCase() }}</a> <div class="dropdown-menu">
</li> <div class="dropdown-content">
</ul> <button v-for="loc in SUPPORT_LOCALES" :key="loc" @click.prevent="setLocale(loc)" class="dropdown-item">
{{ loc.toUpperCase() }}
</button>
</div>
</div>
</div> </div>
</template> </template>
+56
View File
@@ -0,0 +1,56 @@
<template>
<aside class="menu m-3" :class="{ 'is-collapsed': collapsed }">
<div class="level">
<p class="level-item" v-if="!collapsed">CMMS</p>
<div class="level-item">
<button class="button" :title="collapsed ? 'Expand menu' : 'Collapse menu'" @click="toggleSidebar">
<span class="icon">
<i class="fa-solid" :class="collapsed ? 'fa-angles-right' : 'fa-angles-left'"></i>
</span>
</button>
</div>
</div>
<ul class="menu-list">
<li v-for="item in items" :key="item.to">
<RouterLink :to="item.to" active-class="is-active" :class="{ 'has-text-centered': collapsed }"
:title="collapsed ? item.label : null">
<span class="icon">
<i class="fa-solid" :class="item.icon"></i>
</span>
<span v-if="!collapsed"> {{ t(item.label) }}</span>
</RouterLink>
</li>
</ul>
<p class="menu-label" v-if="!collapsed">{{ displayName }}</p>
</aside>
</template>
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { RouterLink } from 'vue-router';
import { displayName } from '@/global.js';
const { t } = useI18n()
const collapsed = ref(localStorage.getItem('sidebarCollapsed') === 'true');
function toggleSidebar() {
collapsed.value = !collapsed.value;
localStorage.setItem('sidebarCollapsed', collapsed.value);
}
// Example items — replace `to` with real routes as pages are added.
const items = [
{ label: 'menu.dashboard', icon: 'fa-gauge-high', to: '/' },
// { label: 'Assets', icon: 'fa-boxes-stacked', to: '/assets' },
// { label: 'Work Orders', icon: 'fa-clipboard-list', to: '/work-orders' },
// { label: 'Maintenance', icon: 'fa-screwdriver-wrench', to: '/maintenance' },
// { label: 'Reports', icon: 'fa-chart-line', to: '/reports' },
{ label: 'menu.userSettings', icon: 'fa-user-gear', to: '/user_settings' },
{ label: 'menu.settings', icon: 'fa-gear', to: '/settings' },
{ label: 'menu.logout', icon: 'fa-right-from-bracket', to: '/logout' },
];
</script>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class="dropdown is-hoverable">
<div class="dropdown-trigger">
<button class="button">
<span>{{ t('userSettings.themeSelect') }}</span>
</button>
</div>
<div class="dropdown-menu">
<div class="dropdown-content">
<button class="dropdown-item" @click.prevent="setTheme('light')">
{{ t('userSettings.light') }}
</button>
<button class="dropdown-item" @click.prevent="setTheme('dark')">
{{ t('userSettings.dark') }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
import { setTheme } from './theme';
const {t} = useI18n();
</script>
+21
View File
@@ -0,0 +1,21 @@
import { ref } from 'vue';
const theme = ref(localStorage.getItem('theme') || 'auto');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
export function applyTheme() {
const resolved = theme.value === 'auto' ? (prefersDark.matches ? 'dark' : 'light') : theme.value;
document.documentElement.setAttribute('data-theme', resolved);
}
export function setTheme(value) {
theme.value = value;
localStorage.setItem('theme', value);
applyTheme();
}
prefersDark.addEventListener('change', () => {
if (theme.value === 'auto') applyTheme();
});
+22
View File
@@ -0,0 +1,22 @@
@import 'vue-multiselect/dist/vue-multiselect.css';
@import '@fortawesome/fontawesome-free/css/all.min.css';
@import 'bulma/css/bulma.min.css';
html {
background-color: var(--bulma-scheme-main-ter);
}
.dropdown {
--bulma-dropdown-menu-min-width: 4rem;
}
a.dropdown-item,
button.dropdown-item {
padding-inline-end: 1rem;
}
[data-theme=dark],
.theme-dark {
--bulma-scheme-main-ter-l: 20%;
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

+5
View File
@@ -0,0 +1,5 @@
import { ref } from "vue";
export const isLogedIn = ref(false);
export const displayName = ref("");
+18 -3
View File
@@ -1,4 +1,19 @@
{ {
"userSettings": {
"themeSelect": "Select theme",
"light": "Light",
"dark": "Dark"
},
"menu": {
"settings": "Settings",
"userSettings": "User settings",
"dashboard": "Dashboard",
"logout": "Logout"
},
"settings": {
"users": "Users",
"groups": "Groups"
},
"common": { "common": {
"save": "Save", "save": "Save",
"cancel": "Cancel", "cancel": "Cancel",
@@ -9,8 +24,8 @@
"search": "Search", "search": "Search",
"loading": "Loading..." "loading": "Loading..."
}, },
"app": { "login": {
"hello": "HELLO", "username": "Username",
"world": "WORLD" "password": "Password"
} }
} }
+18 -3
View File
@@ -1,4 +1,19 @@
{ {
"userSettings": {
"themeSelect": "Izberi temo",
"light": "Svetlo",
"dark": "Temno"
},
"menu": {
"settings": "Nastavitve",
"userSettings": "Uporabniške nastavitve",
"dashboard": "Nadzorna plošča",
"logout": "Odjava"
},
"settings": {
"users": "Uporabni",
"groups": "Skupine"
},
"common": { "common": {
"save": "Shrani", "save": "Shrani",
"cancel": "Prekliči", "cancel": "Prekliči",
@@ -9,8 +24,8 @@
"search": "Iskanje", "search": "Iskanje",
"loading": "Nalaganje..." "loading": "Nalaganje..."
}, },
"app": { "login": {
"hello": "ZDRAVO", "username": "Uporabniško ime",
"world": "SVET" "password": "Geslo"
} }
} }
+9 -5
View File
@@ -3,13 +3,10 @@ import { createRouter, createWebHashHistory } from 'vue-router'
import { routes, handleHotUpdate } from 'vue-router/auto-routes' import { routes, handleHotUpdate } from 'vue-router/auto-routes'
import { i18n, setLocale, initialLocale } from './i18n.js' import { i18n, setLocale, initialLocale } from './i18n.js'
import '@fortawesome/fontawesome-free/css/all.min.css' import './css/all.css'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
import 'vue-multiselect/dist/vue-multiselect.css';
import App from './app.vue' import App from './app.vue'
import { isLogedIn } from './global.js'
await setLocale(initialLocale()) await setLocale(initialLocale())
@@ -22,5 +19,12 @@ if (import.meta.hot) {
handleHotUpdate(router) handleHotUpdate(router)
} }
router.beforeEach(async (to, from) => {
if (!isLogedIn.value && to.name != '/login' ) {
// redirect the user to the login page
return { name: '/login' }
}
})
createApp(App).use(router).use(i18n).mount('#app') createApp(App).use(router).use(i18n).mount('#app')
+2 -14
View File
@@ -1,22 +1,10 @@
<template> <template>
<p>{{ t('app.world') }}</p> <h1 class="title">{{ t('menu.dashboard') }}</h1>
<p v-for="value in j">
{{ value }}
</p>
</template> </template>
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n';
const { t } = useI18n(); const { t } = useI18n();
const j = ref([]);
onMounted(async () => {
const res = await fetch("/php/notifications.php")
j.value = await res.json();
});
</script> </script>
+71
View File
@@ -0,0 +1,71 @@
<template>
<div class="columns is-vcentered" style="height: 50rem;">
<div class="column is-4 is-offset-4">
<form @submit.prevent="logIn">
<div class="field">
<label class="label">{{ t('login.username') }}</label>
<div class="control">
<input class="input" type="text" v-model="userName">
</div>
</div>
<div class="field">
<label class="label">{{ t('login.password') }}</label>
<div class="control">
<input class="input" type="password" v-model="password">
</div>
</div>
<div class="field">
<div class="control">
<button class="button is-primary">{{ t('common.confirm') }}</button>
</div>
</div>
</form>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { displayName, isLogedIn } from '../global';
import { useRouter } from 'vue-router';
const { t } = useI18n();
const route = useRouter();
onMounted(async () => {
console.log("HE");
const res = await fetch("/php/user/is_loged_in.php");
if (res.ok) {
const body = await res.json();
displayName.value = body.displayname;
isLogedIn.value = true;
route.push("/");
}
});
const userName = ref();
const password = ref();
async function logIn() {
const mes = {
"username": userName.value,
"pass": password.value
}
const res = await fetch("/php/user/ldapLogin.php", {
"body": JSON.stringify(mes),
"method": "POST"
});
const body = await res.json();
if (res.ok) {
isLogedIn.value = true;
displayName.value = body.displayname;
route.push("/");
} else {
console.log(body.error);
isLogedIn.value = true;
}
}
</script>
+18
View File
@@ -0,0 +1,18 @@
<template>
</template>
<script setup>
import { onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { displayName, isLogedIn } from '../global';
const router = useRouter();
onMounted(async () => {
await fetch("/php/user/logout.php");
isLogedIn.value = false;
displayName.value = "";
router.push("/login");
});
</script>
+26
View File
@@ -0,0 +1,26 @@
<template>
<h1 class="title">{{ t('menu.settings') }}</h1>
<div class="tabs">
<ul>
<li v-for="i in routes" :class="{ 'is-active': route.path === i.path }">
<RouterLink :to="i.path">
{{ t(i.title) }}
</RouterLink>
</li>
</ul>
</div>
<RouterView />
</template>
<script setup>
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
const { t } = useI18n();
const route = useRoute();
const routes = [
{"title": "settings.users", "path": "/settings/users"},
{"title": "settings.groups", "path": "/settings/groups"}
];
</script>
+8
View File
@@ -0,0 +1,8 @@
<template>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
+16
View File
@@ -0,0 +1,16 @@
<template>
<table class="table">
<thead>
<tr>
<th>user</th>
<th>email</th>
<th>group</th>
</tr>
</thead>
</table>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
-8
View File
@@ -1,8 +0,0 @@
<template>
{{ t('common.save') }}
</template>
<script setup>
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
</script>
+16
View File
@@ -0,0 +1,16 @@
<template>
<h1 class="title">{{ t('menu.userSettings') }}</h1>
<LanguageSwitcher />
<ThemeSwitcher />
</template>
<script setup>
import ThemeSwitcher from '@/components/ThemeSwitcher.vue';
import LanguageSwitcher from '@/components/LanguageSwitcher.vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>