Compare commits

...

2 Commits

Author SHA1 Message Date
7853c6e285 Convert admin page to Vue 2023-12-16 09:19:40 +01:00
f674b0d934 Standardize admin page 2023-12-16 09:18:28 +01:00
21 changed files with 930 additions and 183 deletions

View File

@@ -4,6 +4,7 @@ var webpack = require("webpack");
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const SymlinkWebpackPlugin = require('symlink-webpack-plugin'); const SymlinkWebpackPlugin = require('symlink-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader')
module.exports = { module.exports = {
entry: { entry: {
@@ -16,29 +17,28 @@ module.exports = {
}, },
devtool: "inline-source-map", devtool: "inline-source-map",
module: { module: {
rules: [ rules: [{
{ test: /\.vue$/,
loader: 'vue-loader'
}, {
test: /\.js$/, test: /\.js$/,
exclude: /node_modules/, exclude: file => (/node_modules/.test(file) && !/\.vue\.js/.test(file)),
use: { use: {
loader: 'babel-loader', loader: 'babel-loader',
options: { options: {
presets: ['@babel/preset-env'], presets: ['@babel/preset-env'],
}, }
}, },
}, }, {
{
test: /\.html$/i, test: /\.html$/i,
loader: "html-loader", loader: "html-loader",
}, }, {
{
test: /\.(woff|woff2|eot|ttf|otf)$/i, test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource' type: 'asset/resource'
}, }, {
{
test: /\.s[ac]ss$/i, test: /\.s[ac]ss$/i,
use: [ use: [
'style-loader', 'vue-style-loader',
'css-loader', 'css-loader',
'resolve-url-loader', 'resolve-url-loader',
{ {
@@ -49,12 +49,10 @@ module.exports = {
} }
} }
] ]
}, }, {
{
test: /\.css$/i, test: /\.css$/i,
use: ["style-loader", "css-loader"], use: ["vue-style-loader", "css-loader"],
}, }, {
{
test: /\.(png|svg|jpg|jpeg|gif)$/i, test: /\.(png|svg|jpg|jpeg|gif)$/i,
use: { use: {
loader: "url-loader", loader: "url-loader",
@@ -92,7 +90,12 @@ module.exports = {
}] }]
}), }),
new SymlinkWebpackPlugin({ origin: '../files/', symlink: 'files' }), new SymlinkWebpackPlugin({ origin: '../files/', symlink: 'files' }),
new CleanWebpackPlugin() new CleanWebpackPlugin(),
new webpack.DefinePlugin({
__VUE_OPTIONS_API__: 'true',
__VUE_PROD_DEVTOOLS__: 'false'
}),
new VueLoaderPlugin()
], ],
resolve: { resolve: {
extensions: ['', '.js'], extensions: ['', '.js'],

View File

@@ -11,6 +11,9 @@ admin_config = Config
admin_upload = Upload admin_upload = Upload
save = Save save = Save
admin_save_success = Saved admin_save_success = Saved
admin_create_success= Created
admin_delete_success= Deleted
no_auth = No authorization
track_main = Main track track_main = Main track
track_off-track = Off-track track_off-track = Off-track
@@ -64,7 +67,7 @@ id_project = Project ID
project = Project project = Project
projects = Projects projects = Projects
new_project = New Project new_project = New Project
update_project = Update Project update_project = Update project messages
hikes = Hikes hikes = Hikes
mode = Mode mode = Mode
mode_previz = Project in preparation mode_previz = Project in preparation
@@ -76,6 +79,7 @@ end = End
feeds = Feeds feeds = Feeds
id_feed = Feed ID id_feed = Feed ID
ref_feed_id = Ref. Feed ID ref_feed_id = Ref. Feed ID
new_feed = New feed
id_spot = Spot ID id_spot = Spot ID
name = Name name = Name
status = Status status = Status

View File

@@ -11,6 +11,9 @@ admin_config = Configuración
admin_upload = Cargar admin_upload = Cargar
save = Guardar save = Guardar
admin_save_success = Guardado admin_save_success = Guardado
admin_create_success= Creado
admin_delete_success= Eliminado
no_auth = No autorización
track_main = Camino principal track_main = Camino principal
track_off-track = Variante track_off-track = Variante
@@ -64,7 +67,7 @@ id_project = Proyecto ID
project = Proyecto project = Proyecto
projects = Proyectos projects = Proyectos
new_project = Nuevo proyecto new_project = Nuevo proyecto
update_project = Actualizar el proyecto update_project = Actualizar los mensajes del proyecto
hikes = Senderos hikes = Senderos
mode = Modo mode = Modo
mode_previz = Proyecto en preparación mode_previz = Proyecto en preparación
@@ -76,6 +79,7 @@ end = Fin
feeds = Feeds feeds = Feeds
id_feed = ID Feed id_feed = ID Feed
ref_feed_id = ID Feed ref. ref_feed_id = ID Feed ref.
new_feed = Nuevo feed
id_spot = ID Spot id_spot = ID Spot
name = Descripción name = Descripción
status = Estado status = Estado

View File

@@ -11,6 +11,9 @@ admin_config = Paramètres
admin_upload = Uploader admin_upload = Uploader
save = Sauvegarder save = Sauvegarder
admin_save_success = Sauvegardé admin_save_success = Sauvegardé
admin_create_success= Créé
admin_delete_success= Supprimé
no_auth = Pas d'authorisation
track_main = Trajet principal track_main = Trajet principal
track_off-track = Variante track_off-track = Variante
@@ -64,7 +67,7 @@ id_project = ID projet
project = Projet project = Projet
projects = Projets projects = Projets
new_project = Nouveau projet new_project = Nouveau projet
update_project = Mettre à jour le projet update_project = Mettre à jour les messages du projet
hikes = Randonnées hikes = Randonnées
mode = Mode mode = Mode
mode_previz = Projet en cours de préparation mode_previz = Projet en cours de préparation
@@ -76,6 +79,7 @@ end = Arrivée
feeds = Feeds feeds = Feeds
id_feed = ID Feed id_feed = ID Feed
ref_feed_id = ID Feed ref. ref_feed_id = ID Feed ref.
new_feed = Nouveau feed
id_spot = ID Spot id_spot = ID Spot
name = Description name = Description
status = Statut status = Statut

View File

@@ -709,40 +709,66 @@ class Spot extends Main
return self::getJsonResult($bSuccess, $sDesc, array($sType=>array($asResult))); return self::getJsonResult($bSuccess, $sDesc, array($sType=>array($asResult)));
} }
public function delAdminSettings($sType, $iId) { public function createAdminSettings($sType) {
$bSuccess = false; $bSuccess = false;
$sDesc = ''; $sDesc = '';
$asResult = array();
switch($sType) {
case 'project':
$oProject = new Project($this->oDb);
$iNewProjectId = $oProject->createProjectId();
$oFeed = new Feed($this->oDb);
$oFeed->createFeedId($iNewProjectId);
$bSuccess = $iNewProjectId > 0;
$asResult = array(
'project' => array($oProject->getProject()),
'feed' => array($oFeed->getFeed())
);
break;
case 'feed':
$oFeed = new Feed($this->oDb);
$iNewFeedId = $oFeed->createFeedId($this->oProject->getProjectId());
$bSuccess = $iNewFeedId > 0;
$asResult = array(
'feed' => array($oFeed->getFeed())
);
break;
}
return self::getJsonResult($bSuccess, $sDesc, $asResult);
}
public function deleteAdminSettings($sType, $iId) {
$bSuccess = false;
$sDesc = '';
$asResult = array();
switch($sType) { switch($sType) {
case 'project': case 'project':
$oProject = new Project($this->oDb, $iId); $oProject = new Project($this->oDb, $iId);
$asResult = $oProject->delete(); $asResult = $oProject->delete();
$sDesc = $asResult['project'][0]['desc']; $sDesc = $asResult['project'][0]['desc'];
$bSuccess = $asResult['project'][0]['del'];
break; break;
case 'feed': case 'feed':
$oFeed = new Feed($this->oDb, $iId); $oFeed = new Feed($this->oDb, $iId);
$asResult = array('feed'=>array($oFeed->delete())); $asResult = array('feed' => array($oFeed->delete()));
$sDesc = $asResult['feed'][0]['desc']; $sDesc = $asResult['feed'][0]['desc'];
$bSuccess = $asResult['feed'][0]['del'];
break;
case 'user':
$asResult = array('user' => array($this->oUser->removeUser($iId)));
$sDesc = $asResult['user'][0]['desc'];
$bSuccess = $asResult['user'][0]['result'];
break; break;
} }
$bSuccess = ($sDesc=='');
return self::getJsonResult($bSuccess, $sDesc, $asResult); return self::getJsonResult($bSuccess, $sDesc, $asResult);
} }
public function createProject() {
$oProject = new Project($this->oDb);
$iNewProjectId = $oProject->createProjectId();
$oFeed = new Feed($this->oDb);
$oFeed->createFeedId($iNewProjectId);
return self::getJsonResult($iNewProjectId>0, '', array(
'project' => array($oProject->getProject()),
'feed' => array($oFeed->getFeed())
));
}
public static function decToDms($dValue, $sType) { public static function decToDms($dValue, $sType) {
if($sType=='lat') $sDirection = ($dValue >= 0)?'N':'S'; //Latitude if($sType=='lat') $sDirection = ($dValue >= 0)?'N':'S'; //Latitude
else $sDirection = ($dValue >= 0)?'E':'W'; //Longitude else $sDirection = ($dValue >= 0)?'E':'W'; //Longitude

View File

@@ -20,6 +20,7 @@ class User extends PhpObject {
//Cookie //Cookie
const COOKIE_ID_USER = 'subscriber'; const COOKIE_ID_USER = 'subscriber';
const COOKIE_DURATION = 60 * 60 * 24 * 365; //1 year const COOKIE_DURATION = 60 * 60 * 24 * 365; //1 year
/** /**
* Database Handle * Database Handle
* @var Db * @var Db
@@ -33,7 +34,7 @@ class User extends PhpObject {
public function __construct(Db &$oDb) { public function __construct(Db &$oDb) {
parent::__construct(__CLASS__); parent::__construct(__CLASS__);
$this->oDb = &$oDb; $this->oDb = &$oDb;
$this->iUserId = 0; $this->setUserId(0);
$this->asUserInfo = array( $this->asUserInfo = array(
'id' => 0, 'id' => 0,
Db::getId(self::USER_TABLE) => 0, Db::getId(self::USER_TABLE) => 0,
@@ -47,6 +48,51 @@ class User extends PhpObject {
$this->checkUserCookie(); $this->checkUserCookie();
} }
public function getUserId() {
return $this->iUserId;
}
public function setUserId($iUserId) {
$this->iUserId = 0;
if($iUserId > 0) {
$asUser = $this->getActiveUserInfo($iUserId);
if(!empty($asUser)) {
$this->iUserId = $iUserId;
$this->asUserInfo = $asUser;
}
}
}
public function getUserInfo() {
return $this->asUserInfo;
}
public function getActiveUserInfo($iUserId) {
$asUsersInfo = array();
if($iUserId > 0) $asUsersInfo = $this->getActiveUsersInfo($iUserId);
return empty($asUsersInfo)?array():array_shift($asUsersInfo);
}
public function getActiveUsersInfo($iUserId=-1) {
//Mapping between user fields and DB fields
$asSelect = array_keys($this->asUserInfo);
$asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE)." AS id";
//Non-admin cannot access clearance info
if(!$this->checkUserClearance(self::CLEARANCE_ADMIN)) unset($asSelect['clearance']);
$asInfo = array(
'select' => $asSelect,
'from' => self::USER_TABLE,
'constraint'=> array('active'=>self::USER_ACTIVE)
);
if($iUserId != -1) $asInfo['constraint'][Db::getId(self::USER_TABLE)] = $iUserId;
return $this->oDb->selectRows($asInfo);
}
public function getLang() { public function getLang() {
return $this->asUserInfo['language']; return $this->asUserInfo['language'];
} }
@@ -95,20 +141,25 @@ class User extends PhpObject {
return Spot::getResult($bSuccess, $sDesc); return Spot::getResult($bSuccess, $sDesc);
} }
public function removeUser() { public function removeUser($iUserId=0) {
$iUserId = ($iUserId > 0)?$iUserId:$this->getUserId();
$bSelf = ($iUserId == $this->getUserId());
$bSuccess = false; $bSuccess = false;
$sDesc = ''; $sDesc = '';
if($this->iUserId > 0) { if($bSelf || $this->checkUserClearance(self::CLEARANCE_ADMIN)) {
$iUserId = $this->oDb->updateRow(self::USER_TABLE, $this->getUserId(), array('active'=>self::USER_INACTIVE)); if($this->getUserId() > 0) {
if($iUserId==0) $sDesc = 'lang:error_commit_db'; $iUserId = $this->oDb->updateRow(self::USER_TABLE, $iUserId, array('active' => self::USER_INACTIVE));
else { if($iUserId==0) $sDesc = 'lang:error_commit_db';
$sDesc = 'lang:nl_unsubscribed'; else {
$this->updateCookie(-60 * 60); //Set Cookie in the past, deleting it $sDesc = 'lang:nl_unsubscribed';
$bSuccess = true; if($bSelf) $this->updateCookie(-60 * 60); //Set Cookie in the past, deleting it
$bSuccess = true;
}
} }
else $sDesc = 'lang:nl_unknown_email';
} }
else $sDesc = 'lang:nl_unknown_email'; else $sDesc = 'lang:no_auth';
return Spot::getResult($bSuccess, $sDesc); return Spot::getResult($bSuccess, $sDesc);
} }
@@ -131,49 +182,6 @@ class User extends PhpObject {
} }
} }
public function getUserId() {
return $this->iUserId;
}
public function setUserId($iUserId) {
$this->iUserId = 0;
$asUser = $this->getActiveUserInfo($iUserId);
if(!empty($asUser)) {
$this->iUserId = $iUserId;
$this->asUserInfo = $asUser;
}
}
public function getUserInfo() {
return $this->asUserInfo;
}
public function getActiveUserInfo($iUserId) {
$asUsersInfo = array();
if($iUserId > 0) $asUsersInfo = $this->getActiveUsersInfo($iUserId);
return empty($asUsersInfo)?array():array_shift($asUsersInfo);
}
public function getActiveUsersInfo($iUserId=-1) {
//Mapping between user fields and DB fields
$asSelect = array_keys($this->asUserInfo);
$asSelect[array_search('id', $asSelect)] = Db::getId(self::USER_TABLE)." AS id";
//Non-admin cannot access clearance info
if(!$this->checkUserClearance(self::CLEARANCE_ADMIN)) unset($asSelect['clearance']);
$asInfo = array(
'select' => $asSelect,
'from' => self::USER_TABLE,
'constraint'=> array('active'=>self::USER_ACTIVE)
);
if($iUserId != -1) $asInfo['constraint'][Db::getId(self::USER_TABLE)] = $iUserId;
return $this->oDb->selectRows($asInfo);
}
public function checkUserClearance($iClearance) public function checkUserClearance($iClearance)
{ {
return ($this->asUserInfo['clearance'] >= $iClearance); return ($this->asUserInfo['clearance'] >= $iClearance);

View File

@@ -71,17 +71,17 @@ if($sAction!='')
case 'add_comment': case 'add_comment':
$sResult = $oSpot->addComment($iId, $sContent); $sResult = $oSpot->addComment($iId, $sContent);
break; break;
case 'admin_new':
$sResult = $oSpot->createProject();
break;
case 'admin_get': case 'admin_get':
$sResult = $oSpot->getAdminSettings(); $sResult = $oSpot->getAdminSettings();
break; break;
case 'admin_set': case 'admin_set':
$sResult = $oSpot->setAdminSettings($sType, $iId, $sField, $oValue); $sResult = $oSpot->setAdminSettings($sType, $iId, $sField, $oValue);
break; break;
case 'admin_del': case 'admin_create':
$sResult = $oSpot->delAdminSettings($sType, $iId); $sResult = $oSpot->createAdminSettings($sType);
break;
case 'admin_delete':
$sResult = $oSpot->deleteAdminSettings($sType, $iId);
break; break;
case 'generate_cron': case 'generate_cron':
$sResult = $oSpot->genCronFile(); $sResult = $oSpot->genCronFile();

338
package-lock.json generated
View File

@@ -28,14 +28,19 @@
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
"simplebar": "^6.2.5", "simplebar": "^6.2.5",
"style-loader": "^3.3.3", "style-loader": "^3.3.3",
"url-loader": "^4.1.1" "url-loader": "^4.1.1",
"vue": "^3.3.8",
"vue-style-loader": "^4.1.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.2", "@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2", "@babel/preset-env": "^7.23.2",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"resolve-url-loader": "^5.0.0", "resolve-url-loader": "^5.0.0",
"symlink-webpack-plugin": "^1.1.0", "symlink-webpack-plugin": "^1.1.0",
"vue-loader": "^17.3.1",
"vue-template-compiler": "^2.7.15",
"webpack": "^5.89.0", "webpack": "^5.89.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
} }
@@ -457,7 +462,6 @@
"version": "7.23.3", "version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
"integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==",
"dev": true,
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@@ -1863,6 +1867,126 @@
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@vue-leaflet/vue-leaflet": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@vue-leaflet/vue-leaflet/-/vue-leaflet-0.10.1.tgz",
"integrity": "sha512-RNEDk8TbnwrJl8ujdbKgZRFygLCxd0aBcWLQ05q/pGv4+d0jamE3KXQgQBqGAteE1mbQsk3xoNcqqUgaIGfWVg==",
"dev": true,
"dependencies": {
"vue": "^3.2.25"
},
"peerDependencies": {
"@types/leaflet": "^1.5.7",
"leaflet": "^1.6.0"
},
"peerDependenciesMeta": {
"@types/leaflet": {
"optional": true
}
}
},
"node_modules/@vue/compiler-core": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz",
"integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==",
"dependencies": {
"@babel/parser": "^7.23.0",
"@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz",
"integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==",
"dependencies": {
"@vue/compiler-core": "3.3.8",
"@vue/shared": "3.3.8"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz",
"integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==",
"dependencies": {
"@babel/parser": "^7.23.0",
"@vue/compiler-core": "3.3.8",
"@vue/compiler-dom": "3.3.8",
"@vue/compiler-ssr": "3.3.8",
"@vue/reactivity-transform": "3.3.8",
"@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.5",
"postcss": "^8.4.31",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz",
"integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==",
"dependencies": {
"@vue/compiler-dom": "3.3.8",
"@vue/shared": "3.3.8"
}
},
"node_modules/@vue/reactivity": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz",
"integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==",
"dependencies": {
"@vue/shared": "3.3.8"
}
},
"node_modules/@vue/reactivity-transform": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz",
"integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==",
"dependencies": {
"@babel/parser": "^7.23.0",
"@vue/compiler-core": "3.3.8",
"@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.5"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz",
"integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==",
"dependencies": {
"@vue/reactivity": "3.3.8",
"@vue/shared": "3.3.8"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz",
"integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==",
"dependencies": {
"@vue/runtime-core": "3.3.8",
"@vue/shared": "3.3.8",
"csstype": "^3.1.2"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz",
"integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==",
"dependencies": {
"@vue/compiler-ssr": "3.3.8",
"@vue/shared": "3.3.8"
},
"peerDependencies": {
"vue": "3.3.8"
}
},
"node_modules/@vue/shared": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
"integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw=="
},
"node_modules/@webassemblyjs/ast": { "node_modules/@webassemblyjs/ast": {
"version": "1.11.6", "version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
@@ -2639,6 +2763,11 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"node_modules/d3": { "node_modules/d3": {
"version": "7.8.5", "version": "7.8.5",
"resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz",
@@ -3009,6 +3138,12 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
"dev": true
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -3203,6 +3338,11 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/esutils": { "node_modules/esutils": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -3516,6 +3656,12 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/hash-sum": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
"dev": true
},
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -3528,6 +3674,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/he": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
"dev": true,
"bin": {
"he": "bin/he"
}
},
"node_modules/html-loader": { "node_modules/html-loader": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/html-loader/-/html-loader-4.2.0.tgz", "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-4.2.0.tgz",
@@ -4045,6 +4200,17 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/magic-string": {
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/merge-stream": { "node_modules/merge-stream": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -4100,6 +4266,14 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5260,6 +5434,166 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
}, },
"node_modules/vue": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz",
"integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==",
"dependencies": {
"@vue/compiler-dom": "3.3.8",
"@vue/compiler-sfc": "3.3.8",
"@vue/runtime-dom": "3.3.8",
"@vue/server-renderer": "3.3.8",
"@vue/shared": "3.3.8"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-loader": {
"version": "17.3.1",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.3.1.tgz",
"integrity": "sha512-nmVu7KU8geOyzsStyyaxID/uBGDMS8BkPXb6Lu2SNkMawriIbb+hYrNtgftHMKxOSkjjjTF5OSSwPo3KP59egg==",
"dev": true,
"dependencies": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"watchpack": "^2.4.0"
},
"peerDependencies": {
"webpack": "^4.1.0 || ^5.0.0-0"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/vue-loader/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/vue-loader/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/vue-loader/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/vue-loader/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/vue-loader/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/vue-loader/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
"integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==",
"dependencies": {
"hash-sum": "^1.0.2",
"loader-utils": "^1.0.2"
}
},
"node_modules/vue-style-loader/node_modules/hash-sum": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
"integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA=="
},
"node_modules/vue-style-loader/node_modules/json5": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dependencies": {
"minimist": "^1.2.0"
},
"bin": {
"json5": "lib/cli.js"
}
},
"node_modules/vue-style-loader/node_modules/loader-utils": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^1.0.1"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.15",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.15.tgz",
"integrity": "sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og==",
"dev": true,
"dependencies": {
"de-indent": "^1.0.2",
"he": "^1.2.0"
}
},
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

View File

@@ -2,9 +2,12 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.2", "@babel/core": "^7.23.2",
"@babel/preset-env": "^7.23.2", "@babel/preset-env": "^7.23.2",
"@vue-leaflet/vue-leaflet": "^0.10.1",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"resolve-url-loader": "^5.0.0", "resolve-url-loader": "^5.0.0",
"symlink-webpack-plugin": "^1.1.0", "symlink-webpack-plugin": "^1.1.0",
"vue-loader": "^17.3.1",
"vue-template-compiler": "^2.7.15",
"webpack": "^5.89.0", "webpack": "^5.89.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"
}, },
@@ -40,6 +43,8 @@
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
"simplebar": "^6.2.5", "simplebar": "^6.2.5",
"style-loader": "^3.3.3", "style-loader": "^3.3.3",
"url-loader": "^4.1.1" "url-loader": "^4.1.1",
"vue": "^3.3.8",
"vue-style-loader": "^4.1.3"
} }
} }

67
src/Spot.vue Normal file
View File

@@ -0,0 +1,67 @@
<script>
import Project from './components/project.vue';
import Admin from './components/admin.vue';
const aoRoutes = {
'project': Project,
'admin': Admin
};
export default {
data() {
return {
//spot: window.oSpot,
hash: {}
};
},
inject: ['spot'],
computed: {
currentView() {
this.spot.vars('page', this.hash.page);
return aoRoutes[this.hash.page];
}
},
mounted() {
window.addEventListener('hashchange', () => {this.onHashChange();});
var oEvent = new Event('hashchange');
window.dispatchEvent(oEvent);
}
,
methods: {
_hash(hash, bReboot) {
bReboot = bReboot || false;
if(!hash) return window.location.hash.slice(1);
else window.location.hash = '#'+hash;
if(bReboot) location.reload();
},
onHashChange() {
let asHash = this.getHash();
if(asHash.hash !='' && asHash.page != '') this.hash = asHash;
else if(!this.hash.page) this.setHash(this.spot.consts.default_page);
},
getHash() {
let sHash = this._hash();
let asHash = sHash.split(this.spot.consts.hash_sep);
let sPage = asHash.shift() || '';
return {hash:sHash, page:sPage, items:asHash};
},
setHash(sPage, asItems, bReboot) {
bReboot = bReboot || false;
sPage = sPage || '';
asItems = asItems || [];
if(typeof asItems == 'string') asItems = [asItems];
if(sPage != '') {
let sItems = (asItems.length > 0)?this.spot.consts.hash_sep+asItems.join(this.spot.consts.hash_sep):'';
this._hash(sPage+sItems, bReboot);
}
}
}
}
</script>
<template>
<div id="#main">
<component :is="currentView" />
</div>
</template>

231
src/components/admin.vue Normal file
View File

@@ -0,0 +1,231 @@
<script>
import SpotButton from './spotButton.vue';
import AdminInput from './adminInput.vue';
export default {
components: {
SpotButton,
AdminInput
},
data() {
return {
elems: {},
feedbacks: []
};
},
inject: ['spot'],
methods: {
l(id) {
return this.spot.lang(id);
},
async setProjects() {
let aoElemTypes = await this.spot.get2('admin_get');
for(const [sType, aoElems] of Object.entries(aoElemTypes)) {
this.elems[sType] = {};
for(const [iKey, oElem] of Object.entries(aoElems)) {
oElem.type = sType;
this.elems[sType][oElem.id] = oElem;
}
}
},
createElem(sType) {
this.spot.get2('admin_create', {type: sType})
.then((aoNewElemTypes) => {
console.log(aoNewElemTypes);
for(const [sType, aoNewElems] of Object.entries(aoNewElemTypes)) {
for(const [iKey, oNewElem] of Object.entries(aoNewElems)) {
oNewElem.type = sType;
this.elems[sType][oNewElem.id] = oNewElem;
this.spot.onFeedback('success', this.spot.lang('admin_create_success'), {'create':sType});
}
}
})
.catch((sMsg) => {console.log(sMsg);this.spot.onFeedback('error', sMsg, {'create':sType});});
},
deleteElem(oElem) {
const asInputs = {
type: oElem.type,
id: oElem.id
};
this.spot.get(
'admin_delete',
(asData) => {
delete this.elems[asInputs.type][asInputs.id];
this.spot.onFeedback('success', this.spot.lang('admin_delete_success'), asInputs);
},
asInputs,
(sError) => {
this.spot.onFeedback('error', sError, asInputs);
}
);
},
updateElem(oElem, oEvent) {
if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
let sOldVal = this.elems[oElem.type][oElem.id][oEvent.target.name];
let sNewVal = oEvent.target.value;
if(sOldVal != sNewVal) {
let asInputs = {
type: oElem.type,
id: oElem.id,
field: oEvent.target.name,
value: sNewVal
};
this.spot.get2('admin_set', asInputs)
.then((asData) => {
this.elems[oElem.type][oElem.id][oEvent.target.name] = sNewVal;
this.spot.onFeedback('success', this.spot.lang('admin_save_success'), asInputs);
})
.catch((sError) => {
oEvent.target.value = sOldVal;
this.spot.onFeedback('error', sError, asInputs);
});
}
},
queue(oElem, oEvent) {
if(typeof this.spot.tmp('wait') != 'undefined') clearTimeout(this.spot.tmp('wait'));
this.spot.tmp('wait', setTimeout(() => {this.updateElem(oElem, oEvent);}, 2000));
},
updateProject() {
this.spot.get2('update_project')
.then((asData, sMsg) => {this.spot.onFeedback('success', sMsg, {'update':'project'});})
.catch((sMsg) => {this.spot.onFeedback('error', sMsg, {'update':'project'});});
}
},
mounted() {
this.spot.addPage('admin', {
onFeedback: (sType, sMsg, asContext) => {
delete asContext.a;
delete asContext.t;
sMsg += ' (';
for(const [sKey, sElem] of Object.entries(asContext)) {
sMsg += sKey+'='+sElem+' / ' ;
}
sMsg = sMsg.slice(0, -3)+')';
this.feedbacks.push({type:sType, msg:sMsg});
}
});
this.setProjects();
}
}
</script>
<template>
<div id="admin">
<a name="back" class="button" href="#project"><i class="fa fa-back push"></i>{{ l('nav_back') }}</a>
<h1>{{ l('projects') }}</h1>
<div id="project_section">
<table>
<thead>
<tr>
<th>{{ l('id_project') }}</th>
<th>{{ l('project') }}</th>
<th>{{ l('mode') }}</th>
<th>{{ l('code_name') }}</th>
<th>{{ l('start') }}</th>
<th>{{ l('end') }}</th>
<th>{{ l('delete') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="project in elems.project">
<td>{{ project.id }}</td>
<td><AdminInput :type="'text'" :name="'name'" :elem="project" /></td>
<td>{{ project.mode }}</td>
<td><AdminInput :type="'text'" :name="'codename'" :elem="project" /></td>
<td><AdminInput :type="'date'" :name="'active_from'" :elem="project" /></td>
<td><AdminInput :type="'date'" :name="'active_to'" :elem="project" /></td>
<td><SpotButton :iconClass="'close fa-lg'" @click="deleteElem(project)" /></td>
</tr>
</tbody>
</table>
<SpotButton :buttonClass="'new'" :buttonText="l('new_project')" :iconClass="'new'" @click="createElem('project')" />
</div>
<h1>{{ l('feeds') }}</h1>
<div id="feed_section">
<table>
<thead>
<tr>
<th>{{ l('id_feed') }}</th>
<th>{{ l('ref_feed_id') }}</th>
<th>{{ l('id_spot') }}</th>
<th>{{ l('id_project') }}</th>
<th>{{ l('name') }}</th>
<th>{{ l('status') }}</th>
<th>{{ l('last_update') }}</th>
<th>{{ l('delete') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="feed in elems.feed">
<td>{{ feed.id }}</td>
<td><AdminInput :type="'text'" :name="'ref_feed_id'" :elem="feed" /></td>
<td><AdminInput :type="'number'" :name="'id_spot'" :elem="feed" /></td>
<td><AdminInput :type="'number'" :name="'id_project'" :elem="feed" /></td>
<td>{{ feed.name }}</td>
<td>{{ feed.status }}</td>
<td>{{ feed.last_update }}</td>
<td><SpotButton :iconClass="'close fa-lg'" @click="deleteElem(feed)" /></td>
</tr>
</tbody>
</table>
<SpotButton :buttonClass="'new'" :buttonText="l('new_feed')" :iconClass="'new'" @click="createElem('feed')" />
</div>
<h1>Spots</h1>
<div id="spot_section">
<table>
<thead>
<tr>
<th>{{ l('id_spot') }}</th>
<th>{{ l('ref_spot_id') }}</th>
<th>{{ l('name') }}</th>
<th>{{ l('model') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="spot in elems.spot">
<td>{{ spot.id }}</td>
<td>{{ spot.ref_spot_id }}</td>
<td>{{ spot.name }}</td>
<td>{{ spot.model }}</td>
</tr>
</tbody>
</table>
</div>
<h1>{{ l('active_users') }}</h1>
<div id="user_section">
<table>
<thead>
<tr>
<th>{{ l('id_user') }}</th>
<th>{{ l('user_name') }}</th>
<th>{{ l('language') }}</th>
<th>{{ l('time_zone') }}</th>
<th>{{ l('clearance') }}</th>
<th>{{ l('delete') }}</th>
</tr>
</thead>
<tbody>
<tr v-for="user in elems.user">
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.language }}</td>
<td>{{ user.timezone }}</td>
<td><AdminInput :type="'number'" :name="'clearance'" :elem="user" /></td>
<td><SpotButton :iconClass="'close fa-lg'" @click="deleteElem(user)" /></td>
</tr>
</tbody>
</table>
</div>
<h1>{{ l('toolbox') }}</h1>
<div id="toolbox">
<SpotButton :buttonClass="'refresh'" :buttonText="l('update_project')" :iconClass="'refresh'" @click="updateProject" />
</div>
<div id="feedback" class="feedback">
<p v-for="feedback in feedbacks" :class="feedback.type">{{ feedback.msg }}</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,17 @@
<script>
export default {
props: {
type: String,
name: String,
elem: Object
},
computed: {
value() {
return this.elem[this.name];
}
}
}
</script>
<template>
<input :type="type" :name="name" :value="value" @change="$parent.updateElem(elem, $event)" @keyup="$parent.queue(elem, $event)" />
</template>

View File

@@ -0,0 +1,44 @@
<script>
//Leaflet
import 'leaflet';
import 'leaflet-geometryutil';
import 'leaflet.heightgraph';
import '../scripts/leaflet.helpers';
import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet";
export default {
components: {
LMap,
LTileLayer
},
data() {
return {
server: this.spot.consts.server,
zoom: 13
};
},
inject: ['spot'],
mounted() {
if(this.$parent.hash.items.length==0) this.$parent.setHash(this.$parent.hash.page, [this.spot.vars('default_project_codename')]);
}
}
</script>
<template>
<div id="projects">
<div id="background"></div>
<div id="submap">
<div class="loader fa fa-fw fa-map flicker" id="map_loading"></div>
</div>
<div id="map">
<l-map ref="map" v-model:zoom="zoom" :center="[47.41322, -1.219482]">
<l-tile-layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
layer-type="base"
name="OpenStreetMap"
></l-tile-layer>
</l-map>
</div>
</div>
<div id="mobile" class="mobile"></div>
</template>

View File

@@ -0,0 +1,22 @@
<script>
import SpotIcon from './spotIcon.vue';
export default {
components: {
SpotIcon
},
props: {
buttonClass: String,
buttonText: String,
iconClass: String
},
data() {
return {
margin: !!this.buttonText
}
}
}
</script>
<template>
<button :class="buttonClass"><SpotIcon :iconClass="iconClass" :margin="margin" />{{ buttonText }}</button>
</template>

View File

@@ -0,0 +1,17 @@
<script>
export default {
props: {
iconClass: String,
margin: Boolean,
otherClasses: String
},
data() {
return {
classNames: 'fa fa-'+this.iconClass+(this.margin?' push':'')+(this.otherClasses?' '+this.otherClasses:'')
}
}
}
</script>
<template>
<i :class="classNames"></i>
</template>

View File

@@ -1,71 +0,0 @@
<div id="admin">
<a name="back" class="button" href="[#]server[#]"><i class="fa fa-back push"></i>[#]lang:nav_back[#]</a>
<h1>[#]lang:projects[#]</h1>
<div id="project_section">
<table>
<thead>
<tr>
<th>[#]lang:id_project[#]</th>
<th>[#]lang:project[#]</th>
<th>[#]lang:mode[#]</th>
<th>[#]lang:code_name[#]</th>
<th>[#]lang:start[#]</th>
<th>[#]lang:end[#]</th>
<th>[#]lang:delete[#]</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div id="new"></div>
</div>
<h1>[#]lang:feeds[#]</h1>
<div id="feed_section">
<table>
<thead>
<tr>
<th>[#]lang:id_feed[#]</th>
<th>[#]lang:ref_feed_id[#]</th>
<th>[#]lang:id_spot[#]</th>
<th>[#]lang:id_project[#]</th>
<th>[#]lang:name[#]</th>
<th>[#]lang:status[#]</th>
<th>[#]lang:last_update[#]</th>
<th>[#]lang:delete[#]</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<h1>Spots</h1>
<div id="spot_section">
<table>
<thead>
<tr>
<th>[#]lang:id_spot[#]</th>
<th>[#]lang:ref_spot_id[#]</th>
<th>[#]lang:name[#]</th>
<th>[#]lang:model[#]</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<h1>[#]lang:active_users[#]</h1>
<div id="user_section">
<table>
<thead>
<tr>
<th>[#]lang:id_user[#]</th>
<th>[#]lang:user_name[#]</th>
<th>[#]lang:language[#]</th>
<th>[#]lang:time_zone[#]</th>
<th>[#]lang:clearance[#]</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
<h1>[#]lang:toolbox[#]</h1>
<div id="toolbox"></div>
<div id="feedback" class="feedback"></div>
</div>

View File

@@ -20,12 +20,12 @@
<meta name="msapplication-config" content="images/icons/browserconfig.xml?v=GvmqYyKwbb"> <meta name="msapplication-config" content="images/icons/browserconfig.xml?v=GvmqYyKwbb">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<script type="text/javascript">window.params = [#]GLOBAL_VARS[#];</script> <script type="text/javascript">window.params = [#]GLOBAL_VARS[#];</script>
<script type="text/javascript" src="[#]filepath_js[#]"></script>
<title>Spotty</title> <title>Spotty</title>
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<div id="main"></div> <div id="main"></div>
</div> </div>
<script type="module" src="[#]filepath_js[#]"></script>
</body> </body>
</html> </html>

View File

@@ -16,21 +16,26 @@ console.log(Logo);
//Masks //Masks
import Spot from './spot.js'; import Spot from './spot.js';
import Project from './page.project.js'; //import Project from './page.project.js';
import Upload from './page.upload.js'; //import Upload from './page.upload.js';
import Admin from './page.admin.js'; //import Admin from './page.admin.js';
//const Upload = () => import('@scripts/page.upload.js'); window.oSpot = new Spot(params);
let oSpot = new Spot(params); //let oProject = new Project(oSpot);
//oSpot.addPage('project', oProject);
let oProject = new Project(oSpot); //let oUpload = new Upload(oSpot);
oSpot.addPage('project', oProject); //oSpot.addPage('upload', oUpload);
let oUpload = new Upload(oSpot); //let oAdmin = new Admin(oSpot);
oSpot.addPage('upload', oUpload); //oSpot.addPage('admin', oAdmin);
let oAdmin = new Admin(oSpot); //$(() => {oSpot.init();});
oSpot.addPage('admin', oAdmin);
$(() => {oSpot.init();}); import { createApp } from 'vue';
import SpotVue from '../Spot.vue';
const oSpotVue = createApp(SpotVue);
oSpotVue.provide('spot', window.oSpot);
oSpotVue.mount('#main');

View File

@@ -75,7 +75,7 @@ export default class Spot {
if(oData.desc.substr(0, this.consts.lang_prefix.length)==this.consts.lang_prefix) oData.desc = this.lang(oData.desc.substr(5)); if(oData.desc.substr(0, this.consts.lang_prefix.length)==this.consts.lang_prefix) oData.desc = this.lang(oData.desc.substr(5));
if(oData.result==this.consts.error) fOnError(oData.desc); if(oData.result==this.consts.error) fOnError(oData.desc);
else fOnSuccess(oData.data, oData.desc); else if(fOnSuccess) fOnSuccess(oData.data, oData.desc);
}) })
.fail((jqXHR, textStatus, errorThrown) => { .fail((jqXHR, textStatus, errorThrown) => {
fonProgress('fail'); fonProgress('fail');
@@ -83,6 +83,32 @@ export default class Spot {
}); });
} }
async get2(sAction, oVars) {
oVars = oVars || {};
oVars['a'] = sAction;
oVars['t'] = this.consts.timezone;
let oUrl = new URL(this.consts.server+this.consts.process_page);
oUrl.search = new URLSearchParams(oVars).toString();
try {
let oUrl = new URL(this.consts.server+this.consts.process_page);
oUrl.search = new URLSearchParams(oVars).toString();
const oRequest = await fetch(oUrl, {method: 'GET', /*body: JSON.stringify(oVars),*/ headers: {"Content-Type": "application/json"}});
if(!oRequest.ok) throw new Error('Error HTTP '+oRequest.status+': '+oRequest.statusText);
else {
let oResponse = await oRequest.json();
if(oResponse.desc.substr(0, this.consts.lang_prefix.length)==this.consts.lang_prefix) oResponse.desc = this.lang(oData.desc.substr(this.consts.lang_prefix.length));
if(oResponse.result == this.consts.error) return Promise.reject(oResponse.desc);
else return Promise.resolve(oResponse.data, oResponse.desc);
}
}
catch(oError) {
throw oError;
}
}
lang(sKey, asParams) { lang(sKey, asParams) {
asParams = asParams || []; asParams = asParams || [];
if(typeof asParams == 'string') asParams = [asParams]; if(typeof asParams == 'string') asParams = [asParams];
@@ -176,6 +202,7 @@ export default class Spot {
asContext = asContext || {}; asContext = asContext || {};
let sPage = this.vars('page'); let sPage = this.vars('page');
if(this.pages[sPage].onFeedback) this.pages[sPage].onFeedback(sType, sMsg, asContext); if(this.pages[sPage].onFeedback) this.pages[sPage].onFeedback(sType, sMsg, asContext);
else console.log({type:sType, msg:sMsg, context:asContext});
} }
onKeydown(oEvent) { onKeydown(oEvent) {