diff --git a/application/config/navigation.php b/application/config/navigation.php index c70aba57c..4d4dcc22a 100644 --- a/application/config/navigation.php +++ b/application/config/navigation.php @@ -208,7 +208,14 @@ $config['navigation_header'] = array( 'expand' => true, 'sort' => 30, 'requiredPermissions' => 'lehre/anrechnungszeitfenster:rw' - ) + ), + 'dashboardadmin' => array( + 'link' => site_url('dashboard/Admin'), + 'description' => 'Dashboard Admin', + 'expand' => true, + 'sort' => 40, + 'requiredPermissions' => 'dashboard/admin:r' + ) ) ) ) diff --git a/application/controllers/api/frontend/v1/dashboard/Board.php b/application/controllers/api/frontend/v1/dashboard/Board.php new file mode 100644 index 000000000..c50fec128 --- /dev/null +++ b/application/controllers/api/frontend/v1/dashboard/Board.php @@ -0,0 +1,121 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about addresses + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class Board extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'list' => 'dashboard/admin:r', + 'create' => 'dashboard/admin:rw', + 'update' => 'dashboard/admin:rw', + 'delete' => 'dashboard/admin:rw' + ]); + + // Models + $this->load->model('dashboard/Dashboard_model', 'DashboardModel'); + } + + public function list() + { + $result = $this->DashboardModel->load(); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($result); + } + + public function create() + { + $dashboard_kurzbz = $this->input->post('dashboard_kurzbz'); + + $result = $this->DashboardModel->insert([ + 'dashboard_kurzbz' => $dashboard_kurzbz + ]); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } + + public function update() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('dashboard_id', 'Dashboard ID', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $dashboard_id = $this->input->post('dashboard_id'); + $dashboard_kurzbz = $this->input->post('dashboard_kurzbz'); + $beschreibung = $this->input->post('beschreibung'); + + $result = $this->DashboardModel->update([ + 'dashboard_id' => $dashboard_id + ], [ + 'dashboard_kurzbz' => $dashboard_kurzbz, + 'beschreibung' => $beschreibung + ]); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($result); + } + + public function delete() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('dashboard_id', 'Dashboard ID', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $dashboard_id = $this->input->post('dashboard_id'); + + //delete all presets + $this->load->model('dashboard/Dashboard_Preset_model', 'DashboardPresetModel'); + + $result = $this->DashboardPresetModel->delete([ + 'dashboard_id' => $dashboard_id + ]); + $this->getDataOrTerminateWithError($result); + + //delete all widgets + $this->load->model('dashboard/Dashboard_Widget_model', 'DashboardWidgetModel'); + + $result = $this->DashboardWidgetModel->delete([ + 'dashboard_id' => $dashboard_id + ]); + $this->getDataOrTerminateWithError($result); + + $result = $this->DashboardModel->delete($dashboard_id); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($result); + } +} diff --git a/application/controllers/api/frontend/v1/dashboard/Preset.php b/application/controllers/api/frontend/v1/dashboard/Preset.php new file mode 100644 index 000000000..5983d9660 --- /dev/null +++ b/application/controllers/api/frontend/v1/dashboard/Preset.php @@ -0,0 +1,200 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about addresses + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class Preset extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'list' => 'dashboard/admin:r', + 'getBatch' => 'dashboard/admin:r', + 'addWidget' => 'dashboard/admin:rw', + 'removeWidget' => 'dashboard/admin:rw' + ]); + + // Load language phrases + $this->loadPhrases([ + 'ui' + ]); + + // Libraries + $this->load->library('dashboard/DashboardLib'); + + // Models + $this->load->model('ressource/Funktion_model', 'FunktionModel'); + } + + public function list($dashboard_kurzbz) + { + $sql = " + WITH + dashboard_presets AS ( + SELECT + * + FROM + dashboard.tbl_dashboard_preset dp + JOIN + dashboard.tbl_dashboard d ON d.dashboard_id = dp.dashboard_id + WHERE + d.dashboard_kurzbz = {$this->db->escape($dashboard_kurzbz)} + ), + general AS ( + SELECT + 'general' AS funktion_kurzbz, + 'Allgemein' AS beschreibung + ) + + ( + SELECT + f.funktion_kurzbz, + f.beschreibung, + COUNT(p.preset_id) AS has_preset + FROM + general f + LEFT JOIN + dashboard_presets p ON p.funktion_kurzbz IS NULL + GROUP BY + f.funktion_kurzbz, f.beschreibung + ) + UNION ALL + ( + SELECT + f.funktion_kurzbz, + f.beschreibung, + COUNT(p.preset_id) AS has_preset + FROM + public.tbl_funktion f + LEFT JOIN + dashboard_presets p ON p.funktion_kurzbz = f.funktion_kurzbz + GROUP BY + f.funktion_kurzbz, f.beschreibung + ORDER BY + f.beschreibung ASC + ) + "; + + $result = $this->FunktionModel->execReadOnlyQuery($sql); + + $funktionen = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($funktionen); + } + + public function getBatch() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('db', 'Dashboard', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $db = $this->input->post('db'); + $funktionen = $this->input->post('funktionen') ?: []; + + $result = []; + + foreach ($funktionen as $funktion) { + $conf = $this->dashboardlib->getPreset($db, $funktion); + if ($conf) { + $preset = json_decode($conf->preset, true); + if (!isset($preset[$funktion]) || !isset($preset[$funktion]['widgets'])) + $result[$funktion] = []; + else + $result[$funktion] = $preset[$funktion]['widgets']; + } else { + $result[$funktion] = []; + } + } + + return $this->terminateWithSuccess($result); + } + + public function addWidget() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('dashboard', 'Dashboard', 'required'); + $this->form_validation->set_rules('funktion_kurzbz', 'Funktion', 'required'); + $this->form_validation->set_rules('widget[widget]', 'Widget', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $dashboard_kurzbz = $this->input->post('dashboard'); + $funktion_kurzbz = $this->input->post('funktion_kurzbz'); + $widget = $this->input->post('widget'); + + if (!isset($widget['widgetid'])) + $widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz); + + $preset = $this->dashboardlib->getPresetOrCreateEmptyPreset($dashboard_kurzbz, $funktion_kurzbz); + + $preset_decoded = json_decode($preset->preset, true); + + $this->dashboardlib->addWidgetsToWidgets($preset_decoded, $dashboard_kurzbz, $funktion_kurzbz, [$widget]); + + $preset->preset = json_encode($preset_decoded); + + $result = $this->dashboardlib->insertOrUpdatePreset($preset); + + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($widget['widgetid']); + } + + public function removeWidget() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('db', 'Dashboard', 'required'); + $this->form_validation->set_rules('funktion_kurzbz', 'Funktion', 'required'); + $this->form_validation->set_rules('widgetid', 'Widget', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $dashboard_kurzbz = $this->input->post('db'); + $funktion_kurzbz = $this->input->post('funktion_kurzbz'); + $widgetid = $this->input->post('widgetid'); + + $preset = $this->dashboardlib->getPreset($dashboard_kurzbz, $funktion_kurzbz); + if (!$preset) + show_404(); + + $preset_decoded = json_decode($preset->preset, true); + + if (!$this->dashboardlib->removeWidgetFromWidgets($preset_decoded, $funktion_kurzbz, $widgetid)) + show_404(); + + $preset->preset = json_encode($preset_decoded); + + $result = $this->dashboardlib->insertOrUpdatePreset($preset); + + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess(array('msg' => $this->p->t('dashboard', 'success_savePreset'))); + } +} diff --git a/application/controllers/api/frontend/v1/dashboard/User.php b/application/controllers/api/frontend/v1/dashboard/User.php new file mode 100644 index 000000000..9d020649e --- /dev/null +++ b/application/controllers/api/frontend/v1/dashboard/User.php @@ -0,0 +1,159 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about the users dashboard + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class User extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'get' => 'dashboard/benutzer:r', + 'addWidget' => 'dashboard/benutzer:rw', + 'removeWidget' => 'dashboard/benutzer:rw' + ]); + + // Libraries + $this->load->library('dashboard/DashboardLib'); + + // Models + $this->load->model('ressource/Funktion_model', 'FunktionModel'); + } + + public function get($dashboard_kurzbz) + { + $dashboard = $this->dashboardlib->getDashboardByKurzbz($dashboard_kurzbz); + if (!$dashboard) + show_404(); + + $uid = $this->authlib->getAuthObj()->username; + + /*$mergedconfig = $this->dashboardlib->getMergedConfig($dashboard->dashboard_id, $uid); + + $this->terminateWithSuccess([ + 'general' => call_user_func_array( + 'array_merge_recursive', + $mergedconfig + ) + ]);*/ + $defaultconfig = $this->dashboardlib->getDefaultConfig($dashboard->dashboard_id); + $userconfig = $this->dashboardlib->getUserConfig($dashboard->dashboard_id, $uid); + + $defaultconfig_squashed = $defaultconfig ? call_user_func_array('array_replace_recursive', $defaultconfig) : []; + $userconfig_squashed = $userconfig ? call_user_func_array('array_replace_recursive', $userconfig) : []; + + $mergedconfig = array_replace_recursive($defaultconfig_squashed, $userconfig_squashed); + + $this->terminateWithSuccess([ + DashboardLib::SECTION_IF_FUNKTION_KURZBZ_IS_NULL => $mergedconfig + ]); + } + + public function addWidget() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('dashboard', 'Dashboard', 'required'); + $this->form_validation->set_rules('widget[widget]', 'Widget', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $widget = $this->input->post('widget'); + $dashboard_kurzbz = $this->input->post('dashboard'); + $uid = $this->authlib->getAuthObj()->username; + + if (!isset($widget['widgetid'])) + $widget['widgetid'] = $this->dashboardlib->generateWidgetId($dashboard_kurzbz); + + $override = $this->dashboardlib->getOverrideOrCreateEmptyOverride($dashboard_kurzbz, $uid); + + $override_decoded = json_decode($override->override, true); + + if (!isset($override_decoded['general']) || !is_array($override_decoded['general'])) + $override_decoded['general'] = []; + + if (!isset($override_decoded['general']['widgets'])) + $override_decoded['general']['widgets'] = []; + + $override_decoded['general']['widgets'][$widget['widgetid']] = $widget; + + // NOTE(chris): remove doubles in other funktionen + foreach ($override_decoded as $funktion => $array) { + if ($funktion == 'general') + continue; + if (isset($array['widgets']) && isset($array['widgets'][$widget['widgetid']])) + unset($override_decoded[$funktion]['widgets'][$widget['widgetid']]); + } + + $override->override = json_encode($override_decoded); + + $result = $this->dashboardlib->insertOrUpdateOverride($override); + + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($widget['widgetid']); + } + + public function removeWidget() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('dashboard', 'Dashboard', 'required'); + $this->form_validation->set_rules('widget', 'Widget', 'required'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $widget_id = $this->input->post('widget'); + $dashboard_kurzbz = $this->input->post('dashboard'); + $uid = $this->authlib->getAuthObj()->username; + + $override = $this->dashboardlib->getOverride($dashboard_kurzbz, $uid); + if (!$override) + show_404(); + + $override_decoded = json_decode($override->override, true); + + foreach (array_keys($override_decoded) as $k) { + if (!isset($override_decoded[$k]["widgets"])) { + unset($override_decoded[$k]); + continue; + } + if (isset($override_decoded[$k]["widgets"][$widget_id])) { + unset($override_decoded[$k]["widgets"][$widget_id]); + } + if (!$override_decoded[$k]["widgets"]) { + unset($override_decoded[$k]); + } + } + + $override->override = json_encode($override_decoded); + + $result = $this->dashboardlib->insertOrUpdateOverride($override); + + $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess(); + } +} diff --git a/application/controllers/api/frontend/v1/dashboard/Widget.php b/application/controllers/api/frontend/v1/dashboard/Widget.php new file mode 100644 index 000000000..ac8c682e8 --- /dev/null +++ b/application/controllers/api/frontend/v1/dashboard/Widget.php @@ -0,0 +1,137 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + * This controller operates between (interface) the JS (GUI) and the back-end + * Provides data to the ajax get calls about the users dashboard + * This controller works with JSON calls on the HTTP GET or POST and the output is always JSON + */ +class Widget extends FHCAPI_Controller +{ + public function __construct() + { + parent::__construct([ + 'get' => ['dashboard/benutzer:r', 'dashboard/admin:r'], + 'list' => 'dashboard/admin:r', + 'listAllowed' => ['dashboard/benutzer:rw', 'dashboard/admin:r'], + 'setAllowed' => 'dashboard/admin:rw' + ]); + + // Libraries + $this->load->library('dashboard/DashboardLib'); + + // Models + $this->load->model('dashboard/Widget_model', 'WidgetModel'); + } + + public function get($id) + { + $result = $this->WidgetModel->load($id); + + $widget = $this->getDataOrTerminateWithError($result); + + if (!$widget) + return $this->terminateWithSuccess([ + "widget_id" => 0, + "widget_kurzbz" => "notfound", + "arguments" => [ + "className" => 'alert-danger', + "title" => 'Widget Not Found', + "msg" => 'The widget with the id ' . $id . ' could not be found' + ], + "setup" => [ + "name" => 'Widget Not Found', + "file" => absoluteJsImportUrl('public/js/components/DashboardWidget/Default.js'), + "width" => 1, + "height" => 1 + ] + ]); + + $widget = current($widget); + $widget->arguments = json_decode($widget->arguments); + $tmpsetup = json_decode($widget->setup); + $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); + $widget->setup = $tmpsetup; + + $this->terminateWithSuccess($widget); + } + + public function list($dashboard) + { + $result = $this->WidgetModel->getWithAllowedForDashboard($dashboard); + + $widgets = $this->getDataOrTerminateWithError($result); + + $widgets = array_map(function ($widget) { + $widget->arguments = json_decode($widget->arguments); + $tmpsetup = json_decode($widget->setup); + $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); + $widget->setup = $tmpsetup; + return $widget; + }, $widgets); + + $this->terminateWithSuccess($widgets); + } + + public function listAllowed($dashboard) + { + $result = $this->WidgetModel->getForDashboard($dashboard); + + $widgets = $this->getDataOrTerminateWithError($result); + + $widgets = array_map(function ($widget) { + $widget->arguments = json_decode($widget->arguments); + $tmpsetup = json_decode($widget->setup); + $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); + $widget->setup = $tmpsetup; + return $widget; + }, $widgets); + + $this->terminateWithSuccess($widgets); + } + + public function setAllowed() + { + $this->load->library('form_validation'); + + $this->form_validation->set_rules('dashboard_id', 'Dashboard', 'required'); + $this->form_validation->set_rules('widget_id', 'Widget', 'required'); + $this->form_validation->set_rules('allowed', 'Allowed', 'is_bool'); + + if (!$this->form_validation->run()) + $this->terminateWithValidationErrors($this->form_validation->error_array()); + + $data = [ + 'dashboard_id' => $this->input->post('dashboard_id'), + 'widget_id' => $this->input->post('widget_id') + ]; + + $this->load->model('dashboard/Dashboard_Widget_model', 'DashboardWidgetModel'); + + if ($this->input->post('allowed')) + $result = $this->DashboardWidgetModel->insert($data); + else + $result = $this->DashboardWidgetModel->delete($data); + + $data = $this->getDataOrTerminateWithError($result); + + $this->terminateWithSuccess($data); + } +} diff --git a/application/controllers/dashboard/Admin.php b/application/controllers/dashboard/Admin.php new file mode 100644 index 000000000..702c04bab --- /dev/null +++ b/application/controllers/dashboard/Admin.php @@ -0,0 +1,52 @@ +. + */ + +if (! defined('BASEPATH')) exit('No direct script access allowed'); + +/** + */ +class Admin extends Auth_Controller +{ + /** + * Constructor + */ + public function __construct() + { + // Set required permissions + parent::__construct( + array( + 'index' => 'dashboard/admin:rw', + 'preview' => 'dashboard/admin:r', + ) + ); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Public methods + public function index() + { + $this->load->view('dashboard/admin.php', []); + } + + public function preview($dashboard_kurzbz = 'CIS') + { + $this->load->view('dashboard/preview.php', [ + 'dashboard_kurzbz' => $dashboard_kurzbz + ]); + } +} diff --git a/application/controllers/dashboard/Api.php b/application/controllers/dashboard/Api.php deleted file mode 100644 index 422bf0675..000000000 --- a/application/controllers/dashboard/Api.php +++ /dev/null @@ -1,76 +0,0 @@ - 'dashboard/admin:rw', - 'getNews' => 'dashboard/benutzer:r', - 'getAmpeln' => 'dashboard/benutzer:r', - ) - ); - - $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'); - } -} diff --git a/application/controllers/dashboard/Config.php b/application/controllers/dashboard/Config.php deleted file mode 100644 index f6db9509f..000000000 --- a/application/controllers/dashboard/Config.php +++ /dev/null @@ -1,216 +0,0 @@ - '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, $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, $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, $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, $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) - { - $preset = json_decode($conf->preset, true); - if (!isset($preset[$funktion]) || !isset($preset[$funktion]['widgets'])) - $result[$funktion] = []; - else - $result[$funktion] = $preset[$funktion]['widgets']; - } - else - $result[$funktion] = []; - } - - return $this->outputJsonSuccess($result); - } -} diff --git a/application/controllers/dashboard/Dashboard.php b/application/controllers/dashboard/Dashboard.php deleted file mode 100644 index 3773a6d73..000000000 --- a/application/controllers/dashboard/Dashboard.php +++ /dev/null @@ -1,86 +0,0 @@ - '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) ?: []); - } -} diff --git a/application/controllers/dashboard/DashboardDemo.php b/application/controllers/dashboard/DashboardDemo.php deleted file mode 100644 index 35d530384..000000000 --- a/application/controllers/dashboard/DashboardDemo.php +++ /dev/null @@ -1,58 +0,0 @@ - 'dashboard/benutzer:r', - 'admin' => 'dashboard/admin:rw' - ) - ); - - $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', []); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Public methods - public function admin() - { - $this->load->view('dashboard/dashboard_demo_admin.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'); - } -} diff --git a/application/controllers/dashboard/Widget.php b/application/controllers/dashboard/Widget.php deleted file mode 100644 index 9966ddc12..000000000 --- a/application/controllers/dashboard/Widget.php +++ /dev/null @@ -1,134 +0,0 @@ - ['dashboard/benutzer:r', 'dashboard/admin:r'], - 'getAll' => 'dashboard/admin:r', - 'getWidgetsForDashboard' => ['dashboard/benutzer:rw', 'dashboard/admin:r'], - '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" => [ - "className" => 'alert-danger', - "title" => 'Widget Not Found', - "msg" => 'The widget with the id ' . $widget_id . ' could not be found' - ], - "setup" => [ - "name" => 'Widget Not Found', - "file" => absoluteJsImportUrl('public/js/components/DashboardWidget/Default.js'), - "width" => 1, - "height" => 1 - ] - ]); - - $widget = current(getData($widget)); - $widget->arguments = json_decode($widget->arguments); - $tmpsetup = json_decode($widget->setup); - $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); - $widget->setup = $tmpsetup; - - return $this->outputJsonSuccess($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)); - - $tmpwidgets = getData($result) ?: []; - $widgets = array_map(function($widget) { - $widget->arguments = json_decode($widget->arguments); - $tmpsetup = json_decode($widget->setup); - $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); - $widget->setup = $tmpsetup; - return $widget; - }, $tmpwidgets); - - $this->outputJsonSuccess($widgets); - } - - 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) - ]); - } - - $tmpwidgets = getData($result) ?: []; - $widgets = array_map(function($widget) { - $widget->arguments = json_decode($widget->arguments); - $tmpsetup = json_decode($widget->setup); - $tmpsetup->file = absoluteJsImportUrl($tmpsetup->file); - $widget->setup = $tmpsetup; - return $widget; - }, $tmpwidgets); - - $this->outputJsonSuccess($widgets); - } - - 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)); - } -} diff --git a/application/libraries/PermissionLib.php b/application/libraries/PermissionLib.php index 42502f999..d3fdc6642 100644 --- a/application/libraries/PermissionLib.php +++ b/application/libraries/PermissionLib.php @@ -50,6 +50,7 @@ class PermissionLib const LOGINAS_PERSONIDS_BLACKLIST = 'permission_loginas_personids_blacklist'; private $_ci; // CI instance + private $access_rights; // current users access rights private static $bb; // benutzerberechtigung /** @@ -61,6 +62,8 @@ class PermissionLib // Loads CI instance $this->_ci =& get_instance(); + $this->access_rights = null; + $this->_ci->config->load('permission'); // Loads permission configuration // If it's NOT called from command line @@ -69,8 +72,10 @@ class PermissionLib // API Caller rights initialization $authObj = $this->_ci->authlib->getAuthObj(); self::$bb = new benutzerberechtigung(); - if ($authObj) + if ($authObj) { self::$bb->getBerechtigungen($authObj->{AuthLib::AO_USERNAME}); + $this->access_rights = self::$bb->berechtigungen; + } } } @@ -340,6 +345,16 @@ class PermissionLib } } + /** + * Returns the access rights for the current user + * + * @return array|null + */ + public function getAccessRights() + { + return $this->access_rights; + } + //------------------------------------------------------------------------------------------------------------------ // Private methods diff --git a/application/libraries/dashboard/DashboardLib.php b/application/libraries/dashboard/DashboardLib.php index f6d7d6599..9b5306d94 100644 --- a/application/libraries/dashboard/DashboardLib.php +++ b/application/libraries/dashboard/DashboardLib.php @@ -49,7 +49,7 @@ class DashboardLib public function getMergedConfig($dashboard_id, $uid) { - $defaultconfig = $this->getDefaultConfig($dashboard_id, $uid); + $defaultconfig = $this->getDefaultConfig($dashboard_id); $userconfig = $this->getUserConfig($dashboard_id, $uid); $mergedconfig = array_replace_recursive($defaultconfig, $userconfig); @@ -57,14 +57,28 @@ class DashboardLib return $mergedconfig; } - public function getDefaultConfig($dashboard_id, $uid) + public function getDefaultConfig($dashboard_id) { - $res_presets = $this->_ci->DashboardPresetModel->getPresets($dashboard_id, $uid); + $funktion_kurzbzs = []; + $rights = $this->_ci->permissionlib->getAccessRights(); + if ($rights) + $funktion_kurzbzs = array_unique(array_map(function ($right) { + return $right->funktion_kurzbz; + }, $rights)); + + $this->_ci->DashboardPresetModel->db->where_in('funktion_kurzbz', $funktion_kurzbzs); + $this->_ci->DashboardPresetModel->db->or_where('funktion_kurzbz IS NULL'); + + $this->_ci->DashboardPresetModel->addOrder('funktion_kurzbz', 'DESC'); + + $result = $this->_ci->DashboardPresetModel->loadWhere([ + 'dashboard_id' => $dashboard_id + ]); $defaultconfig = array(); - if (hasData($res_presets)) + if (hasData($result)) { - $presets = getData($res_presets); + $presets = getData($result); foreach ($presets as $presetobj) { $preset = json_decode($presetobj->preset, true); @@ -137,8 +151,10 @@ class DashboardLib $dashboard = $this->getDashboardByKurzbz($dashboard_kurzbz); $funktion_kurzbz = ($section === self::SECTION_IF_FUNKTION_KURZBZ_IS_NULL) ? null : $section; - $result = $this->_ci->DashboardPresetModel - ->getPresetByDashboardAndFunktion($dashboard->dashboard_id, $funktion_kurzbz); + $result = $this->_ci->DashboardPresetModel->loadWhere([ + 'dashboard_id' => $dashboard->dashboard_id, + 'funktion_kurzbz' => $funktion_kurzbz + ]); if (hasData($result)) { @@ -195,11 +211,11 @@ class DashboardLib { foreach ($addwigets as $widget) { - if(!isset($widget->widgetid)) + if(!isset($widget['widgetid'])) { - $widget->widgetid = $this->generateWidgetId($dashboard_kurzbz); + $widget['widgetid'] = $this->generateWidgetId($dashboard_kurzbz); } - $this->addWidgetToWidgets($widgets, $section, $widget, $widget->widgetid); + $this->addWidgetToWidgets($widgets, $section, $widget, $widget['widgetid']); } } diff --git a/application/models/dashboard/Dashboard_Preset_model.php b/application/models/dashboard/Dashboard_Preset_model.php index ca10ce98a..42570d091 100644 --- a/application/models/dashboard/Dashboard_Preset_model.php +++ b/application/models/dashboard/Dashboard_Preset_model.php @@ -11,57 +11,4 @@ class Dashboard_Preset_model extends DB_Model $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 = <<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)); - } } diff --git a/application/views/dashboard/dashboard_demo.php b/application/views/dashboard/admin.php similarity index 67% rename from application/views/dashboard/dashboard_demo.php rename to application/views/dashboard/admin.php index 8efc230b7..1e338e125 100644 --- a/application/views/dashboard/dashboard_demo.php +++ b/application/views/dashboard/admin.php @@ -8,9 +8,15 @@ $this->load->view( 'axios027' => true, 'restclient' => true, 'vue3' => true, - 'customJSModules' => ['public/js/apps/Dashboard.js'], + 'primevue3' => true, + 'vuedatepicker11' => true, + 'customJSs' => [ + 'vendor/moment/luxonjs/luxon.min.js' + ], + 'customJSModules' => ['public/js/apps/Dashboard/Admin.js'], 'customCSSs' => [ - 'public/css/components/dashboard.css' + 'public/css/components/dashboard.css', + 'public/css/components/primevue.css', ], 'navigationcomponent' => true ) @@ -25,7 +31,7 @@ $this->load->view(

Dashboard

- + diff --git a/application/views/dashboard/dashboard_demo_admin.php b/application/views/dashboard/preview.php similarity index 67% rename from application/views/dashboard/dashboard_demo_admin.php rename to application/views/dashboard/preview.php index 0d92146a8..f8c37c0c8 100644 --- a/application/views/dashboard/dashboard_demo_admin.php +++ b/application/views/dashboard/preview.php @@ -8,7 +8,12 @@ $this->load->view( 'axios027' => true, 'restclient' => true, 'vue3' => true, - 'customJSModules' => ['public/js/apps/DashboardAdmin.js'], + 'vuedatepicker11' => true, + 'primevue3' => true, + 'customJSs' => [ + 'vendor/moment/luxonjs/luxon.min.js' + ], + 'customJSModules' => ['public/js/apps/Dashboard/Preview.js'], 'customCSSs' => [ 'public/css/components/dashboard.css' ], @@ -23,9 +28,9 @@ $this->load->view(
-

Dashboard

+

Dashboard

- +
diff --git a/public/css/components/dashboard/news.css b/public/css/components/dashboard/news.css index 4c4616aa5..116c96ddd 100644 --- a/public/css/components/dashboard/news.css +++ b/public/css/components/dashboard/news.css @@ -193,3 +193,6 @@ word-break: break-word; } +.news-list-item p { + word-break: break-word; +} \ No newline at end of file diff --git a/public/js/api/factory/dashboard/board.js b/public/js/api/factory/dashboard/board.js new file mode 100644 index 000000000..b5e9b5be3 --- /dev/null +++ b/public/js/api/factory/dashboard/board.js @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2026 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 3 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, see . + */ + +export default { + list() { + return { + method: 'get', + url: 'api/frontend/v1/dashboard/board/list' + }; + }, + add(params) { + return { + method: 'post', + url: 'api/frontend/v1/dashboard/board/create', + params + }; + }, + update(params) { + return { + method: 'post', + url: 'api/frontend/v1/dashboard/board/update', + params + }; + }, + delete(dashboard_id) { + return { + method: 'post', + url: 'api/frontend/v1/dashboard/board/delete', + params: { dashboard_id } + }; + } +} \ No newline at end of file diff --git a/public/js/api/factory/dashboard/preset.js b/public/js/api/factory/dashboard/preset.js new file mode 100644 index 000000000..3f380581e --- /dev/null +++ b/public/js/api/factory/dashboard/preset.js @@ -0,0 +1,47 @@ +/** + * Copyright (C) 2026 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 3 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, see . + */ + +export default { + list(dashboard_kurzbz) { + return { + method: 'get', + url: 'api/frontend/v1/dashboard/preset/list/' + + encodeURIComponent(dashboard_kurzbz) + }; + }, + getBatch(params) { + return { + method: 'post', + url: 'api/frontend/v1/dashboard/preset/getBatch', + params + }; + }, + addWidget(params) { + return { + method: 'post', + url: 'api/frontend/v1/dashboard/preset/addWidget', + params + }; + }, + removeWidget(params) { + return { + method: 'post', + url: 'api/frontend/v1/dashboard/preset/removeWidget', + params + }; + } +}; \ No newline at end of file diff --git a/public/js/api/factory/dashboard/user.js b/public/js/api/factory/dashboard/user.js new file mode 100644 index 000000000..e660d077e --- /dev/null +++ b/public/js/api/factory/dashboard/user.js @@ -0,0 +1,45 @@ +/** + * Copyright (C) 2026 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 3 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, see . + */ + +export default { + get(dashboard) { + return { + method: 'get', + url: '/api/frontend/v1/dashboard/user/get/' + dashboard + }; + }, + addWidget(dashboard, widget) { + return { + method: 'post', + url: '/api/frontend/v1/dashboard/user/addWidget', + params: { + dashboard, + widget + } + }; + }, + removeWidget(dashboard, widget) { + return { + method: 'post', + url: '/api/frontend/v1/dashboard/user/removeWidget', + params: { + dashboard, + widget + } + }; + } +}; \ No newline at end of file diff --git a/public/js/api/factory/dashboard/widget.js b/public/js/api/factory/dashboard/widget.js new file mode 100644 index 000000000..d7d1bbc86 --- /dev/null +++ b/public/js/api/factory/dashboard/widget.js @@ -0,0 +1,46 @@ +/** + * Copyright (C) 2026 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 3 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, see . + */ + +export default { + get(widget) { + return { + method: 'get', + url: '/api/frontend/v1/dashboard/widget/get/' + widget + }; + }, + list(dashboard) { + return { + method: 'get', + url: '/api/frontend/v1/dashboard/widget/list/' + dashboard + }; + }, + listAllowed(dashboard) { + return { + method: 'get', + url: '/api/frontend/v1/dashboard/widget/listAllowed/' + dashboard + }; + }, + setAllowed(dashboard_id, widget_id, allowed) { + return { + method: 'post', + url: '/api/frontend/v1/dashboard/widget/setAllowed', + params: { + dashboard_id, widget_id, allowed + } + }; + } +}; \ No newline at end of file diff --git a/public/js/apps/Dashboard/Admin.js b/public/js/apps/Dashboard/Admin.js index 4a5fcee2f..32909a50a 100644 --- a/public/js/apps/Dashboard/Admin.js +++ b/public/js/apps/Dashboard/Admin.js @@ -1,16 +1,67 @@ -import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js'; +import { CoreNavigationCmpt } from '../../components/navigation/Navigation.js'; import DashboardAdmin from '../../components/Dashboard/Admin.js'; + import PluginsPhrasen from '../../plugins/Phrasen.js'; +import ApiRenderers from '../../api/factory/renderers.js'; + const app = Vue.createApp({ - name: 'AdminApp', - data: () => ({ - appSideMenuEntries: {} - }), - components: { - CoreNavigationCmpt, - DashboardAdmin - } + name: 'DashboardAdminApp', + data: () => ({ + appSideMenuEntries: {}, + renderers: null + }), + components: { + CoreNavigationCmpt, + DashboardAdmin + }, + provide() { + return { + // TODO(chris): move those two into the components that need it + renderers: Vue.computed(() => this.renderers), + timezone: FHC_JS_DATA_STORAGE_OBJECT.timezone + }; + }, + created() { + this.$api + .call(ApiRenderers.loadRenderers()) + .then(res => { + for (let rendertype of Object.keys(res.data)) { + let modalTitle = null; + let modalContent = null; + let calendarEvent = null; + if (res.data[rendertype].modalTitle) + modalTitle = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalTitle))); + if (res.data[rendertype].modalContent) + modalContent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].modalContent))); + if (res.data[rendertype].calendarEvent) + calendarEvent = Vue.markRaw(Vue.defineAsyncComponent(() => import(res.data[rendertype].calendarEvent))); + + if (res.data[rendertype].calendarEventStyles) { + var head = document.head; + if (!head.querySelector(`link[href="${res.data[rendertype].calendarEventStyles}"]`)) { + var link = document.createElement("link"); + link.type = "text/css"; + link.rel = "stylesheet"; + link.href = res.data[rendertype].calendarEventStyles; + head.appendChild(link); + } + } + + if (this.renderers === null) { + this.renderers = {}; + } + if (!this.renderers[rendertype]) { + this.renderers[rendertype] = {} + } + this.renderers[rendertype].modalTitle = modalTitle; + this.renderers[rendertype].modalContent = modalContent; + this.renderers[rendertype].calendarEvent = calendarEvent; + } + }) + .catch(this.$fhcAlert.handleSystemErrors); + } }); app.use(PluginsPhrasen); +app.directive('tooltip', primevue.tooltip); app.mount('#main'); \ No newline at end of file diff --git a/public/js/apps/Dashboard/Preview.js b/public/js/apps/Dashboard/Preview.js new file mode 100644 index 000000000..a270c3028 --- /dev/null +++ b/public/js/apps/Dashboard/Preview.js @@ -0,0 +1,17 @@ +import {CoreNavigationCmpt} from '../../components/navigation/Navigation.js'; +import CoreDashboard from '../../components/Dashboard/Dashboard.js'; +import PluginsPhrasen from '../../plugins/Phrasen.js'; + +const app = Vue.createApp({ + name: 'DashboardPreviewApp', + data: () => ({ + appSideMenuEntries: {} + }), + components: { + CoreNavigationCmpt, + CoreDashboard + } +}); +app.use(PluginsPhrasen); +app.directive('tooltip', primevue.tooltip); +app.mount('#main'); \ No newline at end of file diff --git a/public/js/apps/DashboardAdmin.js b/public/js/apps/DashboardAdmin_DEPR.js similarity index 100% rename from public/js/apps/DashboardAdmin.js rename to public/js/apps/DashboardAdmin_DEPR.js diff --git a/public/js/components/Bootstrap/Confirm.js b/public/js/components/Bootstrap/Confirm.js index a56885473..f6ac52abc 100644 --- a/public/js/components/Bootstrap/Confirm.js +++ b/public/js/components/Bootstrap/Confirm.js @@ -17,7 +17,7 @@ export default { ` } diff --git a/public/js/components/Dashboard/Admin.js b/public/js/components/Dashboard/Admin.js index f1a837880..ff117a956 100644 --- a/public/js/components/Dashboard/Admin.js +++ b/public/js/components/Dashboard/Admin.js @@ -3,15 +3,20 @@ import DashboardAdminEdit from "./Admin/Edit.js"; import DashboardAdminWidgets from "./Admin/Widgets.js"; import DashboardAdminPresets from "./Admin/Presets.js"; +import ApiDashboardBoard from "../../api/factory/dashboard/board.js"; +import ApiDashboardWidget from "../../api/factory/dashboard/widget.js"; + export default { + name: 'DashboardAdmin', components: { DashboardAdminEdit, DashboardAdminWidgets, - DashboardAdminPresets + DashboardAdminPresets, }, provide() { return { - adminMode: true + adminMode: true, + widgetsSetup: Vue.computed(() => this.dashboards[this.current] ? this.dashboards[this.current].widgetSetup : null) }; }, data() { @@ -22,9 +27,6 @@ export default { }; }, 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); } @@ -35,33 +37,50 @@ export default { BsPrompt.popup('New Dashboard name').then( name => { _name = name; - return axios.post(this.apiurl + '/Dashboard/create', { + const params = { 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); + }; + return this.$api + .call(ApiDashboardBoard.add(params)) + .then(response =>{ + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); + + let newDashboard = { + dashboard_id: response.data, + dashboard_kurzbz: _name, + beschreibung: '' + }; + this.dashboards.push(newDashboard); + this.current = newDashboard.dashboard_id; + }) + .catch(this.$fhcAlert.handleSystemError); + }); }, 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)); + return this.$api + .call(ApiDashboardBoard.update(dashboard)) + .then(response =>{ + + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successSave')); + + let old = this.dashboards.find(el => el.dashboard_id == dashboard.dashboard_id); + old.dashboard_kurzbz = dashboard.dashboard_kurzbz; + old.beschreibung = dashboard.beschreibung; + }) + .catch(this.$fhcAlert.handleSystemError); }, 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)); + return this.$api + .call(ApiDashboardBoard.delete(dashboard_id)) + .then(response => { + this.$fhcAlert.alertSuccess(this.$p.t('ui', 'successDelete')); + + }) + .catch(this.$fhcAlert.handleSystemError) + .finally(() => { + this.current = -1; + this.dashboards = this.dashboards.filter(el => el.dashboard_id != dashboard_id); + }); }, assignWidgets(widgets) { this.widgets = widgets; @@ -72,22 +91,35 @@ export default { } }, created() { - axios.get(this.apiurl + '/Dashboard').then(res => { - this.dashboards = res.data.retval; - }).catch(err => console.error('ERROR:', err)); + this.$api + .call(ApiDashboardBoard.list()) + .then(result => { + this.dashboards = result.data.retval; + for (const dashboard of this.dashboards) { + this.$api + .call(ApiDashboardWidget.list(dashboard.dashboard_id)) + .then(res => { + dashboard.widgetSetup = res.data; + }) + .catch(this.$fhcAlert.handleSystemError); + } + }) + .catch(this.$fhcAlert.handleSystemError); }, template: `
+
- -
+
- +
diff --git a/public/js/components/Dashboard/Admin/Edit.js b/public/js/components/Dashboard/Admin/Edit.js index ec841a9ad..c40d91183 100644 --- a/public/js/components/Dashboard/Admin/Edit.js +++ b/public/js/components/Dashboard/Admin/Edit.js @@ -18,7 +18,8 @@ export default { }, methods: { sendDelete() { - BsConfirm.popup('Sure?').then(() => this.$emit('delete', this.dashboard_id)).catch(); + BsConfirm.popup(this.$p.t('ui', 'confirm_delete') + " " + this.$p.t('ui', 'deleteInfo')) + .then(() => this.$emit('delete', this.dashboard_id)).catch(); } }, template: `
@@ -31,8 +32,8 @@ export default {
- - + +
` } diff --git a/public/js/components/Dashboard/Admin/Presets.js b/public/js/components/Dashboard/Admin/Presets.js index 8d6a07803..ef1c06e00 100644 --- a/public/js/components/Dashboard/Admin/Presets.js +++ b/public/js/components/Dashboard/Admin/Presets.js @@ -1,6 +1,7 @@ import DashboardSection from "../Section.js"; import DashboardWidgetPicker from "../Widget/Picker.js"; import ObjectUtils from "../../../helpers/ObjectUtils.js"; +import ApiDashboardPreset from "../../../api/factory/dashboard/preset.js"; export default { components: { @@ -17,9 +18,6 @@ export default { tmpLoading: '' }), 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); } @@ -28,6 +26,7 @@ export default { widgetAdd(section_name, widget) { this.$refs.widgetpicker.getWidget().then(widget_id => { widget.widget = widget_id; + widget.id = 'loading_' + String((new Date()).valueOf()); delete widget.custom; widget.preset = 1; let loading = {...widget}; @@ -36,130 +35,153 @@ export default { if (section.name == section_name) section.widgets.push(loading); }); - - axios.post(this.apiurl + '/Config/addWidgetsToPreset', { - db: this.dashboard, + + const params = { + dashboard: this.dashboard, funktion_kurzbz: section_name, - widgets: [widget] - }).then(result => { - let newId = Object.keys(result.data.retval.data[section_name].widgets).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(() => {}); + widget + }; + + return this.$api + .call(ApiDashboardPreset.addWidget(params)) + .then(result => { + let newId = result.data; + 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); + } + }); + this.funktionen.forEach(funktion => { + if(funktion.funktion_kurzbz === section_name && funktion.has_preset < 1) { + funktion.has_preset = 1; + } + }); + }) + .catch(this.$fhcAlert.handleSystemError); + }) + .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; - } - } + const section = this.sections.find(section => section.name == section_name); + for (var wid in section.widgets) { + if (section.widgets[wid].id == k) { + payload[k] = ObjectUtils.mergeDeep(section.widgets[wid], payload[k]); + // NOTE(chris): remove internal props + for (var prop of ['_x', '_y', '_w', '_h', 'index', 'id']) + if (payload[k][prop]) + delete payload[k][prop]; 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); - }); + this.$api + .call(Object.entries(payload).map(([key, widget]) => [ + key, + ApiDashboardPreset.addWidget({ + dashboard: this.dashboard, + funktion_kurzbz: section_name, + widget + }) + ])) + .then(result => { + 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(this.$fhcAlert.handleSystemError); }, widgetRemove(section_name, id) { - axios.post(this.apiurl + '/Config/removeWidgetFromPreset', { + const params = { 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); - }); + }; + return this.$api + .call(ApiDashboardPreset.removeWidget(params)) + .then(result => { + this.sections.forEach(section => { + if (section.name == section_name) + section.widgets = section.widgets.filter(widget => widget.id != id); + }); + }) + .catch(this.$fhcAlert.handleSystemError); }, loadSections(evt) { let funktionen = Array.from(evt.target.querySelectorAll("option:checked"),e=>e.value); this.sections = []; this.tmpLoading = funktionen.join('###'); - axios.get(this.apiurl + '/Config/presetBatch', {params: { + + const params = { db: this.dashboard, funktionen - }}).then(res => { - if (this.tmpLoading !== funktionen.join('###')) - return; // NOTE(chris): prevent race condition - 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]); + }; + + return this.$api + .call(ApiDashboardPreset.getBatch(params)) + .then(result => { + if (this.tmpLoading !== funktionen.join('###')) + return; // NOTE(chris): prevent race condition + for (var section in result.data) { + let widgets = []; + for (var wid in result.data[section]) { + result.data[section][wid].id = wid; + result.data[section][wid].custom = 1; + widgets.push(result.data[section][wid]); + } + this.sections.push({ + name: section, + widgets + }); } - this.sections.push({ - name: section, - widgets - }); - } - }).catch(err => console.error('ERROR:', err)); + }) + .catch(this.$fhcAlert.handleSystemError); + + }, + loadFunktionen() { + this.$api + .call(ApiDashboardPreset.list(this.dashboard)) + .then(result => { + this.funktionen = result.data; + }) + .catch(this.$fhcAlert.handleSystemError); } }, created() { - axios.get(this.apiurl + '/Config/funktionen').then(res => { - this.funktionen = {general: 'GENERAL'}; - res.data.retval.forEach(funktion => { - this.funktionen[funktion.funktion_kurzbz] = funktion.beschreibung; - }); - }).catch(err => console.error('ERROR:', err)); + this.loadFunktionen(); }, watch: { dashboard() { // TODO(chris): this should be done without a watcher this.loadSections({target:this.$refs.funktionenList}); + this.loadFunktionen(); } }, template: `
diff --git a/public/js/components/Dashboard/Admin/Widgets.js b/public/js/components/Dashboard/Admin/Widgets.js index 645d3f8cc..a0c7b2139 100644 --- a/public/js/components/Dashboard/Admin/Widgets.js +++ b/public/js/components/Dashboard/Admin/Widgets.js @@ -1,3 +1,5 @@ +import ApiDashboardWidget from "../../../api/factory/dashboard/widget.js"; + export default { emits: [ "change", @@ -7,34 +9,25 @@ export default { 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)); + + this.$api + .call(ApiDashboardWidget.setAllowed(this.dashboard_id, widget_id, allow)) + .catch(this.$fhcAlert.handleSystemError); } }, created() { - axios.get(this.apiurl + '/Widget/getAll', { - params:{ - dashboard_id: this.dashboard_id - } - }).then( - result => { - this.$emit('assignWidgets', result.data.retval.map(el => ({ + this.$api + .call(ApiDashboardWidget.list(this.dashboard_id)) + .then(result => { + this.$emit('assignWidgets', result.data.map(el => ({ ...el, - ...{setup:JSON.parse(el.setup),arguments:JSON.parse(el.arguments),allowed:!!el.allowed} + allowed: !!el.allowed }))); - } - ).catch(err => console.error('ERROR:', err)); + }) + .catch(this.$fhcAlert.handleSystemError); }, template: `
diff --git a/public/js/components/Dashboard/Dashboard.js b/public/js/components/Dashboard/Dashboard.js index 652a2778e..e92e34f29 100644 --- a/public/js/components/Dashboard/Dashboard.js +++ b/public/js/components/Dashboard/Dashboard.js @@ -2,7 +2,8 @@ import DashboardSection from "./Section.js"; import DashboardWidgetPicker from "./Widget/Picker.js"; import ObjectUtils from "../../helpers/ObjectUtils.js"; -import ApiDashboard from '../../api/factory/cis/dashboard.js'; +import ApiDashboardWidget from '../../api/factory/dashboard/widget.js'; +import ApiDashboardUser from '../../api/factory/dashboard/user.js'; export default { name: 'Dashboard', @@ -20,181 +21,147 @@ export default { type: Object, required: true, validator(value) { - return value && value.name && value.uid && value.timezone + return value && value.name && value.timezone } } }, data() { return { - sections: [], - widgets: null, - editMode: false, - viewDataInternal: this.viewData + widgets: [], + originalWidgets: {}, + widgetsSetup: null, + editMode: false } }, provide() { return { editMode: Vue.computed(()=>this.editMode), - widgetsSetup: Vue.computed(() => this.widgets), + widgetsSetup: Vue.computed(() => this.widgetsSetup), timezone: Vue.computed(() => this.viewData.timezone) } }, - 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 => { - 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; - widget.id = 'loading_' + String((new Date()).valueOf()); - 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[section_name].widgets).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(() => {}); + // TODO(chris): remove section_name? (change order of params => get rid of it) + this.$refs.widgetpicker + .getWidget() + .then(widget_id => { + widget.widget = widget_id; + widget.id = 'loading_' + String((new Date()).valueOf()); + let loading = { ...widget }; + loading.loading = true; + this.widgets.push(loading); + + this.$api + .call(ApiDashboardUser.addWidget(this.dashboard, widget)) + .then(result => { + widget.id = result.data; + this.widgets.splice(this.widgets.indexOf(loading), 1); + this.widgets.push(widget); + this.originalWidgets[widget.id] = structuredClone(ObjectUtils.deepToRaw(widget)); + }) + .catch(this.$fhcAlert.handleSystemError); + }) + .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,preset:1}) - if (payload[k][prop]) - delete payload[k][prop]; - break; - } - } + for (var wid in this.widgets) { + if (this.widgets[wid].id == k) { + payload[k] = ObjectUtils.mergeDeep(this.widgets[wid], payload[k]); + // NOTE(chris): remove internal props + for (var prop of ['_x','_y','_w','_h','index','id','preset']) + if (payload[k][prop]) + delete payload[k][prop]; 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]; + this.$api + .call(Object.entries(payload).map(([key, widget]) => [ + key, + ApiDashboardUser.addWidget(this.dashboard, widget) + ])) + .then(result => { + const failed = result + .filter(o => o.status == 'rejected') + .map(o => o.reason.config.errorHeader); + + this.widgets.forEach((widget, i) => { + if (failed.includes(widget.id)) { + this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id])); + /** NOTE(chris): if you wanna hide or unhide a + * preset and it fails: switch around the hidden + * value to revert it properly (checkboxes can't + * really handle it otherwise) + */ + if (payload[widget.id].hidden !== undefined) { + this.widgets[i].hidden = payload[widget.id].hidden; + this.$nextTick(() => { + this.widgets[i] = structuredClone(ObjectUtils.deepToRaw(this.originalWidgets[widget.id])); + }); } - }); - } - }); - }).catch(error => { - // TODO(chris): revert placement on failure - console.error('ERROR: ', error); - alert('ERROR: ' + error.response.data.retval); - }); + } else if (payload[widget.id]) { + payload[widget.id].id = widget.id; + payload[widget.id].index = widget.index; + this.widgets[i] = payload[widget.id]; + this.originalWidgets[widget.id] = structuredClone(ObjectUtils.deepToRaw(this.widgets[i])); + } + }); + }) + .catch(this.$fhcAlert.handleSystemError); }, 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); - }); + this.$api + .call(ApiDashboardUser.removeWidget(this.dashboard, id)) + .then(() => { + this.widgets = this.widgets.filter(widget => widget.id != id); + }) + .catch(this.$fhcAlert.handleSystemError); } }, created() { this.$p.loadCategory('dashboard'); - axios.get(this.apiurl + '/Widget/getWidgetsForDashboard', { - params: { - db: this.dashboard - } - }).then(res => { - this.widgets = res.data.retval; - }).catch(err => console.error('ERROR:', err)); - axios.get(this.apiurl + '/Config', {params:{ - db: this.dashboard - }}).then(res => { - for (var name in res.data.retval) { - let widgets = []; - let remove = []; - for (var wid in res.data.retval[name].widgets) { - res.data.retval[name].widgets[wid].id = wid; - if (res.data.retval[name].widgets[wid].custom || res.data.retval[name].widgets[wid].preset) - widgets.push(res.data.retval[name].widgets[wid]); - else + this.$api + .call(ApiDashboardWidget.listAllowed(this.dashboard)) + .then(res => { + this.widgetsSetup = res.data; + }) + .catch(this.$fhcAlert.handleSystemError); + + this.$api + .call(ApiDashboardUser.get(this.dashboard)) + .then(res => { + const widgets = []; + const remove = []; + + for (var wid in res.data.general.widgets) { + let widget = res.data.general.widgets[wid]; + widget.id = wid; + if (widget.custom || widget.preset) { + widgets.push(widget); + this.originalWidgets[wid] = structuredClone(widget); + } else { remove.push(wid); + } } - this.sections.push({ - name: name, - widgets: widgets - }); - remove.forEach(wid => this.widgetRemove(name, wid)); - } - this.sections = this.sections.sort((section1, section2) => { - if(section1.name == 'custom') - return 1; - if (section2.name == 'custom') - return -1; - return section2.widgets.length - section1.widgets.length; - }); - }).catch(err => console.error('ERROR:', err)); - }, - async beforeMount() { - if (!this.viewData.name || !this.viewData.uid) { - const res = await this.$api.call(ApiDashboard.getViewData()); - this.viewDataInternal = res.data - } + + remove.forEach(wid => this.widgetRemove('general', wid)); + + this.widgets = widgets; + }) + .catch(this.$fhcAlert.handleSystemError); }, template: `
-

- {{ $p.t('global/personalGreeting', [ viewDataInternal?.name ]) }} +

+ {{ $p.t('global/personalGreeting', [ viewData?.name ]) }}

- - + +
` } diff --git a/public/js/components/Dashboard/Item.js b/public/js/components/Dashboard/Item.js index 06a03ae3a..79e61293d 100644 --- a/public/js/components/Dashboard/Item.js +++ b/public/js/components/Dashboard/Item.js @@ -1,5 +1,5 @@ import BsModal from "../Bootstrap/Modal.js"; -import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js"; +import { useCachedWidgetLoader } from "../../composables/Dashboard/CachedWidgetLoader.js"; import HeightTransition from "../Tranistion/HeightTransition.js"; export default { @@ -70,6 +70,14 @@ export default { ready() { return this.component && this.arguments !== null; }, + visible: { + get() { + return !this.hidden; + }, + set(value) { + this.$emit('remove', this.hidden); + } + } }, methods: { unpin(){ @@ -142,8 +150,14 @@ export default { this.isLoading = false; }, }, + setup() { + const { actions } = useCachedWidgetLoader(); + return { + loadWidget: actions.load + }; + }, async created() { - this.widget = await CachedWidgetLoader.loadWidget(this.id); + this.widget = await this.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; @@ -185,7 +199,7 @@ export default {
- +
diff --git a/public/js/components/Dashboard/Section.js b/public/js/components/Dashboard/Section.js index a238de289..817cc52a3 100644 --- a/public/js/components/Dashboard/Section.js +++ b/public/js/components/Dashboard/Section.js @@ -1,7 +1,7 @@ import BsConfirm from "../Bootstrap/Confirm.js"; import DropGrid from '../Drop/Grid.js' import DashboardItem from "./Item.js"; -import CachedWidgetLoader from "../../composables/Dashboard/CachedWidgetLoader.js"; +import { useCachedWidgetLoader } from "../../composables/Dashboard/CachedWidgetLoader.js"; import WidgetIcon from "./Widget/WidgetIcon.js" export default { @@ -125,23 +125,23 @@ export default { }, checkResizeLimit(item, w, h) { // NOTE(chris): widgets needs to be loaded for this to work - let widget = CachedWidgetLoader.getWidget(item.widget); + let widget = this.widgetState[item.widget]; if (widget) { - let minmaxW = widget.setup.width; + let minmaxW = { ...widget.setup.width }; if (minmaxW.max) minmaxW.min = minmaxW.min || 1; else - minmaxW = {min:minmaxW,max:minmaxW}; + minmaxW = { min: minmaxW, max: minmaxW }; if (w < minmaxW.min) w = minmaxW.min; if (w > minmaxW.max) w = minmaxW.max; - let minmaxH = widget.setup.height; + let minmaxH = { ...widget.setup.height }; if (minmaxH.max) minmaxH.min = minmaxH.min || 1; else - minmaxH = {min:minmaxH,max:minmaxH}; + minmaxH = { min: minmaxH, max: minmaxH }; if (h < minmaxH.min) h = minmaxH.min; if (h > minmaxH.max) @@ -151,7 +151,7 @@ export default { }, 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)); + BsConfirm.popup(this.$p.t('dashboard', 'alert_deleteWidget')).then(() => this.$emit('widgetRemove', this.name, item.id)); } else { let update = {}; update[item.id] = { hidden: !revert }; @@ -199,6 +199,13 @@ export default { this.$emit('widgetUpdate', this.name, payload); } }, + setup() { + const { state: widgetState } = useCachedWidgetLoader(); + + return { + widgetState + }; + }, mounted() { let self = this; let cont = self.$refs.container; diff --git a/public/js/composables/Dashboard/CachedWidgetLoader.js b/public/js/composables/Dashboard/CachedWidgetLoader.js index a92e3e557..4bc2d4992 100644 --- a/public/js/composables/Dashboard/CachedWidgetLoader.js +++ b/public/js/composables/Dashboard/CachedWidgetLoader.js @@ -1,29 +1,36 @@ -let __widgets = {}; -let __widgetsStarted = {}; -let __path = FHC_JS_DATA_STORAGE_OBJECT.app_root + FHC_JS_DATA_STORAGE_OBJECT.ci_router + '/dashboard/Widget'; +import ApiWidget from "../../api/factory/dashboard/widget.js"; -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!'); +const promises = Vue.ref([]); +const stateRef = Vue.ref([]); +const state = Vue.readonly(stateRef); - __widgetsStarted[id] = new Promise((resolve, reject) => { - axios.get(__path, {params:{id}}).then(res => { - __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; +export function useCachedWidgetLoader() { + const $api = Vue.inject('$api'); + const $fhcAlert = Vue.inject('$fhcAlert'); + + function load(id) { + if (state.value[id]) + return Promise.resolve(state.value[id]); + + if (!promises.value[id]) + promises.value[id] = new Promise((resolve, reject) => { + $api + .call(ApiWidget.get(id)) + .then(res => { + stateRef.value[id] = res.data; + promises.value[id] = undefined; + resolve(state.value[id]); + }) + .catch($fhcAlert.handleSystemError); + }); + + return promises.value[id]; } + + return { + state, + actions: { + load + } + }; } \ No newline at end of file diff --git a/public/js/helpers/ObjectUtils.js b/public/js/helpers/ObjectUtils.js index 348d18843..c68e92e1e 100644 --- a/public/js/helpers/ObjectUtils.js +++ b/public/js/helpers/ObjectUtils.js @@ -1,31 +1,72 @@ -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; - } - }); +/** + * 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 + */ +function 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]; - return prev; - }, {}); - } + 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; + }, {}); +} + +/** + * Extends VUEs toRaw() function to nested Proxies + * @see https://www.reddit.com/r/javascript/comments/10gzynk/deep_cloning_objects_in_javascript_the_modern_way/ + * + * @param object sourceObj - Object to transform + * @returns object + */ +function deepToRaw(sourceObj) { + const objectIterator = input => { + if (Array.isArray(input)) + return input.map(objectIterator); + if (Vue.isRef(input) || Vue.isReactive(input) || Vue.isProxy(input)) + return objectIterator(Vue.toRaw(input)); + if (input && typeof input === 'object') { + /** use custom handling of 'Date' objects to avoid data loss if treating it like any other object. + * reminder: + * typeof (new Date()) ==> 'object' + * Object.keys(new Date()) ==> [] + */ + if (input instanceof Date) + return input; + + return Object.keys(input).reduce((acc, key) => { + acc[key] = objectIterator(input[key]); + return acc; + }, {}); + } + + return input; + }; + + return objectIterator(sourceObj); +} + +export { + mergeDeep, + deepToRaw +} +export default { + mergeDeep, + deepToRaw } \ No newline at end of file diff --git a/public/js/plugins/Api.js b/public/js/plugins/Api.js index 889d694eb..115dd28fa 100644 --- a/public/js/plugins/Api.js +++ b/public/js/plugins/Api.js @@ -430,16 +430,16 @@ export default { fhcApiAxios.interceptors.response.use( response => { - if (response.config?.errorHandling == 'off' - || response.config?.errorHandling === false - || response.config?.errorHandling == 'fail') + const errorConfig = get_error_handler(response.config); + + if (!errorConfig.success) return clean_return_value(response); - // NOTE(chris): loop through errors - if (response.data.errors) - response.data.errors = response.data.errors.filter( - err => (response.config[err.type + 'ErrorHandler'] || app.config.globalProperties.$api._defaultErrorHandlers[err.type])(err, response.config) - ); + const errors = popHandleableErrors(errorConfig, response.data.errors); + + for (var type in errors) { + errorConfig.handler[type](errors[type]); + } return clean_return_value(response); }, diff --git a/system/phrasesupdate.php b/system/phrasesupdate.php index 3b3b6f995..f7628f8ef 100644 --- a/system/phrasesupdate.php +++ b/system/phrasesupdate.php @@ -57196,6 +57196,87 @@ I have been informed that I am under no obligation to consent to the transmissio ) ), + // ### Phrases Dashboard Admin START + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'deleteInfo', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Mit dieser Aktion werden auch alle Voreinstellungen der verbundenen Widgets gelöscht.', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'This action will also delete all presets of the connected widgets.', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'dashboard', + 'phrase' => 'success_savePreset', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Voreinstellung erfolgreich aktualisiert', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Preset successfully updated', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), array( + 'app' => 'core', + 'category' => 'dashboard', + 'phrase' => 'alert_deleteWidget', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Sind Sie sicher, dass Sie dieses Widget löschen möchten?', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Are you sure you want to delete this widget?', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + array( + 'app' => 'core', + 'category' => 'ui', + 'phrase' => 'confirm_delete', + 'insertvon' => 'system', + 'phrases' => array( + array( + 'sprache' => 'German', + 'text' => 'Möchten Sie wirklich löschen?', + 'description' => '', + 'insertvon' => 'system' + ), + array( + 'sprache' => 'English', + 'text' => 'Do you really want to delete?', + 'description' => '', + 'insertvon' => 'system' + ) + ) + ), + // ### Phrases Dashboard Admin END );