| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780 |
- <?php defined('BX_DOL') or die('hack attempt');
- /**
- * Copyright (c) UNA, Inc - https://una.io
- * MIT License - https://opensource.org/licenses/MIT
- *
- * @defgroup UnaView UNA Studio Representation classes
- * @ingroup UnaStudio
- * @{
- */
- class BxBaseStudioDashboard extends BxDolStudioDashboard
- {
- public function __construct()
- {
- parent::__construct();
- }
- public function getPageCss()
- {
- return array_merge(parent::getPageCss(), array(
- 'page_layouts.css',
- 'dashboard.css',
- ));
- }
- public function getPageJs()
- {
- return array_merge(parent::getPageJs(), array(
- 'jquery.anim.js',
- 'dashboard.js'
- ));
- }
- public function getPageJsClass()
- {
- return 'BxDolStudioDashboard';
- }
- public function getPageJsObject()
- {
- return 'oBxDolStudioDashboard';
- }
- public function getPageCaption()
- {
- return parent::getPageCaption() . $this->getPageJsCode();
- }
- public function getPageJsCode($aOptions = array(), $bWrap = true)
- {
- $aStatusTitles = [];
- foreach(['ok', 'warn', 'fail', 'undef'] as $sStatus)
- $aStatusTitles[$sStatus] = _t('_sys_audit_title_' . $sStatus);
- $aOptions = array_merge($aOptions, array(
- 'sActionUrl' => BX_DOL_URL_STUDIO . 'dashboard.php',
- 'aStatusTitles' => $aStatusTitles // the status words the Updates rows get once the check is in
- ));
- return parent::getPageJsCode($aOptions, $bWrap);
- }
- public function getPageCode($sPage = '', $bWrap = true)
- {
- $sResult = parent::getPageCode($sPage, $bWrap);
- if($sResult === false)
- return false;
- $oPage = BxDolPage::getObjectInstance('sys_std_dashboard', BxDolStudioTemplate::getInstance());
- return $sResult . $oPage->getCode();
- }
- /**
- * The Version block's asynchronous part: the system update state and the count of apps with updates, as status rows.
- */
- public function getPageCodeVersionAvailable()
- {
- // Row statuses are the status-row modifiers (ok / warn / undef); the audit class that names them isn't loaded here.
- // Latest Stable and Latest Beta each show the newest version on that channel: green when the site has it, orange when it is newer.
- $bUpgradeAvailable = false;
- $aRows = [];
- foreach(['stable', 'beta'] as $sChannel) {
- list($sVersion, $bNewer, $bUpgrade) = $this->getVersionChannelInfo($sChannel);
- if($sVersion)
- $aRows[$sChannel] = ['status' => $bNewer ? 'warn' : 'ok', 'value' => $sVersion];
- else
- $aRows[$sChannel] = ['status' => 'undef', 'value' => _t('_adm_dbd_txt_ht_na')];
- // the Update button in the block caption follows the channel the site is set to
- if($bUpgrade && $sChannel == (getParam('sys_upgrade_channel') == 'beta' ? 'beta' : 'stable'))
- $bUpgradeAvailable = true;
- }
- // The Apps row is named after the number of installed apps; its value is the number waiting for an update.
- $sApps = $this->getAppsCountText();
- $mixedUpdates = BxDolStudioInstallerUtils::getInstance()->checkUpdates();
- if(!is_array($mixedUpdates))
- $aApps = ['status' => 'undef', 'name' => $sApps, 'value' => _t('_adm_dbd_txt_ht_na')];
- else if(($iUpdates = count($mixedUpdates)) > 0)
- $aApps = ['status' => 'warn', 'name' => $sApps, 'value' => '<a href="' . BX_DOL_URL_STUDIO . 'store.php?page=updates">' . _t('_adm_dbd_txt_ver_apps_updates', $iUpdates) . '</a>'];
- else
- $aApps = ['status' => 'ok', 'name' => $sApps, 'value' => _t('_adm_dbd_txt_ver_up_to_date')];
- return [
- 'upgrade' => (int)$bUpgradeAvailable,
- 'stable' => $aRows['stable'],
- 'beta' => $aRows['beta'],
- 'apps' => $aApps
- ];
- }
- public function serviceGetWidgetNotices() {
- $iResult = 0;
- //--- Check Version: a newer release on the channel the site follows
- list(, $bNewer) = $this->getVersionChannelInfo(getParam('sys_upgrade_channel') == 'beta' ? 'beta' : 'stable');
- if($bNewer)
- $iResult += 1;
- //--- Check Host Requirements
- $oAudit = new BxDolStudioToolsAudit();
- foreach($this->aItemsHTools as $sTitle => $sFunc) {
- $aResult = $oAudit->checkRequirements(BX_DOL_AUDIT_FAIL, $sFunc);
- if(!empty($aResult)) {
- $iResult += 1;
- break;
- }
- }
- return $iResult;
- }
- /**
- * "23 Apps": everything installed besides the system itself (apps, templates and languages).
- */
- protected function getAppsCountText()
- {
- $iApps = 0;
- foreach(BxDolModuleQuery::getInstance()->getModules() as $aModule)
- if($aModule['type'] != 'system')
- $iApps++;
- return _t('_adm_dbd_txt_ver_apps_count', $iApps);
- }
- /**
- * Whether the site can receive automatic updates. They come with the UNA account's Pro and Max plans (https://unacms.com/start), which
- * the site joins with the marketplace key pair from Settings > System; swap the check for the account API's answer once it reports the plan.
- */
- public function isAutoUpdateAccess()
- {
- return getParam('sys_oauth_key') != '' && getParam('sys_oauth_secret') != '';
- }
- public function serviceGetBlockVersion()
- {
- $sJsObject = $this->getPageJsObject();
- $aSysInfo = BxDolModuleQuery::getInstance()->getModuleByName('system');
- // The update switches mirror the Settings > General options.
- $aToggles = [
- ['name' => 'sys_autoupdate', 'title' => _t('_adm_dbd_txt_ver_auto'), 'icon' => 'refresh-cw', 'on' => getParam('sys_autoupdate') == 'on'],
- ['name' => 'sys_upgrade_channel', 'title' => _t('_adm_dbd_txt_ver_beta'), 'icon' => 'flask-conical', 'on' => getParam('sys_upgrade_channel') == 'beta'],
- ['name' => 'sys_autoupdate_force_modified_files', 'title' => _t('_adm_dbd_txt_ver_force'), 'icon' => 'zap', 'on' => getParam('sys_autoupdate_force_modified_files') == 'on'],
- ];
- $aTmplVarsToggles = [];
- foreach($aToggles as $aToggle)
- $aTmplVarsToggles[] = [
- 'js_object' => $sJsObject,
- 'name' => $aToggle['name'],
- 'title' => $aToggle['title'],
- 'icon' => $this->getChartIcon($aToggle['icon'])['svg'],
- 'state' => $aToggle['on'] ? 'on' : 'off',
- 'checked' => $aToggle['on'] ? 'true' : 'false',
- 'checked_attr' => $aToggle['on'] ? 'checked' : ''
- ];
- // when the running version was applied: the last update, or the installation itself when it was never updated
- $iApplied = $aSysInfo['updated'] > 0 ? $aSysInfo['updated'] : $aSysInfo['date'];
- // the last status row: whether the site can receive automatic updates (see isAutoUpdateAccess)
- $bAccess = $this->isAutoUpdateAccess();
- $sContent = BxDolStudioTemplate::getInstance()->parseHtmlByName('dbd_versions.html', array(
- 'js_object' => $sJsObject,
- 'domain' => getParam('site_title'),
- 'version' => bx_get_ver(),
- 'version_time' => _t('_adm_dbd_txt_ver_applied', bx_time_js($iApplied, BX_FORMAT_DATE_TIME, true)),
- 'bx_repeat:toggles' => $aTmplVarsToggles,
- 'installed' => bx_time_js($aSysInfo['date'], BX_FORMAT_DATE, true),
- 'updated' => bx_time_js($iApplied, BX_FORMAT_DATE, true),
- 'icon_installed' => $this->getChartIcon('package-check')['svg'],
- 'icon_updated' => $this->getChartIcon('history')['svg'],
- 'icon_stable' => $this->getChartIcon('cloud-download')['svg'],
- 'icon_beta' => $this->getChartIcon('flask-conical')['svg'],
- 'icon_apps' => $this->getChartIcon('layout-grid')['svg'],
- 'apps' => $this->getAppsCountText(),
- 'checking' => _t('_adm_dbd_txt_ver_checking'),
- 'icon_access' => $this->getChartIcon('badge-check')['svg'],
- 'access_class' => $bAccess ? ' bx-dbd-htools-item-ok' : '', // the same bx_if twice in one row would be cut as one span by the template parser
- 'bx_if:access_active' => ['condition' => $bAccess, 'content' => []],
- 'bx_if:access_inactive' => ['condition' => !$bAccess, 'content' => [
- 'access_url' => 'https://unacms.com/start' // a bx_if block only sees its own content
- ]]
- ));
- // The Update button sits in the block caption; dashboard.js shows it once the check finds an upgrade for the site's channel.
- return array(
- 'content' => $sContent,
- 'menu' => '<button type="button" class="bx-btn bx-btn-primary bx-dbd-version-update" onclick="' . $sJsObject . '.performUpgrade()" style="display:none">' . _t('_adm_dbd_btn_update') . '</button>'
- );
- }
- /**
- * The system module's storage as Space rows per engine scope: the Storage app's files and images (an image's resized copies count
- * towards its size, not its file count), each linking to its tab of the Storage app, then everything else the system keeps (account
- * pictures, comment and editor images, wiki files, queues) as "System Media", linking to the Storage app itself. The system module has no icon of its own, and
- * resolving one would register a template location named 'system' and break page rendering, so these rows use Lucide glyphs.
- * @return array ['local' => rows, 'remote' => rows with objects on a remote engine]
- */
- protected function getSystemStorageItems()
- {
- $aGroups = [
- 'files' => ['objects' => ['sys_files'], 'counted' => ['sys_files'], 'label' => _t('_adm_dbd_txt_su_files'), 'icon' => 'file', 'url' => BX_DOL_URL_STUDIO . 'storages.php?page=files'],
- 'images' => ['objects' => ['sys_images', 'sys_images_resized'], 'counted' => ['sys_images'], 'label' => _t('_adm_dbd_txt_su_images'), 'icon' => 'image', 'url' => BX_DOL_URL_STUDIO . 'storages.php?page=images'],
- 'media' => ['objects' => [], 'counted' => [], 'label' => _t('_adm_dbd_txt_su_system_media'), 'icon' => 'hard-drive', 'url' => BX_DOL_URL_STUDIO . 'storages.php'],
- ];
- $aTotals = [];
- foreach($aGroups as $sGroup => $aGroup)
- $aTotals[$sGroup] = ['local' => ['size' => 0, 'files' => 0, 'objects' => 0], 'remote' => ['size' => 0, 'files' => 0, 'objects' => 0]];
- foreach($this->oDb->getStorageObjects('sys') as $aObject) {
- $sGroup = 'media';
- foreach($aGroups as $sName => $aGroup)
- if(in_array($aObject['object'], $aGroup['objects'])) {
- $sGroup = $sName;
- break;
- }
- $sScope = $aObject['engine'] == 'Local' ? 'local' : 'remote';
- $aTotals[$sGroup][$sScope]['size'] += (int)$aObject['current_size'];
- $aTotals[$sGroup][$sScope]['objects']++;
- if(in_array($aObject['object'], $aGroups[$sGroup]['counted']))
- $aTotals[$sGroup][$sScope]['files'] += (int)$aObject['current_number'];
- }
- $aResult = ['local' => [], 'remote' => []];
- foreach($aGroups as $sGroup => $aGroup)
- foreach(['local', 'remote'] as $sScope) {
- $aTotal = $aTotals[$sGroup][$sScope];
- if($sScope == 'remote' && $aTotal['objects'] == 0)
- continue;
- $aResult[$sScope][] = [
- 'label' => $aGroup['label'],
- 'value' => $aTotal['size'],
- 'icon' => $this->getChartIcon($aGroup['icon']),
- 'url' => $aGroup['url'],
- 'note' => !empty($aGroup['counted']) ? _t($aTotal['files'] == 1 ? '_adm_dbd_txt_su_file_count_1' : '_adm_dbd_txt_su_files_count', $aTotal['files']) : ''
- ];
- }
- return $aResult;
- }
- /**
- * Users: a chart in the hero and the rows under it as its legend. The period tabs in the block caption (dbd_users_tabs.html, Total first
- * and selected) pick the time axis: the last 7 days, 30 days or year as a bar per day or month, or Total as the running total over all
- * time (a point per month, or per year once that spans more than two years). The rows pick the series: Accounts (added; first and selected),
- * Active users (last seen), a row per profile type (its profiles added, from the app's content table) and a row per permission level a
- * profile can hold (its current members by when they joined it; the pseudo levels are not rows), each showing its figure for the
- * chosen period; the hero shows the charted one's figure and title on the left and, on the right, how many of it are online now (a session
- * within the online window: every online account is an active user, a profile type's are those whose account is online). dashboard.js
- * (initUsersChart) draws from the data of every series over every axis, which all come with the block.
- */
- public function serviceGetBlockUsers()
- {
- $sJsObject = $this->getPageJsObject();
- $iNow = time();
- // the axes: all time first (the default tab), a point per month since the first account, per year once that spans more than two years
- $iFirst = $this->oDb->getAccountsFirstAdded();
- $iYears = $iFirst > 0 ? (int)date('Y', $iNow) - (int)date('Y', $iFirst) + 1 : 1;
- $iMonths = $iFirst > 0 ? ($iYears - 1) * 12 + (int)date('n', $iNow) - (int)date('n', $iFirst) + 1 : 1;
- $aAxes = [
- 'all' => $iMonths > 24 ? ['year', $iYears, 'Y', 'Y'] : ['month', max($iMonths, 1), 'M', 'M Y'],
- '7d' => ['day', 7, 'D', 'j M Y'],
- '30d' => ['day', 30, 'j', 'j M Y'],
- '1y' => ['month', 12, 'M', 'M Y'],
- ];
- $aPeriods = $aTmplVarsPeriods = [];
- foreach($aAxes as $sPeriod => $aAxis) {
- $aPeriods[$sPeriod] = [
- 'title' => _t('_adm_dbd_txt_us_' . $sPeriod),
- 'label' => _t('_adm_dbd_txt_us_' . $sPeriod . '_label'),
- 'type' => $sPeriod == 'all' ? 'line' : 'bars' // the Total tab charts the running total of the buckets instead of the buckets
- ];
- $aTmplVarsPeriods[] = [
- 'js_object' => $sJsObject,
- 'name' => $sPeriod,
- 'title' => $aPeriods[$sPeriod]['title'],
- 'selected' => $sPeriod == 'all' ? 'true' : 'false',
- 'tabindex' => $sPeriod == 'all' ? '0' : '-1'
- ];
- }
- // the series and where each is counted from
- $oDb = $this->oDb;
- $iOnline = $this->oDb->getAccountsOnlineCount();
- $aSeries = [
- 'accounts' => ['title' => _t('_adm_dbd_txt_us_accounts'), 'online' => $iOnline],
- 'active' => ['title' => _t('_adm_dbd_txt_us_active'), 'online' => $iOnline],
- ];
- $aSources = [
- 'accounts' => function($sFormat, $iSince) use($oDb) { return $oDb->getAccountsAddedBy($sFormat, $iSince); },
- 'active' => function($sFormat, $iSince) use($oDb) { return $oDb->getAccountsSeenBy($sFormat, $iSince); },
- ];
- // one row per enabled profile app (the modules flagged with the profile subtype), at zero too, with its pending profiles as a note;
- // its series counts the app's content table by its added field, so an app without one gets a row that charts nothing
- $aCounts = [];
- foreach($this->oDb->getProfilesByType() as $aRow)
- $aCounts[$aRow['type']] = $aRow;
- $aTmplVarsProfiles = [];
- foreach(BxDolModuleQuery::getInstance()->getModulesBy(['type' => 'modules_subtypes', 'value' => BX_DOL_MODULE_SUBTYPE_PROFILE]) as $aModule) {
- if(empty($aModule['enabled']))
- continue;
- $sSeries = '';
- $oModule = BxDolModule::getInstance($aModule['name']);
- $aCnf = $oModule && !empty($oModule->_oConfig->CNF) ? $oModule->_oConfig->CNF : [];
- if(!empty($aCnf['TABLE_ENTRIES']) && !empty($aCnf['FIELD_ADDED'])) {
- $sSeries = 'profiles_' . $aModule['name'];
- $sTable = $aCnf['TABLE_ENTRIES'];
- $sField = $aCnf['FIELD_ADDED'];
- $aSeries[$sSeries] = ['title' => $aModule['title'], 'online' => $this->oDb->getProfilesOnlineCount($aModule['name'])];
- $aSources[$sSeries] = function($sFormat, $iSince) use($oDb, $sTable, $sField) { return $oDb->getEntriesAddedBy($sTable, $sField, $sFormat, $iSince); };
- }
- $aRow = $aCounts[$aModule['name']] ?? ['total' => 0, 'active' => 0];
- $iPending = (int)$aRow['total'] - (int)$aRow['active'];
- $aTmplVarsProfiles[] = [
- 'js_object' => $sJsObject,
- 'series' => $sSeries,
- 'icon_url' => BxDolStudioUtils::getModuleIcon($aModule),
- 'name' => $aModule['title'],
- 'bx_if:show_note' => ['condition' => $iPending > 0, 'content' => ['note' => _t('_adm_dbd_txt_us_pending', $iPending)]],
- 'value' => number_format((int)$aRow['total'])
- ];
- }
- // one row per permission level a profile can hold (BxDolStudioDashboardQuery::getLevels), with its current members; its series is when
- // they joined it, and its icon the level's own (a Lucide name), the shield when that is missing
- $aLevelCounts = $this->oDb->getLevelsMembersCount();
- $aTmplVarsLevels = [];
- foreach($this->oDb->getLevels() as $aLevel) {
- $iLevel = (int)$aLevel['ID'];
- $sSeries = 'level_' . $iLevel;
- $sTitle = _t($aLevel['Name']);
- $aSeries[$sSeries] = ['title' => $sTitle, 'online' => $this->oDb->getLevelOnlineCount($iLevel)];
- $aSources[$sSeries] = function($sFormat, $iSince) use($oDb, $iLevel) { return $oDb->getLevelMembersSinceBy($iLevel, $sFormat, $iSince); };
- $sIcon = !empty($aLevel['Icon']) ? $this->getChartIcon($aLevel['Icon'])['svg'] : '';
- $aTmplVarsLevels[] = [
- 'js_object' => $sJsObject,
- 'series' => $sSeries,
- 'icon' => $sIcon ?: $this->getChartIcon('shield')['svg'],
- 'name' => $sTitle,
- 'value' => number_format((int)($aLevelCounts[$iLevel] ?? 0))
- ];
- }
- // every series over every axis; the Total axis carries the running total
- foreach($aSeries as $sSeries => &$aOne) {
- $aOne['periods'] = [];
- foreach($aAxes as $sPeriod => $aAxis) {
- $aItems = $this->getUsersChartBuckets($aAxis[0], $aAxis[1], $aAxis[2], $aAxis[3], $aSources[$sSeries]);
- if($sPeriod == 'all') {
- $iRunning = 0;
- foreach($aItems as &$aItem) {
- $iRunning += $aItem['count'];
- $aItem['total'] = $iRunning;
- }
- unset($aItem);
- }
- $aOne['periods'][$sPeriod] = $aItems;
- }
- }
- unset($aOne);
- $sContent = BxDolStudioTemplate::getInstance()->parseHtmlByName('dbd_users.html', [
- 'js_object' => $sJsObject,
- 'icon_accounts' => $this->getChartIcon('at-sign')['svg'],
- 'icon_active' => $this->getChartIcon('users')['svg'],
- 'accounts' => number_format($this->oDb->getAccountsCount()),
- 'active' => number_format(array_sum(array_column($aSeries['active']['periods']['all'], 'count'))), // dashboard.js keeps every row's figure on the chosen period
- 'bx_if:show_profiles' => ['condition' => !empty($aTmplVarsProfiles), 'content' => ['bx_repeat:profiles' => $aTmplVarsProfiles]],
- 'bx_if:show_levels' => ['condition' => !empty($aTmplVarsLevels), 'content' => ['bx_repeat:levels' => $aTmplVarsLevels]],
- 'chart_data' => json_encode([
- 'period' => 'all',
- 'series_default' => 'accounts',
- 'periods' => $aPeriods,
- 'series' => $aSeries,
- 'txt_peak' => _t('_adm_dbd_txt_us_peak', '{0}') // the chart's accessible name names the peak bucket's count
- ])
- ]);
- // the period switcher sits in the block caption, like the Server block's tabs
- return [
- 'content' => $sContent,
- 'menu' => BxDolStudioTemplate::getInstance()->parseHtmlByName('dbd_users_tabs.html', ['js_object' => $sJsObject, 'bx_repeat:periods' => $aTmplVarsPeriods])
- ];
- }
- /**
- * A series' buckets over an axis: the last $iCount days, months or years up to now, each with the short label under its bar, its full
- * name for the tooltip and what $fCounts (a MySQL date format and the axis start, giving bucket => count) counted in it.
- */
- protected function getUsersChartBuckets($sUnit, $iCount, $sLabel, $sTitle, $fCounts)
- {
- $aFormats = ['day' => ['%Y-%m-%d', 'Y-m-d'], 'month' => ['%Y-%m', 'Y-m'], 'year' => ['%Y', 'Y']];
- list($sSqlFormat, $sPhpFormat) = $aFormats[$sUnit];
- $iNow = time();
- $aBuckets = [];
- for($i = $iCount - 1; $i >= 0; $i--) {
- switch($sUnit) {
- case 'month':
- $iTime = strtotime(date('Y-m-01', $iNow) . ' -' . $i . ' months');
- break;
- case 'year':
- $iTime = mktime(0, 0, 0, 1, 1, (int)date('Y', $iNow) - $i);
- break;
- default:
- $iTime = strtotime(date('Y-m-d', $iNow) . ' -' . $i . ' days');
- }
- $aBuckets[date($sPhpFormat, $iTime)] = ['label' => date($sLabel, $iTime), 'title' => date($sTitle, $iTime), 'count' => 0];
- }
- $sFirst = array_key_first($aBuckets);
- $iSince = $sUnit == 'day' ? strtotime($sFirst . ' 00:00:00') : ($sUnit == 'month' ? strtotime($sFirst . '-01 00:00:00') : mktime(0, 0, 0, 1, 1, (int)$sFirst));
- foreach($fCounts($sSqlFormat, $iSince) as $sBucket => $iAdded)
- if(isset($aBuckets[$sBucket]))
- $aBuckets[$sBucket]['count'] = (int)$iAdded;
- return array_values($aBuckets);
- }
- public function serviceGetBlockSpace($bDynamic = false)
- {
- $bInclideScriptSpace = false; //Use dynamic loading by default if this setting is enabled.
- $sJsObject = $this->getPageJsObject();
- $aChartData = array();
- if(!$bDynamic) {
- // Two charts and two lists: what lives on this server (the database, the Storage app's files and images, the rest of the system's
- // media, then every installed module's local storage, at zero too) and what lives on remote storage (whichever of those have
- // objects on a remote engine); when no remote storage is in use the second chart and list are a "not connected" placeholder.
- // Rows link to the page that manages them; the Storage app's rows also count their files.
- $aLocal = array(
- array('label' => _t('_adm_dbd_txt_su_database'), 'value' => $this->getDbSize(), 'icon' => $this->getChartIcon('database')),
- );
- $aRemote = array();
- if($bInclideScriptSpace) {
- $iSizeDiskTotal = $this->getFolderSize(BX_DIRECTORY_PATH_ROOT);
- $iSizeDiskMedia = $this->getFolderSize(BX_DIRECTORY_STORAGE);
-
- $aLocal[] = array('label' => _t('_adm_dbd_txt_su_system'), 'value' => $iSizeDiskTotal - $iSizeDiskMedia, 'icon' => $this->getChartIcon('hard-drive'));
- }
- $aSystem = $this->getSystemStorageItems();
- $aLocal = array_merge($aLocal, $aSystem['local']);
- $aRemote = array_merge($aRemote, $aSystem['remote']);
- $aModules = BxDolModuleQuery::getInstance()->getModulesBy(array('type' => 'all'));
- foreach($aModules as $aModule) {
- if($aModule['name'] == 'system')
- continue;
- $aIcon = $this->getChartIcon(BxDolStudioUtils::getModuleIcon($aModule), true);
- $sUrl = BX_DOL_URL_STUDIO . 'module.php?name=' . $aModule['name'];
- $aSizes = $this->oDb->getModuleStorageSizes($aModule['name']);
- $aLocal[] = array('label' => $aModule['title'], 'value' => $aSizes['local']['size'], 'icon' => $aIcon, 'url' => $sUrl);
- if($aSizes['remote']['objects'] > 0)
- $aRemote[] = array('label' => $aModule['title'], 'value' => $aSizes['remote']['size'], 'icon' => $aIcon, 'url' => $sUrl);
- }
- $fDiskTotal = @disk_total_space(BX_DIRECTORY_PATH_ROOT);
- $aChartLocal = $this->getChartData($this->getChartItems($aLocal), array_sum(array_column($aLocal, 'value')), $fDiskTotal !== false ? (float)$fDiskTotal : 0);
- $aChartLocal['label'] = bx_process_output(_t('_adm_dbd_txt_su_local'));
- $sRemote = bx_process_output(_t('_adm_dbd_txt_su_remote'));
- if($this->oDb->isRemoteStorageUsed()) {
- $aChartRemote = $this->getChartData($this->getChartItems($aRemote), array_sum(array_column($aRemote, 'value')));
- $aChartRemote['label'] = $sRemote;
- $aRowsRemote = $aChartRemote['items'];
- }
- else {
- $sOff = bx_process_output(_t('_adm_dbd_txt_su_remote_off'));
- $aChartRemote = array('label' => $sRemote, 'items' => array(), 'used' => $sOff, 'total' => '', 'used_bytes' => 0, 'total_bytes' => 0);
- $aRowsRemote = array(array('name' => $sRemote, 'bytes' => 0, 'size' => $sOff, 'icon' => $this->getChartIcon('cloud-off'), 'placeholder' => true));
- }
- $aChartData = array(
- 'charts' => array($aChartLocal, $aChartRemote),
- 'groups' => array(array('items' => $aChartLocal['items']), array('items' => $aRowsRemote)),
- );
- }
- $sContent = BxDolStudioTemplate::getInstance()->parseHtmlByName('dbd_space.html', array(
- 'bx_if:show_content' => array(
- 'condition' => !$bDynamic,
- 'content' => array(
- 'js_object' => $sJsObject,
- 'chart_data' => $this->getChartJson($aChartData)
- )
- ),
- 'bx_if:show_loader' => array(
- 'condition' => $bDynamic,
- 'content' => array(
- 'js_object' => $sJsObject,
- )
- )
- ));
- return array('content' => $sContent);
- }
- public function serviceGetBlockHostTools($bDynamic = false)
- {
- $sJsObject = $this->getPageJsObject();
- $oTemplate = BxDolStudioTemplate::getInstance();
- // the links in the audit report (?action=audit_send_test_email / phpinfo) come back to this page: answered before anything is rendered
- (new BxDolStudioToolsAudit())->processRequest();
- $aTmplVarsGauges = $aTmplVarsItems = [];
- if($bDynamic) {
- // Overview gauges: a donut per metric, its arc a share of the ring's circumference (2 * pi * 40); the ring shows the
- // metric's figure with its unit, when it has one, small underneath.
- $fRing = 251.327;
- foreach($this->getServerMetrics() as $sMetric => $aMetric) {
- $sNa = _t('_adm_dbd_txt_ht_na');
- $aTmplVarsGauges[] = [
- 'name' => $sMetric,
- 'class_add' => $aMetric !== null && $aMetric['percent'] >= 90 ? ' bx-dbd-gauge-high' : '',
- 'title' => _t('_adm_dbd_txt_ht_' . $sMetric),
- 'value' => $aMetric !== null ? $aMetric['value'] : $sNa,
- 'label' => $aMetric !== null ? trim($aMetric['value'] . ' ' . $aMetric['unit']) . ($aMetric['percent'] >= 90 ? ', ' . _t('_adm_dbd_txt_ht_high') : '') : $sNa, // the red arc alone would not tell a screen reader
- 'bx_if:show_unit' => [
- 'condition' => $aMetric !== null && $aMetric['unit'] !== '',
- 'content' => ['unit' => $aMetric !== null ? $aMetric['unit'] : '']
- ],
- 'dash' => $aMetric !== null ? round($aMetric['percent'] / 100 * $fRing, 2) : 0,
- 'ring' => $fRing
- ];
- }
- // Status rows: a row per section of the last audit with its headline (a version) or its outcome, and the issue count when it has issues.
- foreach($this->getAuditReport()['sections'] as $aSection) {
- $aSummary = $this->getAuditSectionSummary($aSection);
- $aTmplVarsItems[] = [
- 'status' => $aSummary['status'],
- 'status_title' => _t('_sys_audit_title_' . $aSummary['status']), // read out in place of the dot's colour
- 'icon' => $this->getChartIcon($aSection['icon'])['svg'],
- 'title' => $aSection['title'],
- 'value' => $aSummary['value'],
- 'bx_if:show_desc' => [
- 'condition' => $aSummary['desc'] !== '',
- 'content' => ['desc' => $aSummary['desc']]
- ]
- ];
- }
- }
- $sContent = $oTemplate->parseHtmlByName('dbd_htools.html', [
- 'js_object' => $sJsObject,
- 'bx_if:show_content' => [
- 'condition' => $bDynamic,
- 'content' => [
- 'bx_repeat:gauges' => $aTmplVarsGauges,
- 'bx_repeat:items' => $aTmplVarsItems,
- ]
- ],
- 'bx_if:show_loader' => [
- 'condition' => !$bDynamic,
- 'content' => [
- 'js_object' => $sJsObject,
- ]
- ]
- ]);
- if($bDynamic)
- return ['content' => $sContent];
- // The tab switcher sits in the block caption; the panels it drives arrive with the Overview.
- return [
- 'content' => $sContent,
- 'menu' => $oTemplate->parseHtmlByName('dbd_htools_tabs.html', ['js_object' => $sJsObject])
- ];
- }
- /**
- * Server block, Audit tab: a hero with when the last audit ran, a bar of its outcomes and the button to run a new one,
- * then the sections as an accordion of status rows, and the note on what needs a person.
- */
- public function getBlockHostToolsAudit()
- {
- $oAudit = new BxDolStudioToolsAudit();
- $aReport = $this->getAuditReport();
- $sJsObject = $this->getPageJsObject();
- $sChevron = $this->getChartIcon('chevron-down')['svg'];
- $aTotals = ['ok' => 0, 'warn' => 0, 'fail' => 0, 'undef' => 0];
- $aTmplVarsSections = [];
- foreach($aReport['sections'] as $aSection) {
- $aSummary = $this->getAuditSectionSummary($aSection);
- foreach($aSummary['counts'] as $sType => $iCount)
- $aTotals[$sType] += $iCount;
- $aTmplVarsRows = [];
- foreach($aSection['rows'] as $aRow)
- $aTmplVarsRows[] = $this->getHostToolsRowVars($aRow, $oAudit);
- $aTmplVarsSections[] = [
- 'js_object' => $sJsObject,
- 'name' => $aSection['name'],
- 'status' => $aSummary['status'],
- 'icon' => $this->getChartIcon($aSection['icon'])['svg'],
- 'icon_chevron' => $sChevron,
- 'title' => $aSection['title'],
- 'value' => $aSummary['value'],
- 'bx_repeat:rows' => $aTmplVarsRows
- ];
- }
- // The bar: a segment per outcome, passed first; the same counts are written out under the button.
- $iTotal = max(1, array_sum($aTotals));
- $aTmplVarsSegments = $aSummary = [];
- foreach(['ok' => '_adm_dbd_txt_ht_passed', 'warn' => '_adm_dbd_txt_ht_warnings', 'fail' => '_adm_dbd_txt_ht_failed', 'undef' => '_adm_dbd_txt_ht_unknown'] as $sType => $sKey) {
- if($aTotals[$sType] == 0)
- continue;
- $sLabel = _t($sType == 'warn' && $aTotals[$sType] == 1 ? '_adm_dbd_txt_ht_warning' : $sKey, $aTotals[$sType]);
- $aTmplVarsSegments[] = ['type' => $sType, 'share' => round($aTotals[$sType] / $iTotal * 100, 2), 'label' => bx_html_attribute($sLabel)];
- $aSummary[] = $sLabel;
- }
- ob_start();
- $oAudit->generateJs();
- $sJs = ob_get_clean();
- return $sJs . BxDolStudioTemplate::getInstance()->parseHtmlByName('dbd_htools_audit.html', [
- 'js_object' => $sJsObject,
- 'time' => bx_time_js($aReport['time'], BX_FORMAT_DATE_TIME),
- 'bar_label' => bx_html_attribute(implode(', ', $aSummary)),
- 'bx_repeat:segments' => $aTmplVarsSegments,
- 'summary' => implode(' · ', $aSummary),
- 'bx_repeat:sections' => $aTmplVarsSections
- ]);
- }
- /**
- * A section's outcome for the Status tab and the audit accordion: its worst row status (unknown rows only count when
- * there is nothing else), its headline (a version) or its outcome as the value, and, under a headline that stands for
- * several rows, the issue count as the note.
- * @return array status ('ok', 'warn', 'fail', 'undef'), value, desc, counts (rows per status)
- */
- protected function getAuditSectionSummary($aSection)
- {
- $aCounts = ['ok' => 0, 'warn' => 0, 'fail' => 0, 'undef' => 0];
- foreach($aSection['rows'] as $aRow)
- if(isset($aCounts[$aRow['type']]))
- $aCounts[$aRow['type']]++;
- $sStatus = 'undef';
- foreach(['fail', 'warn', 'ok'] as $sType)
- if($aCounts[$sType] > 0) {
- $sStatus = $sType;
- break;
- }
- $aIssues = [];
- if($aCounts['fail'] > 0)
- $aIssues[] = _t('_adm_dbd_txt_ht_failed', $aCounts['fail']);
- if($aCounts['warn'] > 0)
- $aIssues[] = _t($aCounts['warn'] == 1 ? '_adm_dbd_txt_ht_warning' : '_adm_dbd_txt_ht_warnings', $aCounts['warn']);
- $sIssues = implode(', ', $aIssues);
- $bHeadline = $aSection['value'] !== '';
- return [
- 'status' => $sStatus,
- 'value' => $bHeadline ? $aSection['value'] : ($sIssues !== '' ? $sIssues : _t($sStatus == 'ok' ? '_sys_audit_title_ok' : '_sys_audit_title_undef')),
- 'desc' => $bHeadline && count($aSection['rows']) > 1 ? $sIssues : '',
- 'counts' => $aCounts
- ];
- }
- /**
- * One audit row as status-row template variables; a row without a value shows its status word instead.
- */
- protected function getHostToolsRowVars($aRow, $oAudit)
- {
- return [
- 'status' => $aRow['type'],
- 'title' => $aRow['name'],
- 'value' => $aRow['value'] !== '' ? $aRow['value'] : $oAudit->typeToTitle($aRow['type']),
- 'bx_if:show_desc' => [
- 'condition' => $aRow['msg'] !== '',
- 'content' => ['desc' => $aRow['msg']]
- ]
- ];
- }
- public function serviceGetBlockCache()
- {
- $sJsObject = $this->getPageJsObject();
- $sChartData = $this->getCacheChartData();
- $bChartData = $sChartData !== false;
- $sContent = BxDolStudioTemplate::getInstance()->parseHtmlByName('dbd_cache.html', [
- 'bx_if:show_chart' => [
- 'condition' => $bChartData,
- 'content' => [
- 'js_object' => $sJsObject,
- 'chart_data' => $sChartData,
- ]
- ],
- 'bx_if:show_empty' => [
- 'condition' => !$bChartData,
- 'content' => [
- 'message' => MsgBox(_t('_adm_dbd_msg_c_all_disabled'))
- ]
- ],
- ]);
- return ['content' => $sContent];
- }
- public function serviceGetBlockQueues()
- {
- $sChartData = $this->getQueuesChartData();
- if($sChartData === false)
- return ['content' => ''];
- // "Clear all queues" is the hero's button (chart data), each row that can be emptied carries its own clear button.
- return ['content' => BxDolStudioTemplate::getInstance()->parseHtmlByName('dbd_queues.html', [
- 'js_object' => $this->getPageJsObject(),
- 'chart_data' => $sChartData,
- ])];
- }
- /**
- * @return array [latest version on the channel or '' when the check failed, whether it is newer than the installed one, whether an upgrade patch can be run]
- */
- private function getVersionChannelInfo($sChannel)
- {
- $oUpgrader = bx_instance('BxDolUpgrader');
- $aUpdateInfo = $oUpgrader->getVersionUpdateInfo($sChannel);
- if(!is_array($aUpdateInfo) || empty($aUpdateInfo['latest_version']))
- return ['', false, false];
- return [$aUpdateInfo['latest_version'], $oUpgrader->isNewVersionAvailable($aUpdateInfo), $oUpgrader->isUpgradeAvailable($aUpdateInfo)];
- }
- }
- /** @} */
|