mirror of
https://github.com/FH-Complete/FHC-Core.git
synced 2026-07-16 22:42:16 +00:00
Merge branch 'feature-24647/Konfigurierbare_Dashboards' into feature-25999/C4
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
defined('BASEPATH') || exit('No direct script access allowed');
|
||||
|
||||
class Api extends Auth_Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'dashboard/admin:rw',
|
||||
'getNews' => 'dashboard/admin:rw',
|
||||
'getAmpeln' => 'dashboard/admin:rw',
|
||||
)
|
||||
);
|
||||
|
||||
$this->load->library('AuthLib', null, 'AuthLib');
|
||||
|
||||
$this->_setAuthUID();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
echo 'Dashboard API Controller';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get News.
|
||||
*/
|
||||
public function getNews()
|
||||
{
|
||||
$limit = $this->input->get('limit');
|
||||
|
||||
$this->load->model('content/News_model', 'NewsModel');
|
||||
|
||||
$result = $this->NewsModel->getAll($limit);
|
||||
|
||||
if(hasData($result))
|
||||
{
|
||||
|
||||
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->terminateWithJsonError('fehler entdeckt');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Ampeln.
|
||||
*/
|
||||
public function getAmpeln(){
|
||||
|
||||
$this->load->model('content/Ampel_model', 'AmpelModel');
|
||||
$result = $this->AmpelModel->getByUser($this->_uid);
|
||||
|
||||
if(hasData($result))
|
||||
{
|
||||
|
||||
$this->outputJson(getData($result), REST_Controller::HTTP_OK);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->terminateWithJsonError('fehler entdeckt');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the UID of the logged user and checks if it is valid
|
||||
*/
|
||||
private function _setAuthUID()
|
||||
{
|
||||
$this->_uid = getAuthUID();
|
||||
|
||||
if (!$this->_uid) show_error('User authentification failed');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
defined('BASEPATH') || exit('No direct script access allowed');
|
||||
/**
|
||||
* Description of Config
|
||||
*
|
||||
* @author bambi
|
||||
*/
|
||||
class Config extends Auth_Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'dashboard/benutzer:r',
|
||||
'dummy' => 'dashboard/benutzer:r',
|
||||
'genWidgetId' => 'dashboard/benutzer:rw',
|
||||
'addWidgetsToPreset' => 'dashboard/admin:rw',
|
||||
'removeWidgetFromPreset' => 'dashboard/admin:rw',
|
||||
'addWidgetsToUserOverride' => 'dashboard/benutzer:rw',
|
||||
'removeWidgetFromUserOverride' => 'dashboard/benutzer:rw',
|
||||
'Funktionen' => 'dashboard/admin:r',
|
||||
'Preset' => 'dashboard/admin:r',
|
||||
'PresetBatch' => 'dashboard/admin:r'
|
||||
)
|
||||
);
|
||||
|
||||
$this->load->library('dashboard/DashboardLib', null, 'DashboardLib');
|
||||
$this->load->library('AuthLib', null, 'AuthLib');
|
||||
$this->load->model('ressource/Funktion_model', 'FunktionModel');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$dashboard_kurzbz = $this->input->get('db');
|
||||
$uid = $this->AuthLib->getAuthObj()->username;
|
||||
|
||||
$dashboard = $this->DashboardLib->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
if(!$dashboard) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError(array(
|
||||
'error' => 'Dashboard ' . $dashboard_kurzbz . ' not found.'
|
||||
));
|
||||
}
|
||||
|
||||
$mergedconfig = $this->DashboardLib->getMergedConfig($dashboard->dashboard_id, $uid);
|
||||
$this->outputJsonSuccess($mergedconfig);
|
||||
}
|
||||
|
||||
public function genWidgetId()
|
||||
{
|
||||
$dashboard_kurzbz = $this->input->get('db');
|
||||
$widgetid = $this->DashboardLib->generateWidgetId($dashboard_kurzbz);
|
||||
$this->outputJsonSuccess(array(
|
||||
'widgetid' => $widgetid
|
||||
));
|
||||
}
|
||||
|
||||
public function addWidgetsToPreset()
|
||||
{
|
||||
$input = json_decode($this->input->raw_input_stream);
|
||||
$dashboard_kurzbz = $input->db;
|
||||
$funktion_kurzbz = $input->funktion_kurzbz;
|
||||
|
||||
$preset = $this->DashboardLib->getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz);
|
||||
|
||||
$preset_decoded = json_decode($preset->preset, true);
|
||||
|
||||
$this->DashboardLib->addWidgetsToWidgets($preset_decoded['widgets'],
|
||||
$dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
|
||||
|
||||
$preset->preset = json_encode($preset_decoded);
|
||||
|
||||
$result = $this->DashboardLib->insertOrUpdatePreset($preset);
|
||||
if( isError($result) ) {
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('preset could not be saved');
|
||||
}
|
||||
|
||||
$this->outputJsonSuccess(array('msg' => 'preset successfully stored.', 'data' => $preset_decoded));
|
||||
}
|
||||
|
||||
public function removeWidgetFromPreset()
|
||||
{
|
||||
$input = json_decode($this->input->raw_input_stream);
|
||||
$dashboard_kurzbz = $input->db;
|
||||
$funktion_kurzbz = $input->funktion_kurzbz;
|
||||
$widgetid = $input->widgetid;
|
||||
|
||||
$preset = $this->DashboardLib->getPreset($dashboard_kurzbz, $funktion_kurzbz);
|
||||
if( $preset === null ) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('preset for dashboard '
|
||||
. $dashboard_kurzbz . ' and funktion ' . $funktion_kurzbz
|
||||
. ' not found.');
|
||||
}
|
||||
|
||||
$preset_decoded = json_decode($preset->preset, true);
|
||||
if (!$this->DashboardLib->removeWidgetFromWidgets($preset_decoded['widgets'],
|
||||
$funktion_kurzbz, $widgetid))
|
||||
{
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('widgetid ' . $widgetid . ' not found');
|
||||
}
|
||||
|
||||
$preset->preset = json_encode($preset_decoded);
|
||||
$result = $this->DashboardLib->insertOrUpdatePreset($preset);
|
||||
if( isError($result) )
|
||||
{
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('failed to remove widget');
|
||||
}
|
||||
$this->outputJsonSuccess(array('msg' => 'preset successfully updated.'));
|
||||
}
|
||||
|
||||
public function addWidgetsToUserOverride()
|
||||
{
|
||||
$input = json_decode($this->input->raw_input_stream);
|
||||
$dashboard_kurzbz = $input->db;
|
||||
$funktion_kurzbz = $input->funktion_kurzbz;
|
||||
$uid = $this->AuthLib->getAuthObj()->username;
|
||||
|
||||
$override = $this->DashboardLib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid);
|
||||
|
||||
$override_decoded = json_decode($override->override, true);
|
||||
|
||||
$this->DashboardLib->addWidgetsToWidgets($override_decoded['widgets'],
|
||||
$dashboard_kurzbz, $funktion_kurzbz, $input->widgets);
|
||||
|
||||
$override->override = json_encode($override_decoded);
|
||||
|
||||
$result = $this->DashboardLib->insertOrUpdateOverride($override);
|
||||
if( isError($result) ) {
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('override could not be saved');
|
||||
}
|
||||
|
||||
$this->outputJsonSuccess(array('msg' => 'override successfully stored.', 'data' => $override_decoded));
|
||||
}
|
||||
|
||||
public function removeWidgetFromUserOverride()
|
||||
{
|
||||
$input = json_decode($this->input->raw_input_stream);
|
||||
$dashboard_kurzbz = $input->db;
|
||||
$funktion_kurzbz = $input->funktion_kurzbz;
|
||||
$uid = $this->AuthLib->getAuthObj()->username;
|
||||
$widgetid = $input->widgetid;
|
||||
|
||||
$override = $this->DashboardLib->getOverride($dashboard_kurzbz, $uid);
|
||||
if( empty($override) ) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('userconfig for dashboard '
|
||||
. $dashboard_kurzbz . ' not found.');
|
||||
}
|
||||
|
||||
$override_decoded = json_decode($override->override, true);
|
||||
|
||||
if( !$this->DashboardLib->removeWidgetFromWidgets($override_decoded['widgets'],
|
||||
$funktion_kurzbz, $widgetid) )
|
||||
{
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError('widgetid ' . $widgetid . ' not found');
|
||||
}
|
||||
|
||||
$override->override = json_encode($override_decoded);
|
||||
$result = $this->DashboardLib->insertOrUpdateOverride($override, $uid);
|
||||
if( isError($result) )
|
||||
{
|
||||
http_response_code(500);
|
||||
$this->terminateWithJsonError('failed to remove widget');
|
||||
}
|
||||
$this->outputJsonSuccess(array('msg' => 'override successfully updated.'));
|
||||
}
|
||||
|
||||
public function Funktionen()
|
||||
{
|
||||
$funktionen = $this->FunktionModel->load();
|
||||
|
||||
if (isError($funktionen)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError([
|
||||
'error' => getError($funktionen)
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->outputJsonSuccess(getData($funktionen) ?: []);
|
||||
}
|
||||
|
||||
public function Preset()
|
||||
{
|
||||
$db = $this->input->get('db');
|
||||
$funktion = $this->input->get('funktion');
|
||||
|
||||
$conf = $this->DashboardLib->getPreset($db, $funktion);
|
||||
|
||||
if (!$conf)
|
||||
return $this->outputJsonSuccess(['widgets' => [$funktion => []]]);
|
||||
|
||||
return $this->outputJsonSuccess(json_decode($conf->preset, true));
|
||||
}
|
||||
|
||||
public function PresetBatch()
|
||||
{
|
||||
$db = $this->input->get('db');
|
||||
$funktionen = $this->input->get('funktionen');
|
||||
$result = [];
|
||||
|
||||
foreach ($funktionen as $funktion) {
|
||||
$conf = $this->DashboardLib->getPreset($db, $funktion);
|
||||
if ($conf)
|
||||
$result[$funktion] = json_decode($conf->preset, true)['widgets'][$funktion];
|
||||
else
|
||||
$result[$funktion] = [];
|
||||
}
|
||||
|
||||
return $this->outputJsonSuccess($result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
defined('BASEPATH') || exit('No direct script access allowed');
|
||||
/**
|
||||
* Description of Widget
|
||||
*
|
||||
* @author chris
|
||||
*/
|
||||
class Dashboard extends Auth_Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'dashboard/admin:r',
|
||||
'Create' => 'dashboard/admin:rw',
|
||||
'Update' => 'dashboard/admin:rw',
|
||||
'Delete' => 'dashboard/admin:rw'
|
||||
)
|
||||
);
|
||||
|
||||
$this->load->library('dashboard/DashboardLib', null, 'DashboardLib');
|
||||
$this->load->model('dashboard/Dashboard_model', 'DashboardModel');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$result = $this->DashboardModel->load();
|
||||
|
||||
if (isError($result)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError([
|
||||
'error' => getError($result)
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function Create()
|
||||
{
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
$result = $this->DashboardModel->insert($input);
|
||||
|
||||
if (isError($result)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError([
|
||||
'error' => getError($result)
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function Update()
|
||||
{
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
$result = $this->DashboardModel->update($input->dashboard_id, $input);
|
||||
|
||||
if (isError($result)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError([
|
||||
'error' => getError($result)
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function Delete()
|
||||
{
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
$result = $this->DashboardModel->delete($input->dashboard_id);
|
||||
|
||||
if (isError($result)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError([
|
||||
'error' => getError($result)
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
if (! defined('BASEPATH')) exit('No direct script access allowed');
|
||||
|
||||
/**
|
||||
*/
|
||||
class DashboardDemo extends Auth_Controller
|
||||
{
|
||||
private $_uid; // uid of the logged user
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// Set required permissions
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'user:r',
|
||||
)
|
||||
);
|
||||
|
||||
$this->load->library('AuthLib');
|
||||
$this->load->library('WidgetLib');
|
||||
|
||||
$this->_setAuthUID(); // sets property uid
|
||||
|
||||
$this->setControllerId(); // sets the controller id
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Public methods
|
||||
public function index()
|
||||
{
|
||||
$this->load->view('dashboard/dashboard_demo.php', []);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* Retrieve the UID of the logged user and checks if it is valid
|
||||
*/
|
||||
private function _setAuthUID()
|
||||
{
|
||||
$this->_uid = getAuthUID();
|
||||
|
||||
if (!$this->_uid) show_error('User authentification failed');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
defined('BASEPATH') || exit('No direct script access allowed');
|
||||
/**
|
||||
* Description of Widget
|
||||
*
|
||||
* @author chris
|
||||
*/
|
||||
class Widget extends Auth_Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(
|
||||
array(
|
||||
'index' => 'dashboard/benutzer:r',
|
||||
'getAll' => 'dashboard/admin:r',
|
||||
'getWidgetsForDashboard' => 'dashboard/benutzer:rw',
|
||||
'setAllowed' => 'dashboard/admin:rw'
|
||||
)
|
||||
);
|
||||
|
||||
$this->load->library('dashboard/DashboardLib', null, 'DashboardLib');
|
||||
$this->load->model('dashboard/Widget_model', 'WidgetModel');
|
||||
$this->load->model('dashboard/Dashboard_Widget_model', 'DashboardWidgetModel');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$widget_id = $this->input->get('id');
|
||||
|
||||
$widget = $this->WidgetModel->load($widget_id);
|
||||
|
||||
if (isError($widget) || !getData($widget))
|
||||
return $this->outputJsonSuccess([
|
||||
"widget_id" => 0,
|
||||
"widget_kurzbz" => "notfound",
|
||||
"arguments" => json_encode([
|
||||
"className" => 'alert-danger',
|
||||
"title" => 'Widget Not Found',
|
||||
"msg" => 'The widget with the id ' . $widget_id . ' could not be found'
|
||||
]),
|
||||
"setup" => json_encode([
|
||||
"name" => 'Widget Not Found',
|
||||
"file" => 'DashboardWidget/Default.js',
|
||||
"width" => 1,
|
||||
"height" => 1
|
||||
])
|
||||
]);
|
||||
return $this->outputJsonSuccess(current(getData($widget)));
|
||||
}
|
||||
|
||||
public function getAll()
|
||||
{
|
||||
$dashboard_id = $this->input->get('dashboard_id');
|
||||
$result = $this->WidgetModel->getWithAllowedForDashboard($dashboard_id);
|
||||
|
||||
if (isError($result))
|
||||
return $this->outputJsonError(getError($result));
|
||||
|
||||
$this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function getWidgetsForDashboard()
|
||||
{
|
||||
$db = $this->input->get('db');
|
||||
$result = $this->WidgetModel->getForDashboard($db);
|
||||
|
||||
if (isError($result)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError([
|
||||
'error' => getError($result)
|
||||
]);
|
||||
}
|
||||
|
||||
$this->outputJsonSuccess(getData($result) ?: []);
|
||||
}
|
||||
|
||||
public function setAllowed() {
|
||||
$input = $this->getPostJSON();
|
||||
|
||||
$dashboard_id = $input->dashboard_id;
|
||||
$widget_id = $input->widget_id;
|
||||
$action = $input->action;
|
||||
|
||||
if ($action == 'add') {
|
||||
$result = $this->DashboardWidgetModel->insert([
|
||||
'dashboard_id' => $dashboard_id,
|
||||
'widget_id' => $widget_id
|
||||
]);
|
||||
} elseif ($action == 'delete') {
|
||||
$result = $this->DashboardWidgetModel->delete([
|
||||
'dashboard_id' => $dashboard_id,
|
||||
'widget_id' => $widget_id
|
||||
]);
|
||||
} else {
|
||||
http_response_code(404); // TODO(chris): 400?
|
||||
$this->terminateWithJsonError([
|
||||
'error' => 'action value invalid'
|
||||
]);
|
||||
}
|
||||
if (isError($result)) {
|
||||
http_response_code(404);
|
||||
$this->terminateWithJsonError([
|
||||
'error' => getError($result)
|
||||
]);
|
||||
}
|
||||
return $this->outputJsonSuccess(getData($result));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
defined('BASEPATH') || exit('No direct script access allowed');
|
||||
/**
|
||||
* Description of DashboardLib
|
||||
*
|
||||
* @author bambi
|
||||
*/
|
||||
class DashboardLib
|
||||
{
|
||||
const WIDGET_ID_RANDOM_BYTES = 16;
|
||||
const DEFAULT_DASHBOARD_KURZBZ = 'fhcomplete';
|
||||
const SECTION_IF_FUNKTION_KURZBZ_IS_NULL = 'general';
|
||||
const USEROVERRIDE_SECTION = 'custom';
|
||||
|
||||
private $_ci; // CI instance
|
||||
|
||||
public function __construct($params=null)
|
||||
{
|
||||
// Loads CI instance
|
||||
$this->_ci =& get_instance();
|
||||
|
||||
$this->_ci->load->model('dashboard/Dashboard_model', 'DashboardModel');
|
||||
$this->_ci->load->model('dashboard/Dashboard_Preset_model', 'DashboardPresetModel');
|
||||
$this->_ci->load->model('dashboard/Dashboard_Override_model', 'DashboardOverrideModel');
|
||||
}
|
||||
|
||||
public function generateWidgetId($dashboard_kurzbz='')
|
||||
{
|
||||
$dashboard_kurzbz = (!empty($dashboard_kurzbz)) ? $dashboard_kurzbz
|
||||
: self::DEFAULT_DASHBOARD_KURZBZ;
|
||||
$widgetid_input = time() . '_' . $dashboard_kurzbz . '_'
|
||||
. bin2hex(random_bytes(self::WIDGET_ID_RANDOM_BYTES));
|
||||
$widgetid = md5($widgetid_input);
|
||||
return $widgetid;
|
||||
}
|
||||
|
||||
public function getDashboardByKurzbz($dashboard_kurzbz)
|
||||
{
|
||||
$dashboard = null;
|
||||
$result = $this->_ci->DashboardModel->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
if( isSuccess($result) && ($dashboards = getData($result)) )
|
||||
{
|
||||
$dashboard = $dashboards[0];
|
||||
}
|
||||
return $dashboard;
|
||||
}
|
||||
|
||||
public function getMergedConfig($dashboard_id, $uid)
|
||||
{
|
||||
$defaultconfig = $this->getDefaultConfig($dashboard_id, $uid);
|
||||
$userconfig = $this->getUserConfig($dashboard_id, $uid);
|
||||
|
||||
$mergedconfig = array_replace_recursive($defaultconfig, $userconfig);
|
||||
|
||||
return $mergedconfig;
|
||||
}
|
||||
|
||||
public function getDefaultConfig($dashboard_id, $uid)
|
||||
{
|
||||
$res_presets = $this->_ci->DashboardPresetModel->getPresets($dashboard_id, $uid);
|
||||
$defaultconfig = array();
|
||||
|
||||
if( isSuccess($res_presets) && hasData($res_presets) )
|
||||
{
|
||||
$presets = getData($res_presets);
|
||||
foreach ($presets as $presetobj)
|
||||
{
|
||||
if( null !== ($preset = json_decode($presetobj->preset, true)) )
|
||||
{
|
||||
$defaultconfig = array_replace_recursive($defaultconfig,
|
||||
$preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $defaultconfig;
|
||||
}
|
||||
|
||||
public function getUserConfig($dashboard_id, $uid)
|
||||
{
|
||||
$res_userconfig = $this->_ci->DashboardOverrideModel->getOverride($dashboard_id, $uid);
|
||||
$userconfig = array();
|
||||
|
||||
if( isSuccess($res_userconfig) && hasData($res_userconfig) )
|
||||
{
|
||||
$data = getData($res_userconfig);
|
||||
if( null !== ($decodedconfig = json_decode($data[0]->override, true)) )
|
||||
{
|
||||
$userconfig = $decodedconfig;
|
||||
}
|
||||
}
|
||||
|
||||
return $userconfig;
|
||||
}
|
||||
|
||||
public function getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid)
|
||||
{
|
||||
$override = $this->getOverride($dashboard_kurzbz, $uid);
|
||||
if( null !== $override ) {
|
||||
return $override;
|
||||
}
|
||||
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
|
||||
$emptyoverride = new stdClass();
|
||||
$emptyoverride->dashboard_id = $dashboard->dashboard_id;
|
||||
$emptyoverride->uid = $uid;
|
||||
$emptyoverride->override = '{"widgets": {"' . self::USEROVERRIDE_SECTION . '": {}}}';
|
||||
|
||||
return $emptyoverride;
|
||||
}
|
||||
|
||||
public function getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz)
|
||||
{
|
||||
$preset = $this->getPreset($dashboard_kurzbz, $funktion_kurzbz);
|
||||
if( null !== $preset ) {
|
||||
return $preset;
|
||||
}
|
||||
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
|
||||
$emptypreset = new stdClass();
|
||||
$emptypreset->dashboard_id = $dashboard->dashboard_id;
|
||||
$emptypreset->funktion_kurzbz = $funktion_kurzbz;
|
||||
$section = ($funktion_kurzbz !== null) ? $funktion_kurzbz : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
|
||||
$emptypreset->preset = '{"widgets": {"' . $funktion_kurzbz . '": {}}}';
|
||||
|
||||
return $emptypreset;
|
||||
}
|
||||
|
||||
public function getPreset($dashboard_kurzbz, $section)
|
||||
{
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
$preset = null;
|
||||
|
||||
$funktion_kurzbz = ($section === self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL) ? null : $section;
|
||||
$result = $this->_ci->DashboardPresetModel
|
||||
->getPresetByDashboardAndFunktion($dashboard->dashboard_id, $funktion_kurzbz);
|
||||
|
||||
if( isSuccess($result) && hasData($result) && ($presets = getData($result)) )
|
||||
{
|
||||
$preset = $presets[0];
|
||||
}
|
||||
|
||||
return $preset;
|
||||
}
|
||||
|
||||
public function getOverride($dashboard_kurzbz, $uid)
|
||||
{
|
||||
$dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz);
|
||||
$override = null;
|
||||
|
||||
$result = $this->_ci->DashboardOverrideModel
|
||||
->getOverride($dashboard->dashboard_id, $uid);
|
||||
|
||||
if( isSuccess($result) && hasData($result) && ($overrides = getData($result)) )
|
||||
{
|
||||
$override = $overrides[0];
|
||||
}
|
||||
|
||||
return $override;
|
||||
}
|
||||
|
||||
public function insertOrUpdatePreset($preset)
|
||||
{
|
||||
if( isset($preset->preset_id) && $preset->preset_id > 0 )
|
||||
{
|
||||
$result = $this->_ci->DashboardPresetModel->update($preset->preset_id, $preset);
|
||||
} else
|
||||
{
|
||||
$result = $this->_ci->DashboardPresetModel->insert($preset);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function insertOrUpdateOverride($override)
|
||||
{
|
||||
if( isset($override->override_id) && $override->override_id > 0 )
|
||||
{
|
||||
$result = $this->_ci->DashboardOverrideModel->update($override->override_id, $override);
|
||||
} else
|
||||
{
|
||||
$result = $this->_ci->DashboardOverrideModel->insert($override);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function addWidgetsToWidgets(&$widgets, $dashboard_kurzbz, $section, $addwigets)
|
||||
{
|
||||
foreach ($addwigets as $widget)
|
||||
{
|
||||
if(!isset($widget->widgetid))
|
||||
{
|
||||
$widget->widgetid = $this->generateWidgetId($dashboard_kurzbz);
|
||||
}
|
||||
$this->addWidgetToWidgets($widgets, $section, $widget, $widget->widgetid);
|
||||
}
|
||||
}
|
||||
|
||||
public function addWidgetToWidgets(&$widgets, $section, $widget, $widgetid)
|
||||
{
|
||||
$section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
|
||||
if( !isset($widgets[$section]) || !is_array($widgets[$section]) )
|
||||
{
|
||||
$widgets[$section] = array();
|
||||
}
|
||||
|
||||
$widgets[$section][$widgetid] = $widget;
|
||||
}
|
||||
|
||||
public function removeWidgetFromWidgets(&$widgets, $section, $widgetid)
|
||||
{
|
||||
$section = ($section !== null) ? $section : self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL;
|
||||
if(isset($widgets[$section]) && isset($widgets[$section][$widgetid]) )
|
||||
{
|
||||
unset($widgets[$section][$widgetid]);
|
||||
if(empty($widgets[$section]) && $section !== self::USEROVERRIDE_SECTION) {
|
||||
unset($widgets[$section]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,19 @@ class News_model extends DB_Model
|
||||
$this->dbTable = 'campus.tbl_news';
|
||||
$this->pk = 'news_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all News ordered by date. (most actual on top)
|
||||
* @param null $limit Amount of news.
|
||||
* @return array
|
||||
*/
|
||||
public function getAll($limit = null)
|
||||
{
|
||||
return $this->loadWhere('
|
||||
text IS NOT NULL
|
||||
AND datum <= NOW() AND (datum_bis IS NULL OR datum_bis >= now()::date)
|
||||
ORDER BY datum DESC
|
||||
LIMIT '. $this->escape($limit)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
class Dashboard_Override_model extends DB_Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->dbTable = 'dashboard.tbl_dashboard_benutzer_override';
|
||||
$this->pk = 'override_id';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Overrides of given uid.
|
||||
* @param integer dashboard_id
|
||||
* @param string $uid
|
||||
* @return array
|
||||
*/
|
||||
public function getOverride($dashboard_id, $uid)
|
||||
{
|
||||
return $this->loadWhere(array('dashboard_id' => $dashboard_id, 'uid'=> $uid));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
class Dashboard_Preset_model extends DB_Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->dbTable = 'dashboard.tbl_dashboard_preset';
|
||||
$this->pk = 'preset_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Presets of given uid.
|
||||
* @param integer dashboard_id
|
||||
* @param string $uid
|
||||
* @return array
|
||||
*/
|
||||
public function getPresets($dashboard_id, $uid)
|
||||
{
|
||||
// TODO: get Funktionen for uid and load all preset for all funktionen for uid
|
||||
//return $this->loadWhere(array('dashboard_id' => $dashboard_id, 'funktion_kurzbz'=> null));
|
||||
$sql = <<<EOSQL
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
dashboard.tbl_dashboard_preset
|
||||
WHERE
|
||||
dashboard_id = ?
|
||||
AND (
|
||||
funktion_kurzbz IN (
|
||||
SELECT
|
||||
DISTINCT funktion_kurzbz
|
||||
FROM
|
||||
public.tbl_benutzerfunktion
|
||||
WHERE
|
||||
uid = ?
|
||||
AND
|
||||
NOW()::date
|
||||
BETWEEN
|
||||
COALESCE(datum_von, '1970-01-01')
|
||||
AND
|
||||
COALESCE(datum_bis, '2170-12-31')
|
||||
)
|
||||
OR
|
||||
funktion_kurzbz IS NULL
|
||||
)
|
||||
ORDER BY
|
||||
funktion_kurzbz DESC
|
||||
EOSQL;
|
||||
|
||||
return $this->execQuery($sql, array($dashboard_id, $uid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Preset by Dashboard and Funktion
|
||||
* @param integer dashboard_id
|
||||
* @param string funktion_kurzbz
|
||||
* @return array
|
||||
*/
|
||||
public function getPresetByDashboardAndFunktion($dashboard_id, $funktion_kurzbz)
|
||||
{
|
||||
return $this->loadWhere(array('dashboard_id' => $dashboard_id, 'funktion_kurzbz' => $funktion_kurzbz));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
class Dashboard_Widget_model extends DB_Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->dbTable = 'dashboard.tbl_dashboard_widget';
|
||||
$this->pk = ['dashboard_id', 'widget_id'];
|
||||
$this->hasSequence = false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
class Dashboard_model extends DB_Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->dbTable = 'dashboard.tbl_dashboard';
|
||||
$this->pk = 'dashboard_id';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Dashboard by kurzbz.
|
||||
* @param string dashboard_kurzbz
|
||||
* @return array
|
||||
*/
|
||||
public function getDashboardByKurzbz($dashboard_kurzbz)
|
||||
{
|
||||
return $this->loadWhere(array('dashboard_kurzbz' => $dashboard_kurzbz));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
class Widget_model extends DB_Model
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->dbTable = 'dashboard.tbl_widget';
|
||||
$this->pk = 'widget_id';
|
||||
}
|
||||
|
||||
public function getWithAllowedForDashboard($dashboard_id)
|
||||
{
|
||||
$this->addSelect($this->dbTable . '.*');
|
||||
$this->addSelect('CASE WHEN dashboard_id = ? THEN 1 ELSE 0 END AS allowed', false);
|
||||
$this->addJoin('dashboard.tbl_dashboard_widget', 'widget_id', 'LEFT');
|
||||
|
||||
return $this->execQuery($this->db->get_compiled_select($this->dbTable), [$dashboard_id]);
|
||||
}
|
||||
|
||||
public function getForDashboard($db)
|
||||
{
|
||||
$this->addSelect($this->dbTable . '.*');
|
||||
$this->addJoin('dashboard.tbl_dashboard_widget', 'widget_id');
|
||||
$this->addJoin('dashboard.tbl_dashboard', 'dashboard_id');
|
||||
|
||||
return $this->loadWhere(['dashboard_kurzbz' => $db]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
$this->load->view('templates/FHC-Header',
|
||||
array(
|
||||
'title' => 'FH-Complete',
|
||||
'bootstrap5' => true,
|
||||
'fontawesome6' => true,
|
||||
'axios027' => true,
|
||||
'restclient' => true,
|
||||
'vue3' => true,
|
||||
'customJSModules' => ['public/js/apps/Dashboard.js'],
|
||||
'customCSSs' => [
|
||||
'public/css/components/dashboard.css'
|
||||
],
|
||||
'navigationcomponent' => true
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<core-navigation-cmpt :add-side-menu-entries="appSideMenuEntries"></core-navigation-cmpt>
|
||||
|
||||
<div id="content">
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2">Dashboard</h1>
|
||||
</div>
|
||||
<core-dashboard dashboard="CIS" apiurl="<?= site_url('dashboard'); ?>"></core-dashboard>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php $this->load->view('templates/FHC-Footer'); ?>
|
||||
@@ -0,0 +1,27 @@
|
||||
.alert-danger .form-check-input:checked {
|
||||
border-color: #842029;
|
||||
background-color: #842029;
|
||||
}
|
||||
|
||||
.draganddropcontainer {
|
||||
grid-template-columns:repeat(4,1fr);
|
||||
gap: 1rem;
|
||||
place-items: stretch;
|
||||
place-content: stretch;
|
||||
}
|
||||
@media(max-width: 577px) {
|
||||
.draganddropcontainer {
|
||||
grid-template-columns:repeat(2,1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.mirror-x {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.cursor-nw-resize {
|
||||
cursor: nw-resize;
|
||||
}
|
||||
.cursor-move {
|
||||
cursor: move;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import {CoreNavigationCmpt} from '../components/navigation/Navigation.js';
|
||||
import CoreDashboard from '../components/Dashboard/Dashboard.js';
|
||||
|
||||
Vue.createApp({
|
||||
data: () => ({
|
||||
appSideMenuEntries: {}
|
||||
}),
|
||||
components: {
|
||||
CoreNavigationCmpt,
|
||||
CoreDashboard
|
||||
/*,
|
||||
"CoreFilterCmpt": CoreFilterCmpt,
|
||||
"verticalsplit": verticalsplit,
|
||||
"searchbar": searchbar*/
|
||||
}
|
||||
}).mount('#main');
|
||||
@@ -0,0 +1,38 @@
|
||||
export default {
|
||||
name: "BaseModal",
|
||||
props: [
|
||||
"id",
|
||||
"cModalClass", // optional: add custom classes to modal class
|
||||
"cModalDialogClass" // optional: add custom classes to modal-dialog class
|
||||
],
|
||||
onMounted()
|
||||
{
|
||||
const modal = new bootstrap.Modal(this.$refs.modal, {})
|
||||
modal.show();
|
||||
},
|
||||
template: `
|
||||
<div class="modal fade"
|
||||
ref="modal"
|
||||
:class="this.cModalClass"
|
||||
:id="this.id"
|
||||
:aria-labelledby="this.id + '_label'" aria-hidden="true" tabindex="-1">
|
||||
<div class="modal-dialog" :class="this.cModalDialogClass">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" :id="this.id + '_label'">
|
||||
<slot name="title"></slot>
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<slot name="footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Abbrechen</button><!-- default -->
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
export default {
|
||||
name: "BaseOffcanvas",
|
||||
props: [
|
||||
"id",
|
||||
"cOffcanvasClass", // optional: add custom classes to offcanvas class
|
||||
"closeFunc"
|
||||
],
|
||||
computed: {
|
||||
OffcanvasClass() {
|
||||
return this.cOffcanvasClass || 'offcanvas-end'; // default: slide in from right to left
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="offcanvas"
|
||||
:class="this.OffcanvasClass"
|
||||
:id="this.id"
|
||||
:aria-labelledby="this.id + '_label'"
|
||||
tabindex="-1"
|
||||
data-bs-backdrop="false">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" :id="this.id + '_label'">
|
||||
<slot name="title"></slot>
|
||||
</h5>
|
||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" @click="closeFunc" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<div>
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import BsModal from './Modal.js';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BsModal
|
||||
},
|
||||
mixins: [
|
||||
BsModal
|
||||
],
|
||||
props: {
|
||||
dialogClass: {
|
||||
type: [String,Array,Object],
|
||||
default: 'modal-dialog-centered'
|
||||
},
|
||||
/*
|
||||
* NOTE(chris):
|
||||
* Hack to expose in "emits" declared events to $props which we use
|
||||
* in the v-bind directive to forward all events.
|
||||
* @see: https://github.com/vuejs/core/issues/3432
|
||||
*/
|
||||
onHideBsModal: Function,
|
||||
onHiddenBsModal: Function,
|
||||
onHidePreventedBsModal: Function,
|
||||
onShowBsModal: Function,
|
||||
onShownBsModal: Function
|
||||
},
|
||||
data: () => ({
|
||||
result: true
|
||||
}),
|
||||
mounted() {
|
||||
this.modal = this.$refs.modalContainer.modal;
|
||||
},
|
||||
popup(msg, options) {
|
||||
return BsModal.popup.bind(this)(msg, options);
|
||||
},
|
||||
template: `<bs-modal ref="modalContainer" class="bootstrap-alert" v-bind="$props">
|
||||
<template v-slot:default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
|
||||
</template>
|
||||
</bs-modal>`
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import BsAlert from './Alert';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
BsAlert
|
||||
],
|
||||
data: () => ({
|
||||
result: false
|
||||
}),
|
||||
popup(msg, options) {
|
||||
return BsAlert.popup.bind(this)(msg, options);
|
||||
},
|
||||
template: `<bs-modal ref="modalContainer" class="bootstrap-confirm" v-bind="$props">
|
||||
<template v-slot:default>
|
||||
<slot></slot>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button type="button" class="btn btn-primary" @click="result=true;this.hide()">OK</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
</template>
|
||||
</bs-modal>`
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
export default {
|
||||
data: () => ({
|
||||
modal: null
|
||||
}),
|
||||
props: {
|
||||
backdrop: {
|
||||
type: [Boolean,String],
|
||||
default: true,
|
||||
validator(value) {
|
||||
return ['static', true, false].includes(value);
|
||||
}
|
||||
},
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
keyboard: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
noCloseBtn: Boolean,
|
||||
dialogClass: [String,Array,Object]
|
||||
},
|
||||
emits: [
|
||||
"hideBsModal",
|
||||
"hiddenBsModal",
|
||||
"hidePreventedBsModal",
|
||||
"showBsModal",
|
||||
"shownBsModal"
|
||||
],
|
||||
methods: {
|
||||
dispose() {
|
||||
return this.modal.dispose();
|
||||
},
|
||||
handleUpdate() {
|
||||
return this.modal.handleUpdate();
|
||||
},
|
||||
hide() {
|
||||
return this.modal.hide();
|
||||
},
|
||||
show(relatedTarget) {
|
||||
return this.modal.show(relatedTarget);
|
||||
},
|
||||
toggle() {
|
||||
return this.modal.toggle();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.modal)
|
||||
this.modal = new bootstrap.Modal(this.$refs.modal, {
|
||||
backdrop: this.backdrop,
|
||||
focus: this.focus,
|
||||
keyboard: this.keyboard
|
||||
});
|
||||
},
|
||||
popup(body, options, title, footer) {
|
||||
const BsModal = this;
|
||||
return new Promise((resolve,reject) => {
|
||||
const instance = Vue.createApp({
|
||||
setup() {
|
||||
return () => Vue.h(BsModal, {...{
|
||||
class: 'fade'
|
||||
},...options, ...{
|
||||
ref: 'modal',
|
||||
'onHidden.bs.modal': instance.unmount
|
||||
}}, {
|
||||
title: () => title,
|
||||
default: () => body,
|
||||
footer: () => footer
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.modal.show();
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.$refs.modal)
|
||||
this.$refs.modal.result !== false ? resolve(this.$refs.modal.result) : reject();
|
||||
},
|
||||
unmounted() {
|
||||
wrapper.parentElement.removeChild(wrapper);
|
||||
}
|
||||
});
|
||||
const wrapper = document.createElement("div");
|
||||
document.body.appendChild(wrapper);
|
||||
instance.mount(wrapper);
|
||||
});
|
||||
},
|
||||
template: `<div ref="modal" class="bootstrap-modal modal" tabindex="-1" @[\`hide.bs.modal\`]="$emit('hideBsModal')" @[\`hidden.bs.modal\`]="$emit('hiddenBsModal')" @[\`hidePrevented.bs.modal\`]="$emit('hidePreventedBsModal')" @[\`show.bs.modal\`]="$emit('showBsModal')" @[\`shown.bs.modal\`]="$emit('shownBsModal')">
|
||||
<div class="modal-dialog" :class="dialogClass">
|
||||
<div class="modal-content">
|
||||
<div v-if="$slots.title" class="modal-header">
|
||||
<h5 class="modal-title"><slot name="title"/></h5>
|
||||
<button v-if="!noCloseBtn" type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body px-4 py-5">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div v-if="$slots.footer" class="modal-footer">
|
||||
<slot name="footer"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import BsAlert from './Alert';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
BsAlert
|
||||
],
|
||||
props: {
|
||||
placeholder: String,
|
||||
default: String
|
||||
},
|
||||
data: () => ({
|
||||
value: '',
|
||||
result: false
|
||||
}),
|
||||
created() {
|
||||
if (this.default)
|
||||
this.value = this.default;
|
||||
},
|
||||
popup(msg, options) {
|
||||
if (typeof options === 'string')
|
||||
options = { default: options };
|
||||
return BsAlert.popup.bind(this)(msg, options);
|
||||
},
|
||||
template: `<bs-modal ref="modalContainer" class="bootstrap-prompt" v-bind="$props">
|
||||
<template v-slot:default>
|
||||
<slot></slot>
|
||||
<div>
|
||||
<input ref="input" type="text" class="form-control" :placeholder="placeholder" v-model="value">
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button type="button" class="btn btn-primary" @click="result=value;this.hide()">OK</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
</template>
|
||||
</bs-modal>`
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import BsPrompt from "../Bootstrap/Prompt.js";
|
||||
import DashboardAdminEdit from "./Admin/Edit.js";
|
||||
import DashboardAdminWidgets from "./Admin/Widgets.js";
|
||||
import DashboardAdminPresets from "./Admin/Presets.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DashboardAdminEdit,
|
||||
DashboardAdminWidgets,
|
||||
DashboardAdminPresets
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
adminMode: true
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dashboards: [],
|
||||
current: -1,
|
||||
widgets: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
apiurl() {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
|
||||
},
|
||||
dashboard() {
|
||||
return this.dashboards.find(el => el.dashboard_id == this.current);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dashboardAdd() {
|
||||
let _name = '';
|
||||
BsPrompt.popup('New Dashboard name').then(
|
||||
name => {
|
||||
_name = name;
|
||||
return axios.post(this.apiurl + '/Dashboard/Create', {
|
||||
dashboard_kurzbz: name
|
||||
})
|
||||
}
|
||||
).then(res => {
|
||||
let newDashboard = {
|
||||
dashboard_id: res.data.retval,
|
||||
dashboard_kurzbz: _name,
|
||||
beschreibung: ''
|
||||
};
|
||||
this.dashboards.push(newDashboard);
|
||||
this.current = newDashboard.dashboard_id;
|
||||
}).catch(err => err !== undefined ? console.error('ERROR:', err) : 0);
|
||||
},
|
||||
dashboardUpdate(dashboard) {
|
||||
// TODO(chris): Loading or message
|
||||
axios.post(this.apiurl + '/Dashboard/Update', dashboard).then(() => {
|
||||
let old = this.dashboards.find(el => el.dashboard_id == dashboard.dashboard_id);
|
||||
old.dashboard_kurzbz = dashboard.dashboard_kurzbz;
|
||||
old.beschreibung = dashboard.beschreibung;
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
dashboardDelete(dashboard_id) {
|
||||
axios.post(this.apiurl + '/Dashboard/Delete', {dashboard_id}).then(() => {
|
||||
this.current = -1;
|
||||
this.dashboards = this.dashboards.filter(el => el.dashboard_id != dashboard_id);
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
assignWidgets(widgets) {
|
||||
this.widgets = widgets;
|
||||
/*while (this.widgets.length)
|
||||
this.widgets.pop();
|
||||
for (var i in widgets)
|
||||
this.widgets.push(widgets[i]);*/
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios.get(this.apiurl + '/Dashboard').then(res => {
|
||||
//console.log(res.data.retval);
|
||||
this.dashboards = res.data.retval;
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
template: `<div class="dashboard-admin">
|
||||
<div class="input-group">
|
||||
<label for="dashbaord-select" class="input-group-text">Dashboard:</label>
|
||||
<select id="dashbaord-select" class="form-select" v-model="current">
|
||||
<option v-for="dashboard in dashboards" :key="dashboard.dashboard_id" :value="dashboard.dashboard_id">{{dashboard.dashboard_kurzbz}}</option>
|
||||
</select>
|
||||
<button class="btn btn-outline-secondary" type="button" @click="dashboardAdd"><i class="fa-solid fa-plus"></i></button>
|
||||
</div>
|
||||
<div v-if="dashboard">
|
||||
<ul class="nav nav-tabs mt-3" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="edit-tab" data-bs-toggle="tab" data-bs-target="#edit" type="button" role="tab" aria-controls="edit" aria-selected="false">Edit</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="widgets-tab" data-bs-toggle="tab" data-bs-target="#widgets" type="button" role="tab" aria-controls="widgets" aria-selected="true">Widgets</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="presets-tab" data-bs-toggle="tab" data-bs-target="#presets" type="button" role="tab" aria-controls="presets" aria-selected="false">Presets</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content pt-3">
|
||||
<div class="tab-pane fade" id="edit" role="tabpanel" aria-labelledby="edit-tab">
|
||||
<dashboard-admin-edit v-bind="dashboard" :key="dashboard.dashboard_id" @change="dashboardUpdate($event)" @delete="dashboardDelete($event)"></dashboard-admin-edit>
|
||||
</div>
|
||||
<div class="tab-pane fade show active" id="widgets" role="tabpanel" aria-labelledby="widgets-tab">
|
||||
<dashboard-admin-widgets :key="dashboard.dashboard_id" :dashboard_id="dashboard.dashboard_id" :widgets="widgets" @change="test" @assign-widgets="assignWidgets"></dashboard-admin-widgets>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="presets" role="tabpanel" aria-labelledby="presets-tab">
|
||||
<dashboard-admin-presets :dashboard="dashboard.dashboard_kurzbz" :widgets="widgets"></dashboard-admin-presets>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import BsConfirm from '../../Bootstrap/Confirm.js';
|
||||
|
||||
export default {
|
||||
emits: [
|
||||
"change",
|
||||
"delete"
|
||||
],
|
||||
props: {
|
||||
dashboard_id: Number,
|
||||
dashboard_kurzbz: String,
|
||||
beschreibung: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
kurzbz: this.dashboard_kurzbz,
|
||||
desc: this.beschreibung
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sendDelete() {
|
||||
BsConfirm.popup('Sure?').then(() => this.$emit('delete', this.dashboard_id)).catch();
|
||||
}
|
||||
},
|
||||
template: `<div class="dashboard-admin-edit px-3">
|
||||
<div class="mb-3">
|
||||
<label for="dashboard-admin-edit-kurzbz">Kurz Bezeichnung</label>
|
||||
<input id="dashboard-admin-edit-kurzbz" type="text" class="form-control" v-model="kurzbz">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="dashboard-admin-edit-beschreibung">Beschreibung</label>
|
||||
<textarea id="dashboard-admin-edit-beschreibung" class="form-control" v-model="desc"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-danger" @click="sendDelete">Delete</button>
|
||||
<button class="btn btn-primary" @click="$emit('change', {dashboard_id,dashboard_kurzbz:kurzbz,beschreibung:desc})">Update</button>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import DashboardSection from "../Section.js";
|
||||
import DashboardWidgetPicker from "../Widget/Picker.js";
|
||||
import ObjectUtils from "../../../composables/ObjectUtils.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DashboardSection,
|
||||
DashboardWidgetPicker
|
||||
},
|
||||
props: {
|
||||
dashboard: String,
|
||||
widgets: Array
|
||||
},
|
||||
data: () => ({
|
||||
funktionen: {},
|
||||
sections: [],
|
||||
}),
|
||||
computed: {
|
||||
apiurl() {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
|
||||
},
|
||||
pickerWidgets() {
|
||||
return this.widgets.filter(widget => widget.allowed);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
widgetAdd(section_name, widget) {
|
||||
this.$refs.widgetpicker.getWidget().then(widget_id => {
|
||||
widget.widget = widget_id;
|
||||
delete widget.custom;
|
||||
let loading = {...widget};
|
||||
loading.loading = true;
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name)
|
||||
section.widgets.push(loading);
|
||||
});
|
||||
|
||||
axios.post(this.apiurl + '/Config/addWidgetsToPreset', {
|
||||
db: this.dashboard,
|
||||
funktion_kurzbz: section_name,
|
||||
widgets: [widget]
|
||||
}).then(result => {
|
||||
let newId = Object.keys(result.data.retval.data.widgets[section_name]).pop();
|
||||
widget.id = newId;
|
||||
widget.custom = 1;
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name) {
|
||||
section.widgets.splice(section.widgets.indexOf(loading),1);
|
||||
section.widgets.push(widget);
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('ERROR: ', error);
|
||||
alert('ERROR: ' + error.response.data.retval);
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
widgetUpdate(section_name, payload) {
|
||||
payload = payload[section_name];
|
||||
for (var k in payload) {
|
||||
for (var i in this.sections) {
|
||||
if (this.sections[i].name == section_name) {
|
||||
for (var wid in this.sections[i].widgets) {
|
||||
if (this.sections[i].widgets[wid].id == k) {
|
||||
payload[k] = ObjectUtils.mergeDeep(this.sections[i].widgets[wid], payload[k]);
|
||||
// NOTE(chris): remove internal props
|
||||
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1})
|
||||
if (payload[k][prop])
|
||||
delete payload[k][prop];
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
payload[k].widgetid = k;
|
||||
delete payload[k].custom;
|
||||
}
|
||||
axios.post(this.apiurl + '/Config/addWidgetsToPreset', {
|
||||
db: this.dashboard,
|
||||
funktion_kurzbz: section_name,
|
||||
widgets: payload
|
||||
}).then(() => {
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name) {
|
||||
section.widgets.forEach((widget, i) => {
|
||||
if (payload[widget.id]) {
|
||||
payload[widget.id].id = widget.id;
|
||||
payload[widget.id].index = widget.index;
|
||||
section.widgets[i] = payload[widget.id];
|
||||
section.widgets[i].custom = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
// TODO(chris): revert placement on failure
|
||||
console.error('ERROR: ', error);
|
||||
alert('ERROR: ' + error.response.data.retval);
|
||||
});
|
||||
},
|
||||
widgetRemove(section_name, id) {
|
||||
axios.post(this.apiurl + '/Config/removeWidgetFromPreset', {
|
||||
db: this.dashboard,
|
||||
funktion_kurzbz: section_name,
|
||||
widgetid: id
|
||||
}).then(() => {
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name)
|
||||
section.widgets = section.widgets.filter(widget => widget.id != id);
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('ERROR: ', error);
|
||||
alert('ERROR: ' + error.response.data.retval);
|
||||
});
|
||||
},
|
||||
loadSections(evt) {
|
||||
let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value);
|
||||
this.sections = [];
|
||||
axios.get(this.apiurl + '/Config/PresetBatch', {params: {
|
||||
db: this.dashboard,
|
||||
funktionen
|
||||
}}).then(res => {
|
||||
for (var section in res.data.retval) {
|
||||
let widgets = [];
|
||||
for (var wid in res.data.retval[section]) {
|
||||
res.data.retval[section][wid].id = wid;
|
||||
res.data.retval[section][wid].custom = 1;
|
||||
widgets.push(res.data.retval[section][wid]);
|
||||
}
|
||||
this.sections.push({
|
||||
name: section,
|
||||
widgets
|
||||
});
|
||||
}
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios.get(this.apiurl + '/Config/Funktionen').then(res => {
|
||||
//console.log(res.data.retval);
|
||||
this.funktionen = {general: 'GENERAL'};
|
||||
res.data.retval.forEach(funktion => {
|
||||
this.funktionen[funktion.funktion_kurzbz] = funktion.beschreibung;
|
||||
});
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
template: `<div class="dashboard-admin-presets">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<select class="form-control" multiple @input="loadSections">
|
||||
<option v-for="name,id in funktionen" :key="id" :value="id">{{ name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<dashboard-section v-for="section in sections" :key="section.name" :name="section.name" :widgets="section.widgets" @widget-add="widgetAdd" @widget-update="widgetUpdate" @widget-remove="widgetRemove"></dashboard-section>
|
||||
</div>
|
||||
</div>
|
||||
<dashboard-widget-picker ref="widgetpicker" :widgets="pickerWidgets"></dashboard-widget-picker>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
emits: [
|
||||
"change",
|
||||
"assignWidgets"
|
||||
],
|
||||
props: {
|
||||
dashboard_id: Number,
|
||||
widgets: Array
|
||||
},
|
||||
computed: {
|
||||
apiurl() {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sendChange(widget_id) {
|
||||
let allow = !this.widgets.find(el => el.widget_id == widget_id).allowed;
|
||||
axios.post(this.apiurl + '/Widget/setAllowed', {
|
||||
dashboard_id: this.dashboard_id,
|
||||
widget_id,
|
||||
action: allow ? 'add' : 'delete'
|
||||
}).catch(err => console.error('ERROR: ' + err));
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios.get(this.apiurl + '/Widget/getAll', {
|
||||
params:{
|
||||
dashboard_id: this.dashboard_id
|
||||
}
|
||||
}).then(
|
||||
result => {
|
||||
this.$emit('assignWidgets', result.data.retval.map(el => ({
|
||||
...el,
|
||||
...{setup:JSON.parse(el.setup),arguments:JSON.parse(el.arguments),allowed:!!el.allowed}
|
||||
})));
|
||||
}
|
||||
).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
template: `<div class="dashboard-admin-widgets">
|
||||
<div v-for="widget in widgets" :key="widget.widget_id" class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" role="switch" :id="'dashboard-admin-widgets-' + widget.widget_id" v-model="widget.allowed" @input.prevent="sendChange(widget.widget_id)">
|
||||
<label class="form-check-label" :for="'dashboard-admin-widgets-' + widget.widget_id">{{(widget.setup && widget.setup.name) || widget.widget_kurzbz}}</label>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import DashboardSection from "./Section.js";
|
||||
import DashboardWidgetPicker from "./Widget/Picker.js";
|
||||
import ObjectUtils from "../../composables/ObjectUtils.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DashboardSection,
|
||||
DashboardWidgetPicker
|
||||
},
|
||||
props: [
|
||||
"dashboard"
|
||||
],
|
||||
data: () => ({
|
||||
sections: [],
|
||||
widgets: null
|
||||
}),
|
||||
computed: {
|
||||
apiurl() {
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
widgetAdd(section_name, widget) {
|
||||
if (this.widgets === null) {
|
||||
axios.get(this.apiurl + '/Widget/getWidgetsForDashboard', {params:{
|
||||
db: this.dashboard
|
||||
}}).then(res => {
|
||||
//console.log(res.data.retval);
|
||||
res.data.retval.forEach(widget => {
|
||||
widget.arguments = JSON.parse(widget.arguments);
|
||||
widget.setup = JSON.parse(widget.setup);
|
||||
});
|
||||
this.widgets = res.data.retval;
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
}
|
||||
this.$refs.widgetpicker.getWidget().then(widget_id => {
|
||||
widget.widget = widget_id;
|
||||
let loading = {...widget};
|
||||
loading.loading = true;
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name)
|
||||
section.widgets.push(loading);
|
||||
});
|
||||
|
||||
axios.post(this.apiurl + '/Config/addWidgetsToUserOverride', {
|
||||
db: this.dashboard,
|
||||
funktion_kurzbz: section_name,
|
||||
widgets: [widget]
|
||||
}).then(result => {
|
||||
let newId = Object.keys(result.data.retval.data.widgets[section_name]).pop();
|
||||
widget.id = newId;
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name) {
|
||||
section.widgets.splice(section.widgets.indexOf(loading),1);
|
||||
section.widgets.push(widget);
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('ERROR: ', error);
|
||||
alert('ERROR: ' + error.response.data.retval);
|
||||
});
|
||||
}).catch(() => {});
|
||||
},
|
||||
widgetUpdate(section_name, payload) {
|
||||
payload = payload[section_name];
|
||||
for (var k in payload) {
|
||||
for (var i in this.sections) {
|
||||
if (this.sections[i].name == section_name) {
|
||||
for (var wid in this.sections[i].widgets) {
|
||||
if (this.sections[i].widgets[wid].id == k) {
|
||||
payload[k] = ObjectUtils.mergeDeep(this.sections[i].widgets[wid], payload[k]);
|
||||
// NOTE(chris): remove internal props
|
||||
for (var prop in {_x:1,_y:1,_w:1,_h:1,index:1,id:1})
|
||||
if (payload[k][prop])
|
||||
delete payload[k][prop];
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
payload[k].widgetid = k;
|
||||
}
|
||||
axios.post(this.apiurl + '/Config/addWidgetsToUserOverride', {
|
||||
db: this.dashboard,
|
||||
funktion_kurzbz: section_name,
|
||||
widgets: payload
|
||||
}).then(() => {
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name) {
|
||||
section.widgets.forEach((widget, i) => {
|
||||
if (payload[widget.id]) {
|
||||
payload[widget.id].id = widget.id;
|
||||
payload[widget.id].index = widget.index;
|
||||
section.widgets[i] = payload[widget.id];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
// TODO(chris): revert placement on failure
|
||||
console.error('ERROR: ', error);
|
||||
alert('ERROR: ' + error.response.data.retval);
|
||||
});
|
||||
},
|
||||
widgetRemove(section_name, id) {
|
||||
axios.post(this.apiurl + '/Config/removeWidgetFromUserOverride', {
|
||||
db: this.dashboard,
|
||||
funktion_kurzbz: section_name,
|
||||
widgetid: id
|
||||
}).then(() => {
|
||||
this.sections.forEach(section => {
|
||||
if (section.name == section_name)
|
||||
section.widgets = section.widgets.filter(widget => widget.id != id);
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('ERROR: ', error);
|
||||
alert('ERROR: ' + error.response.data.retval);
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
axios.get(this.apiurl + '/Config', {params:{
|
||||
db: this.dashboard
|
||||
}}).then(res => {
|
||||
//console.log(res.data.retval);
|
||||
for (var name in res.data.retval.widgets) {
|
||||
let widgets = [];
|
||||
for (var wid in res.data.retval.widgets[name]) {
|
||||
res.data.retval.widgets[name][wid].id = wid;
|
||||
widgets.push(res.data.retval.widgets[name][wid]);
|
||||
}
|
||||
this.sections.push({
|
||||
name: name,
|
||||
widgets: widgets
|
||||
});
|
||||
}
|
||||
}).catch(err => console.error('ERROR:', err));
|
||||
},
|
||||
template: `<div class="core-dashboard">
|
||||
<dashboard-section v-for="section in sections" :key="section.name" :name="section.name" :widgets="section.widgets" @widgetAdd="widgetAdd" @widgetUpdate="widgetUpdate" @widgetRemove="widgetRemove"></dashboard-section>
|
||||
<dashboard-widget-picker ref="widgetpicker" :widgets="widgets"></dashboard-widget-picker>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import BsModal from "../Bootstrap/Modal.js";
|
||||
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BsModal
|
||||
},
|
||||
data: () => ({
|
||||
component: '',
|
||||
arguments: null,
|
||||
target: false,
|
||||
widget: null,
|
||||
tmpConfig: {},
|
||||
isLoading: false,
|
||||
hasConfig: true
|
||||
}),
|
||||
emits: [
|
||||
"change",
|
||||
"remove",
|
||||
"dragstart",
|
||||
"resizestart",
|
||||
],
|
||||
props: [
|
||||
"id",
|
||||
"config",
|
||||
"width",
|
||||
"height",
|
||||
"custom",
|
||||
"hidden",
|
||||
"editMode",
|
||||
"loading"
|
||||
],
|
||||
computed: {
|
||||
isResizeable() {
|
||||
if (!this.widget)
|
||||
return false;
|
||||
return this.widget.setup.width.max || this.widget.setup.height.max;
|
||||
},
|
||||
ready() {
|
||||
return this.component && this.arguments !== null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mouseDown(e) {
|
||||
this.target = e.target;
|
||||
},
|
||||
startDrag(e) {
|
||||
if (this.$refs.dragHandle.contains(this.target)) {
|
||||
this.$emit('dragstart', e);
|
||||
} else if (this.isResizeable && this.$refs.resizeHandle.contains(this.target)) {
|
||||
if (this.isResizeable)
|
||||
this.$emit('resizestart', e);
|
||||
else
|
||||
e.preventDefault();
|
||||
} else {
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
openConfig() {
|
||||
this.tmpConfig = {...this.arguments};
|
||||
this.$refs.config.show();
|
||||
},
|
||||
setConfig(hasConfig) {
|
||||
this.hasConfig = hasConfig;
|
||||
},
|
||||
changeConfig() {
|
||||
this.isLoading = true;
|
||||
let config = {...this.tmpConfig};
|
||||
this.sendChangeConfig(config);
|
||||
},
|
||||
changeConfigManually() {
|
||||
let config = {...this.arguments};
|
||||
this.sendChangeConfig(config);
|
||||
},
|
||||
sendChangeConfig(config) {
|
||||
for (var k in config) {
|
||||
if (this.widget.arguments[k] == config[k]) {
|
||||
delete config[k];
|
||||
}
|
||||
}
|
||||
this.$emit('change', config);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
config() {
|
||||
this.arguments = {...this.widget.arguments, ...this.config};
|
||||
this.tmpConfig = {...this.arguments};
|
||||
this.$refs.config.hide();
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.widget = await CachedWidgetLoader.loadWidget(this.id);
|
||||
let component = (await import('../' + this.widget.setup.file)).default;
|
||||
this.$options.components['widget' + this.widget.widget_id] = component;
|
||||
this.component = 'widget' + this.widget.widget_id;
|
||||
this.arguments = {...this.widget.arguments, ...this.config};
|
||||
this.tmpConfig = {...this.arguments};
|
||||
},
|
||||
template: `<div v-if="loading">
|
||||
<div class="d-flex justify-content-center align-items-center h-100">
|
||||
<i class="fa-solid fa-spinner fa-pulse fa-3x"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="!hidden || editMode" :class="'dashboard-item card overflow-hidden ' + (arguments ? arguments.className : '')" @mousedown="mouseDown($event)" @dragstart="startDrag($event)" :draggable="!!editMode">
|
||||
<div v-if="editMode && widget" class="card-header d-flex ps-0 pe-2">
|
||||
<span ref="dragHandle" class="col-auto mx-2 px-2 cursor-move"><i class="fa-solid fa-grip-vertical"></i></span>
|
||||
<span class="col">{{ widget.setup.name }}</span>
|
||||
<a v-if="hasConfig" class="col-auto px-1" href="#" @click.prevent="openConfig"><i class="fa-solid fa-gear"></i></a>
|
||||
<a v-if="custom" class="col-auto px-1" href="#" @click.prevent="$emit('remove')">
|
||||
<i class="fa-solid fa-trash"></i>
|
||||
</a>
|
||||
<div v-else class="col-auto px-1 form-switch">
|
||||
<input class="form-check-input ms-0" type="checkbox" role="switch" id="flexSwitchCheckChecked" :checked="!hidden" @input="$emit('remove', hidden)">
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="ready" class="card-body overflow-hidden">
|
||||
<component :is="component" :config="arguments" :width="width" :height="height" @setConfig="setConfig" @change="changeConfigManually"></component>
|
||||
</div>
|
||||
<div v-else class="card-body overflow-hidden text-center d-flex flex-column justify-content-center"><i class="fa-solid fa-spinner fa-pulse fa-3x"></i></div>
|
||||
<bs-modal ref="config">
|
||||
<template v-slot:title>
|
||||
{{ widget ? 'Config for ' + widget.setup.name : '' }}
|
||||
</template>
|
||||
<template v-slot:default>
|
||||
<component v-if="ready && !isLoading" :is="component" :config="tmpConfig" @change="changeConfig" :configMode="true"></component>
|
||||
<div v-else class="text-center"><i class="fa-solid fa-spinner fa-pulse fa-3x"></i></div>
|
||||
</template>
|
||||
<template v-slot:footer>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" @click="changeConfig">Save changes</button>
|
||||
</template>
|
||||
</bs-modal>
|
||||
<div v-if="editMode && isResizeable" class="card-footer d-flex justify-content-end p-0">
|
||||
<span ref="resizeHandle" class="col-auto px-1 cursor-nw-resize" @dragstart.prevent="$emit('resize')"><i class="fa-solid fa-up-right-and-down-left-from-center mirror-x"></i></span>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
import BsConfirm from "../Bootstrap/Confirm.js";
|
||||
import DashboardItem from "./Item.js";
|
||||
import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js";
|
||||
|
||||
// TODO(chris): handle overflow (moving outside the box)
|
||||
export default {
|
||||
components: {
|
||||
DashboardItem
|
||||
},
|
||||
inject: {
|
||||
adminMode: {
|
||||
default: false
|
||||
}
|
||||
},
|
||||
props: [
|
||||
"name",
|
||||
"widgets"
|
||||
],
|
||||
emits: [
|
||||
"widgetAdd",
|
||||
"widgetUpdate",
|
||||
"widgetRemove"
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
gridWidth: 0,
|
||||
containerRect: {top:0,left:0},
|
||||
changeHeight: 1,
|
||||
movedObjects: [],
|
||||
editMode: this.adminMode ? 1 : 0,
|
||||
gridXLast: 0,
|
||||
gridYLast: 0,
|
||||
dataTransfer: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
this.widgets.forEach((item,i) => item.index = i);
|
||||
return this.widgets;
|
||||
},
|
||||
itemCoords() {
|
||||
if (!this.gridWidth)
|
||||
return [];
|
||||
let itemCoords = this.items.map(item => item.place[this.gridWidth] || this.createItemPlacement(item));
|
||||
// TODO(chris): verify positions & sizes
|
||||
let occupiers = [];
|
||||
let wrongPlacedItems = [];
|
||||
let gridWidth = this.gridWidth;
|
||||
this.items.forEach(item => {
|
||||
let x = item._x !== undefined ? item._x : itemCoords[item.index].x;
|
||||
let y = item._y !== undefined ? item._y : itemCoords[item.index].y;
|
||||
let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
|
||||
let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
|
||||
// TODO(chris): check with and height params here?
|
||||
for (var i = 0; i < w; i++) {
|
||||
for (var j = 0; j < h; j++) {
|
||||
var c = (y+j-1) * gridWidth + (x+i-1);
|
||||
// NOTE(chris): check for overlaping items
|
||||
if (occupiers[c] !== undefined) {
|
||||
//console.log('try to add ' + item.index + ' to ' + x + '/' + y + ', but ' + occupiers[c] + ' is already there');
|
||||
// NOTE(chris): remove possible other entries of this item
|
||||
for (var c2 = c; c2; c2--)
|
||||
if (occupiers[c2] == item.index)
|
||||
occupiers[c2] = undefined;
|
||||
wrongPlacedItems.push(item);
|
||||
return;
|
||||
}
|
||||
occupiers[c] = item.index;
|
||||
}
|
||||
}
|
||||
});
|
||||
wrongPlacedItems.forEach(item => {
|
||||
let w = item._w !== undefined ? item._w : itemCoords[item.index].w;
|
||||
let h = item._h !== undefined ? item._h : itemCoords[item.index].h;
|
||||
for (var c = 0; c < occupiers.length + gridWidth; c++) {
|
||||
if (occupiers[c] === undefined) {
|
||||
var occupied = false, i, j;
|
||||
for (i = 0; i < w; i++) {
|
||||
for (j = 0; j < h; j++) {
|
||||
if (occupiers[c + i + j * gridWidth] !== undefined) {
|
||||
i = w;
|
||||
occupied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!occupied) {
|
||||
item.place[gridWidth].x = c%gridWidth + 1;
|
||||
item.place[gridWidth].y = Math.floor(c/gridWidth) + 1;
|
||||
for (i = 0; i < w; i++) {
|
||||
for (j = 0; j < h; j++) {
|
||||
occupiers[c + i + j * gridWidth] = item.index;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return itemCoords;
|
||||
},
|
||||
gridHeight() {
|
||||
if (!this.gridWidth || !this.changeHeight)
|
||||
return 0;
|
||||
let minH = 0;
|
||||
this.itemCoords.forEach((item,i) => minH = Math.max(minH, (!this.editMode && this.items[i].hidden) ? 0 : item.y + item.h - 1));
|
||||
// TODO(chris): the extraline should only be present if all slots are occupied
|
||||
return minH + this.editMode;
|
||||
},
|
||||
gridOccupiers() {
|
||||
let occupiers = [];
|
||||
let gridWidth = this.gridWidth;
|
||||
this.items.forEach(item => {
|
||||
let x = item._x !== undefined ? item._x : this.itemCoords[item.index].x;
|
||||
let y = item._y !== undefined ? item._y : this.itemCoords[item.index].y;
|
||||
let w = item._w !== undefined ? item._w : this.itemCoords[item.index].w;
|
||||
let h = item._h !== undefined ? item._h : this.itemCoords[item.index].h;
|
||||
for (var i = 0; i < w; i++) {
|
||||
for (var j = 0; j < h; j++) {
|
||||
var c = (y+j-1) * gridWidth + (x+i-1);
|
||||
occupiers[c] = item.index;
|
||||
}
|
||||
}
|
||||
});
|
||||
return occupiers;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addWidget(evt) {
|
||||
if (evt.target != this.$refs.container || !this.editMode)
|
||||
return;
|
||||
const rect = this.containerRect;
|
||||
const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
|
||||
const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
|
||||
if (this.gridOccupiers[gridY * this.gridWidth + gridX] === undefined) {
|
||||
let widget = { widget: 1, config: {}, place: {}, custom: 1 };
|
||||
widget.place[this.gridWidth] = {
|
||||
x: gridX,
|
||||
y: gridY,
|
||||
w: 1,
|
||||
h: 1
|
||||
};
|
||||
this.$emit('widgetAdd', this.name, widget);
|
||||
}
|
||||
},
|
||||
createItemPlacement(item) {
|
||||
// TODO(chris): create correct default placement if it is not there
|
||||
item.place[this.gridWidth] = {x:1,y:1,w:1,h:1};
|
||||
/*var freeList = [], nextId = 0;
|
||||
this.items.forEach(item => {
|
||||
if (!item.place[this.gridWidth]) {
|
||||
if (!this.gridWidth) {
|
||||
item.place[this.gridWidth] = {x:1,y:nextId++,w:1,h:1};
|
||||
} else {
|
||||
// TODO(chris): IMPLEMENT widths & heights
|
||||
if (freeList[nextId])
|
||||
while (freeList[++nextId]);
|
||||
freeList[nextId] = 1;
|
||||
item.place[this.gridWidth] = {x:(nextId%this.gridWidth)+1,y:Math.floor(nextId/this.gridWidth)+1,w:1,h:1};
|
||||
}
|
||||
}
|
||||
});*/
|
||||
return item.place[this.gridWidth];
|
||||
},
|
||||
startDrag(evt, item) {
|
||||
this.gridXLast = -1;
|
||||
this.gridYLast = -1;
|
||||
item._x = this.itemCoords[item.index].x;
|
||||
item._y = this.itemCoords[item.index].y;
|
||||
|
||||
evt.dataTransfer.dropEffect = 'move';
|
||||
evt.dataTransfer.effectAllowed = 'move';
|
||||
this.dataTransfer = {
|
||||
action: 'm',
|
||||
id: item.index,
|
||||
w: this.itemCoords[item.index].w,
|
||||
h: this.itemCoords[item.index].h
|
||||
}
|
||||
},
|
||||
startResize(evt, item) {
|
||||
this.gridXLast = -1;
|
||||
this.gridYLast = -1;
|
||||
item._w = this.itemCoords[item.index].w;
|
||||
item._h = this.itemCoords[item.index].h;
|
||||
|
||||
evt.dataTransfer.setDragImage(evt.target, -99999, -99999);
|
||||
evt.dataTransfer.dropEffect = 'move';
|
||||
evt.dataTransfer.effectAllowed = 'move';
|
||||
this.dataTransfer = {
|
||||
action: 'r',
|
||||
id: item.index,
|
||||
x: this.itemCoords[item.index].x,
|
||||
y: this.itemCoords[item.index].y
|
||||
}
|
||||
},
|
||||
occupyFields(id, x, y, w, h) {
|
||||
var c;
|
||||
while ((c = this.movedObjects.pop())) {
|
||||
if (this.items[c]._y !== undefined) {
|
||||
this.items[c].place[this.gridWidth].y = this.items[c]._y;
|
||||
this.items[c]._y = undefined;
|
||||
}
|
||||
}
|
||||
var move = {};
|
||||
move[id] = this.items[id];
|
||||
this.getOccupiedItems(x,y,w,h,move);
|
||||
h = y + h;
|
||||
y = 0;
|
||||
for (x in move) {
|
||||
if (x != id) {
|
||||
c = move[x]._y !== undefined ? move[x]._y : this.itemCoords[x].y;
|
||||
if (c < h)
|
||||
y = Math.max(h-c, y);
|
||||
}
|
||||
}
|
||||
for (x in move) {
|
||||
if (x != id) {
|
||||
this.movedObjects.push(x);
|
||||
if (move[x]._y === undefined) {
|
||||
move[x]._y = this.itemCoords[x].y;
|
||||
}
|
||||
move[x].place[this.gridWidth].y = move[x]._y + y;
|
||||
}
|
||||
}
|
||||
},
|
||||
getOccupiedItems(x, y, w, h, move) {
|
||||
var i, j, c;
|
||||
for (i = 0; i < w; i++) {
|
||||
for (j = 0; j < h; j++) {
|
||||
c = (y+j-1) * this.gridWidth + (x+i-1);
|
||||
if (this.gridOccupiers[c] !== undefined && !move[this.gridOccupiers[c]]) {
|
||||
move[this.gridOccupiers[c]] = this.items[this.gridOccupiers[c]];
|
||||
c = this.itemCoords[this.gridOccupiers[c]];
|
||||
this.getOccupiedItems(c.x, c.y + 1, c.w, c.h, move);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onDragOver(evt) {
|
||||
let id, x, y, w, h;
|
||||
const action = this.dataTransfer.action;
|
||||
const rect = this.containerRect;
|
||||
const gridX = Math.floor(this.gridWidth * (evt.clientX - rect.left) / this.$refs.container.clientWidth);
|
||||
const gridY = Math.floor(this.gridHeight * (evt.clientY - rect.top) / this.$refs.container.clientHeight);
|
||||
|
||||
if (this.gridXLast == gridX && this.gridYLast == gridY)
|
||||
return;
|
||||
this.gridXLast = gridX;
|
||||
this.gridYLast = gridY;
|
||||
|
||||
if (action == 'm') {
|
||||
x = Math.max(gridX + 1, 1);
|
||||
y = Math.max(gridY + 1, 1);
|
||||
w = parseInt(this.dataTransfer.w);
|
||||
h = parseInt(this.dataTransfer.h);
|
||||
|
||||
if (x + w > this.gridWidth + 1)
|
||||
x = this.gridWidth + 1 - w;
|
||||
|
||||
id = this.dataTransfer.id;
|
||||
this.occupyFields(id, x, y, w, h);
|
||||
|
||||
this.itemCoords[id].x = x;
|
||||
this.itemCoords[id].y = y;
|
||||
} else if (action == 'r') {
|
||||
x = parseInt(this.dataTransfer.x);
|
||||
y = parseInt(this.dataTransfer.y);
|
||||
w = gridX + 2 - x;
|
||||
h = gridY + 2 - y;
|
||||
w = Math.max(1, w);
|
||||
h = Math.max(1, h);
|
||||
|
||||
if (x + w > this.gridWidth + 1)
|
||||
w = this.gridWidth + 1 - x;
|
||||
|
||||
id = this.dataTransfer.id;
|
||||
let widget = CachedWidgetLoader.getWidget(this.items[id].widget);
|
||||
if (widget) {
|
||||
let minmaxW = widget.setup.width;
|
||||
if (minmaxW.max)
|
||||
minmaxW.min = minmaxW.min || 1;
|
||||
else
|
||||
minmaxW = {min:minmaxW,max:minmaxW};
|
||||
if (w < minmaxW.min)
|
||||
w = minmaxW.min;
|
||||
if (w > minmaxW.max)
|
||||
w = minmaxW.max;
|
||||
|
||||
let minmaxH = widget.setup.height;
|
||||
if (minmaxH.max)
|
||||
minmaxH.min = minmaxH.min || 1;
|
||||
else
|
||||
minmaxH = {min:minmaxH,max:minmaxH};
|
||||
if (h < minmaxH.min)
|
||||
h = minmaxH.min;
|
||||
if (h > minmaxH.max)
|
||||
h = minmaxH.max;
|
||||
}
|
||||
|
||||
this.occupyFields(id, x, y, w, h);
|
||||
|
||||
this.itemCoords[id].w = w;
|
||||
this.itemCoords[id].h = h;
|
||||
}
|
||||
},
|
||||
onDrop() {
|
||||
let id = 0;
|
||||
let update = {};
|
||||
while ((id = this.movedObjects.pop())) {
|
||||
if (this.items[id]._y !== undefined) {
|
||||
if (this.itemCoords[id].y != this.items[id]._y) {
|
||||
update[this.items[id].id] = {place:{}};
|
||||
update[this.items[id].id].place[this.gridWidth] = {y:this.itemCoords[id].y};
|
||||
}
|
||||
this.items[id]._y = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
id = this.dataTransfer.id;
|
||||
|
||||
const action = this.dataTransfer.action;
|
||||
update[this.items[id].id] = {place:{}};
|
||||
update[this.items[id].id].place[this.gridWidth] = {};
|
||||
|
||||
if (action == 'm') {
|
||||
if (this.items[id]._x !== undefined) {
|
||||
if (this.itemCoords[id].x != this.items[id]._x) {
|
||||
update[this.items[id].id].place[this.gridWidth].x = this.itemCoords[id].x;
|
||||
}
|
||||
this.items[id]._x = undefined;
|
||||
}
|
||||
if (this.items[id]._y !== undefined) {
|
||||
if (this.itemCoords[id].y != this.items[id]._y) {
|
||||
update[this.items[id].id].place[this.gridWidth].y = this.itemCoords[id].y;
|
||||
}
|
||||
this.items[id]._y = undefined;
|
||||
}
|
||||
} else if (action == 'r') {
|
||||
update[this.items[id].id].place[this.gridWidth].w = this.itemCoords[id].w;
|
||||
update[this.items[id].id].place[this.gridWidth].h = this.itemCoords[id].h;
|
||||
}
|
||||
|
||||
if (update[this.items[id].id].place[this.gridWidth].x === undefined &&
|
||||
update[this.items[id].id].place[this.gridWidth].y === undefined &&
|
||||
update[this.items[id].id].place[this.gridWidth].w === undefined &&
|
||||
update[this.items[id].id].place[this.gridWidth].h === undefined) {
|
||||
delete update[this.items[id].id].place[this.gridWidth];
|
||||
}
|
||||
|
||||
this.updatePreset(update);
|
||||
|
||||
// TODO(chris): find better way to trigger change for gridHeight
|
||||
this.changeHeight++
|
||||
},
|
||||
removeWidget(item, revert) {
|
||||
if (item.custom) {
|
||||
BsConfirm.popup('Are you sure you want to delete this widget?').then(() => this.$emit('widgetRemove', this.name, item.id));
|
||||
} else {
|
||||
let update = {};
|
||||
update[item.id] = { hidden: !revert };
|
||||
this.updatePreset(update);
|
||||
}
|
||||
},
|
||||
saveConfig(config, item) {
|
||||
let payload = {};
|
||||
payload[item.id] = { config };console.log(payload);
|
||||
this.updatePreset(payload);
|
||||
},
|
||||
updatePreset(update) {
|
||||
let payload = {};
|
||||
payload[this.name] = update;
|
||||
this.$emit('widgetUpdate', this.name, payload);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let self = this;
|
||||
let cont = self.$refs.container;
|
||||
self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
|
||||
self.containerRect = cont.getBoundingClientRect();
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
for (const child of cont.children) {
|
||||
child.style.display = 'none';
|
||||
}
|
||||
self.gridWidth = window.getComputedStyle(cont).getPropertyValue('grid-template-columns').split(" ").length;
|
||||
self.containerRect = cont.getBoundingClientRect();
|
||||
for (const child of cont.children) {
|
||||
child.style.display = '';
|
||||
}
|
||||
});
|
||||
},
|
||||
template: `<div class="dashboard-section">
|
||||
<h3 class="d-flex">
|
||||
<span class="col">{{name}}</span>
|
||||
<button class="col-auto btn" @click.prevent="editMode = editMode ? 0 : 1"><i class="fa-solid fa-gear"></i></button>
|
||||
</h3>
|
||||
<div class="position-relative" :style="'height:0;padding-bottom:' + (gridHeight * 100/gridWidth) + '%'">
|
||||
<div ref="container"
|
||||
class="position-absolute top-0 left-0 w-100 h-100 draganddropcontainer"
|
||||
:style="'display:grid;grid-template-rows:repeat('+gridHeight+',1fr)'"
|
||||
@click="addWidget($event)"
|
||||
@drop="onDrop($event, 1)"
|
||||
@dragover.prevent="onDragOver"
|
||||
@dragenter.prevent>
|
||||
|
||||
<dashboard-item
|
||||
v-for="item in items"
|
||||
:key="item.id"
|
||||
:id="item.widget"
|
||||
:loading="item.loading"
|
||||
:config="item.config"
|
||||
:custom="item.custom"
|
||||
:hidden="item.hidden"
|
||||
:editMode="editMode"
|
||||
:x="itemCoords[item.index] ? itemCoords[item.index].x : -1"
|
||||
:y="itemCoords[item.index] ? itemCoords[item.index].y : -1"
|
||||
:style="itemCoords[item.index] ? {'grid-column-start':itemCoords[item.index].x,'grid-column-end':itemCoords[item.index].x+itemCoords[item.index].w,'grid-row-start':itemCoords[item.index].y,'grid-row-end':itemCoords[item.index].y+itemCoords[item.index].h} : {}"
|
||||
:width="itemCoords[item.index] ? itemCoords[item.index].w : 0"
|
||||
:height="itemCoords[item.index] ? itemCoords[item.index].h : 0"
|
||||
@dragstart="startDrag($event, item)"
|
||||
@resizestart="startResize($event, item)"
|
||||
@change="saveConfig($event, item)"
|
||||
@remove="removeWidget(item, $event)">
|
||||
</dashboard-item>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import BsModal from "../../Bootstrap/Modal.js";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
BsModal
|
||||
},
|
||||
props: [
|
||||
"widgets"
|
||||
],
|
||||
data: () => ({
|
||||
callbacks: {}
|
||||
}),
|
||||
methods: {
|
||||
getWidget() {
|
||||
return new Promise((resolve,reject) => {
|
||||
this.callbacks = {resolve,reject};
|
||||
this.$refs.modal.show();
|
||||
});
|
||||
},
|
||||
close() {
|
||||
if (this.callbacks.reject)
|
||||
this.callbacks.reject();
|
||||
this.callbacks = {};
|
||||
},
|
||||
pick(widget_id) {
|
||||
if (this.callbacks.resolve)
|
||||
this.callbacks.resolve(widget_id);
|
||||
this.callbacks = {};
|
||||
this.$refs.modal.hide();
|
||||
},
|
||||
path(src) {
|
||||
if (src[0] == '/')
|
||||
return FHC_JS_DATA_STORAGE_OBJECT.app_root + src;
|
||||
return src;
|
||||
}
|
||||
},
|
||||
template: `<div class="dashboard-widget-picker">
|
||||
<bs-modal ref="modal" class="fade" :dialog-class="{'modal-fullscreen-sm-down': 1, 'modal-xl': widgets && widgets.length > 0}" @hiddenBsModal="close">
|
||||
<template v-slot:title>Create new widget</template>
|
||||
<template v-slot:default>
|
||||
<div v-if="widgets" class="row">
|
||||
<div v-if="!widgets.length">
|
||||
No Widgets available
|
||||
</div>
|
||||
<div v-for="widget in widgets" :key="widget.widget_id" class="col-sm-6 col-md-4 col-lg-3 col-xl-2">
|
||||
<div class="card h-100" @click="pick(widget.widget_id)">
|
||||
<img class="card-img-top" :src="path(widget.setup.icon)" :alt="'pictogram for ' + (widget.setup.name || widget.widget_kurzbz)">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ widget.setup.name || widget.widget_kurzbz }}</h5>
|
||||
<p class="card-text">{{ widget.beschreibung }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-center"><i class="fa-solid fa-spinner fa-pulse fa-3x"></i></div>
|
||||
</template>
|
||||
</bs-modal>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export default {
|
||||
props: [
|
||||
"config",
|
||||
"width",
|
||||
"height",
|
||||
"configMode"
|
||||
],
|
||||
emits: [
|
||||
"setConfig",
|
||||
"change" // TODO(chris): do we need this?
|
||||
],
|
||||
computed: {
|
||||
apiurl() {
|
||||
const app_root = FHC_JS_DATA_STORAGE_OBJECT.app_root;
|
||||
const ci_router = FHC_JS_DATA_STORAGE_OBJECT.ci_router;
|
||||
return app_root + ci_router;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDateTime: function(dateTime) {
|
||||
const dt = new Date(dateTime);
|
||||
return dt.getDate() + '.' + dt.getMonth() + '.' + dt.getFullYear() + ' | '
|
||||
+ dt.getHours() + ':' + dt.getMinutes();
|
||||
},
|
||||
getDate: function(dateTime){
|
||||
const dt = new Date(dateTime);
|
||||
return dt.getDate() + '.' + dt.getMonth() + '.' + dt.getFullYear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,345 @@
|
||||
import AbstractWidget from './Abstract';
|
||||
import BaseOffcanvas from '../Base/Offcanvas';
|
||||
|
||||
export default {
|
||||
name: 'WidgetsAmpel',
|
||||
components: { BaseOffcanvas },
|
||||
data: () => ({
|
||||
filter: '',
|
||||
source: '',
|
||||
ampeln: []
|
||||
}),
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
computed: {
|
||||
widgetAmpeln () {
|
||||
return this.ampeln.slice(0, 4); // show only newest 4 ampeln
|
||||
},
|
||||
offcanvasAmpeln ()
|
||||
{
|
||||
switch(this.filter)
|
||||
{
|
||||
case 'verpflichtend': return this.ampeln.filter(item => item.verpflichtend);
|
||||
case 'ueberfaellig': return this.ampeln.filter(item => (new Date() > new Date(item.deadline)) && !item.bestaetigt);
|
||||
default: return this.ampeln;
|
||||
}
|
||||
},
|
||||
count () {
|
||||
return {
|
||||
verpflichtend: this.ampeln.filter(item => item.verpflichtend).length,
|
||||
ueberfaellig: this.ampeln.filter(item => (new Date() > new Date(item.deadline)) && !item.bestaetigt).length,
|
||||
alle: this.ampeln.length
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
closeOffcanvasAmpeln()
|
||||
{
|
||||
for (let i = 0; i < this.offcanvasAmpeln.length; i++)
|
||||
{
|
||||
let ampelId = this.offcanvasAmpeln[i].ampel_id;
|
||||
this.$refs['ampelCollapse_' + ampelId][0].classList.remove('show');
|
||||
}
|
||||
},
|
||||
openOffcanvasAmpel(ampelId){
|
||||
// Close earlier opened Ampeln
|
||||
this.closeOffcanvasAmpeln();
|
||||
|
||||
// Show given Ampel
|
||||
this.$refs['ampelCollapse_' + ampelId][0].classList.add('show');
|
||||
},
|
||||
closeOffcanvas(){
|
||||
this.filter = '';
|
||||
},
|
||||
confirm(ampelId){
|
||||
let indexToRemove = this.ampeln.findIndex((obj => obj.ampel_id === ampelId));
|
||||
this.ampeln.splice(indexToRemove, 1);
|
||||
},
|
||||
changeDisplay(){
|
||||
this.filter = '';
|
||||
if (this.source == 'offen')
|
||||
{
|
||||
this.ampeln = TEST_OFFENE_AMPELN;
|
||||
}
|
||||
|
||||
if (this.source == 'alle')
|
||||
{
|
||||
this.ampeln = TEST_ALLE_AMPELN;
|
||||
// axios
|
||||
// .get(this.apiurl + '/dashboard/Api/getAmpeln')
|
||||
// .then(res => { this.ampeln = res.data })
|
||||
// .catch(err => { console.error('ERROR: ', err.response.data) });
|
||||
}
|
||||
},
|
||||
validateBtnTxt(buttontext){
|
||||
return buttontext == null ? 'Bestätigen' : buttontext;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$emit('setConfig', false);
|
||||
this.ampeln = TEST_OFFENE_AMPELN;
|
||||
},
|
||||
template: `
|
||||
<div class="widgets-ampel w-100 h-100">
|
||||
<div class="d-flex flex-column justify-content-between">
|
||||
<div class="d-flex">
|
||||
<header class="me-auto"><b>Neueste Ampeln</b></header>
|
||||
<div class="mb-2 text-danger"><a href="#allAmpelOffcanvas" data-bs-toggle="offcanvas">Alle Ampeln</a></div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a v-if="count.ueberfaellig > 0" href="#allAmpelOffcanvas" data-bs-toggle="offcanvas" @click="filter = 'ueberfaellig'" class="text-decoration-none"><span class="badge bg-danger me-1"><i class="fa fa-solid fa-bolt"></i> Überfällig: <b>{{ count.ueberfaellig }}</b></span></a>
|
||||
</div>
|
||||
<div v-for="ampel in widgetAmpeln" :key="ampel.ampel_id" class="mt-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="position-relative">
|
||||
<div class="d-flex">
|
||||
<div class="text-muted small me-auto"><small>Deadline: {{ getDate(ampel.deadline) }}</small></div>
|
||||
<div v-if="(new Date() > new Date(ampel.deadline)) && !ampel.bestaetigt "><span class="badge bg-danger"><i class="fa fa-solid fa-bolt"></i></span></div>
|
||||
<div v-if="ampel.verpflichtend"><span class="badge bg-warning ms-1"><i class="fa fa-solid fa-triangle-exclamation"></i></span></div>
|
||||
<div v-if="ampel.bestaetigt"><span class="badge bg-success ms-1"><i class="fa fa-solid fa-circle-check"></i></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#allAmpelOffcanvas" data-bs-toggle="offcanvas" class="stretched-link" @click="openOffcanvasAmpel(ampel.ampel_id)">{{ ampel.kurzbz }}</a><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="ampeln.length == 0" class="card card-body mt-4 p-4 text-center">
|
||||
<span class="text-success h2"><i class="fa fa-solid fa-circle-check"></i></span>
|
||||
<span class="text-success h5">Super!</span><br>
|
||||
<span class="small">Keine offenen Ampeln.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- All Ampeln Offcanvas -->
|
||||
<BaseOffcanvas id="allAmpelOffcanvas" :closeFunc="closeOffcanvas">
|
||||
<template #title><header><b>Alle meine Ampeln</b></header></template>
|
||||
<template #body>
|
||||
<div class="d-flex justify-content-evenly">
|
||||
<div class="form-check form-check-inline form-control-sm">
|
||||
<input class="form-check-input" type="radio" v-model="source" id="offen" value="offen" @change="changeDisplay" checked>
|
||||
<label class="form-check-label" for="offen">Offene Ampeln</label>
|
||||
</div>
|
||||
<div class="form-check form-check-inline form-control-sm">
|
||||
<input class="form-check-input" type="radio" v-model="source" id="alle" value="alle" @change="changeDisplay">
|
||||
<label class="form-check-label" for="alle">Alle Ampeln</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col"><button class="btn btn-light w-100" @click="filter = ''"><small>Alle: <b>{{ count.alle }}</b></small></button></div>
|
||||
<div class="row row-cols-2 g-2 mt-1">
|
||||
<div class="col"><button class="btn btn-danger w-100" @click="filter = 'ueberfaellig'"><i class="fa fa-solid fa-bolt me-2"></i><small>Überfällig: <b>{{ count.ueberfaellig }}</b></small></button></div>
|
||||
<div class="col"><button class="btn btn-warning w-100" @click="filter = 'verpflichtend'"><i class="fa fa-solid fa-triangle-exclamation me-2"></i><small>Pflicht: <b>{{ count.verpflichtend }}</b></small></button></div>
|
||||
</div>
|
||||
<div v-for="ampel in offcanvasAmpeln" :key="ampel.ampel_id" class="mt-2">
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item small">
|
||||
<div class="position-relative"><!-- prevents streched-link from stretching outside this parent element -->
|
||||
<div class="d-flex">
|
||||
<span class="small text-muted me-auto"><small>Deadline: {{ getDate(ampel.deadline) }}</small></span>
|
||||
<div v-if="(new Date() > new Date(ampel.deadline)) && !ampel.bestaetigt"><span class="badge bg-danger"><i class="fa fa-solid fa-bolt"></span></div>
|
||||
<div v-if="ampel.verpflichtend"><span class="badge bg-warning ms-1"><i class="fa fa-solid fa-triangle-exclamation"></span></div>
|
||||
<div v-if="ampel.bestaetigt"><span class="badge bg-success ms-1"><i class="fa fa-solid fa-circle-check"></i></span></div>
|
||||
</div>
|
||||
<a :href="'#ampelCollapse_' + ampel.ampel_id" data-bs-toggle="collapse" class="stretched-link">{{ ampel.kurzbz }}</a><br>
|
||||
</div>
|
||||
<div class="collapse my-3" :id="'ampelCollapse_' + ampel.ampel_id" :ref="'ampelCollapse_' + ampel.ampel_id">
|
||||
{{ ampel.beschreibung[0] }}
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button class="btn btn-sm btn-primary" :class="{disabled: ampel.bestaetigt}" @click="confirm(ampel.ampel_id)">{{ validateBtnTxt(ampel.buttontext[0]) }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</BaseOffcanvas>`
|
||||
}
|
||||
|
||||
const TEST_ALLE_AMPELN = [
|
||||
{
|
||||
ampel_id: 0,
|
||||
kurzbz: 'Ampeltitel 1',
|
||||
deadline: '2022-12-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'1-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'1-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 1,
|
||||
kurzbz: 'Ampeltitel 2 kann auch etwas länger sein',
|
||||
deadline: '2023-10-03',
|
||||
verfallszeit: 20,
|
||||
verpflichtend: true,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Gelesen', 'Read'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'2-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'2-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 2,
|
||||
kurzbz: 'Ampeltitel 3',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: null, // Dauerampel, Bis zur Bestätigung
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'3-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'3-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 3,
|
||||
kurzbz: 'Ampeltitel 4',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 4,
|
||||
kurzbz: 'Ampeltitel 5',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 5,
|
||||
kurzbz: 'Ampeltitel 6',
|
||||
deadline: '2022-10-31',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 6,
|
||||
kurzbz: 'Ampeltitel 7',
|
||||
deadline: '2020-12-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: true,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},{
|
||||
ampel_id: 7,
|
||||
kurzbz: 'Ampeltitel 8',
|
||||
deadline: '2022-09-25',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 8,
|
||||
kurzbz: 'Ampeltitel 9',
|
||||
deadline: '2022-10-01',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const TEST_OFFENE_AMPELN = [
|
||||
{
|
||||
ampel_id: 0,
|
||||
kurzbz: 'Ampeltitel 1',
|
||||
deadline: '2022-12-31',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'1-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'1-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 1,
|
||||
kurzbz: 'Ampeltitel 2 kann auch etwas länger sein',
|
||||
deadline: '2023-10-03',
|
||||
verfallszeit: 20,
|
||||
verpflichtend: true,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Gelesen', 'Read'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'2-Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'2-Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 7,
|
||||
kurzbz: 'Ampeltitel 8',
|
||||
deadline: '2022-09-25',
|
||||
verfallszeit: 40,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
},
|
||||
{
|
||||
ampel_id: 8,
|
||||
kurzbz: 'Ampeltitel 9',
|
||||
deadline: '2022-10-01',
|
||||
verfallszeit: 10,
|
||||
verpflichtend: false,
|
||||
bestaetigt: false,
|
||||
buttontext: ['Bestätigen', 'Confirm'],
|
||||
insertamum: '2022-09-21 15:25:00',
|
||||
beschreibung: [
|
||||
'Deutscher Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.',
|
||||
'Englischer Text Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod.'
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
import AbstractWidget from './Abstract';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
computed: {
|
||||
css() {
|
||||
return ['dashboard-widget-default', this.config.css];
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$emit('setConfig', false)
|
||||
},
|
||||
template: `<div :class="css">
|
||||
<h5 class="card-title">{{ config.title }}</h5>
|
||||
<p class="card-text">{{ config.msg }}</p>
|
||||
</div>`
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import AbstractWidget from './Abstract';
|
||||
import BsModal from '../Bootstrap/Modal';
|
||||
const MAX_LOADED_NEWS = 10;
|
||||
|
||||
export default {
|
||||
name: 'WidgetsNews',
|
||||
components: { BsModal },
|
||||
data: () => ({
|
||||
allNewsList: [],
|
||||
singleNews: {}
|
||||
}),
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
computed: {
|
||||
newsList(){
|
||||
//Return news amount depending on widget width and size
|
||||
let quantity = this.width;
|
||||
|
||||
if (this.width === 1) {
|
||||
quantity = this.height === 1 ? 4 : 10;
|
||||
}
|
||||
|
||||
return this.allNewsList.slice(0, quantity);
|
||||
}
|
||||
},
|
||||
created(){
|
||||
axios
|
||||
.get(this.apiurl + '/dashboard/Api/getNews', {params: {limit: MAX_LOADED_NEWS}})
|
||||
.then(res => { this.allNewsList = res.data })
|
||||
.catch(err => { console.error('ERROR: ', err.response.data) });
|
||||
|
||||
this.$emit('setConfig', false);
|
||||
},
|
||||
methods: {
|
||||
setSingleNews(singleNews){
|
||||
this.singleNews = singleNews;
|
||||
this.$refs.newsModal.show();
|
||||
}
|
||||
},
|
||||
template: `<div class="widgets-news w-100 h-100">
|
||||
<div class="d-flex flex-column h-100">
|
||||
<div class="d-flex">
|
||||
<header><b>Top News</b></header>
|
||||
<a href="#allNewsModal" data-bs-toggle="modal" class="ms-auto mb-2">
|
||||
<i class="fa fa-arrow-up-right-from-square me-1"></i>Alle News</a>
|
||||
</div>
|
||||
<div v-if="width == 1">
|
||||
<div v-for="news in newsList" :key="news.id" class="mt-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<a href="#newsModal" class="stretched-link" @click="setSingleNews(news)">{{ news.betreff }}</a><br>
|
||||
<span class="small text-muted">{{ formatDateTime(news.insertamum) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="width > 1 && height === 1" class="h-100" :class="'row row-cols-' + width">
|
||||
<div v-for="news in newsList" :key="news.id">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<a href="#newsModal" class="card-title h5 stretched-link" @click="setSingleNews(news)">{{ news.betreff }}</a><br>
|
||||
<span class="small text-muted">{{ formatDateTime(news.insertamum) }} </span>
|
||||
<p class="card-text pt-3" style="overflow: hidden; display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical;">{{ news.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-100" :class="'row row-cols-' + width">
|
||||
<div v-for="news in newsList" :key="news.id">
|
||||
<div class="card h-100">
|
||||
<img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="card-img-top">
|
||||
<div class="card-footer"><span class="card-subtitle small text-muted">{{ formatDateTime(news.insertamum) }}</span></div>
|
||||
<div class="card-body">
|
||||
<a href="#newsModal" class="card-title h5 stretched-link" @click="setSingleNews(news)">{{ news.betreff }}</a><br>
|
||||
<p class="card-text pt-3">{{ news.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- News Modal -->
|
||||
<BsModal ref="newsModal" id="newsModal" dialog-class="modal-lg">
|
||||
<template #title>
|
||||
<div class="row">
|
||||
<div class="col-5"><img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="img-fluid rounded-start"></div>
|
||||
<div class="col-7 d-flex align-items-end">
|
||||
<p>{{ singleNews.betreff }}<br><small class="text-muted">{{ formatDateTime(singleNews.insertamum) }}</small></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>{{ singleNews.text }}</template>
|
||||
</BsModal>
|
||||
|
||||
<!-- All News Modal -->
|
||||
<BsModal ref="allNewsModal" id="allNewsModal" dialog-class="modal-fullscreen">
|
||||
<template #title>Alle News</template>
|
||||
<template #default>
|
||||
<div class="row row-cols-5 g-4 h-100 px-5">
|
||||
<div v-for="news in allNewsList" :key="news.id">
|
||||
<div class="card h-100">
|
||||
<img src="../../skin/images/fh_technikum_wien_illustration_klein.png" class="card-img-top">
|
||||
<div class="card-footer"><span class="card-subtitle small">{{ formatDateTime(news.insertamum) }}</span></div>
|
||||
<div class="card-body">
|
||||
<a href="" class="card-title h5 stretched-link" @click="setSingleNews1(news)">{{ news.betreff }}</a><br>
|
||||
<p class="card-text">{{ news.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BsModal>`
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import AbstractWidget from './Abstract';
|
||||
|
||||
export default {
|
||||
name: 'WidgetsUrl',
|
||||
data: () => ({
|
||||
links: []
|
||||
}),
|
||||
mixins: [
|
||||
AbstractWidget
|
||||
],
|
||||
computed: {
|
||||
tagName() {
|
||||
return this.config.tag !== undefined && this.config.tag.length > 0 ? this.config.tag : 'Meine Urls';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addLink(){
|
||||
let linkId = this.links.length;
|
||||
|
||||
this.links.push({
|
||||
id: linkId,
|
||||
tag: this.config.tag,
|
||||
title: this.title,
|
||||
url: this.url
|
||||
})
|
||||
},
|
||||
removeLink(linkId){
|
||||
let indexToRemove = this.links.findIndex((obj => obj.id === linkId));
|
||||
this.links.splice(indexToRemove, 1);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.links = TEST_LINKS;
|
||||
// this.links = TEST_KEINE_LINKS;
|
||||
},
|
||||
template: `
|
||||
<div class="widgets-url w-100 h-100">
|
||||
<div v-if="configMode">
|
||||
<div class="mb-3">
|
||||
|
||||
<header><b>Neuer Link</b></header><br>
|
||||
<div>
|
||||
<input class="form-control form-control-sm" placeholder="Titel" type="text" v-model="title" name="title" maxlength="50" required>
|
||||
<input class="form-control form-control-sm mt-2" type="url" placeholder="URL" v-model="url" name="url" required>
|
||||
<button class="btn btn-outline-secondary btn-sm w-100 mt-2" @click="addLink()" type="button">Link speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-column justify-content-between">
|
||||
<header><b>{{ widgetTag }}</b></header>
|
||||
<div v-for="link in links" :key="link.id" class="d-flex mt-2">
|
||||
<a target="_blank" :href="link.url"><i class="fa fa-solid fa-arrow-up-right-from-square"></i> {{ link.title }}</a>
|
||||
<a class="ms-auto" href="#" @click.prevent="removeLink(link.id)" v-show="configMode"><i class="fa fa-regular fa-trash-can"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
const TEST_KEINE_LINKS = [];
|
||||
const TEST_LINKS = [
|
||||
{
|
||||
id: 0,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 0',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 1',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 2',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
tag: 'Zeitverwaltung',
|
||||
title: 'Zeitverwaltung' + 'link 3',
|
||||
url: 'https://www.technikum-wien.at'
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,31 @@
|
||||
let __widgets = {};
|
||||
let __widgetsStarted = {};
|
||||
let __path = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard/Widget';
|
||||
|
||||
export default {
|
||||
getWidget(id) {
|
||||
return __widgets[id];
|
||||
},
|
||||
loadWidget(id) {
|
||||
if (__widgets[id])
|
||||
return Promise.resolve(__widgets[id]);
|
||||
if (__widgetsStarted[id])
|
||||
return __widgetsStarted[id];
|
||||
if (!__path)
|
||||
return Promise.reject('Widget could not be loaded because there is no path yet!');
|
||||
|
||||
__widgetsStarted[id] = new Promise((resolve, reject) => {
|
||||
axios.get(__path, {params:{id}}).then(res => {
|
||||
res.data.retval.arguments = JSON.parse(res.data.retval.arguments);
|
||||
res.data.retval.setup = JSON.parse(res.data.retval.setup);
|
||||
__widgets[id] = res.data.retval;
|
||||
__widgetsStarted[id] = undefined;
|
||||
resolve(__widgets[id]);
|
||||
}).catch(error => reject(error.response.data.retval.error));
|
||||
});
|
||||
return __widgetsStarted[id];
|
||||
},
|
||||
setPath(path) {
|
||||
__path = path;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
/**
|
||||
* Performs a deep merge of objects and returns new object. Does not modify
|
||||
* objects (immutable) and merges arrays via concatenation.
|
||||
*
|
||||
* @param {...object} objects - Objects to merge
|
||||
* @returns {object} New object with merged key/values
|
||||
*/
|
||||
mergeDeep(...objects) {
|
||||
const isObject = obj => obj && typeof obj === 'object';
|
||||
|
||||
return objects.reduce((prev, obj) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const pVal = prev[key];
|
||||
const oVal = obj[key];
|
||||
|
||||
if (Array.isArray(pVal) && Array.isArray(oVal)) {
|
||||
prev[key] = pVal.concat(...oVal);
|
||||
}
|
||||
else if (isObject(pVal) && isObject(oVal)) {
|
||||
prev[key] = this.mergeDeep(pVal, oVal);
|
||||
}
|
||||
else {
|
||||
prev[key] = oVal;
|
||||
}
|
||||
});
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
@@ -28,7 +28,8 @@ require_once('dbupdate_3.4/example2.php');
|
||||
...
|
||||
*/
|
||||
|
||||
require_once('dbupdate_3.4/26173_index_webservicelog.php');
|
||||
// add Dashboard Schema, Tables and Permissions
|
||||
include __DIR__ . '/dbupdate_3.4/dbupdate_dashboard.php';
|
||||
|
||||
// *** Pruefung und hinzufuegen der neuen Attribute und Tabellen
|
||||
echo '<H2>Pruefe Tabellen und Attribute!</H2>';
|
||||
@@ -124,6 +125,11 @@ $tabellen=array(
|
||||
"campus.tbl_zeitsperretyp" => array("zeitsperretyp_kurzbz","beschreibung","farbe"),
|
||||
"campus.tbl_zeitwunsch" => array("stunde","mitarbeiter_uid","tag","gewicht","updateamum","updatevon","insertamum","insertvon", "zeitwunsch_id", "zeitwunsch_gueltigkeit_id"),
|
||||
"campus.tbl_zeitwunsch_gueltigkeit" => array("zeitwunsch_gueltigkeit_id","mitarbeiter_uid","von","bis","insertamum","insertvon", "updateamum","updatevon"),
|
||||
"dashboard.tbl_dashboard" => array("dashboard_id", "dashboard_kurzbz", "beschreibung"),
|
||||
"dashboard.tbl_dashboard_benutzer_override" => array("override_id", "dashboard_id", "uid", "override"),
|
||||
"dashboard.tbl_dashboard_preset" => array("preset_id", "dashboard_id", "funktion_kurzbz", "preset"),
|
||||
"dashboard.tbl_dashboard_widget" => array("dashboard_id", "widget_id"),
|
||||
"dashboard.tbl_widget" => array("widget_id", "widget_kurzbz", "beschreibung"),
|
||||
"fue.tbl_aktivitaet" => array("aktivitaet_kurzbz","beschreibung","sort"),
|
||||
"fue.tbl_aufwandstyp" => array("aufwandstyp_kurzbz","bezeichnung"),
|
||||
"fue.tbl_projekt" => array("projekt_kurzbz","nummer","titel","beschreibung","beginn","ende","oe_kurzbz","budget","farbe","aufwandstyp_kurzbz","ressource_id","anzahl_ma","aufwand_pt","projekt_id","projekttyp_kurzbz","zeitaufzeichnung"),
|
||||
|
||||
@@ -0,0 +1,465 @@
|
||||
<?php
|
||||
/* Copyright (C) 2017 fhcomplete.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
|
||||
*
|
||||
* Authors: Harald Bamberger <harald.bamberger@technikum-wien.at>,
|
||||
*
|
||||
* Beschreibung:
|
||||
* Dashboard DB Aenderungen
|
||||
*/
|
||||
if (! defined('DB_NAME')) exit('No direct script access allowed');
|
||||
|
||||
if (($result = $db->db_query("SELECT schema_name FROM information_schema.schemata WHERE schema_name='dashboard'")))
|
||||
{
|
||||
if ($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = <<<EODASHBOARDSQL
|
||||
|
||||
--
|
||||
-- Name: dashboard; Type: SCHEMA; Schema: -; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE SCHEMA dashboard;
|
||||
|
||||
|
||||
ALTER SCHEMA dashboard OWNER TO fhcomplete;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_with_oids = false;
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard; Type: TABLE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE TABLE dashboard.tbl_dashboard (
|
||||
dashboard_id integer NOT NULL,
|
||||
dashboard_kurzbz character varying(32) NOT NULL,
|
||||
beschreibung text
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_dashboard OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override; Type: TABLE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE TABLE dashboard.tbl_dashboard_benutzer_override (
|
||||
override_id integer NOT NULL,
|
||||
dashboard_id integer NOT NULL,
|
||||
uid character varying(32) NOT NULL,
|
||||
override jsonb NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_dashboard_benutzer_override OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override_override_id_seq; Type: SEQUENCE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_dashboard_benutzer_override_override_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_dashboard_benutzer_override_override_id_seq OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override_override_id_seq; Type: SEQUENCE OWNED BY; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER SEQUENCE dashboard.tbl_dashboard_benutzer_override_override_id_seq OWNED BY dashboard.tbl_dashboard_benutzer_override.override_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_dashboard_id_seq; Type: SEQUENCE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_dashboard_dashboard_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_dashboard_dashboard_id_seq OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_dashboard_id_seq; Type: SEQUENCE OWNED BY; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER SEQUENCE dashboard.tbl_dashboard_dashboard_id_seq OWNED BY dashboard.tbl_dashboard.dashboard_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset; Type: TABLE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE TABLE dashboard.tbl_dashboard_preset (
|
||||
preset_id integer NOT NULL,
|
||||
dashboard_id integer NOT NULL,
|
||||
funktion_kurzbz character varying(16),
|
||||
preset jsonb NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_dashboard_preset OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset_preset_id_seq; Type: SEQUENCE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_dashboard_preset_preset_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_dashboard_preset_preset_id_seq OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset_preset_id_seq; Type: SEQUENCE OWNED BY; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER SEQUENCE dashboard.tbl_dashboard_preset_preset_id_seq OWNED BY dashboard.tbl_dashboard_preset.preset_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_widget; Type: TABLE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE TABLE dashboard.tbl_dashboard_widget (
|
||||
dashboard_id integer NOT NULL,
|
||||
widget_id integer NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_dashboard_widget OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_widget; Type: TABLE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE TABLE dashboard.tbl_widget (
|
||||
widget_id integer NOT NULL,
|
||||
widget_kurzbz character varying(32) NOT NULL,
|
||||
beschreibung text,
|
||||
arguments jsonb NOT NULL,
|
||||
setup jsonb NOT NULL
|
||||
);
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_widget OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_widget_widget_id_seq; Type: SEQUENCE; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
CREATE SEQUENCE dashboard.tbl_widget_widget_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
ALTER TABLE dashboard.tbl_widget_widget_id_seq OWNER TO fhcomplete;
|
||||
|
||||
--
|
||||
-- Name: tbl_widget_widget_id_seq; Type: SEQUENCE OWNED BY; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER SEQUENCE dashboard.tbl_widget_widget_id_seq OWNED BY dashboard.tbl_widget.widget_id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard dashboard_id; Type: DEFAULT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard ALTER COLUMN dashboard_id SET DEFAULT nextval('dashboard.tbl_dashboard_dashboard_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override override_id; Type: DEFAULT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_benutzer_override ALTER COLUMN override_id SET DEFAULT nextval('dashboard.tbl_dashboard_benutzer_override_override_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset preset_id; Type: DEFAULT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_preset ALTER COLUMN preset_id SET DEFAULT nextval('dashboard.tbl_dashboard_preset_preset_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_widget widget_id; Type: DEFAULT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_widget ALTER COLUMN widget_id SET DEFAULT nextval('dashboard.tbl_widget_widget_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override tbl_dashboard_benutzer_override_pkey; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_benutzer_override
|
||||
ADD CONSTRAINT tbl_dashboard_benutzer_override_pkey PRIMARY KEY (override_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override tbl_dashboard_benutzer_override_uk1; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_benutzer_override
|
||||
ADD CONSTRAINT tbl_dashboard_benutzer_override_uk1 UNIQUE (dashboard_id, uid);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard tbl_dashboard_kurz_bz_key; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard
|
||||
ADD CONSTRAINT tbl_dashboard_kurz_bz_key UNIQUE (dashboard_kurzbz);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard tbl_dashboard_pkey; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard
|
||||
ADD CONSTRAINT tbl_dashboard_pkey PRIMARY KEY (dashboard_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset tbl_dashboard_preset_pkey; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_preset
|
||||
ADD CONSTRAINT tbl_dashboard_preset_pkey PRIMARY KEY (preset_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset tbl_dashboard_preset_uk1; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_preset
|
||||
ADD CONSTRAINT tbl_dashboard_preset_uk1 UNIQUE (dashboard_id, funktion_kurzbz);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_widget tbl_dashboard_widget_pkey; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_widget
|
||||
ADD CONSTRAINT tbl_dashboard_widget_pkey PRIMARY KEY (dashboard_id, widget_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_widget tbl_widget_pkey; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_widget
|
||||
ADD CONSTRAINT tbl_widget_pkey PRIMARY KEY (widget_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_widget tbl_widget_widget_kurzbz_key; Type: CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_widget
|
||||
ADD CONSTRAINT tbl_widget_widget_kurzbz_key UNIQUE (widget_kurzbz);
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override tbl_dashboard_benutzer_override_fk1; Type: FK CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_benutzer_override
|
||||
ADD CONSTRAINT tbl_dashboard_benutzer_override_fk1 FOREIGN KEY (dashboard_id) REFERENCES dashboard.tbl_dashboard(dashboard_id) ON UPDATE CASCADE ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_benutzer_override tbl_dashboard_benutzer_override_fk2; Type: FK CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_benutzer_override
|
||||
ADD CONSTRAINT tbl_dashboard_benutzer_override_fk2 FOREIGN KEY (uid) REFERENCES public.tbl_benutzer(uid) ON UPDATE CASCADE ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset tbl_dashboard_preset_fk1; Type: FK CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_preset
|
||||
ADD CONSTRAINT tbl_dashboard_preset_fk1 FOREIGN KEY (dashboard_id) REFERENCES dashboard.tbl_dashboard(dashboard_id) ON UPDATE CASCADE ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_preset tbl_dashboard_preset_fk2; Type: FK CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_preset
|
||||
ADD CONSTRAINT tbl_dashboard_preset_fk2 FOREIGN KEY (funktion_kurzbz) REFERENCES public.tbl_funktion(funktion_kurzbz) ON UPDATE CASCADE ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_widget tbl_dashboard_widget_fk1; Type: FK CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_widget
|
||||
ADD CONSTRAINT tbl_dashboard_widget_fk1 FOREIGN KEY (dashboard_id) REFERENCES dashboard.tbl_dashboard(dashboard_id) ON UPDATE CASCADE ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: tbl_dashboard_widget tbl_dashboard_widget_fk2; Type: FK CONSTRAINT; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY dashboard.tbl_dashboard_widget
|
||||
ADD CONSTRAINT tbl_dashboard_widget_fk2 FOREIGN KEY (widget_id) REFERENCES dashboard.tbl_widget(widget_id) ON UPDATE CASCADE ON DELETE RESTRICT;
|
||||
|
||||
|
||||
--
|
||||
-- Name: SCHEMA dashboard; Type: ACL; Schema: -; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT USAGE ON SCHEMA dashboard TO web;
|
||||
GRANT USAGE ON SCHEMA dashboard TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
-- Name: TABLE tbl_dashboard; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT SELECT ON TABLE dashboard.tbl_dashboard TO web;
|
||||
GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE dashboard.tbl_dashboard TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
-- Name: TABLE tbl_dashboard_benutzer_override; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT SELECT,INSERT,UPDATE ON TABLE dashboard.tbl_dashboard_benutzer_override TO web;
|
||||
GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE dashboard.tbl_dashboard_benutzer_override TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
-- Name: SEQUENCE tbl_dashboard_benutzer_override_override_id_seq; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT ALL ON SEQUENCE dashboard.tbl_dashboard_benutzer_override_override_id_seq TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
-- Name: SEQUENCE tbl_dashboard_dashboard_id_seq; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT ALL ON SEQUENCE dashboard.tbl_dashboard_dashboard_id_seq TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
-- Name: TABLE tbl_dashboard_preset; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT SELECT ON TABLE dashboard.tbl_dashboard_preset TO web;
|
||||
GRANT SELECT,INSERT,DELETE,UPDATE ON TABLE dashboard.tbl_dashboard_preset TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
-- Name: SEQUENCE tbl_dashboard_preset_preset_id_seq; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT ALL ON SEQUENCE dashboard.tbl_dashboard_preset_preset_id_seq TO vilesci;
|
||||
|
||||
|
||||
--
|
||||
-- Name: TABLE tbl_dashboard_widget; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT SELECT ON TABLE dashboard.tbl_dashboard_widget TO web;
|
||||
|
||||
|
||||
--
|
||||
-- Name: TABLE tbl_widget; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT SELECT ON TABLE dashboard.tbl_widget TO web;
|
||||
|
||||
|
||||
--
|
||||
-- Name: SEQUENCE tbl_widget_widget_id_seq; Type: ACL; Schema: dashboard; Owner: fhcomplete
|
||||
--
|
||||
|
||||
GRANT ALL ON SEQUENCE dashboard.tbl_widget_widget_id_seq TO vilesci;
|
||||
|
||||
EODASHBOARDSQL;
|
||||
|
||||
if (!$db->db_query($qry))
|
||||
{
|
||||
echo '<strong>Schema Dashboard: '.$db->db_last_error().'</strong><br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo '<br>Neues Schema dashboard hinzugefuegt';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add permission: dashboard/admin
|
||||
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'dashboard/admin';"))
|
||||
{
|
||||
if($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES('dashboard/admin', 'Adminberechtigung');";
|
||||
|
||||
if(!$db->db_query($qry))
|
||||
{
|
||||
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo 'system.tbl_berechtigung: Added permission for dashboard/admin<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add permission: dashboard/benutzer
|
||||
if($result = @$db->db_query("SELECT 1 FROM system.tbl_berechtigung WHERE berechtigung_kurzbz = 'dashboard/benutzer';"))
|
||||
{
|
||||
if($db->db_num_rows($result) == 0)
|
||||
{
|
||||
$qry = "INSERT INTO system.tbl_berechtigung(berechtigung_kurzbz, beschreibung) VALUES('dashboard/benutzer', 'Benutzerberechtigung');";
|
||||
|
||||
if(!$db->db_query($qry))
|
||||
{
|
||||
echo '<strong>system.tbl_berechtigung '.$db->db_last_error().'</strong><br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
echo 'system.tbl_berechtigung: Added permission for dashboard/benutzer<br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user