diff --git a/config/config.php b/config/config.php index 490f2a4f09..389c83efe1 100644 --- a/config/config.php +++ b/config/config.php @@ -163,6 +163,29 @@ $config->duplicateTime = 30; $config->maxCount = 500; $config->moreLinks = array(); +/* 渠成平台设置。CNE Api settings. */ +$config->demoAccounts = ''; // 用于演示的账号列表,该账号安装的应用30钟后会自动删除。 In account list for demo, app instance of demo will be removed in 30 minutes. +$config->demoAppLife = 30; // Demo安装的应用实例存续时长(分钟)。The minutes life of instance which demo account installed. +$config->CNE = new stdclass(); +$config->CNE->api = new stdclass(); +$config->CNE->api->host = ''; +$config->CNE->api->auth = 'X-Auth-Token'; +$config->CNE->api->token = 'reiquai7pei7thei4iePe5ait3Kaiquu'; // Please set token in my.php. +$config->CNE->api->headers = array('Content-Type: application/json'); +$config->CNE->api->channel = 'stable'; + +$config->CNE->app = new stdclass; +$config->CNE->app->domain = 'dev.haogs.cn'; + +$config->cloud = new stdclass; +$config->cloud->api = new stdclass; +$config->cloud->api->host = 'https://api.qucheng.com'; +$config->cloud->api->auth = 'X-Auth-Token'; +$config->cloud->api->token = 'gwaN4KynqNqQoPD7eN8s'; // Please set token in my.php. +$config->cloud->api->headers = array('Content-Type: application/json'); +$config->cloud->api->channel = 'stable'; +$config->cloud->api->switchChannel = false; + /* 配置参数过滤。Filter param settings. */ $filterConfig = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'filter.php'; if(file_exists($filterConfig)) include $filterConfig; diff --git a/db/update18.6.sql b/db/update18.6.sql new file mode 100644 index 0000000000..11f043e699 --- /dev/null +++ b/db/update18.6.sql @@ -0,0 +1,73 @@ +-- DROP TABLE IF EXISTS `zt_space`; +CREATE TABLE `zt_space` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(200) NOT NULL, + `k8space` char(64) NOT NULL, + `owner` char(30) NOT NULL, + `default` tinyint(1) NOT NULL DEFAULT 0, + `createdAt` datetime NOT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `zt_instance`; +CREATE TABLE IF NOT EXISTS `zt_instance` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `space` mediumint(8) unsigned NOT NULL DEFAULT 0, + `solution` mediumint(8) unsigned NOT NULL DEFAULT 0, + `name` char(50) DEFAULT '', + `appID` mediumint(8) unsigned NOT NULL DEFAULT 0, + `appName` char(50) NOT NULL DEFAULT '', + `appVersion` char(20) NOT NULL DEFAULT '', + `chart` char(50) NOT NULL DEFAULT '', + `logo` varchar(255) DEFAULT '', + `version` char(50) NOT NULL DEFAULT '', + `desc` text, + `introduction` varchar(500) DEFAULT '', + `source` char(20) NOT NULL DEFAULT '', + `channel` char(20) DEFAULT '', + `k8name` char(64) NOT NULL DEFAULT '', + `status` char(20) NOT NULL DEFAULT '', + `pinned` enum('0', '1') NOT NULL DEFAULT '0', + `domain` char(255) NOT NULL DEFAULT '', + `smtpSnippetName` char(30) NULL DEFAULT '', + `ldapSnippetName` char(30) NULL DEFAULT '', + `ldapSettings` text, + `dbSettings` text, + `autoBackup` tinyint(1) NOT NULL DEFAULT 0, + `backupKeepDays` int unsigned NOT NULL DEFAULT 1, + `autoRestore` tinyint(1) NOT NULL DEFAULT 0, + `env` text, + `createdBy` char(30) NOT NULL DEFAULT '', + `createdAt` datetime NOT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + KEY `space` (`space`), + KEY `k8name` (`k8name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- DROP TABLE IF EXISTS `zt_solution`; +CREATE TABLE IF NOT EXISTS `zt_solution` ( + `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT, + `name` char(50), + `appID` mediumint(8) unsigned NOT NULL, + `appName` char(50) NOT NULL, + `appVersion` char(20) NOT NULL, + `version` char(50) NOT NULL, + `chart` char(50) NOT NULL, + `cover` varchar(255), + `desc` text, + `introduction` varchar(500), + `source` char(20) NOT NULL, + `channel` char(20), + `components` text, + `status` char(20) NOT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT 0, + `createdBy` char(30) NOT NULL, + `createdAt` datetime NOT NULL, + `updatedDate` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +REPLACE INTO `zt_space` (`id`, `name`,`k8space`,`owner`,`default`,`createdAt`) VALUES (1, 'default space', 'quickon-app', 'qadmin', 1, current_date()); diff --git a/module/admin/config.php b/module/admin/config.php index 79e8e87446..94961a2238 100755 --- a/module/admin/config.php +++ b/module/admin/config.php @@ -15,6 +15,7 @@ $config->admin->menuGroup['message'] = array('mail', 'webhook', 'sms', 'messag $config->admin->menuGroup['dev'] = array('dev', 'entry', 'editor'); $config->admin->menuGroup['extension'] = array('extension'); $config->admin->menuGroup['convert'] = array('convert'); +$config->admin->menuGroup['platform'] = array('system', 'repo', 'account', 'host', 'serverroom', 'ops', 'tree'); $config->admin->menuModuleGroup['model']['custom|set'] = array('project', 'issue', 'risk', 'opportunity', 'nc'); $config->admin->menuModuleGroup['model']['custom|required'] = array('project', 'build'); diff --git a/module/admin/lang/de.php b/module/admin/lang/de.php index db7f986962..2c4af48235 100755 --- a/module/admin/lang/de.php +++ b/module/admin/lang/de.php @@ -78,6 +78,7 @@ $lang->admin->registerNotice->success = "Sie haben sich erfolgreich registrie $lang->admin->bind = new stdclass(); $lang->admin->bind->caption = 'Konto verknüpfen'; $lang->admin->bind->success = "Konto wurde verknüpft!"; +$lang->admin->bind->submit = "Binden"; $lang->admin->setModule = new stdclass(); $lang->admin->setModule->module = 'Module'; @@ -180,6 +181,8 @@ $lang->admin->menuSetting['dev']['name'] = 'Develop'; $lang->admin->menuSetting['dev']['desc'] = 'Support for secondary development of the system.'; $lang->admin->menuSetting['convert']['name'] = 'Data Import'; $lang->admin->menuSetting['convert']['desc'] = 'Data import from third-party systems.'; +$lang->admin->menuSetting['platform']['name'] = 'DevOps'; +$lang->admin->menuSetting['platform']['desc'] = 'Settings of DevOps elements such as resources and environment.'; $lang->admin->updateDynamics = 'Dynamics'; $lang->admin->updatePatch = 'Patch'; diff --git a/module/admin/lang/en.php b/module/admin/lang/en.php index c9ac499fcf..64e45dc97f 100755 --- a/module/admin/lang/en.php +++ b/module/admin/lang/en.php @@ -78,6 +78,7 @@ $lang->admin->registerNotice->success = "You have signed up!"; $lang->admin->bind = new stdclass(); $lang->admin->bind->caption = 'Link Account'; $lang->admin->bind->success = "Account is linked!"; +$lang->admin->bind->submit = "Bind"; $lang->admin->setModule = new stdclass(); $lang->admin->setModule->module = 'Module'; @@ -180,6 +181,8 @@ $lang->admin->menuSetting['dev']['name'] = 'Develop'; $lang->admin->menuSetting['dev']['desc'] = 'Support for secondary development of the system.'; $lang->admin->menuSetting['convert']['name'] = 'Data Import'; $lang->admin->menuSetting['convert']['desc'] = 'Data import from third-party systems.'; +$lang->admin->menuSetting['platform']['name'] = 'DevOps'; +$lang->admin->menuSetting['platform']['desc'] = 'Settings of DevOps elements such as resources and environment.'; $lang->admin->updateDynamics = 'Dynamics'; $lang->admin->updatePatch = 'Patch'; diff --git a/module/admin/lang/fr.php b/module/admin/lang/fr.php index cdcc0941ae..36e8d65cb0 100755 --- a/module/admin/lang/fr.php +++ b/module/admin/lang/fr.php @@ -78,6 +78,7 @@ $lang->admin->registerNotice->success = "Vous êtes enregistré !"; $lang->admin->bind = new stdclass(); $lang->admin->bind->caption = 'Lier au compte'; $lang->admin->bind->success = "Le compte est associé !"; +$lang->admin->bind->submit = "Lier"; $lang->admin->setModule = new stdclass(); $lang->admin->setModule->module = 'Module'; @@ -180,6 +181,8 @@ $lang->admin->menuSetting['dev']['name'] = 'Develop'; $lang->admin->menuSetting['dev']['desc'] = 'Support for secondary development of the system.'; $lang->admin->menuSetting['convert']['name'] = 'Data Import'; $lang->admin->menuSetting['convert']['desc'] = 'Data import from third-party systems.'; +$lang->admin->menuSetting['platform']['name'] = 'DevOps'; +$lang->admin->menuSetting['platform']['desc'] = 'Settings of DevOps elements such as resources and environment.'; $lang->admin->updateDynamics = 'Dynamics'; $lang->admin->updatePatch = 'Patch'; diff --git a/module/admin/lang/menu.php b/module/admin/lang/menu.php index c181bade36..6c41a5b1d6 100644 --- a/module/admin/lang/menu.php +++ b/module/admin/lang/menu.php @@ -39,13 +39,15 @@ $lang->admin->menuList->dev['name'] = $lang->admin->menuSetting['dev']['name']; $lang->admin->menuList->dev['desc'] = $lang->admin->menuSetting['dev']['desc']; $lang->admin->menuList->dev['order'] = 45; -if($config->db->driver == 'mysql') -{ - $lang->admin->menuList->convert['name'] = $lang->admin->menuSetting['convert']['name']; - $lang->admin->menuList->convert['desc'] = $lang->admin->menuSetting['convert']['desc']; - $lang->admin->menuList->convert['link'] = 'convert|convertjira'; - $lang->admin->menuList->convert['order'] = 50; -} +$lang->admin->menuList->convert['name'] = $lang->admin->menuSetting['convert']['name']; +$lang->admin->menuList->convert['desc'] = $lang->admin->menuSetting['convert']['desc']; +$lang->admin->menuList->convert['link'] = 'convert|convertjira'; +$lang->admin->menuList->convert['order'] = 50; + +$lang->admin->menuList->platform['name'] = $lang->admin->menuSetting['platform']['name']; +$lang->admin->menuList->platform['desc'] = $lang->admin->menuSetting['platform']['desc']; +$lang->admin->menuList->platform['link'] = 'system|browsebackup'; +$lang->admin->menuList->platform['order'] = 55; $lang->admin->menuList->system['subMenu']['mode'] = array('link' => "{$lang->custom->mode}|custom|mode|"); $lang->admin->menuList->system['subMenu']['trash'] = array('link' => "{$lang->action->trash}|action|trash|"); @@ -180,6 +182,46 @@ $lang->admin->menuList->dev['menuOrder']['15'] = 'langItem'; $lang->admin->menuList->dev['menuOrder']['20'] = 'editor'; $lang->admin->menuList->dev['menuOrder']['25'] = 'entry'; +//$lang->admin->menuList->platform['subMenu']['dashboard'] = array('link' => "{$lang->dashboard}|system|dashboard|"); +$lang->admin->menuList->platform['subMenu']['plat'] = array('link' => "{$lang->devops->platform}|system|browsebackup|", 'subModule' => 'system'); +//$lang->admin->menuList->platform['subMenu']['environment'] = array('link' => "{$lang->devops->environment}|gitlab|browse|", 'subModule' => 'gitlab,jenkins,sonarqube,gitea,gogs', 'alias' => 'create,edit,import'); +$lang->admin->menuList->platform['subMenu']['resource'] = array('link' => "{$lang->devops->resource}|host|browse|", 'subModule' => 'host,account,serverroom,ops,tree'); +$lang->admin->menuList->platform['subMenu']['setrules'] = array('link' => "{$lang->devops->rules}|repo|setrules|"); + +$lang->admin->menuList->platform['tabMenu']['plat']['backup'] = array('link' => "{$lang->backup->common}|system|browsebackup|"); +$lang->admin->menuList->platform['tabMenu']['plat']['dblist'] = array('link' => "{$lang->devops->dblist}|system|dblist|"); +$lang->admin->menuList->platform['tabMenu']['plat']['domain'] = array('link' => "{$lang->devops->domain}|system|configdomain|", 'alias' => 'editdomain,domainview'); +$lang->admin->menuList->platform['tabMenu']['plat']['oss'] = array('link' => "{$lang->devops->oss}|system|ossview|"); + +$lang->admin->menuList->platform['tabMenu']['menuOrder']['plat']['5'] = 'backup'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['plat']['10'] = 'dblist'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['plat']['15'] = 'domain'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['plat']['20'] = 'oss'; + +$lang->admin->menuList->platform['tabMenu']['resource']['host'] = array('link' => "{$lang->devops->host}|host|browse|", 'alias' => 'create,edit,view,treemap,changestatus,group', 'subModule' => 'tree'); +$lang->admin->menuList->platform['tabMenu']['resource']['serverroom'] = array('link' => "{$lang->devops->serverroom}|serverroom|browse|", 'alias' => 'create,edit,view'); +$lang->admin->menuList->platform['tabMenu']['resource']['account'] = array('link' => "{$lang->devops->account}|account|browse|", 'alias' => 'create,edit,view'); +$lang->admin->menuList->platform['tabMenu']['resource']['provider'] = array('link' => "{$lang->devops->provider}|ops|provider|", 'alias' => 'provider'); +$lang->admin->menuList->platform['tabMenu']['resource']['city'] = array('link' => "{$lang->devops->city}|ops|city|", 'alias' => 'city'); +$lang->admin->menuList->platform['tabMenu']['resource']['cpuBrand'] = array('link' => "{$lang->devops->cpuBrand}|ops|cpuBrand|", 'alias' => 'cpubrand'); +$lang->admin->menuList->platform['tabMenu']['resource']['os'] = array('link' => "{$lang->devops->os}|ops|os|", 'alias' => 'os'); + +$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['5'] = 'host'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['10'] = 'serverroom'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['15'] = 'account'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['20'] = 'provider'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['25'] = 'city'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['30'] = 'cpuBrand'; +$lang->admin->menuList->platform['tabMenu']['menuOrder']['resource']['35'] = 'os'; + +//$lang->admin->menuList->platform['menuOrder']['5'] = 'dashboard'; +$lang->admin->menuList->platform['menuOrder']['10'] = 'plat'; +//$lang->admin->menuList->platform['menuOrder']['15'] = 'environment'; +$lang->admin->menuList->platform['menuOrder']['20'] = 'resource'; +$lang->admin->menuList->platform['menuOrder']['25'] = 'setrules'; + +$lang->admin->menuList->platform['dividerMenu'] = ',plat,setrules,'; + if($config->edition != 'max') { unset($lang->admin->menuList->model['subMenu']['scrum']); diff --git a/module/admin/lang/vi.php b/module/admin/lang/vi.php index aafc0994bd..a856f16124 100755 --- a/module/admin/lang/vi.php +++ b/module/admin/lang/vi.php @@ -59,6 +59,7 @@ $lang->admin->registerNotice->success = "Bạn đã đăng ký!"; $lang->admin->bind = new stdclass(); $lang->admin->bind->caption = 'Liên kết tài khoản'; $lang->admin->bind->success = "Tài khoản đã được kết nối!"; +$lang->admin->bind->submit = "Trói buộc"; $lang->admin->safe = new stdclass(); $lang->admin->safe->common = 'Chính sách bảo mật'; diff --git a/module/admin/lang/zh-cn.php b/module/admin/lang/zh-cn.php index a1014bb979..fac3b7e559 100755 --- a/module/admin/lang/zh-cn.php +++ b/module/admin/lang/zh-cn.php @@ -78,6 +78,7 @@ $lang->admin->registerNotice->success = "登记账户成功"; $lang->admin->bind = new stdclass(); $lang->admin->bind->caption = '关联社区帐号'; $lang->admin->bind->success = "关联账户成功"; +$lang->admin->bind->submit = "绑定"; $lang->admin->setModule = new stdclass(); $lang->admin->setModule->module = '功能点'; @@ -180,6 +181,8 @@ $lang->admin->menuSetting['dev']['name'] = '二次开发'; $lang->admin->menuSetting['dev']['desc'] = '支持对系统进行二次开发。'; $lang->admin->menuSetting['convert']['name'] = '数据导入'; $lang->admin->menuSetting['convert']['desc'] = '第三方系统的数据导入。'; +$lang->admin->menuSetting['platform']['name'] = 'DevOps设置'; +$lang->admin->menuSetting['platform']['desc'] = '资源、环境等DevOps各要素配置。'; $lang->admin->updateDynamics = '更新动态'; $lang->admin->updatePatch = '补丁更新'; diff --git a/module/admin/lang/zh-tw.php b/module/admin/lang/zh-tw.php index 6f6c404364..a52b191f76 100755 --- a/module/admin/lang/zh-tw.php +++ b/module/admin/lang/zh-tw.php @@ -51,6 +51,7 @@ $lang->admin->registerNotice->success = "登記賬戶成功"; $lang->admin->bind = new stdclass(); $lang->admin->bind->caption = '關聯社區帳號'; $lang->admin->bind->success = "關聯賬戶成功"; +$lang->admin->bind->submit = "關聯"; $lang->admin->safe = new stdclass(); $lang->admin->safe->common = '安全策略'; diff --git a/module/cne/lang/zh-cn.php b/module/cne/lang/zh-cn.php new file mode 100644 index 0000000000..57539e5198 --- /dev/null +++ b/module/cne/lang/zh-cn.php @@ -0,0 +1,28 @@ +CNE->InstallSuccess = '安装成功'; +$lang->CNE->InstallFailure = '安装失败'; +$lang->CNE->serverError = 'CNE服务器出错'; + +$lang->CNE->statusList = array(); +$lang->CNE->statusList['normal'] = '正常'; +$lang->CNE->statusList['abnormal'] = '异常'; +$lang->CNE->statusList['stopped'] = '关闭'; +$lang->CNE->statusList['unknown'] = '无数据'; + +$lang->CNE->statusIcons = array(); +$lang->CNE->statusIcons['normal'] = ""; +$lang->CNE->statusIcons['abnormal'] = ""; +$lang->CNE->statusIcons['stopped'] = ""; +$lang->CNE->statusIcons['unknown'] = ""; + +$lang->CNE->errorList = array(); +//$lang->CNE->errorList[400] = '不能包含特殊字符'; +$lang->CNE->errorList[400] = '请求集群接口失败'; +$lang->CNE->errorList[404] = '服务不存在'; +$lang->CNE->errorList[40004] = '证书与域名不匹配'; +$lang->CNE->errorList[41001] = '证书过期'; +$lang->CNE->errorList[41002] = '证书不匹配'; +$lang->CNE->errorList[41003] = '证书链不完整'; +$lang->CNE->errorList[41004] = '私钥与证书不匹配'; +$lang->CNE->errorList[41005] = '证书解析失败'; +$lang->CNE->errorList[41006] = '密钥解析失败'; diff --git a/module/cne/model.php b/module/cne/model.php new file mode 100644 index 0000000000..0e5a42e876 --- /dev/null +++ b/module/cne/model.php @@ -0,0 +1,1205 @@ + + * @package CNE + * @version $Id$ + * @link https://www.qucheng.com + */ +class cneModel extends model +{ + protected $error; + + /** + * Construct function: set api headers. + * + * @param string $appName + * @access public + * @return void + */ + public function __construct($appName = '') + { + parent::__construct($appName); + + $this->error = new stdclass; + + global $config, $app; + $config->CNE->api->headers[] = "{$config->CNE->api->auth}: {$config->CNE->api->token}"; + $config->cloud->api->headers[] = "{$config->cloud->api->auth}: {$config->cloud->api->token}"; + + if($config->cloud->api->switchChannel && $app->session->cloudChannel) + { + $config->cloud->api->channel = $app->session->cloudChannel; + $config->CNE->api->channel = $app->session->cloudChannel; + } + } + + /** + * Get all instance of app in cluster. + * + * @access public + * @return array + */ + public function instanceList() + { + $apiUrl = "/api/cne/system/app-full-list"; + $result = $this->apiGet($apiUrl, array(), $this->config->CNE->api->headers); + if(empty($result) || $result->code != 200 || empty($result->data)) return array(); + + $instanceList = $result->data; + return array_combine(array_column($instanceList, 'name'), $instanceList); + } + + /** + * Update platform domain. + * + * @param string $domain + * @access public + * @return bool + */ + public function updatePlatformDomain($domain) + { + $instance = new stdclass; + $instance->k8name = 'qucheng'; + $instance->chart = 'qucheng'; + + $instance->spaceData = new stdclass; + $instance->spaceData->k8space = $this->config->k8space; + + $settings = new stdclass; + $settings->settings_map = new stdclass; + $settings->settings_map->env = new stdclass; + $settings->settings_map->env->APP_DOMAIN = $domain; + + return $this->updateConfig($instance, $settings); + } + + /** + * Upgrade platform. + * + * @param string $toVersion + * @access public + * @return bool + */ + public function upgradePlatform($toVersion) + { + $apiUrl = "/api/cne/system/update"; + $result = $this->apiPost($apiUrl, array('version' => $toVersion, 'channel' => $this->config->CNE->api->channel), $this->config->CNE->api->headers); + if($result && $result->code == 200) return true; + + return false; + } + + /** + * Upgrade or degrade platform version. + * + * @param string $version + * @access public + * @return bool + */ + public function setPlatformVersion($version) + { + $instance = new stdclass; + $instance->k8name = 'qucheng'; + $instance->chart = 'qucheng'; + + $instance->spaceData = new stdclass; + $instance->spaceData->k8space = $this->config->k8space; + + return $this->upgradeToVersion($instance, $version); + } + + /** + * Upgrade app instance to version. + * + * @param object $instance + * @param string $toVersion + * @access public + * @return bool + */ + public function upgradeToVersion($instance, $toVersion = '') + { + $setting = array(); + $setting['cluster'] = ''; + $setting['namespace'] = $instance->spaceData->k8space; + $setting['name'] = $instance->k8name; + $setting['channel'] = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + $setting['chart'] = $instance->chart; + $setting['version'] = $toVersion; + + $apiUrl = "/api/cne/app/settings"; + $result = $this->apiPost($apiUrl, $setting, $this->config->CNE->api->headers); + if($result && $result->code == 200) return true; + + return false; + } + + /** + * Update instance config. For example: cpu, memory size, LDAP settings... + * + * @param object $instance + * @param object $settings + * @access public + * @return bool + */ + public function updateConfig($instance, $settings) + { + $apiParams = array(); + $apiParams['cluster'] = ''; + $apiParams['namespace'] = $instance->spaceData->k8space; + $apiParams['name'] = $instance->k8name; + $apiParams['channel'] = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + $apiParams['chart'] = $instance->chart; + + if(isset($instance->version)) $apiParams['version'] = $instance->version; + if(isset($settings->force_restart)) $apiParams['force_restart'] = $settings->force_restart; + if(isset($settings->settings_map)) $apiParams['settings_map'] = $settings->settings_map; + if(isset($settings->settings_snippets)) $apiParams['settings_snippets'] = $settings->settings_snippets; + + $apiUrl = "/api/cne/app/settings"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return true; + + return false; + } + + /** + * Config QLB (Server Load Balancing). + * + * @param object $settings + * @param string $channel + * @access public + * @return bool + */ + public function configQLB($settings, $channel = '') + { + $apiParams = array(); + $apiParams['cluster'] = ''; + $apiParams['channel'] = empty($channel) ? $this->config->CNE->api->channel : $channel; + $apiParams['namespace'] = $settings->namespace; + $apiParams['name'] = $settings->name; + $apiParams['ippool'] = $settings->ippool; + + $apiUrl = "/api/cne/system/qlb/config"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return true; + + return false; + } + + /** + * Get Qucheng Load Balancer Info + * + * @param string $name + * @param string $namespace + * @access public + * @return object + */ + public function getQLBInfo($name, $namespace, $channel = '') + { + $apiParams = array(); + $apiParams['cluster'] = ''; + $apiParams['channel'] = empty($channel) ? $this->config->CNE->api->channel : $channel; + $apiParams['namespace'] = $namespace; + $apiParams['name'] = $name; + + $apiUrl = "/api/cne/system/qlb/config"; + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return $result->data; + + return null; + } + + /** + * Valid Certificate. + * + * @param string $certName + * @param string $pem + * @param string $key + * @param string $domain + * @access public + * @return bool + */ + public function validateCert($certName, $pem, $key, $domain) + { + $apiParams = array(); + $apiParams['name'] = $certName; + $apiParams['certificate_pem'] = $pem; + $apiParams['private_key_pem'] = $key; + + $apiUrl = "/api/cne/system/tls/validator"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code != 200) return $result; + + $match = false; + foreach($result->data->sans as $domainPattern) + { + if(stripos($domainPattern, $domain) !== false) + { + $match = true; + break; + } + } + if(!$match) + { + $result->code = 40004; + $result->message = zget($this->lang->CNE->errorList, 40004); + } + + return $result; + } + + /** + * Upload cert + * + * @param object $cert + * @param string $channel + * @access public + * @return object + */ + public function uploadCert($cert, $channel = '') + { + $apiParams = array(); + $apiParams['cluster'] = ''; + $apiParams['channel'] = empty($channel) ? $this->config->CNE->api->channel : $channel; + $apiParams['name'] = $cert->name; + $apiParams['certificate_pem'] = $cert->certificate_pem; + $apiParams['private_key_pem'] = $cert->private_key_pem; + + $apiUrl = "/api/cne/system/tls/upload"; + // $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + //if($result && $result->code == 200) return $result->data; + + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Upload cert + * + * @param string $certName + * @param string $channel + * @access public + * @return object|null success: return cert info, fail: return null. + */ + public function certInfo($certName, $channel = '') + { + $apiParams = array(); + $apiParams['cluster'] = ''; + $apiParams['channel'] = empty($channel) ? $this->config->CNE->api->channel : $channel; + $apiParams['name'] = $certName; + + $apiUrl = "/api/cne/system/tls/info"; + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return $result->data; + + return null; + } + + /** + * Get default username and password of app. + * + * @param object $instance + * @param string $cluster + * @access public + * @return object|null + */ + public function getDefaultAccount($instance, $cluster = '', $component = '') + { + $apiUrl = '/api/cne/app/account?channel='. (empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel); + $apiParams = array(); + $apiParams['name'] = $instance->k8name; + $apiParams['namespace'] = $instance->spaceData->k8space; + $apiParams['cluster'] = $cluster; + if($component) $apiParams['component'] = $component; + + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers, $this->config->CNE->api->host); + if(!isset($result->code) || $result->code != 200) return null; + + $account = $result->data; + if(isset($account->username) && $account->username && isset($account->password) && $account->password) return $account; + + return null; + } + + /** + * Get suffix domain. + * + * @access public + * @return string + */ + public function sysDomain() + { + $customDomain = $this->loadModel('setting')->getItem('owner=system&module=common§ion=domain&key=customDomain'); + if($customDomain) return $customDomain; + + return getenv('APP_DOMAIN') ? getenv('APP_DOMAIN') : $this->config->CNE->app->domain; + } + + /** + * Get domain. + * + * @param object $instance + * @param string $cluster + * @param string $component + * @access public + * @return object|null + */ + public function getDomain($instance, $cluster = '', $component = '') + { + $apiUrl = '/api/cne/app/domain?channel='. (empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel); + $apiParams = array(); + $apiParams['name'] = $instance->k8name; + $apiParams['namespace'] = $instance->spaceData->k8space; + $apiParams['cluster'] = $cluster; + if($component) $apiParams['component'] = $component; + + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers, $this->config->CNE->api->host); + if(!isset($result->code) || $result->code != 200) return null; + + return $result->data; + } + + /** + * Get cluster metrics of CNE platform. + * + * @param string $cluster + * @access public + * @return object + */ + public function cneMetrics($cluster = '') + { + $metrics = new stdclass; + $metrics->cpu = new stdclass; + $metrics->cpu->usage = 0; + $metrics->cpu->capacity = 0; + $metrics->cpu->allocatable = 0; + $metrics->cpu->rate = 0; + + $metrics->memory = new stdclass; + $metrics->memory->usage = 0; + $metrics->memory->capacity = 0; + $metrics->memory->allocatable = 0; + $metrics->memory->rate = 0; + + $statistics = new stdclass; + $statistics->status = 'unknown'; + $statistics->node_count = 0; + $statistics->metrics = $metrics; + + $apiUrl = "/api/cne/statistics/cluster"; + $result = $this->apiGet($apiUrl, array('cluster' => $cluster), $this->config->CNE->api->headers); + if($result->code != 200) return $statistics; + + $statistics = $result->data; + + $statistics->metrics->cpu->usage = max(round($statistics->metrics->cpu->usage, 4), 0); + $statistics->metrics->cpu->capacity = max(round($statistics->metrics->cpu->capacity, 4), $statistics->metrics->cpu->usage); + $statistics->metrics->cpu->allocatable = round($statistics->metrics->cpu->allocatable, 4); + $statistics->metrics->cpu->rate = $statistics->metrics->cpu->capacity > 0 ? round( $statistics->metrics->cpu->usage / $statistics->metrics->cpu->capacity * 100, 2) : 0; + $statistics->metrics->cpu->rate = min($statistics->metrics->cpu->rate, 100); + + $statistics->metrics->memory->usage = max(round($statistics->metrics->memory->usage, 4), 0); + $statistics->metrics->memory->capacity = max($statistics->metrics->memory->capacity, $statistics->metrics->memory->usage); + $statistics->metrics->memory->rate = $statistics->metrics->memory->capacity > 0 ? round($statistics->metrics->memory->usage / $statistics->metrics->memory->capacity * 100, 2) : 0; + $statistics->metrics->memory->rate = min($statistics->metrics->memory->rate, 100); + return $statistics; + } + + /** + * Get instance metrics. + * + * @param array $instances + * @access public + * @return object + */ + public function instancesMetrics($instances) + { + $instancesMetrics = array(); + + $apiData = array('cluster' => '', 'apps' => array()); + foreach($instances as $instance) + { + if($instance->source == 'external') continue; + + $appData = new stdclass; + $appData->name = $instance->k8name; + $appData->namespace = $instance->spaceData->k8space; + $apiData['apps'][] = $appData; + + $instanceMetric = new stdclass; + $instanceMetric->id = $instance->id; + $instanceMetric->name = $instance->k8name; + $instanceMetric->namespace = $instance->spaceData->k8space; + + $instanceMetric->cpu = new stdclass; + $instanceMetric->cpu->limit = 0; + $instanceMetric->cpu->usage = 0; + $instanceMetric->cpu->rate = 0; + + $instanceMetric->memory = new stdclass; + $instanceMetric->memory->limit = 0; + $instanceMetric->memory->usage = 0; + $instanceMetric->memory->rate = 0; + + $instancesMetrics[$instance->k8name] = $instanceMetric; + } + + $apiUrl = "/api/cne/statistics/app"; + $result = $this->apiPost($apiUrl, $apiData, $this->config->CNE->api->headers); + if(!isset($result->code) || $result->code != 200)return array_combine(array_column($instancesMetrics, 'id'), $instancesMetrics); + + foreach($result->data as $k8sMetric) + { + if(!isset($k8sMetric->metrics)) continue; + + $instancesMetrics[$k8sMetric->name]->cpu->usage = isset($k8sMetric->metrics->cpu) && isset($k8sMetric->metrics->cpu->usage) ? round($k8sMetric->metrics->cpu->usage, 4) : 0; + $instancesMetrics[$k8sMetric->name]->cpu->usage = max($instancesMetrics[$k8sMetric->name]->cpu->usage, 0); + $instancesMetrics[$k8sMetric->name]->cpu->limit = isset($k8sMetric->metrics->cpu) && isset($k8sMetric->metrics->cpu->limit) ? round($k8sMetric->metrics->cpu->limit, 4) : 0; + $instancesMetrics[$k8sMetric->name]->cpu->limit = max($instancesMetrics[$k8sMetric->name]->cpu->limit, $instancesMetrics[$k8sMetric->name]->cpu->usage); + $instancesMetrics[$k8sMetric->name]->cpu->rate = $instancesMetrics[$k8sMetric->name]->cpu->limit > 0 ? round($instancesMetrics[$k8sMetric->name]->cpu->usage / $instancesMetrics[$k8sMetric->name]->cpu->limit * 100, 2) : 0; + + $instancesMetrics[$k8sMetric->name]->memory->usage = isset($k8sMetric->metrics->memory) && isset($k8sMetric->metrics->memory->usage) ? $k8sMetric->metrics->memory->usage : 0; + $instancesMetrics[$k8sMetric->name]->memory->usage = max($instancesMetrics[$k8sMetric->name]->memory->usage, 0); + $instancesMetrics[$k8sMetric->name]->memory->limit = isset($k8sMetric->metrics->memory) && isset($k8sMetric->metrics->memory->limit) ? $k8sMetric->metrics->memory->limit : 0; + $instancesMetrics[$k8sMetric->name]->memory->limit = max( $instancesMetrics[$k8sMetric->name]->memory->limit, $instancesMetrics[$k8sMetric->name]->memory->usage); + $instancesMetrics[$k8sMetric->name]->memory->rate = $instancesMetrics[$k8sMetric->name]->memory->limit > 0 ? round($instancesMetrics[$k8sMetric->name]->memory->usage / $instancesMetrics[$k8sMetric->name]->memory->limit * 100, 2) : 0; + } + + return array_combine(array_column($instancesMetrics, 'id'), $instancesMetrics); + } + + /** + * Print CPU usage. + * + * @param object $metrics + * @static + * @access public + * @return viod + */ + public static function printCpuUsage($metrics) + { + $rate = $metrics->rate; + + $tip = "{$rate}% = {$metrics->usage} / {$metrics->capacity}"; + commonModel::printProgressBar($rate, '', $tip, 'percent'); + } + + /** + * Print memory usage. + * + * @param object $metrics + * @static + * @access public + * @return viod + */ + public static function printMemUsage($metrics) + { + $rate = $metrics->rate; + + $tip = "{$rate}% = " . helper::formatKB($metrics->usage) . ' / ' . helper::formatKB($metrics->capacity); + commonModel::printProgressBar($rate, '', $tip, 'tip'); + } + + /** + * Backup service in k8s cluster. + * + * @param object $instance + * @access public + * @return object + */ + public function backup($instance, $account) + { + $apiParams = new stdclass; + $apiParams->username = $account; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + $apiParams->channel = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + + $apiUrl = "/api/cne/app/backup"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Stauts of backup progress. + * + * @param object $instance + * @param object $backup + * @access public + * @return object + */ + public function backupStatus($instance, $backup) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + $apiParams->backup_name = $backup->backupName; + $apiParams->channel = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + + $apiUrl = "/api/cne/app/backup/status"; + return $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Backup list. + * + * @param object $instance + * @access public + * @return object + */ + public function backupList($instance) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + $apiParams->channel = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + + $apiUrl = "/api/cne/app/backups"; + return $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * (not Finish: waiting cne api) Delete backup. + * + * @param object $instance + * @param string $backupName + * @access public + * @return bool + */ + public function deleteBackup($instance, $backupName) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + $apiParams->backup_name = $backupName; + $apiParams->channel = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + + $apiUrl = "/api/cne/app/backup/remove"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return true; + + return false; + } + + /** + * Backup service in k8s cluster. + * + * @param object $instance + * @param object $backupName + * @param string $account + * @access public + * @return object + */ + public function restore($instance, $backupName, $account) + { + $apiParams = new stdclass; + $apiParams->username = $account; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + $apiParams->backup_name = $backupName; + $apiParams->channel = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + + $apiUrl = "/api/cne/app/restore"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Stauts of restore progress. + * + * @param object $instance + * @param object $restore + * @access public + * @return object + */ + public function restorestatus($instance, $restore) + { + $apiparams = new stdclass; + $apiparams->cluster = ''; + $apiparams->namespace = $instance->spacedata->k8space; + $apiparams->name = $instance->k8name; + $apiparams->restore_name = $restore->restorename; + $apiparams->channel = empty($instance->channel) ? $this->config->cne->api->channel : $instance->channel; + + $apiurl = "/api/cne/app/restore/status"; + return $this->apiget($apiurl, $apiparams, $this->config->cne->api->headers); + } + + /** + * Start an app instance. + * + * @param object $apiParams + * @access public + * @return object + */ + public function startApp($apiParams) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/app/start"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Stop an app instance. + * + * @param object $apiParams + * @access public + * @return object + */ + public function stopApp($apiParams) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/app/stop"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Create snippet. + * + * @param object $apiParams + * @access public + * @return object + */ + public function addSnippet($apiParams) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/snippet/add"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Update snippet. + * + * @param object $apiParams + * @access public + * @return object + */ + public function updateSnippet($apiParams) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/snippet/update"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Remove snippet + * + * @param object $apiParams + * @access public + * @return object + */ + public function removeSnippet($apiParams) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/snippet/remove"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Install app. + * + * @param object $apiParams + * @access public + * @return object + */ + public function installApp($apiParams) + { + //if(!empty($apiParams->settings)) $apiParams->settings = $this->transformSettings($apiParams->settings); + + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/app/install"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * uninstall an app instance. + * + * @param object $apiParams + * @access public + * @return object + */ + public function uninstallApp($apiParams) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/app/uninstall"; + return $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + } + + /** + * Get app install logs. + * + * @param object $instance + * @access public + * @return object + */ + public function getAppLogs($instance) + { + $defaultSpace = $this->loadModel('space')->defaultSpace($this->app->user->account); + + $apiparams = new stdclass; + $apiparams->cluster = ''; + $apiparams->namespace = !empty($instance->spacedata->k8space) ? $instance->spacedata->k8space : $defaultSpace->k8space; + $apiparams->name = $instance->k8name; + $apiparams->tail = 500; + + $apiurl = "/api/cne/app/logs"; + return $this->apiget($apiurl, $apiparams, $this->config->CNE->api->headers); + } + + /** + * Validate SMTP settings. + * + * @param object $apiParams + * @access public + * @return bool + */ + public function validateSMTP($apiParams) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/system/smtp/validator"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return true; + + return false; + } + + /** + * Config app instance. + * + * @param object $apiParams + * @param object $settings + * @access public + * @return true + */ + public function configApp($apiParams, $settings) + { + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiParams->settings = $this->transformSettings($settings); + $apiUrl = "/api/cne/app/settings"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return true; + + return false; + } + + /** + * Transform setting format. + * + * @param array $settings + * @access private + * @return aray + */ + private function transformSettings($settings) + { + $transformedSettings = array(); + foreach($settings as $key => $value) + { + if(strpos($key, 'replicas') !== false && intval($value) < 1) $value = 1; // Replicas must be greater 0. + $transformedSettings[] = array('key' => str_replace('_', '.', $key), 'value' => $value); + } + return $transformedSettings; + } + + /** + * Get settings mapping by instance. + * + * @param object $instance + * @param object $mappings + * @access public + * @return object|null + */ + public function getSettingsMapping($instance, $mappings) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + $apiParams->mappings = $mappings; + + $apiUrl = "/api/cne/app/settings/mapping"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code != 200) return null; + + return $result->data; + } + + /** + * Get app config and resource. + * + * @param object $instance + * @access public + * @return bool|object + */ + public function getAppConfig($instance) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/app/settings/common"; + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code != 200) return false; + + $result->data->min = $result->data->oversold; + $result->data->max = $result->data->resources; + return $result->data; + } + + /** + * Get custom items of app. + * + * @param object $instance + * @access public + * @return bool|object + */ + public function getCustomItems($instance) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->name = $instance->k8name; + + if(empty($apiParams->channel)) $apiParams->channel = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/app/settings/custom"; + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code != 200) return false; + + return $result->data; + } + + /** + * Query status of an app instance. + * + * @param object $instance + * @access public + * @return object|null + */ + public function queryStatus($instance) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->name = $instance->k8name; + $apiParams->chart = $instance->chart; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->channel = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + + $apiUrl = "/api/cne/app/status"; + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return $result; + + return $result; + } + + /** + * Query status of instance list. + * + * @param object $instanceList + * @access public + * @return object|null + */ + public function batchQueryStatus($instanceList) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->apps = array(); + + foreach($instanceList as $instance) + { + $app = new stdclass; + $app->name = $instance->k8name; + $app->chart = $instance->chart; + $app->namespace = $instance->spaceData->k8space; + $app->channel = empty($instance->channel) ? $this->config->CNE->api->channel : $instance->channel; + + $apiParams->apps[] = $app; + } + + $apiUrl = "/api/cne/app/status/multi"; + $result = $this->apiPost($apiUrl, $apiParams, $this->config->CNE->api->headers); + if(empty($result) || $result->code != 200) return array(); + + $statusList = array(); + foreach($result->data as $status) $statusList[$status->name] = $status; + + return $statusList; + } + + /** + * Get all database list of app. + * + * @param object $instance + * @access public + * @return object + */ + public function appDBList($instance) + { + $apiUrl = "/api/cne/app/dbs"; + $apiParams = array(); + $apiParams['cluster'] = ''; + $apiParams['name'] = $instance->k8name; + $apiParams['namespace'] = $instance->spaceData->k8space; + $apiParams['channel'] = $this->config->CNE->api->channel; + + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if(empty($result) || $result->code != 200 || empty($result->data)) return array(); + + $dbList = $result->data; + return array_combine(array_column($dbList, 'name'), $dbList); + } + + /** + * Get detail of app database. + * + * @param object $instance + * @param string $dbName + * @access public + * @return null|object + */ + public function appDBDetail($instance, $dbName) + { + $apiParams = array(); + $apiParams['cluster'] = ''; + $apiParams['name'] = $instance->k8name; + $apiParams['namespace'] = $instance->spaceData->k8space; + $apiParams['db'] = $dbName; + $apiParams['channel'] = $this->config->CNE->api->channel; + + $apiUrl = "/api/cne/app/dbs/detail"; + + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if(empty($result) || $result->code != 200 || empty($result->data)) return; + + return $result->data; + } + + /** + * Get database detail. + * + * @param string $dbService + * @param string $namespace + * @access public + * @return null|object + */ + public function dbDetail($dbService, $namespace) + { + $apiUrl = "/api/cne/component/dbservice/detail"; + $apiParams = array('name' => $dbService, 'namespace' => $namespace, 'channel' => $this->config->CNE->api->channel); + + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if(empty($result) || $result->code != 200 || empty($result->data)) return; + + return $result->data; + } + + /** + * Get all database list. + * + * @param string $namespace + * @param boolean $global true: global database + * @access public + * @return object + */ + public function allDBList($global = true, $namespace = 'quickon-system') + { + $apiUrl = "/api/cne/component/dbservice"; + $apiParams = array( 'global' => ($global ? 'true' : 'false'), 'namespace' => $namespace, 'channel' => $this->config->CNE->api->channel); + + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if(empty($result) || $result->code != 200 || empty($result->data)) return array(); + + $dbList = $result->data; + return array_combine(array_column($dbList, 'name'), $dbList); + } + + /** + * Get shared database list. + * + * @param string $dbType database type. + * @param string $namespace + * @access public + * @return array + */ + public function sharedDBList($dbType = 'mysql', $namespace = 'default') + { + $apiUrl = "/api/cne/component/gdb"; + $apiParams = array('kind' => $dbType, 'namespace' => $namespace, 'channel' => $this->config->CNE->api->channel); + + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if(empty($result) || $result->code != 200 || empty($result->data)) return array(); + + $dbList = $result->data; + return array_combine(array_column($dbList, 'name'), $dbList); + } + + /** + * Validate database name and user. + * + * @param string $dbService + * @param string $dbUser + * @param string $dbName + * @param string $namespace + * @access public + * @return object + */ + public function validateDB($dbService, $dbUser, $dbName, $namespace) + { + $apiParams = array(); + $apiParams['name'] = $dbService; + $apiParams['user'] = $dbUser; + $apiParams['database'] = $dbName; + $apiParams['namespace'] = $namespace; + + $apiUrl = "/api/cne/component/gdb/validation"; + $result = $this->apiGet($apiUrl, $apiParams, $this->config->CNE->api->headers); + if($result && $result->code == 200) return $result->data->validation; + + $validation = new stdclass; + $validation->user = true; + $validation->database = true; + + return $validation; + } + + /** + * Get method of API. + * + * @param string $url + * @param array|object $data + * @param array $header example: array("key1:value1", "key2:value2") + * @param string $host + * @access public + * @return object + */ + public function apiGet($url, $data, $header = array(), $host = '') + { + $requestUri = ($host ? $host : $this->config->CNE->api->host) . $url; + $requestUri .= (strpos($url, '?') !== false ? '&' : '?') . http_build_query($data, '', '&', PHP_QUERY_RFC3986); + $result = json_decode(commonModel::http($requestUri, $data, array(CURLOPT_CUSTOMREQUEST => 'GET'), $header, 'json', 20)); + + if($result && $result->code == 200) return $result; + if($result) return $this->translateError($result); + + return $this->cneServerError(); + } + + /** + * Post method of API. + * + * @param string $url + * @param array|object $data + * @param array $header example: array("key1:value1", "key2:value2") + * @param string $host + * @access public + * @return object + */ + public function apiPost($url, $data, $header = array(), $host = '') + { + $requestUri = ($host ? $host : $this->config->CNE->api->host) . $url; + $result = json_decode(commonModel::http($requestUri, $data, array(CURLOPT_CUSTOMREQUEST => 'POST'), $header, 'json', 20)); + if($result && $result->code == 200) return $result; + if($result) return $this->translateError($result); + + return $this->cneServerError(); + } + + /** + * Put method of API. + * + * @param string $url + * @param array|object $data + * @param array $header example: array("key1:value1", "key2:value2") + * @param string $host + * @access public + * @return object + */ + public function apiPut($url, $data, $header = array(), $host = '') + { + $requestUri = ($host ? $host : $this->config->CNE->api->host) . $url; + $result = json_decode(commonModel::http($requestUri, $data, array(CURLOPT_CUSTOMREQUEST => 'PUT'), $header, 'json', 20)); + if($result && $result->code == 200) return $result; + if($result) return $this->translateError($result); + + return $this->cneServerError(); + } + + /** + * Delete method of API. + * + * @param string $url + * @param array|object $data + * @param array $header example: array("key1:value1", "key2:value2") + * @param string $host + * @access public + * @return object + */ + public function apiDelete($url, $data, $header = array(), $host = '') + { + $requestUri = ($host ? $host : $this->config->CNE->api->host) . $url; + $result = json_decode(commonModel::http($requestUri, $data, array(CURLOPT_CUSTOMREQUEST => 'DELETE'), $header, 'json', 20)); + if($result && $result->code == 200) return $result; + if($result) return $this->translateError($result); + + return $this->cneServerError(); + } + + /** + * Return error object of api server. + * + * @access protected + * @return object + */ + protected function cneServerError() + { + $error = new stdclass; + $error->code = 600; + $error->message = $this->lang->CNE->serverError; + return $error; + } + + /** + * Get error + * + * @access public + * @return object + */ + public function getError() + { + return $this->error; + } + + /** + * Translate error message for multi language. + * + * @param object $apiResult + * @access protected + * @return void + */ + protected function translateError(&$apiResult) + { + $this->error->code = $apiResult->code; + $this->error->message = zget($this->lang->CNE->errorList, $apiResult->code, $this->lang->CNE->serverError); // Translate CNE api error message to multi language. + + $apiResult->message = $this->error->message; + + return $this->error; + } +} diff --git a/module/common/lang/common.php b/module/common/lang/common.php index 509442e28f..9e269ddadb 100644 --- a/module/common/lang/common.php +++ b/module/common/lang/common.php @@ -95,6 +95,12 @@ $lang->zanode = new stdclass(); $lang->holiday = new stdclass(); $lang->contact = new stdclass(); $lang->dimension = new stdclass(); +$lang->space = new stdclass(); +$lang->store = new stdclass(); +$lang->instance = new stdclass(); +$lang->CNE = new stdclass(); +$lang->account = new stdclass(); +$lang->ops = new stdclass(); $lang->programstakeholder = new stdclass(); $lang->researchplan = new stdclass(); diff --git a/module/common/lang/en.php b/module/common/lang/en.php index 8b1f83b8e9..9136f93919 100644 --- a/module/common/lang/en.php +++ b/module/common/lang/en.php @@ -353,12 +353,26 @@ $lang->testcase->case = 'Test Case'; $lang->testcase->testsuite = 'Test Suite'; $lang->testcase->caselib = 'Case Library'; -$lang->devops->compile = 'Pipelines'; -$lang->devops->mr = 'Merge Request'; -$lang->devops->repo = 'Repo'; -$lang->devops->rules = 'Rule'; -$lang->devops->settings = 'Setting Merge Request'; -$lang->devops->set = 'Set'; +$lang->devops->compile = 'Pipelines'; +$lang->devops->mr = 'Merge Request'; +$lang->devops->repo = 'Repo'; +$lang->devops->rules = 'Rule'; +$lang->devops->settings = 'Setting Merge Request'; +$lang->devops->platform = 'Platform'; +$lang->devops->set = 'Set'; +$lang->devops->WIPs = 'WIPs'; +$lang->devops->environment = 'Environment'; +$lang->devops->resource = 'Resource'; +$lang->devops->dblist = 'Database'; +$lang->devops->domain = 'Domain'; +$lang->devops->oss = 'Oss'; +$lang->devops->host = 'Host'; +$lang->devops->account = 'Account'; +$lang->devops->serverroom = 'IDC'; +$lang->devops->provider = 'IDC Provider'; +$lang->devops->cpuBrand = 'CPU Brand'; +$lang->devops->city = 'IDC Location'; +$lang->devops->os = 'OS Version'; $lang->admin->module = 'Module'; $lang->admin->system = 'System'; diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php index faa9f643db..10652bbb46 100644 --- a/module/common/lang/zh-cn.php +++ b/module/common/lang/zh-cn.php @@ -353,12 +353,26 @@ $lang->testcase->case = '用例'; $lang->testcase->testsuite = '套件'; $lang->testcase->caselib = '用例库'; -$lang->devops->compile = '流水线'; -$lang->devops->mr = '合并请求'; -$lang->devops->repo = '代码库'; -$lang->devops->rules = '指令'; -$lang->devops->settings = '合并请求设置'; -$lang->devops->set = '设置'; +$lang->devops->compile = '流水线'; +$lang->devops->mr = '合并请求'; +$lang->devops->repo = '代码库'; +$lang->devops->rules = '指令'; +$lang->devops->settings = '合并请求设置'; +$lang->devops->platform = '平台'; +$lang->devops->set = '设置'; +$lang->devops->WIPs = '制品库'; +$lang->devops->environment = '环境'; +$lang->devops->resource = '资源'; +$lang->devops->dblist = '数据库'; +$lang->devops->domain = '域名'; +$lang->devops->oss = '对象存储'; +$lang->devops->host = '主机'; +$lang->devops->account = '账号'; +$lang->devops->serverroom = '机房'; +$lang->devops->provider = '服务商'; +$lang->devops->cpuBrand = 'CPU品牌'; +$lang->devops->city = '城市'; +$lang->devops->os = '系统版本'; $lang->admin->module = '功能配置'; $lang->admin->system = '系统'; diff --git a/module/instance/config.php b/module/instance/config.php new file mode 100644 index 0000000000..1b3ba07cb3 --- /dev/null +++ b/module/instance/config.php @@ -0,0 +1,60 @@ +instance = new stdclass; +$config->instance->keepDomainList = array(); +$config->instance->keepDomainList['console'] = 'console'; +$config->instance->keepDomainList['demo'] = 'demo'; +$config->instance->keepDomainList['s3'] = 's3'; +$config->instance->keepDomainList['s3-api'] = 's3-api'; + +$config->instance->seniorChartList = array(); +$config->instance->seniorChartList['zentao'] = ['zentao-biz', 'zentao-max']; +$config->instance->seniorChartList['zentao-biz'] = ['zentao-max']; + +$config->instance->adminer = new stdclass(); +$config->instance->adminer->dbTypes = array(); +$config->instance->adminer->dbTypes['mysql'] = 'mysql'; +$config->instance->adminer->dbTypes['postgresql'] = 'pgsql'; + +$features = explode(',', getenv('QUICKON_FEATURES', '')); +$config->instance->enableAutoRestore = in_array('auto-rollback', $features); + +global $lang, $app; +$app->loadLang('space'); + +$config->instance->actionList['start']['icon'] = 'play'; +$config->instance->actionList['start']['className'] = 'ajax-submit'; +$config->instance->actionList['start']['hint'] = $lang->instance->start; +$config->instance->actionList['start']['text'] = $lang->instance->start; +$config->instance->actionList['start']['url'] = array('module' => 'instance', 'method' => 'ajaxStart', 'params' => 'id={id}'); + +$config->instance->actionList['stop']['icon'] = 'off'; +$config->instance->actionList['stop']['className'] = 'ajax-submit'; +$config->instance->actionList['stop']['hint'] = $lang->instance->stop; +$config->instance->actionList['stop']['text'] = $lang->instance->stop; +$config->instance->actionList['stop']['data-confirm'] = $lang->instance->notices['confirmStop']; +$config->instance->actionList['stop']['url'] = array('module' => 'instance', 'method' => 'ajaxStop', 'params' => 'id={id}'); + +$config->instance->actionList['uninstall']['icon'] = 'trash'; +$config->instance->actionList['uninstall']['hint'] = $lang->instance->uninstall; +$config->instance->actionList['uninstall']['text'] = $lang->instance->uninstall; +$config->instance->actionList['uninstall']['className'] = 'ajax-submit'; +$config->instance->actionList['uninstall']['data-confirm'] = $lang->instance->notices['confirmUninstall']; +$config->instance->actionList['uninstall']['url'] = array('module' => 'instance', 'method' => 'ajaxUninstall', 'params' => 'id={id}'); + +$config->instance->actionList['visit']['icon'] = 'menu-my'; +$config->instance->actionList['visit']['hint'] = $lang->instance->visit; +$config->instance->actionList['visit']['text'] = $lang->instance->visit; +$config->instance->actionList['visit']['target'] = '_blank'; +$config->instance->actionList['visit']['url'] = array('module' => 'instance', 'method' => 'visit', 'params' => 'id={id}'); + +$config->instance->actionList['upgrade']['icon'] = 'refresh'; +$config->instance->actionList['upgrade']['data-toggle'] = 'modal'; +$config->instance->actionList['upgrade']['data-size'] = 'sm'; +$config->instance->actionList['upgrade']['text'] = $lang->instance->upgrade; +$config->instance->actionList['upgrade']['hint'] = $lang->instance->upgrade; +$config->instance->actionList['upgrade']['url'] = helper::createLink('instance', 'upgrade', "id={id}"); + +$config->instance->actions = new stdclass(); +$config->instance->actions->view = array(); +$config->instance->actions->view['mainActions'] = array('visit', 'start', 'stop', 'upgrade'); +$config->instance->actions->view['suffixActions'] = array('uninstall'); \ No newline at end of file diff --git a/module/instance/control.php b/module/instance/control.php new file mode 100644 index 0000000000..4ed9e41f52 --- /dev/null +++ b/module/instance/control.php @@ -0,0 +1,865 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.com + */ +class instance extends control +{ + /** + * Construct function. + * + * @param string $moduleName + * @param string $methodName + * @access public + * @return void + */ + public function __construct($moduleName = '', $methodName = '') + { + parent::__construct($moduleName, $methodName); + $this->loadModel('action'); + $this->loadModel('cne'); + $this->loadModel('store'); + } + + /** + * Show instance view. + * + * @param int $id + * @param int $recTotal + * @param int $recPerPage + * @param int $page + * @access public + * @return void + */ + public function view($id, $recTotal = 0, $recPerPage = 20, $pageID = 1, $tab ='baseinfo' ) + { + $this->loadModel('system'); + $this->app->loadLang('system'); + + $instance = $this->instance->getByID($id); + if(empty($instance)) return print(js::alert($this->lang->instance->instanceNotExists) . js::locate($this->createLink('space', 'browse'))); + + $instance->latestVersion = $this->store->appLatestVersion($instance->appID, $instance->version); + + $instance = $this->instance->freshStatus($instance); + + $instanceMetric = $this->cne->instancesMetrics(array($instance)); + $instanceMetric = $instanceMetric[$instance->id]; + $this->lang->switcherMenu = $this->instance->getSwitcher($instance); + + $this->app->loadClass('pager', true); + $pager = new pager($recTotal, $recPerPage, $pageID); + + $backupList = array(); + $latestBackup = new stdclass; + if($tab == 'backup') $backupList = $this->instance->backupList($instance); + if(count($backupList)) $latestBackup = reset($backupList); + + $hasRestoreLog = false; + foreach($backupList as $backup) + { + $backup->latest_restore_time = 0; + $backup->latest_restore_status = ''; + foreach($backup->restores as $restore) + { + $hasRestoreLog = true; + if($restore->create_time > $backup->latest_restore_time) + { + $backup->latest_restore_time = $restore->create_time; + $backup->latest_restore_status = $restore->status; + } + } + } + + $dbList = new stdclass; + $currentResource = new stdclass; + $customItems = array(); + $dbList = $this->cne->appDBList($instance); + $currentResource = $this->cne->getAppConfig($instance); + $customItems = $this->cne->getCustomItems($instance); + + $this->view->title = $instance->appName; + $this->view->instance = $instance; + $this->view->cloudApp = $this->loadModel('store')->getAppInfoByChart($instance->chart, $instance->channel, false); + $this->view->seniorAppList = $tab == 'baseinfo' ? $this->instance->seniorAppList($instance, $instance->channel) : array(); + $this->view->actions = $this->loadModel('action')->getList('instance', $id); + $this->view->users = $this->loadModel('user')->getPairs('noletter'); + $this->view->defaultAccount = $this->cne->getDefaultAccount($instance); + $this->view->instanceMetric = $instanceMetric; + $this->view->currentResource = $currentResource; + $this->view->customItems = $customItems; + $this->view->backupList = $backupList; + $this->view->hasRestoreLog = $hasRestoreLog; + $this->view->latestBackup = $latestBackup; + $this->view->dbList = $dbList; + $this->view->domain = $this->cne->getDomain($instance); + $this->view->tab = $tab; + $this->view->pager = $pager; + + $this->display(); + } + + /** + * Display or save auto backup settings. + * + * @param int $instanceID + * @access public + * @return void + */ + public function backupSettings($instanceID) + { + $instance = $this->instance->getByID($instanceID); + + if($_POST) + { + $this->instance->saveAutoBackupSettings($instance); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $backupSettings = $this->instance->getAutoBackupSettings($instanceID); + + $startTime = strtotime($backupSettings->backupTime); + if($startTime < time()) $startTime = strtotime("+1 day $backupSettings->backupTime"); + + if($backupSettings->autoBackup) + { + $startBackupMessage = sprintf($this->lang->instance->backup->firstStartTime, $instance->name, date('Y-m-d H:i:s', $startTime)); + return $this->send(array('result' => 'success', 'message' => $startBackupMessage)); + } + + return $this->send(array('result' => 'success', 'message' => $this->lang->instance->backup->disableAutoBackup)); + } + + $backupSettings = $this->instance->getAutoBackupSettings($instanceID); + + $this->view->title = $this->lang->instance->backup->autoBackup; + $this->view->instance = $instance; + $this->view->backupSettings = $backupSettings; + + $this->display(); + } + + /** + * Display or save auto restore settings. + * + * @param int $instanceID + * @access public + * @return void + */ + public function restoreSettings($instanceID) + { + $instance = $this->instance->getByID($instanceID); + + if($_POST) + { + $this->instance->saveAutoRestoreSettings($instance); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $restoreSettings = $this->instance->getAutoRestoreSettings($instanceID); + + $startTime = strtotime($restoreSettings->restoreTime); + if($startTime < time()) $startTime = strtotime("+1 day $restoreSettings->restoreTime"); + + if($restoreSettings->autoRestore) + { + $startRestoreMessage = sprintf($this->lang->instance->restore->firstStartTime, $instance->name, date('Y-m-d H:i:s', $startTime)); + return $this->send(array('result' => 'success', 'message' => $startRestoreMessage)); + } + + return $this->send(array('result' => 'success', 'message' => $this->lang->instance->restore->disableAutoRestore)); + } + + $this->view->title = $this->lang->instance->restore->autoRestore; + $this->view->instance = $instance; + $this->view->restoreSettings = $this->instance->getAutoRestoreSettings($instanceID); + + $this->display(); + } + + /** + * Cron task of auto backup. + * + * @param string $key + * @access public + * @return void + */ + public function autoBackup($key) + { + if($this->config->instance->enableAutoRestore) return; // Only one of auto backup and auto restore can be enabled. + + $this->app->saveLog('Run auto backup at: ' . date('Y-md-d H:i:s')); + + if($key != helper::readKey()) return; + + $this->instance->autoBackup(); + } + + /** + * Cron task of auto restore. + * + * @param string $key + * @access public + * @return void + */ + public function autoRestore($key) + { + if(!$this->config->instance->enableAutoRestore) return; // Only one of auto backup and auto restore can be enabled. + + $this->app->saveLog('Run auto restore at: ' . date('Y-md-d H:i:s')); + + if($key != helper::readKey()) return; + + $this->instance->autoRestore(); + } + + /** + * Edit instance app name. + * + * @param int $id + * @access public + * @return void + */ + public function setting($id) + { + $currentResource = new stdclass; + $instance = $this->instance->getByID($id); + $currentResource = $this->cne->getAppConfig($instance); + if(!empty($_POST)) + { + $newInstance = fixer::input('post')->trim('name')->get(); + $memoryKb = $newInstance->memory_kb; + unset($newInstance->memory_kb); + + if(intval($currentResource->max->memory / 1024) != $memoryKb) + { + /* Check free memory size is enough or not. */ + $clusterResource = $this->cne->cneMetrics(); + $freeMemory = intval($clusterResource->metrics->memory->allocatable * 0.9); // Remain 10% memory for system. + if($memoryKb * 1024 > $freeMemory) $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->notEnoughResource)); + + /* Request CNE to adjust memory size. */ + if(!$this->instance->updateMemorySize($instance, $memoryKb * 1024)) + { + $this->send(array('result' => 'fail', 'message' => dao::getError())); + } + } + + $this->instance->updateByID($id, $newInstance); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + if($newInstance->name != $instance->name) + { + $this->action->create('instance', $instance->id, 'editname', '', json_encode(array('result' => array('result' => 'success'), 'data' => array('oldName' => $instance->name, 'newName' => $newInstance->name)))); + } + return $this->send(array('result' => 'success', 'load' => true, 'closeModal' => true)); + } + + $this->view->currentResource = $currentResource; + $this->view->instance = $instance; + + $this->display(); + } + + /** + * Upgrade to senior serial. + * + * @param int $instanceID + * @param int $seniorAppID + * @param string $confirm + * @access public + * @return void + */ + public function toSenior($instanceID, $seniorAppID, $confirm = 'no') + { + $instance = $this->instance->getByID($instanceID); + $cloudApp = $this->store->getAppInfo($seniorAppID, $instance->channel, false); + if(empty($cloudApp)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->noAppInfo)); + + if($confirm == 'yes') + { + $success = $this->instance->upgradeToSenior($instance, $cloudApp); + if($success) $this->send(array('result' => 'success', 'message' => '', 'locate' => $this->inLink('view', "id=$instance->id"))); + + $this->send(array('result' => 'fail', 'message' => dao::getError())); + } + + $this->view->title = $this->lang->instance->upgradeToSenior; + $this->view->instance = $instance; + $this->view->cloudApp = $cloudApp; + + $this->display(); + } + + /** + * Upgrade instnace + * + * @param int $id + * @access public + * @return mixed + */ + public function upgrade($id) + { + $instance = $this->instance->getByID($id); + $instance->latestVersion = $this->store->appLatestVersion($instance->appID, $instance->version); + + if($_POST) + { + if(empty($instance->latestVersion)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->noHigherVersion, 'closeModal' => true)); + + $postData = fixer::input('post')->get(); + + if($postData->confirm == 'yes') $success = $this->instance->upgrade($instance, $instance->latestVersion->version, $instance->latestVersion->app_version); + + $logExtra = array('result' => 'success', 'data' => array('oldVersion' => $instance->appVersion, 'newVersion' => $instance->latestVersion->app_version)); + if(!$success) + { + $logExtra['result'] = 'fail'; + $this->action->create('instance', $instance->id, 'upgrade', '', json_encode($logExtra)); + return $this->send(array('result' => 'fail', 'message' => !empty($logExtra['message']) ? $logExtra['message'] : $this->lang->instance->notices['upgradeFail'], 'closeModal' => true)); + } + + $this->action->create('instance', $instance->id, 'upgrade', '', json_encode($logExtra)); + return $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['upgradeSuccess'], 'load' => $this->createLink('instance', 'view', "id=$id"), 'closeModal' => true)); + } + + $this->view->title = $this->lang->instance->upgrade . $instance->name; + $this->view->instance = $instance; + + $this->display(); + } + + /** + * 访问一个应用。 + * Visit a app. + * + * @param int $id + * @param int $externalID + * @access public + * @return void + */ + public function visit(int $id, int $externalID = 0): void + { + if(!$externalID) + { + $instance = $this->instance->getByID($id); + $url = $this->instance->url($instance); + } + else + { + $pipeline = $this->loadModel('pipeline')->getByID($externalID); + $url = $pipeline->url; + } + + $this->locate($url); + } + + /** + * (Not used at present.) Install app by custom settings. + * + * @param int $id + * @access public + * @return void + */ + public function customInstall($id) + { + // Disable custom installation in version 1.0. + $storeUrl = $this->createLink('store', 'appview', "id=$id"); + return js::execute("window.parent.location.href='{$storeUrl}';"); + } + + /** + * Install app. + * + * @param int $appID + * @access public + * @return void + */ + public function install($appID) + { + $cloudApp = $this->store->getAppInfo($appID); + if(empty($cloudApp)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->noAppInfo)); + + if(empty($this->config->demoAccounts)) + { + $clusterResource = $this->cne->cneMetrics(); + $freeMemory = intval($clusterResource->metrics->memory->allocatable * 0.9); // Remain 10% memory for system. + if($cloudApp->memory > $freeMemory) + { + $this->view->cloudApp = $cloudApp; + $this->view->gapMemory = helper::formatKB(intval(($cloudApp->memory - $freeMemory))); + $this->view->requiredMemory = helper::formatKB(intval($cloudApp->memory)); + $this->view->freeMemory = helper::formatKB(intval($freeMemory)); + + return $this->display('instance','resourceerror'); + } + } + + $versionList = $this->store->appVersionList($cloudApp->id); + $mysqlList = $this->cne->sharedDBList('mysql'); + $pgList = $this->cne->sharedDBList('postgresql'); + if(!empty($_POST)) + { + $customData = fixer::input('post') + ->trim('customName')->setDefault('customName', '') + ->trim('customDomain')->setDefault('customDomain', '') + ->trim('version')->setDefault('version', '') + ->trim('dbType')->setDefault('dbType', 'unsharedDB') + ->trim('dbService') + ->setDefault('app_version', '') + ->get(); + if($customData->version && isset($versionList[$customData->version])) $customData->app_version = $versionList[$customData->version]->app_version; + + if(isset($this->config->instance->keepDomainList[$customData->customDomain]) || $this->instance->domainExists($customData->customDomain)) return $this->send(array('result' => 'fail', 'message' => $customData->customDomain . $this->lang->instance->errors->domainExists)); + + if(!validater::checkLength($customData->customDomain, 20, 2)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->domainLength)); + if(!validater::checkREG($customData->customDomain, '/^[a-z\d]+$/')) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->wrongDomainCharacter)); + + /* If select the version, replace the latest version of App by selected version. */ + if($customData->version) + { + $cloudApp->version = $customData->version; + $cloudApp->app_version = $customData->app_version; + } + + $sharedDB = new stdclass; + if(isset($cloudApp->dependencies->mysql) && $customData->dbType == 'sharedDB') + { + $sharedDB = zget($mysqlList, $customData->dbService); + } + elseif(isset($cloudApp->dependencies->postgresql) && $customData->dbType == 'sharedDB') + { + $sharedDB = zget($pgList, $customData->dbService); + } + $instance = $this->instance->install($cloudApp, $sharedDB, $customData); + if(!$instance) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->notices['installFail'])); + + unset($_GET['onlybody']); + $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['installSuccess'], 'load' => $this->createLink('instance', 'view', "id=$instance->id"), 'closeModal' => true)); + } + + $this->lang->switcherMenu = $this->instance->getInstallSwitcher($cloudApp); + + $this->view->position[] = $this->view->title; + + $this->view->title = $this->lang->instance->install . $cloudApp->alias; + $this->view->cloudApp = $cloudApp; + + $this->view->versionList = array(); + foreach($versionList as $version) $this->view->versionList[$version->version] = $version->app_version . " ({$version->version})"; + + $this->view->thirdDomain = $this->instance->randThirdDomain(); + $this->view->mysqlList = $this->instance->dbListToOptions($mysqlList); + $this->view->pgList = $this->instance->dbListToOptions($pgList); + + $this->display(); + } + + /** + * Uninstall app instance. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxUninstall($instanceID, $type = '') + { + if($type == 'external') + { + $instance = $this->loadModel('pipeline')->getByID($instanceID); + if(!$instance) return $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['success'])); + + return $this->send($this->fetch($instance->type, 'delete', array('id' => $instance->id))); + } + $instance = $this->instance->getByID($instanceID); + if(!$instance) return $this->send(array('result' => 'success', 'message' => $this->lang->instance->notices['success'])); + + $success = $this->instance->uninstall($instance); + $this->action->create('instance', $instance->id, 'uninstall', '', json_encode(array('result' => $success, 'app' => array('alias' => $instance->appName, 'app_version' => $instance->version)))); + if($success) return $this->send(array('result' => 'success', 'message' => zget($this->lang->instance->notices, 'uninstallSuccess'), 'locate' => $this->createLink('space', 'browse'))); + + return $this->send(array('result' => 'fail', 'message' => zget($this->lang->instance->notices, 'uninstallFail'))); + } + + /** + * Start app instance. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxStart($instanceID) + { + $instance = $this->instance->getByID($instanceID); + if(!$instance) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->instanceNotExists)); + + $result = $this->instance->start($instance); + $this->action->create('instance', $instance->id, 'start', '', json_encode(array('result' => $result, 'app' => array('alias' => $instance->appName, 'app_version' => $instance->version)))); + + if($result->code == 200) return $this->send(array('result' => 'success', 'load' => true, 'message' => zget($this->lang->instance->notices, 'startSuccess'))); + + return $this->send(array('result' => 'fail', 'message' => !empty($result->message) ? $result->message : zget($this->lang->instance->notices, 'startFail'))); + } + + /** + * Stop app instance. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxStop($instanceID) + { + $instance = $this->instance->getByID($instanceID); + if(!$instance) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->instanceNotExists)); + + $result = $this->instance->stop($instance); + $this->action->create('instance', $instance->id, 'stop', '', json_encode(array('result' => $result, 'app' => array('alias' => $instance->appName, 'app_version' => $instance->version)))); + if($result->code == 200) return $this->send(array('result' => 'success', 'load' => true, 'message' => zget($this->lang->instance->notices, 'stopSuccess'))); + + return $this->send(array('result' => 'fail', 'message' => !empty($result->message) ? $result->message : zget($this->lang->instance->notices, 'stopFail'))); + } + + /** + * Query status of app instance. + * + * @access public + * @return void + */ + public function ajaxStatus() + { + $postData = fixer::input('post')->setDefault('idList', array())->get(); + + $instances = $this->instance->getByIdList($postData->idList); + $statusList = $this->instance->batchFresh($instances); + + return $this->send(array('result' => 'success', 'data' => $statusList)); + } + + /** + * Backup instnacd by ajax. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxBackup($instanceID) + { + $instance = $this->instance->getByID($instanceID); + $success = $this->instance->backup($instance, $this->app->user); + if(!$success) + { + $this->action->create('instance', $instance->id, 'backup', '', json_encode(array('result' => array('result' => 'fail')))); + return $this->send(array('result' => 'fail', 'message' => zget($this->lang->instance->notices, 'backupFail'))); + } + + $this->action->create('instance', $instance->id, 'backup', '', json_encode(array('result' => array('result' => 'success')))); + return $this->send(array('result' => 'success', 'message' => zget($this->lang->instance->notices, 'backupSuccess'))); + } + + /** + * Restore instance by ajax + * + * @access public + * @return void + */ + public function ajaxRestore() + { + $postData = fixer::input('post') + ->trim('instanceID') + ->trim('backupName')->get(); + + if(empty($postData->instanceID) || empty($postData->backupName)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->wrongRequestData)); + + $instance = $this->instance->getByID($postData->instanceID); + if(empty($instance))return print(js::alert($this->lang->instance->instanceNotExists) . js::locate($this->createLink('space', 'browse'))); + + $success = $this->instance->restore($instance, $this->app->user, $postData->backupName); + if(!$success) + { + $this->action->create('instance', $instance->id, 'restore', '', json_encode(array('result' => array('result' => 'fail')))); + return $this->send(array('result' => 'fail', 'message' => zget($this->lang->instance->notices, 'restoreFail'))); + } + + $this->action->create('instance', $instance->id, 'restore', '', json_encode(array('result' => array('result' => 'success')))); + return $this->send(array('result' => 'success', 'message' => zget($this->lang->instance->notices, 'restoreSuccess'))); + } + + /** + * Delete backup by ajax. + * + * @param int $backupID + * @access public + * @return void + */ + public function ajaxDeleteBackup($backupID) + { + $success = $this->instance->deleteBackup($backupID, $this->app->user); + if(!$success) return $this->send(array('result' => 'fail', 'message' => zget($this->lang->instance->notices, 'deleteFail'))); + + return $this->send(array('result' => 'success', 'message' => zget($this->lang->instance->notices, 'deleteSuccess'))); + } + + /** + * Generate database auth parameters and jump to login page. + * + * @access public + * @return void + */ + public function ajaxDBAuthUrl() + { + $post = fixer::input('post') + ->setDefault('namespace', 'default') + ->setDefault('instanceID', 0) + ->setDefault('dbType', '') + ->get(); + if(empty($post->dbName)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->dbNameIsEmpty)); + + $instance = $this->instance->getByID($post->instanceID); + if(empty($instance)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->instanceNotExists)); + + $detail = $this->loadModel('cne')->appDBDetail($instance, $post->dbName); + if(empty($detail)) return $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->notFoundDB)); + + $dbAuth = array(); + $dbAuth['driver'] = zget($this->config->instance->adminer->dbTypes, $post->dbType, ''); + $dbAuth['server'] = $detail->host . ':' . $detail->port; + $dbAuth['username'] = $detail->username; + $dbAuth['db'] = $detail->database; + $dbAuth['password'] = $detail->password; + + $url = '/adminer?' . http_build_query($dbAuth); + $this->send(array('result' => 'success', 'message' => '', 'data' => array('url' => $url))); + } + + /** + * Adjust instance memory size by ajax. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxAdjustMemory($instanceID) + { + $postData = fixer::input('post')->get(); + + /* Check free memory size is enough or not. */ + $clusterResource = $this->cne->cneMetrics(); + $freeMemory = intval($clusterResource->metrics->memory->allocatable * 0.9); // Remain 10% memory for system. + if($postData->memory_kb * 1024 > $freeMemory) $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->notEnoughResource)); + + /* Request CNE to adjust memory size. */ + $instance = $this->instance->getByID($instanceID); + if(!$this->instance->updateMemorySize($instance, $postData->memory_kb * 1024)) + { + $this->send(array('result' => 'fail', 'message' => dao::getError())); + } + + $this->send(array('result' => 'success', 'message' => '')); + } + + /** + * Switch LDAP between enable and disable by ajax. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxSwitchLDAP($instanceID) + { + $instance = $this->instance->getByID($instanceID); + $postData = fixer::input('post')->get(); + + if($this->instance->switchLDAP($instance, $postData->enableLDAP == 'true')) + { + $this->send(array('result' => 'success', 'message' => '')); + } + + $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->switchLDAPFailed)); + } + + /** + * Switch SMTP between enable and disable by ajax. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxSwitchSMTP($instanceID) + { + $instance = $this->instance->getByID($instanceID); + $postData = fixer::input('post')->get(); + + if($this->instance->switchSMTP($instance, $postData->enableSMTP == 'true')) + { + $this->send(array('result' => 'success', 'message' => '')); + } + + $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->switchSMTPFailed)); + } + + /** + * Update custom settings by ajax. For example: env variables. + * + * @param int $instanceID + * @access public + * @return void + */ + public function ajaxUpdateCustom($instanceID) + { + $instance = $this->instance->getByID($instanceID); + if(!$instance) $this->send(array('result' => 'fail', 'message' => $this->lang->instance->instanceNotExists)); + + $postData = fixer::input('post')->get(); + + $settings = new stdclass; + $settings->force_restart = true; + $settings->settings_map = new stdclass; + $settings->settings_map->custom = $postData; + + if($this->cne->updateConfig($instance, $settings)) + { + $this->action->create('instance', $instanceID, 'updatecustom', '', json_encode($settings)); + $this->send(array('result' => 'success', 'message' => '')); + } + + $this->send(array('result' => 'fail', 'message' => $this->lang->instance->errors->setEnvFailed)); + } + + /** + * Delete expired demo instance by cron. + * + * @access public + * @return void + */ + public function deleteExpiredDemoInstance() + { + if(empty($this->config->demoAccounts)) return $this->send(array('result' => 'fail', 'message' => 'This api is only for demo enviroment.')); + + $this->instance->deleteExpiredDemoInstance(); + + $this->send(array('result' => 'success', 'message' => '')); + } + + /** + * Get instance info for q tool in console. + * + * @param int $id + * @access public + * @return mixed + */ + public function apiDetail($id) + { + if(!$this->checkCneToken()) + { + helper::setStatus(401); + return print(json_encode(array('code' => 401, 'message' => 'Invalid token.'))); + } + + if(empty($id)) return print(json_encode(array('code' => 401, 'message' => 'Invalid id.'))); + + $instance = $this->instance->getByID($id); + if(empty($instance)) return print(json_encode(array('code' => 404, 'message' => 'Not found.', 'data' => array()))); + + $instance->space = $instance->spaceData && isset($instance->spaceData->k8space) ? $instance->spaceData->k8space : ''; + unset($instance->desc); + unset($instance->spaceData); + + return print(json_encode(array('code' => 200, 'message' => 'success', 'data' => $instance))); + } + + /** + * Get instances list by account through api for q tool. + * + * @access public + * @return void + */ + public function apiBrowse() + { + if(!$this->checkCneToken()) + { + helper::setStatus(401); + return print(json_encode(array('code' => 401, 'message' => 'Invalid token.'))); + } + + $requestBody = json_decode(file_get_contents("php://input")); + $account = zget($requestBody, 'account', ''); + if(empty($account)) return print(json_encode(array('code' => 700, 'message' => 'Account is required.', 'data' => new stdclass))); + + $recPerPage = zget($requestBody, 'perPage', 20); + $pageID = zget($requestBody, 'page', 1); + + $this->app->loadClass('pager', true); + $pager = pager::init(0, $recPerPage, $pageID); + + $instanceList = $this->instance->getByAccount($account, $pager); + + $result = new stdclass; + $result->list = $instanceList; + $result->page = $pageID; + $result->perPage = $recPerPage; + $result->pageTotal = $pager->pageTotal; + $result->total = $pager->recTotal; + + return print(json_encode(array('code' => 200, 'message' => 'success', 'data' => $result))); + } + + /** + * Install app by api for q tool. + * + * @access public + * @return void + */ + public function apiInstall() + { + if(!$this->checkCneToken()) + { + helper::setStatus(401); + return print(json_encode(array('code' => 401, 'message' => 'Invalid token.'))); + } + + $requestBody = json_decode(file_get_contents("php://input")); + $chart = zget($requestBody , 'chart', ''); + if(empty($chart)) return print(json_encode(array('code' => 701, 'message' => 'Param chart is required.'))); + + $user = null; + $account = zget($requestBody, 'account', ''); + if($account) $user = $this->loadModel('user')->getById($account); + if(empty($user)) $user = $this->loadModel('company')->getAdmin(); + if(empty($user)) return print(json_encode(array('code' => 703, 'message' => 'No user.'))); + + $this->app->user = $user; + + $name = zget($requestBody , 'name', ''); + $channel = zget($requestBody , 'channel', 'stable'); + $k8name = zget($requestBody , 'k8name', ''); + if($k8name && $this->instance->k8nameExists($k8name)) return print(json_encode(array('code' => 706, 'message' => $k8name . ' has been used, please change it and try again.'))); + + $thirdDomain = zget($requestBody , 'domain', ''); + if($this->instance->domainExists($thirdDomain)) return print(json_encode(array('code' => 705, 'message' => $thirdDomain . ' has been used, please change it and try again.'))); + + $cloudApp = $this->store->getAppInfoByChart($chart, $channel, false); + if(empty($cloudApp)) return print(json_encode(array('code' => 702, 'message' => 'App not found.'))); + + $result = $this->instance->apiInstall($cloudApp, $thirdDomain, $name, $k8name, $channel); + + if($result) return print(json_encode(array('code' => 200, 'message' => 'success', 'data' => new stdclass))); + + return print(json_encode(array('code' => 704, 'message' => 'Fail to install app.', 'data' => new stdclass))); + } + + /** + * Check CNE token. + * + * @access private + * @return bool + */ + private function checkCneToken() + { + $token = zget($_SERVER, 'HTTP_TOKEN'); + return $token == $this->config->CNE->api->token; + } +} diff --git a/module/instance/css/backupsettings.css b/module/instance/css/backupsettings.css new file mode 100644 index 0000000000..4d57b80074 --- /dev/null +++ b/module/instance/css/backupsettings.css @@ -0,0 +1 @@ +#backupSettingForm{ margin-bottom: 40px;} diff --git a/module/instance/css/install.css b/module/instance/css/install.css new file mode 100644 index 0000000000..7f5d8754b9 --- /dev/null +++ b/module/instance/css/install.css @@ -0,0 +1,5 @@ +#installForm .advanced {padding: 10px 0; border-bottom: solid 1px #e4e4e4;} +#installForm .advanced a {font-weight: 700;} +#installForm .advanced .icon {display: inline-block; margin-left: 10px;} +#installForm .advanced a:hover, #installForm table td a:focus, #installForm table td a:hover {color: #61be68;} +#installForm td {height: 46px;} diff --git a/module/instance/css/upgrade.css b/module/instance/css/upgrade.css new file mode 100644 index 0000000000..dc896b7a03 --- /dev/null +++ b/module/instance/css/upgrade.css @@ -0,0 +1 @@ +.modal-message {font-weight: bold; padding: 20px;} diff --git a/module/instance/css/view.css b/module/instance/css/view.css new file mode 100644 index 0000000000..693f0a86bf --- /dev/null +++ b/module/instance/css/view.css @@ -0,0 +1,32 @@ +#mainContent .nav-tabs li>a::before {height: 0;} +.instance-name h3 {display: inline-block; padding-left: 10px;} +.instance-name > span {font-size: 14px; font-weight: bolder; padding: 3px 3px; margin: 0 5px;} +.instance-panel .btn-group .btn {width: 60px;} +.instance-panel .btn-group i {font-size: 24px;} +.instance-status span {display: inline-block; margin-right: 10px; padding-bottom: 1px;} +.instance-status i {font-size: 18px;} +.instance-source span {display: inline-block; margin-right: 10px; width: 100px;} +#senior-app-desc a {color: #61be68} +a#serialDiff {color: #ff9800;} +#seniorAppPanel span.text-info:hover {color: #2196f3;} +.instance-log .panel-body {padding-bottom: 8px; padding-left: 10px;} +.instance-log .panel-body table {margin-bottom: 0;} +.usage-box {border-radius: 3px;} +.c-title {text-align: center; padding-top: 10px; font-size: 15px} +.progress-pie {margin: 10px auto;} +.instance-panel > div > a:last-child { margin-left: 8px;} +.solution-link {display: inline-block; margin-left: 20px;} + +#backup .panel {padding: 10px;} +#backup .panel .btn-toolbar{padding: 0 0 10px 0;} +#backup .panel .panel-heading {height: 50px;} +#backup table.table {margin-bottom: 20px; width: auto;} +#backup table th.actions {padding-left: 15px;} +#backup table td>.btn {padding-left: 5px; padding-right: 5px;} +#backup .btn.btn-info[disabled] {color: rgba(255,255,255,.7); background-color: #61be68;} + +#advance .panel-title span {padding-left: 10px;} +#advance hr {margin: 5px 20px;} +#advance .scalable-input{height: 26px;} +#advance .center{text-align: center;} +#advance .hide{display: none} diff --git a/module/instance/css/view.ui.css b/module/instance/css/view.ui.css new file mode 100644 index 0000000000..449c86ff4e --- /dev/null +++ b/module/instance/css/view.ui.css @@ -0,0 +1,8 @@ +.detail-header .icon-info-sign {color: rgba(var(--color-secondary-500-rgb),var(--tw-bg-opacity));} +#dynamicPager {justify-content: flex-end;} +#dynamicPager .ghost {color: rgb(131, 138, 157);} +#dynamicTable a {color: inherit;} +.progress-container {word-break: keep-all; white-space: nowrap;} +.progress-container i {margin: auto; margin-right: 6px;} +.progress-container .progress {margin: auto; width: 100%; height: .7rem; margin-left: 6px; margin-right: 15px;} +.label-dot {display: inline-block; width: 8px; height: 8px; padding: 0; line-height: 20px; text-indent: -9999em; border-radius: 50%;} \ No newline at end of file diff --git a/module/instance/js/backupsettings.js b/module/instance/js/backupsettings.js new file mode 100644 index 0000000000..34afcd2a27 --- /dev/null +++ b/module/instance/js/backupsettings.js @@ -0,0 +1,64 @@ +$(function() +{ + $(".form-time").datetimepicker({ + weekStart: 1, + todayBtn: 1, + autoclose: 1, + todayHighlight: 1, + startView: 1, + minView: 0, + maxView: 1, + forceParse: 0, + format: 'hh:ii' + }); + + $("input[type=checkbox][name='autoBackup[]']").on('change', function(event) + { + var enabled = $("input[type=checkbox][name='autoBackup[]']:checked").length > 0; + if(enabled) + { + $('.backup-settings').show(); + } + else + { + $('.backup-settings').hide(); + } + }); + + $("input[type=checkbox][name='autoBackup[]']").change(); + + $("#keepDays").on('input', function(event) + { + console.log(event,event.target.value); + event.target.value = event.target.value.replace(/[^\d]+/g,''); + if(Number(event.target.value) < 1) + { + event.target.value = 1; + } + + if(Number(event.target.value) > 30) + { + event.target.value = 30; + } + }); + + $('#backupSettingForm #saveSetting').on('click', function() + { + var settings = $('#backupSettingForm').serialize(); + $.post(createLink('instance', 'backupSettings', 'id=' + instanceID), settings).done(function(response) + { + var res = JSON.parse(response); + + if(res.result == 'success') + { + parent.window.$.closeModal(); + parent.window.bootAlert({title: instanceNotices.success, message:res.message}); + } + else + { + var errMsg = res.message instanceof Array ? res.message.join('
') : res.message; + parent.window.bootAlert({title: instanceNotices.fail, message: errMsg}); + } + }); + }); +}); diff --git a/module/instance/js/custominstall.js b/module/instance/js/custominstall.js new file mode 100644 index 0000000000..777a9bad9d --- /dev/null +++ b/module/instance/js/custominstall.js @@ -0,0 +1,7 @@ +$(function() +{ + $('input[type=number]').on('change', function(event) + { + if($(event.target).val() <= 1) $(event.target).val(1); + }); +}); diff --git a/module/instance/js/install.js b/module/instance/js/install.js new file mode 100644 index 0000000000..9871b0da8d --- /dev/null +++ b/module/instance/js/install.js @@ -0,0 +1,48 @@ +$(function() +{ + $('#installForm').on('submit', function(event) + { + event.preventDefault(); + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.installing + '
', + }); + + $.post($('#installForm').attr('action'), $('#installForm').serializeArray()).done(function(response) + { + loadingDialog.modal('hide'); + + let res = JSON.parse(response); + if(res.result == 'success') + { + config.onlybody = 'no'; + window.parent.$.apps.open(res.locate, 'space'); + } + else + { + alert(res.message); + } + }); + }); + + $("input[type=radio][name='dbType']").on('change', function(event) + { + if(event.target.value == 'sharedDB') + { + $("select[name=dbService]").closest('td').show(); + } + else + { + $("select[name=dbService]").closest('td').hide(); + } + }); + + $(".advanced a").on('click', function(event) + { + let downArrow = $(".advanced a .icon-chevron-double-down"); + let upArrow = $(".advanced a .icon-chevron-double-up"); + if(downArrow.length >0 ) downArrow.removeClass('icon-chevron-double-down').addClass('icon-chevron-double-up'); + if(upArrow.length >0 ) upArrow.removeClass('icon-chevron-double-up').addClass('icon-chevron-double-down'); + }); +}); diff --git a/module/instance/js/restoresettings.js b/module/instance/js/restoresettings.js new file mode 100644 index 0000000000..3363a9043c --- /dev/null +++ b/module/instance/js/restoresettings.js @@ -0,0 +1,49 @@ +$(function() +{ + $(".form-time").datetimepicker({ + weekStart: 1, + todayBtn: 1, + autoclose: 1, + todayHighlight: 1, + startView: 1, + minView: 0, + maxView: 1, + forceParse: 0, + format: 'hh:ii' + }); + + $("input[type=checkbox][name='autoRestore[]']").on('change', function(event) + { + var enabled = $("input[type=checkbox][name='autoRestore[]']:checked").length > 0; + if(enabled) + { + $('.restore-settings').show(); + } + else + { + $('.restore-settings').hide(); + } + }); + + $("input[type=checkbox][name='autoRestore[]']").change(); + + $('#restoreSettingForm #saveSetting').on('click', function() + { + var settings = $('#restoreSettingForm').serialize(); + $.post(createLink('instance', 'restoreSettings', 'id=' + instanceID), settings).done(function(response) + { + var res = JSON.parse(response); + + if(res.result == 'success') + { + parent.window.$.closeModal(); + parent.window.bootAlert({title: instanceNotices.success, message:res.message}); + } + else + { + var errMsg = res.message instanceof Array ? res.message.join('
') : res.message; + parent.window.bootAlert({title: instanceNotices.fail, message: errMsg}); + } + }); + }); +}); diff --git a/module/instance/js/tosenior.js b/module/instance/js/tosenior.js new file mode 100644 index 0000000000..7d38f3d821 --- /dev/null +++ b/module/instance/js/tosenior.js @@ -0,0 +1,14 @@ +$(function(){ + $("#toSeniorForm input#readtrue").on('change', function() + { + if($('#toSeniorForm input#readtrue:checked').length > 0) + { + $('#toSeniorForm button[type=submit]').attr('disabled', false); + } + else + { + $('#toSeniorForm button[type=submit]').attr('disabled', true); + } + }); + $("#toSeniorForm input#readtrue").change(); +}); diff --git a/module/instance/js/upgrade.js b/module/instance/js/upgrade.js new file mode 100644 index 0000000000..ee3d82b731 --- /dev/null +++ b/module/instance/js/upgrade.js @@ -0,0 +1,30 @@ +$(function() +{ + $('#upgradeForm').on('submit', function(event) + { + event.preventDefault(); + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.upgrading + '
', + }); + + $.post($('#upgradeForm').attr('action'), $('#upgradeForm').serializeArray()).done(function(response) + { + loadingDialog.modal('hide'); + window.parent.$('#triggerModal').modal('hide'); + + let res = JSON.parse(response); + if(res.result == 'success') + { + config.onlybody = 'no'; + window.parent.$.apps.reload('space', createLink('space', 'browse'), 'space'); + window.parent.location.reload(); + } + else + { + alert(res.message); + } + }); + }); +}); diff --git a/module/instance/js/view.js b/module/instance/js/view.js new file mode 100644 index 0000000000..84ab6378df --- /dev/null +++ b/module/instance/js/view.js @@ -0,0 +1,502 @@ +$(function() +{ + $('#memBtn').on('click', function(event) + { + bootbox.confirm(instanceNotices.adjustMemory, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.adjusting + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxAdjustMemory', 'id=' + id, 'json'); + var adjustData = {memory_kb: $('select[name=memory_kb]').val()}; + $.post(url, adjustData).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.parent.$.apps.open(createLink('instance', 'view', 'id=' + id), 'space'); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + + }); + }); + + $('#ldapBtn').on('click', function(event) + { + bootbox.confirm(instanceNotices.switchLDAP, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.switching + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxSwitchLDAP', 'id=' + id, 'json'); + var postData = {enableLDAP: $("[name='enableLDAP[]']:checked").length > 0}; + $.post(url, postData).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.parent.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('#smtpBtn').on('click', function(event) + { + bootbox.confirm(instanceNotices.switchSMTP, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.switching + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxSwitchSMTP', 'id=' + id, 'json'); + var postData = {enableSMTP: $("[name='enableSMTP[]']:checked").length > 0}; + $.post(url, postData).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.parent.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('#customBtn').on('click', function(event) + { + var errors = ''; + $("#customForm").find("input[type=text]").each(function(index, item) + { + if($(item).val().trim().length == 0) + { + errors += $(item).attr('placeholder') + instanceNotices.required + '
'; + } + }); + if(errors.length > 0) + { + bootbox.alert( + { + title: instanceNotices.error, + message: errors, + }); + return; + } + + bootbox.confirm(instanceNotices.confirmCustom, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.setting + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxUpdateCustom', 'id=' + id, 'json'); + var formData = $('#customForm').serializeArray(); + var customData = {}; + formData.forEach(function(item, index){ + customData[item.name] = item.value; + }); + + $.post(url, customData).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.parent.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + + }); + }); + + $('.btn-uninstall').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmUninstall, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.uninstalling + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxUninstall', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.parent.$.apps.open(createLink('space', 'browse'), 'space'); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('.btn-start').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmStart, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.starting + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxStart', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('.btn-stop').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmStop, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.stopping + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxStop', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('.btn-backup').on('click', function(event) + { + $('#confirmRestore').modal('hide'); + bootbox.confirm(instanceNotices.confirmBackup, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.backuping + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'ajaxBackup', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + bootbox.alert( + { + title: instanceNotices.success, + message: res.message, + callback: function(){window.location.reload();} + }); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('.btn-restore').click(function() + { + let backupName = $(this).attr('backup-name'); + let instanceID = $(this).attr('instance-id'); + $('#confirmRestore #submitRestore').data('backup-name', backupName); + $('#confirmRestore #submitRestore').data('instance-id', instanceID); + $('#confirmRestore').modal('show'); + }); + + $('#submitRestore').on('click', function(event) + { + $('#confirmRestore').modal('hide'); + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.restoring + '
', + }); + + var instanceID = $(event.target).closest('button').data('instance-id'); + var backupName = $(event.target).closest('button').data('backup-name'); + var url = createLink('instance', 'ajaxRestore', '', 'json'); + $.post(url, { instanceID, backupName }).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + bootbox.alert( + { + title: instanceNotices.success, + message: res.message, + callback: function(){window.location.reload();} + }); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + + $('.btn-delete-backup').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmDelete, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.deleting + '
', + }); + + var id = $(event.target).closest('button').attr('backup-id'); + var url = createLink('instance', 'ajaxDeleteBackup', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + var res = JSON.parse(response); + if(res.result == 'success') + { + bootbox.alert( + { + title: instanceNotices.success, + message: res.message, + callback: function(){window.location.reload();} + }); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('button.db-login').on('click', function(event) + { + var dbName = $(event.target).data('db-name'); + var dbType = $(event.target).data('db-type'); + var instanceID = $(event.target).data('id'); + + $.post(createLink('instance', 'ajaxDBAuthUrl'), {dbName, dbType, instanceID}).done(function(res) + { + var response = JSON.parse(res); + if(response.result == 'success') + { + window.parent.open(response.data.url, 'Adminer'); + } + else + { + bootbox.alert(response.message); + } + }); + }); + + var enableTimer = true; + window.parent.$(window.parent.document).on('showapp', function(event, app) + { + enableTimer = app.code == 'space'; + }); + + setInterval(function() + { + if(!enableTimer) return; + + var statusURL = createLink('instance', 'ajaxStatus'); + $.post(statusURL, {idList: instanceIdList}).done(function(response) + { + var res = JSON.parse(response); + if(res.result == 'success' && res.data instanceof Array) + { + res.data.forEach(function(instance) + { + if($(".instance-status[instance-id=" + instance.id + "]").data('status') != instance.status) window.location.reload(); + }); + } + if(res.locate) window.parent.location.href = res.locate; + }); + }, 1000 * 5); + + $('[data-toggle="tooltip"]').tooltip(); + + /* Count down for demo instance. */ + setInterval(function() + { + var nowSeconds = Math.round((new Date).getTime() / 1000); + $('.count-down').each(function(index, item) + { + var createdAt = $(item).data('created-at'); + var passSeconds = nowSeconds - createdAt; + var leftSeconds = (demoAppLife ? demoAppLife : 30) * 60 - passSeconds; + + if(leftSeconds < 0) + { + window.parent.location.href = createLink('space', 'browse'); + } + else + { + var minutes = Math.floor(leftSeconds / 60); + var seconds = Math.round(leftSeconds % 60); + $(item).find('.left-time').text(('0' + minutes).slice(-2) + ':' + ('0' + seconds).slice(-2)); + } + + }) + }, 1000) + + $('#replicas-edit').on('click', function() + { + $('#replicas-input').show() + $('#replicas-text').hide() + $('#replicas-save').show() + $('#replicas-edit').hide() + }) + + $('#replicas-save').on('click', function() + { + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.adjusting + '
', + }); + + var id = $(event.target).closest('button').attr('instance-id'); + var url = createLink('instance', 'replicas', 'id=' + id, 'json'); + var adjustData = {replicas: $('#replicas-input').val()}; + $.post(url, adjustData).done(function(response) + { + + var res = JSON.parse(response); + if(res.result == 'success') + { + window.parent.location.reload(); + } + else + { + loadingDialog.modal('hide'); + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }) +}) diff --git a/module/instance/js/view.ui.js b/module/instance/js/view.ui.js new file mode 100644 index 0000000000..b2327d1167 --- /dev/null +++ b/module/instance/js/view.ui.js @@ -0,0 +1,43 @@ +window.openAdminer = function() +{ + var dbName = $(this).data('dbname'); + var dbType = $(this).data('dbtype'); + var instanceID = $(this).data('id'); + + $.ajaxSubmit + ( + { + url: $.createLink('instance', 'ajaxDBAuthUrl'), + method: 'POST', data: {dbName, dbType, instanceID}, + onSuccess: (response) => + { + if(response.result == 'success') + { + window.open(response.data.url, 'Adminer'); + } + else + { + zui.Modal.alert(response.message); + } + } + } + ); +} + +var reloadTimes = 0; +window.afterPageUpdate = function() +{ + setTimeout(function() + { + if(reloadTimes > 20) return; + if($('#statusTD').data('reload') === true || $('#memoryRate').data('load') == true) + { + reloadTimes++; + fetchContent({url: $.createLink('instance', 'view', 'id=' + instanceID), + selector: '#instanceInfoContainer', + id: 'instanceInfoContainer', + target: '#instanceInfoContainer', + }); + } + }, 4000); +} diff --git a/module/instance/lang/zh-cn.php b/module/instance/lang/zh-cn.php new file mode 100644 index 0000000000..e4f4b136fc --- /dev/null +++ b/module/instance/lang/zh-cn.php @@ -0,0 +1,333 @@ +instance = new stdclass; +$lang->instance->name = '名称'; +$lang->instance->appName = '应用类型'; +$lang->instance->version = '版本'; +$lang->instance->status = '状态'; +$lang->instance->cpu = 'CPU'; +$lang->instance->mem = '内存'; +$lang->instance->space = '空间'; +$lang->instance->domain = '域名'; +$lang->instance->visitIP = '访问方式'; +$lang->instance->dbType = '数据库'; +$lang->instance->advanceOption = '高级选项'; +$lang->instance->baseInfo = '基本信息'; +$lang->instance->backupAndRestore = '备份'; +$lang->instance->advance = '高级'; +$lang->instance->enableLDAP = '启用LDAP'; +$lang->instance->linkLDAP = '集成LDAP'; +$lang->instance->enableSMTP = '启用SMTP'; +$lang->instance->customSetting = '自定义配置'; +$lang->instance->upgradeToSenior = '升级到高级版'; +$lang->instance->updateDomain = '更新域名'; +$lang->instance->updateLog = '更新日志'; +$lang->instance->start = '启动'; +$lang->instance->restart = '重启'; +$lang->instance->stop = '关闭'; +$lang->instance->install = '安装'; +$lang->instance->update = '更新'; +$lang->instance->upgrade = '升级'; +$lang->instance->customInstall = '自定义安装'; +$lang->instance->uninstall = '删除'; +$lang->instance->visit = '访问'; +$lang->instance->editName = '修改名称'; +$lang->instance->cpuCore = '核'; +$lang->instance->scalable = '应用水平扩容'; +$lang->instance->change = '修改'; + +$lang->instance->systemLDAPInactive = '未开启系统LDAP'; +$lang->instance->toSystemLDAP = '去启用'; + +$lang->instance->enableSMTP = '启用SMTP'; +$lang->instance->systemSMTPInactive = '未开启系统SMTP'; +$lang->instance->toSystemSMTP = '去启用'; + + +$lang->instance->serviceInfo = '服务信息'; +$lang->instance->appTemplate = '应用模板'; +$lang->instance->source = '来源'; +$lang->instance->installBy = '创建者'; +$lang->instance->installAt = '创建时间'; +$lang->instance->runDuration = '已运行'; +$lang->instance->defaultAccount = '默认用户'; +$lang->instance->defaultPassword = '默认密码'; +$lang->instance->operationLog = '操作记录'; +$lang->instance->installedService = '已安装服务'; +$lang->instance->runningService = '运行中的服务'; +$lang->instance->installApp = '安装应用'; +$lang->instance->cpuUsage = 'CPU占用'; +$lang->instance->memUsage = '内存占用'; +$lang->instance->memTotal = '(共%s)'; +$lang->instance->currentMemory = '当前内存'; +$lang->instance->adjustMem = '调整内存'; +$lang->instance->setting = '设置'; +$lang->instance->saveSetting = '保存设置'; +$lang->instance->leftTime = '剩余'; +$lang->instance->switchTo = '升级到'; +$lang->instance->or = '或'; +$lang->instance->hasRead = '已阅读'; +$lang->instance->stopInstanceTips = '关闭服务后才能升级到高级版!'; + +$lang->instance->dbTypes = array(); +$lang->instance->dbTypes['sharedDB'] = '共享数据库'; +$lang->instance->dbTypes['unsharedDB'] = '独享数据库'; + +$lang->instance->backup = new stdclass; +$lang->instance->backup->common = '备份'; +$lang->instance->backup->date = '备份时间'; +$lang->instance->backup->operator = '备份人'; +$lang->instance->backup->type = '备份类型'; +$lang->instance->backup->backupStatus = '备份状态'; +$lang->instance->backup->restoreStatus = '回滚状态'; +$lang->instance->backup->restoreOperator = '回滚人'; +$lang->instance->backup->restoreTime = '回滚时间'; +$lang->instance->backup->action = '操作'; +$lang->instance->backup->restore = '回滚'; +$lang->instance->backup->restoreInfo = '回滚信息'; +$lang->instance->backup->delete = '删除'; +$lang->instance->backup->rebackup = '重试备份'; +$lang->instance->backup->create = '创建备份'; +$lang->instance->backup->database = '数据库'; +$lang->instance->backup->dbType = '类型'; +$lang->instance->backup->dbName = '名称'; +$lang->instance->backup->dbStatus = '状态'; +$lang->instance->backup->dbSpentSeconds = '耗时(秒)'; +$lang->instance->backup->dbSize = '大小'; +$lang->instance->backup->volumne = '数据卷'; +$lang->instance->backup->volName = '名称'; +$lang->instance->backup->volMountName = '挂载目录'; +$lang->instance->backup->volStatus = '状态'; +$lang->instance->backup->volSpentSeconds = '耗时(秒)'; +$lang->instance->backup->volSize = '大小'; +$lang->instance->backup->lastRestore = '上次回滚'; +$lang->instance->backup->restoreDate = '回滚时间'; +$lang->instance->backup->latestBackupAt = '上次备份时间'; +$lang->instance->backup->backupBeforeRestore = '回滚前建议您先备份!'; +$lang->instance->backup->enableAutoBackup = '开启自动备份'; +$lang->instance->backup->autoBackup = '自动备份'; +$lang->instance->backup->cycleDays = '备份周期'; +$lang->instance->backup->backupTime = '备份时间'; +$lang->instance->backup->keepDays = '保留天数'; +$lang->instance->backup->keepDayRange = '请输入1~30之间的整数'; +$lang->instance->backup->firstStartTime = '%s 首次备份将于%s 执行'; +$lang->instance->backup->invalidTime = '无效的时间'; +$lang->instance->backup->disableAutoBackup = '自动备份已关闭'; +$lang->instance->backup->keepBackupBySystem = '备份数据超过1条时系统才会删除过期的自动备份数据。'; + +$lang->instance->backup->cycleList[1] = '每日'; + +$lang->instance->backup->operators = array(); +$lang->instance->backup->operators['auto'] = '自动备份'; + +$lang->instance->backup->statusList = array(); +$lang->instance->backup->statusList['success'] = '成功'; +$lang->instance->backup->statusList['failed'] = '失败'; +$lang->instance->backup->statusList['pending'] = '等待中'; +$lang->instance->backup->statusList['processing'] = '备份中'; +$lang->instance->backup->statusList['inprogress'] = '备份中'; +$lang->instance->backup->statusList['completed'] = '完成'; +$lang->instance->backup->statusList['executedFailed'] = '失败'; +$lang->instance->backup->statusList['uploading'] = '上传中'; +$lang->instance->backup->statusList['deleting'] = '删除中'; +$lang->instance->backup->statusList['uploadFailed'] = '上传失败'; +$lang->instance->backup->statusList['downloading'] = '下载中'; +$lang->instance->backup->statusList['downloadFailed'] = '下载失败'; + +$lang->instance->restore = new stdclass; +$lang->instance->restore->autoRestore = '自动还原'; +$lang->instance->restore->enableAutoRestore = '开启自动还原'; +$lang->instance->restore->cycleDays = '还原周期'; +$lang->instance->restore->restoreTime = '还原时间'; +$lang->instance->restore->invalidTime = '无效的时间'; +$lang->instance->restore->disableAutoRestore = '自动还原已关闭'; +$lang->instance->restore->firstStartTime = '%s 首次自动还原将于%s 执行'; + +$lang->instance->restore->cycleList[1] = '每日'; + +$lang->instance->restore->statusList = array(); +$lang->instance->restore->statusList['pending'] = '等待中'; +$lang->instance->restore->statusList['inprogress'] = '进行中'; +$lang->instance->restore->statusList['completed'] = '完成'; +$lang->instance->restore->statusList['failed'] = '失败'; + +$lang->instance->dbList = '数据库'; +$lang->instance->dbName = '名称'; +$lang->instance->dbStatus = '状态'; +$lang->instance->dbType = '类型'; +$lang->instance->action = '操作'; +$lang->instance->management = '管理'; +$lang->instance->dbReady = '正常'; +$lang->instance->dbWaiting = '等待就绪'; + +$lang->instance->log = new stdclass; +$lang->instance->log->date = '日期'; +$lang->instance->log->message = '内容'; + +$lang->instance->actionList = array(); +$lang->instance->actionList['install'] = '安装了%s'; +$lang->instance->actionList['uninstall'] = '删除了%s'; +$lang->instance->actionList['start'] = '启动了%s'; +$lang->instance->actionList['stop'] = '关闭了%s'; +$lang->instance->actionList['editname'] = '修改了名称'; +$lang->instance->actionList['upgrade'] = '升级了%s'; +$lang->instance->actionList['backup'] = '备份了%s'; +$lang->instance->actionList['restore'] = '回滚了%s'; +$lang->instance->actionList['adjustmemory'] = '调整 %s 的内存到 %s'; +$lang->instance->actionList['adjustreplicas'] = '调整 %s 的副本数到 %s'; +$lang->instance->actionList['enableldap'] = '启用了LDAP'; +$lang->instance->actionList['disableldap'] = '禁用了LDAP'; +$lang->instance->actionList['enablesmtp'] = '启用了SMTP'; +$lang->instance->actionList['disablesmtp'] = '禁用了SMTP'; +$lang->instance->actionList['updatecustom'] = '修改了自定义配置'; +$lang->instance->actionList['tosenior'] = '升级服务到高级版'; +$lang->instance->actionList['saveautobackupsettings'] = '修改了自动备份设置'; +$lang->instance->actionList['saveautorestoresettings'] = '修改了自动还原设置'; +$lang->instance->actionList['autobackup'] = '系统执行了自动备份'; +$lang->instance->actionList['autorestore'] = '系统执行了自动还原'; +$lang->instance->actionList['deleteexpiredbackup'] = '系统删除了过期的自动备份'; + +$lang->instance->sourceList = array(); +$lang->instance->sourceList['cloud'] = '渠成公共市场'; +$lang->instance->sourceList['local'] = '本地市场'; + +$lang->instance->channelList = array(); +$lang->instance->channelList['test'] = '测试版'; +$lang->instance->channelList['stable'] = '稳定版'; + +$lang->instance->statusList = array(); +$lang->instance->statusList['installationFail'] = '安装失败'; +$lang->instance->statusList['creating'] = '创建中'; +$lang->instance->statusList['initializing'] = '初始化'; +$lang->instance->statusList['pulling'] = '下载中'; +$lang->instance->statusList['startup'] = '启动中'; +$lang->instance->statusList['starting'] = '启动中'; +$lang->instance->statusList['running'] = '运行中'; +$lang->instance->statusList['suspending'] = '暂停中'; +$lang->instance->statusList['suspended'] = '已暂停'; +$lang->instance->statusList['installing'] = '安装中'; +$lang->instance->statusList['uninstalling'] = '删除中'; +$lang->instance->statusList['stopping'] = '关闭中'; +$lang->instance->statusList['stopped'] = '已关闭'; +$lang->instance->statusList['destroying'] = '销毁中'; +$lang->instance->statusList['destroyed'] = '已销毁'; +$lang->instance->statusList['abnormal'] = '异常'; +$lang->instance->statusList['upgrading'] = '更新中'; +$lang->instance->statusList['unknown'] = '未知'; + +$lang->instance->htmlStatusesClass = array(); +$lang->instance->htmlStatusesClass['running'] = 'success'; +$lang->instance->htmlStatusesClass['stopped'] = 'default'; +$lang->instance->htmlStatusesClass['abnormal'] = 'danger'; +$lang->instance->htmlStatusesClass['installationFail'] = 'danger'; +$lang->instance->htmlStatusesClass['busy'] = "warning"; + +$lang->instance->memOptions = array(); +$lang->instance->memOptions[128 * 1024] = '128MB'; +$lang->instance->memOptions[256 * 1024] = '256MB'; +$lang->instance->memOptions[512 * 1024] = '512MB'; +$lang->instance->memOptions[1024 * 1024] = '1GB'; +$lang->instance->memOptions[2048 * 1024] = '2GB'; +$lang->instance->memOptions[4096 * 1024] = '4GB'; +$lang->instance->memOptions[8192 * 1024] = '8GB'; +$lang->instance->memOptions[16384 * 1024] = '16GB'; +$lang->instance->memOptions[32768 * 1024] = '32GB'; + +$lang->instance->componentFields = array(); +$lang->instance->componentFields['replicas'] = '副本数'; +$lang->instance->componentFields['cpu_limit'] = 'CPU'; +$lang->instance->componentFields['mem_limit'] = '内存'; + +$lang->instance->notices = array(); +$lang->instance->notices['success'] = '成功'; +$lang->instance->notices['fail'] = '失败'; +$lang->instance->notices['error'] = '错误'; +$lang->instance->notices['confirmStart'] = '确定启动该应用吗?'; +$lang->instance->notices['confirmStop'] = '确定关闭该应用吗?'; +$lang->instance->notices['confirmUninstall'] = '确定删除该应用吗?'; +$lang->instance->notices['startSuccess'] = '启动成功'; +$lang->instance->notices['startFail'] = '启动失败'; +$lang->instance->notices['stopSuccess'] = '关闭成功'; +$lang->instance->notices['stopFail'] = '关闭失败'; +$lang->instance->notices['uninstallSuccess'] = '删除成功'; +$lang->instance->notices['uninstallFail'] = '删除失败'; +$lang->instance->notices['installSuccess'] = '安装成功'; +$lang->instance->notices['installFail'] = '安装失败'; +$lang->instance->notices['upgradeSuccess'] = '升级成功'; +$lang->instance->notices['upgradeFail'] = '升级失败'; +$lang->instance->notices['backupSuccess'] = '备份任务已提交'; +$lang->instance->notices['backupFail'] = '备份失败'; +$lang->instance->notices['restoreSuccess'] = '回滚任务已提交'; +$lang->instance->notices['restoreFail'] = '回滚失败'; +$lang->instance->notices['deleteSuccess'] = '删除成功'; +$lang->instance->notices['deleteFail'] = '删除失败'; +$lang->instance->notices['starting'] = '启动中,请稍候...'; +$lang->instance->notices['stopping'] = '关闭中,请稍候...'; +$lang->instance->notices['installing'] = '安装中,请稍候...'; +$lang->instance->notices['uninstalling'] = '删除中,请稍候...'; +$lang->instance->notices['upgrading'] = '升级中,请稍候...'; +$lang->instance->notices['backuping'] = '备份中,请稍候...'; +$lang->instance->notices['restoring'] = '回滚中,请稍候...'; +$lang->instance->notices['deleting'] = '删除中,请稍候...'; +$lang->instance->notices['adjusting'] = '调整中,请稍候...'; +$lang->instance->notices['switching'] = '提交中,请稍候...'; +$lang->instance->notices['setting'] = '提交中,请稍候...'; +$lang->instance->notices['confirmInstall'] = '确定要安装(%s)?'; +$lang->instance->notices['confirmUpgrade'] = '确定要升级到最新版吗?'; +$lang->instance->notices['confirmBackup'] = '确定要备份吗?'; +$lang->instance->notices['confirmRestore'] = '本操作将用备份的数据覆盖当前的数据,确定要回滚吗?'; +$lang->instance->notices['confirmDelete'] = '确定要删除该备份数据吗?'; +$lang->instance->notices['adjustMemory'] = '确定要调整中内存吗?'; +$lang->instance->notices['switchLDAP'] = '修改LDAP会重启服务,确定要修改LDAP吗?'; +$lang->instance->notices['enableLDAPFailed'] = '启用LDAP失败'; +$lang->instance->notices['disableLDAPFailed'] = '禁用LDAP失败'; +$lang->instance->notices['enableLDAPSuccess'] = '启用LDAP成功'; +$lang->instance->notices['disableLDAPSuccess'] = '禁用LDAP成功'; +$lang->instance->notices['switchSMTP'] = '修改SMTP会重启服务,确定要修改SMTP吗?'; +$lang->instance->notices['enableSMTPFailed'] = '启用SMTP失败'; +$lang->instance->notices['disableSMTPFailed'] = '禁用SMTP失败'; +$lang->instance->notices['enableSMTPSuccess'] = '启用SMTP成功'; +$lang->instance->notices['disableSMTPSuccess'] = '禁用SMTP成功'; +$lang->instance->notices['confirmCustom'] = '修改自定义配置后服务将自动重启以使配置生效。'; +$lang->instance->notices['required'] = '不能为空'; + +$lang->instance->nameChangeTo = ' %s 修改为 %s 。'; +$lang->instance->versionChangeTo = ' %s 升级为 %s 。'; +$lang->instance->toSeniorSerial = '从 %s 升级到 %s 。'; +$lang->instance->adjustMemorySize = '建议调整内存到 %s 。'; +$lang->instance->enableAutoBackup = '开启自动备份'; +$lang->instance->disableAutoBackup = '关闭自动备份'; + +$lang->instance->instanceNotExists = '服务不存在'; +$lang->instance->caplicasTooSmall = '副本数不能小于1'; +$lang->instance->empty = '暂无服务'; +$lang->instance->noComponent = '无组件,点击'; +$lang->instance->noHigherVersion = '未找到更高版本!'; +$lang->instance->backupOnlyRunning = '运行状态才能备份'; +$lang->instance->restoreOnlyRunning = '运行状态才能回滚'; +$lang->instance->howToSelectDB = '如何选择?'; +$lang->instance->appLifeTip = 'demo账号安装的应用有30分钟限制,30分钟后自动删除。'; +$lang->instance->serialDiff = '查看版本区别'; +$lang->instance->descOfSwitchSerial = '您当前使用的是%s,想要体验更多高级功能,可升级至%s。'; +$lang->instance->toSeniorAttention = '重要提示'; +$lang->instance->toSeniorTips = ""; + +$lang->instance->errors = new stdclass; +$lang->instance->errors->domainLength = '域名长度必须介于2-20字符之间'; +$lang->instance->errors->domainExists = '域名已被占用,请使用其它域名。'; +$lang->instance->errors->wrongDomainCharacter = '域名只能是小写英文字母和数字'; +$lang->instance->errors->noAppInfo = '获取应用数据失败,请稍候重试。'; +$lang->instance->errors->notEnoughResource = '集群资源不足'; +$lang->instance->errors->notEnoughMemory = '%s 应用需要 %s 内存,当前可用内存%s ,还需要 %s,请扩充内存资源或删除其他服务后重试!'; +$lang->instance->errors->restoreRunning = '当前回滚正在进行中,请等待当前回滚完成。'; +$lang->instance->errors->noBackup = '无备份数据,'; +$lang->instance->errors->wrongRequestData = '提交的数据有误,请刷新页面后重试。'; +$lang->instance->errors->noDBList = '无数据库或不可访问'; +$lang->instance->errors->notFoundDB = '找不到该数据库'; +$lang->instance->errors->dbNameIsEmpty = '数据库名为空'; +$lang->instance->errors->failToAdjustMemory = '调整内存失败'; +$lang->instance->errors->switchLDAPFailed = '修改LDAP设置失败'; +$lang->instance->errors->switchSMTPFailed = '修改SMTP设置失败'; +$lang->instance->errors->updateCustomFailed = '修改自定义配置失败'; +$lang->instance->errors->failToSenior = '升级到高级版失败'; +$lang->instance->errors->failedToUpdateDomain = '更新域名失败'; diff --git a/module/instance/model.php b/module/instance/model.php new file mode 100644 index 0000000000..4d05b6a972 --- /dev/null +++ b/module/instance/model.php @@ -0,0 +1,2058 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.com + */ +class InstanceModel extends model +{ + /** + * Construct method: load CNE model, and set primaryDomain. + * + * @access public + * @return void + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('cne'); + $this->loadModel('action'); + } + + /** + * Get by id. + * + * @param int $id + * @access public + * @return object|null + */ + public function getByID($id) + { + $instance = $this->dao->select('*')->from(TABLE_INSTANCE) + ->where('id')->eq($id) + ->andWhere('deleted')->eq(0) + ->fetch(); + if(!$instance) return null; + + $instance->spaceData = $this->dao->select('*')->from(TABLE_SPACE)->where('id')->eq($instance->space)->fetch(); + if($instance->solution) + { + $solution = $this->dao->select('*')->from(TABLE_SOLUTION)->where('id')->eq($instance->solution)->fetch(); + $instance->solutionData = $solution; + } + + return $instance; + } + + /** + * Get by id list. + * + * @param array $idList + * @access public + * @return array + */ + public function getByIdList($idList) + { + $instances = $this->dao->select('*')->from(TABLE_INSTANCE) + ->where('id')->in($idList) + ->andWhere('deleted')->eq(0) + ->fetchAll('id'); + + $spaces = $this->dao->select('*')->from(TABLE_SPACE)->where('deleted')->eq(0)->andWhere('id')->in(array_column($instances, 'space'))->fetchAll('id'); + foreach($instances as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); + + $solutionIDList = array_column($instances, 'solution'); + $solutions = $this->dao->select('*')->from(TABLE_SOLUTION)->where('id')->in($solutionIDList)->fetchAll('id'); + foreach($instances as $instance) $instance->solutionData = zget($solutions, $instance->solution, new stdclass); + + return $instances; + } + + /** + * Get instance list by solution. + * + * @param object $solution + * @param string $chart + * @access public + * @return array + */ + public function instanceOfSolution($solution, $chart) + { + $instance = $this->dao->select('id')->from(TABLE_INSTANCE)->where('deleted')->eq(0) + ->andWhere('solution')->eq($solution->id) + ->andWhere('chart')->eq($chart) + ->fetch(); + + if($instance) return $this->getByID($instance->id); + + return array(); + } + + /** + * Get instances list by account. + * + * @param string $account + * @param object $pager + * @access public + * @return array + */ + public function getByAccount($account = '', $pager = null, $pinned = '', $searchParam = '', $status = 'all') + { + $defaultSpace = $this->loadModel('space')->defaultSpace($account ? $account : $this->app->user->account); + + $instances = $this->dao->select('instance.*')->from(TABLE_INSTANCE)->alias('instance') + ->leftJoin(TABLE_SPACE)->alias('space')->on('space.id=instance.space') + ->where('instance.deleted')->eq(0) + ->andWhere('space.id')->eq($defaultSpace->id) + ->beginIF($account)->andWhere('space.owner')->eq($account)->fi() + ->beginIF($pinned)->andWhere('instance.pinned')->eq((int)$pinned)->fi() + ->beginIF($searchParam)->andWhere('instance.name')->like("%{$searchParam}%")->fi() + ->beginIF($status != 'all')->andWhere('instance.status')->eq($status)->fi() + ->orderBy('instance.id desc') + ->beginIF($pager)->page($pager)->fi() + ->fetchAll('id'); + + $spaces = $this->dao->select('*')->from(TABLE_SPACE) + ->where('deleted')->eq(0) + ->andWhere('id')->in(array_column($instances, 'space')) + ->fetchAll('id'); + + foreach($instances as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); + + $solutionIDList = array_column($instances, 'solution'); + $solutions = $this->dao->select('*')->from(TABLE_SOLUTION)->where('id')->in($solutionIDList)->fetchAll('id'); + foreach($instances as $instance) $instance->solutionData = zget($solutions, $instance->solution, new stdclass); + + return $instances; + } + + /** + * Get instance list that has been enabled LDAP. + * + * @access public + * @return array + */ + public function getListEnabledLDAP() + { + $instances = $this->dao->select('*')->from(TABLE_INSTANCE)->where('deleted')->eq(0)->andWhere('length(ldapSnippetName) > 0')->fetchAll('id'); + + $spaces = $this->dao->select('*')->from(TABLE_SPACE) + ->where('deleted')->eq(0) + ->andWhere('id')->in(array_column($instances, 'space')) + ->fetchAll('id'); + + foreach($instances as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); + + return $instances; + } + + /** + * Count instance which is enabled LDAP. + * + * @access public + * @return int + */ + public function countLDAP() + { + $count = $this->dao->select('count(*) as ldapQty')->from(TABLE_INSTANCE)->where('deleted')->eq(0)->andWhere('length(ldapSnippetName) > 0')->fetch(); + return $count->ldapQty; + } + + /** + * Count instance which is enabled SMTP. + * + * @access public + * @return int + */ + public function countSMTP() + { + $count = $this->dao->select('count(*) as Qty')->from(TABLE_INSTANCE)->where('deleted')->eq(0)->andWhere('length(smtpSnippetName) > 0')->fetch(); + return $count->Qty; + } + + /** + * Get quantity of total installed services. + * + * @access public + * @return int + */ + public function totalServices() + { + $defaultSpace = $this->loadModel('space')->defaultSpace($this->app->user->account); + + $count = $this->dao->select('count(*) as qty')->from(TABLE_INSTANCE)->alias('instance') + ->leftJoin(TABLE_SPACE)->alias('space')->on('space.id=instance.space') + ->where('instance.deleted')->eq(0) + ->andWhere('space.id')->eq($defaultSpace->id) + ->fetch(); + + return $count->qty; + } + + /** + * Pin instance to navigation page or Unpin instance. + * + * @param int $instanceID + * @access public + * @return void + */ + public function pinToggle($instanceID) + { + $instance = $this->getByID($instanceID); + $pinned = $instance->pinned == '0' ? '1' : '0'; + $this->dao->update(TABLE_INSTANCE)->set('pinned')->eq($pinned)->where('id')->eq($instanceID)->exec(); + } + + /** + * Switch LDAP between enable and disable. + * + * @param object $instance + * @param bool $enableSMTP + * @access public + * @return bool + */ + public function switchSMTP($instance, $enableSMTP) + { + $this->loadModel('system'); + $snippetName = $this->system->smtpSnippetName(); + + $settings = new stdclass; + if($enableSMTP) + { + $settings->settings_snippets = [$snippetName]; + } + else + { + $settings->settings_snippets = [$snippetName . '-']; + } + + $success = $this->cne->updateConfig($instance, $settings); + if(!$success) + { + dao::$errors[] = $this->lang->instance->errors->failToAdjustMemory; + return false; + } + + if($enableSMTP) + { + $this->dao->update(TABLE_INSTANCE)->set('smtpSnippetName')->eq($snippetName)->where('id')->eq($instance->id)->exec(); + $this->action->create('instance', $instance->id, 'enableSMTP'); + } + else + { + $this->dao->update(TABLE_INSTANCE)->set('smtpSnippetName')->eq('')->where('id')->eq($instance->id)->exec(); + $this->action->create('instance', $instance->id, 'disableSMTP'); + } + + return true; + } + + /** + * Switch LDAP between enable and disable. + * + * @param object $instance + * @param bool $enableLDAP + * @access public + * @return bool + */ + public function switchLDAP($instance, $enableLDAP) + { + $this->loadModel('system'); + $snippetName = $this->system->ldapSnippetName(); + + $settings = new stdclass; + $settings->force_restart = true; + if($enableLDAP) + { + $settings->settings_snippets = [$snippetName]; + } + else + { + $settings->settings_snippets = [$snippetName . '-']; + } + + $success = $this->cne->updateConfig($instance, $settings); + if(!$success) + { + dao::$errors[] = $this->lang->instance->errors->failToAdjustMemory; + return false; + } + + if($enableLDAP) + { + $this->dao->update(TABLE_INSTANCE)->set('ldapSnippetName')->eq($snippetName)->where('id')->eq($instance->id)->exec(); + $this->action->create('instance', $instance->id, 'enableLDAP'); + } + else + { + $this->dao->update(TABLE_INSTANCE)->set('ldapSnippetName')->eq('')->where('id')->eq($instance->id)->exec(); + $this->action->create('instance', $instance->id, 'disableLDAP'); + } + + return true; + } + + /** + * Count old domain. + * + * @access public + * @return int + */ + public function countOldDomain() + { + /* REPLACE(domain, .$sysDomain, '') used for special case: new domain is a.com and old domain is b.a.com, domain of instance is c.b.a.com, */ + $sysDomain = $this->loadModel('cne')->sysDomain(); + $result = $this->dao->select('count(*) as qty')->from(TABLE_INSTANCE) + ->where('deleted')->eq(0) + ->andWhere("REPLACE(domain, '.$sysDomain', '')")->like("%.%") + ->fetch(); + + return $result->qty; + } + + /** + * Update all instances domain by customized domain. + * + * @access public + * @return void + */ + public function updateInstancesDomain() + { + /* REPLACE(domain, .$sysDomain, '') used for special case: new domain is a.com and old domain is b.a.com, domain of instance is c.b.a.com, new domain of instance should be c.a.com */ + $sysDomain = $this->loadModel('cne')->sysDomain(); + $instanceList = $this->dao->select('*')->from(TABLE_INSTANCE) + ->where('deleted')->eq(0) + ->andWhere("REPLACE(domain, '.$sysDomain', '')")->like("%.%") + ->fetchAll('id'); + + $spaces = $this->dao->select('*')->from(TABLE_SPACE)->where('deleted')->eq(0)->andWhere('id')->in(array_column($instanceList, 'space'))->fetchAll('id'); + + foreach($instanceList as $instance) $instance->spaceData = zget($spaces, $instance->space, new stdclass); + + foreach($instanceList as $instance) $this->updateDomain($instance); + } + + /** + * Update domain. + * + * @param object $instance + * @access public + * @return bool + */ + public function updateDomain($instance) + { + /* If domain of instance is same with system domain, not need to update. */ + $sysDomain = $this->cne->sysDomain(); + if(stripos(str_replace($sysDomain, '', $instance->domain), '.') === false) return true; + + $expiredDomains = $this->loadModel('setting')->getItem('owner=system&module=common§ion=domain&key=expiredDomain'); + $expiredDomains = json_decode($expiredDomains, true); + if(empty($expiredDomains)) return true; + + $leftDomain = ''; + foreach($expiredDomains as $expiredDomain) + { + if(stripos($instance->domain, $expiredDomain) === false) continue; + + $leftDomain = trim(str_replace($expiredDomain, '', $instance->domain), '.'); // Pick left domain. + /* If left domain like a.b, skip it, because left domain must be letters without dot(.) . */ + if(stripos($leftDomain, '.') !== false) continue; // Does not pick left domain. + + $newDomain = $leftDomain . '.' . $sysDomain; + + $settings = new stdclass; + $settings->settings_map = new stdclass; + $settings->settings_map->ingress = new stdclass; + $settings->settings_map->ingress->enabled = true; + $settings->settings_map->ingress->host = $newDomain; //$this->fullDomain($leftDomain); + + $settings->settings_map->global = new stdclass; + $settings->settings_map->global->ingress = $settings->settings_map->ingress; + + if($this->cne->updateConfig($instance, $settings)) + { + $this->dao->update(TABLE_INSTANCE)->set('domain')->eq($newDomain)->where('id')->eq($instance->id)->exec(); + } + } + + return true; + } + + /** + * Update instance memory size. + * + * @param object $instnace + * @param int $size + * @access public + * @return bool + */ + public function updateMemorySize($instnace, $size = '') + { + $settings = new stdclass; + $settings->settings_map = new stdclass; + $settings->settings_map->resources = new stdclass; + $settings->settings_map->resources->memory = $size; + + $success = $this->cne->updateConfig($instnace, $settings); + if($success) + { + $this->action->create('instance', $instnace->id, 'adjustMemory', helper::formatKB(intval($size))); + return true; + } + + dao::$errors[] = $this->lang->instance->errors->failToAdjustMemory; + return false; + } + + /** + * Update instance status. + * + * @param int $int + * @param string $status + * @access public + * @return int + */ + public function updateStatus($id, $status) + { + $instanceData = new stdclass; + $instanceData->status = trim($status); + return $this->updateByID($id, $instanceData); + } + + /** + * Update instance by id. + * + * @param int $id + * @param object $newInstance + * @access public + * @return void + */ + public function updateByID($id, $newInstance) + { + return $this->dao->update(TABLE_INSTANCE)->data($newInstance) + ->autoCheck() + ->checkIF(isset($newInstance->name), 'name', 'notempty') + ->checkIF(isset($newInstance->status), 'status', 'in', array_keys($this->lang->instance->statusList)) + ->where('id')->eq($id)->exec(); + } + + + /** + * Soft delete instance by id. + * + * @param int $id + * @access public + * @return void + */ + public function softDeleteByID($id) + { + return $this->dao->update(TABLE_INSTANCE)->set('deleted')->eq(1)->where('id')->eq($id)->exec(); + } + + /** + * If actions are allowed to do. + * + * @param string $action + * @param object $instance + * @access public + * @return boolean + */ + public function canDo($action, $instance) + { + // $busy = in_array($instance->status, array('creating', 'initializing', 'starting', 'stopping', 'suspending', 'pulling', 'destroying')); + switch($action) + { + case 'start': + return $instance->status === 'stopped'; + case 'stop': + return in_array($instance->status, array('abnormal', 'running')); + case 'uninstall': + return !in_array($instance->status, array('destroying')); + case 'visit': + return $instance->status == 'running'; + default: + return false; + } + } + + /** + * Get URL for visiting instance service. + * + * @param object $instance + * @access public + * @return string + */ + public function url($instance) + { + $url = "//" . $instance->domain; + $port = getenv('APP_HTTPS_PORT'); + if($port and $port != '443') $url .= ":$port/"; + + return $url; + } + + /** + * Create third domain. + * + * @param int $length + * @param int $triedTimes + * @access public + * @return string + */ + public function randThirdDomain($length = 4, $triedTimes = 0) + { + if($triedTimes > 16) $length++; + + $thirdDomain = strtolower(helper::randStr($length)); + if(!$this->domainExists($thirdDomain)) return $thirdDomain; + + return $this->randThirdDomain($length, $triedTimes + 1); + } + + /** + * Return full domain. + * + * @param string $thirdDomain + * @access public + * @return string + */ + public function fullDomain($thirdDomain) + { + return $thirdDomain . '.' . $this->cne->sysDomain(); + } + + /** + * Check if the domain exists. + * + * @param string $thirdDomain + * @access public + * @return bool true: exists, false: not exist. + */ + public function domainExists($thirdDomain) + { + $domain = $this->fullDomain($thirdDomain); + return boolval($this->dao->select('id')->from(TABLE_INSTANCE)->where('domain')->eq($domain)->andWhere('deleted')->eq(0)->fetch()); + } + + /** + * Check if the k8name exists. + * + * @param string $k8name + * @access public + * @return bool true: exists, false: not exist. + */ + public function k8nameExists($k8name) + { + return boolval($this->dao->select('id')->from(TABLE_INSTANCE)->where('k8name')->eq($k8name)->andWhere('deleted')->eq(0)->fetch()); + } + + /** + * Mount installation settings by custom data. + * + * @param object $customData + * @param object $dbInfo + * @param object $instance + * @access private + * @return object + */ + private function installationSettingsMap($customData, $dbInfo, $instance) + { + $settingsMap = new stdclass; + if($customData->customDomain) + { + $settingsMap->ingress = new stdclass; + $settingsMap->ingress->enabled = true; + $settingsMap->ingress->host = $this->fullDomain($customData->customDomain); + + $settingsMap->global = new stdclass; + $settingsMap->global->ingress = new stdclass; + $settingsMap->global->ingress->enabled = true; + $settingsMap->global->ingress->host = $settingsMap->ingress->host; + } + + if(empty($customData->dbType) || $customData->dbType == 'unsharedDB' || empty($customData->dbService)) return $settingsMap; + + /* Set DB settings. */ + $dbSettings = new stdclass; + $dbSettings->service = $dbInfo->name; + $dbSettings->namespace = $dbInfo->namespace; + $dbSettings->host = $dbInfo->host; + $dbSettings->port = $dbInfo->port; + $dbSettings->name = 'db_' . $instance->id; + $dbSettings->user = 'user_' . $instance->id; + + $dbSettings = $this->getValidDBSettings($dbSettings, $dbSettings->user, $dbSettings->name); + + $settingsMap->mysql = new stdclass; + $settingsMap->mysql->enabled = false; + + $settingsMap->mysql->auth = new stdclass; + $settingsMap->mysql->auth->user = $dbSettings->user; + $settingsMap->mysql->auth->password = helper::randStr(12); + $settingsMap->mysql->auth->host = $dbSettings->host; + $settingsMap->mysql->auth->port = $dbSettings->port; + $settingsMap->mysql->auth->database = $dbSettings->name; + + $settingsMap->mysql->auth->dbservice = new stdclass; + $settingsMap->mysql->auth->dbservice->name = $dbSettings->service; + $settingsMap->mysql->auth->dbservice->namespace = $dbSettings->namespace; + + return $settingsMap; + } + + /** + * Return valid DBSettings. + * + * @param object $dbSettings + * @param string $defaultUser + * @param string $defaultDBName + * @param int $times + * @access private + * @return null|object + */ + private function getValidDBSettings($dbSettings, $defaultUser, $defaultDBName, $times = 1) + { + if($times >10) return; + + $validatedResult = $this->cne->validateDB($dbSettings->service, $dbSettings->name, $dbSettings->user, $dbSettings->namespace); + if($validatedResult->user && $validatedResult->database) return $dbSettings; + + if(!$validatedResult->user) $dbSettings->user = $defaultUser . '_' . help::randStr(4); + if(!$validatedResult->database) $dbSettings->database = $defaultDBName . '_' . help::randStr(4); + + return $this->getValidDBSettings($dbSettings, $defaultUser, $defaultDBName, $times + 1); + } + + /** + * Install app by request from Web page. + * + * @param object $app + * @param object $dbInfo + * @param object $customData + * @param int $spaceID + * @access public + * @return false|object Failure: return false, Success: return instance + */ + public function install($app, $dbInfo, $customData, $spaceID = null, $settings = array()) + { + $this->loadModel('space'); + if($spaceID) + { + $space = $this->space->getByID($spaceID); + } + else + { + $space = $this->space->defaultSpace($this->app->user->account); + } + + $snippets = array(); + if(isset($customData->ldapSnippet[0])) $snippets['ldapSnippetName'] = $customData->ldapSnippet[0]; + if(isset($customData->smtpSnippet[0])) $snippets['smtpSnippetName'] = $customData->smtpSnippet[0]; + + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + $instance = $this->createInstance($app, $space, $customData->customDomain, $customData->customName, '', $channel, $snippets); + if(!$instance) return false; + + $settingMap = $this->installationSettingsMap($customData, $dbInfo, $instance); + return $this->doCneInstall($instance, $space, $settingMap, $snippets, $settings); + } + + /** + * Install System SLB component. + * + * @param object $app + * @param string $k8name + * @param string $channel + * @access public + * @return object + */ + public function installSysSLB($app, $k8name = 'cne-lb', $channel = 'stable') + { + $this->app->loadLang('system'); + + $space = $this->loadModel('space')->getSystemSpace($this->app->user->account); + + $instance = $this->createInstance($app, $space, '', '', $k8name, $channel); + if(!$instance) + { + dao::$errors[] = $this->lang->system->errors->failToInstallLDAP; + return false; + } + + $instance = $this->doCneInstall($instance, $space, (new stdclass)); + if(!$instance) + { + dao::$errors[] = $this->lang->system->errors->failToInstallLDAP; + return false; + } + + return $instance; + } + + /** + install global SMTP proxy service. + * + * @param object $app + * @param object $smtpSettings + * @param string $instanceName + * @param string $k8name + * @param string $channel + * @access public + * @return object + */ + public function installSysSMTP($app, $smtpSettings, $instanceName = '', $k8name = '', $channel = 'stable') + { + $this->app->loadLang('system'); + + $space = $this->loadModel('space')->getSystemSpace($this->app->user->account); + + $customData = new stdclass; + $customData->dbType = null; + $customData->customDomain = $this->randThirdDomain(); + + $instance = $this->createInstance($app, $space, $customData->customDomain, $instanceName, $k8name, $channel); + if(!$instance) + { + dao::$errors[] = $this->lang->system->errors->failToInstallSMTP; + return false; + } + + $settingsMap = $this->installationSettingsMap($customData, array(), $instance); + + $settingsMap->env = new stdclass; + $settingsMap->env->SMTP_HOST = $smtpSettings->host; + $settingsMap->env->SMTP_PORT = strval($smtpSettings->port); + $settingsMap->env->SMTP_USER = $smtpSettings->user; + $settingsMap->env->SMTP_PASS = $smtpSettings->pass; + $settingsMap->env->AUTHENTICATE_CODE = helper::randStr(24); + + $instance = $this->doCneInstall($instance, $space, $settingsMap); + if(!$instance) + { + dao::$errors[] = $this->lang->system->errors->failToInstallSMTP; + return false; + } + + /* Post snippet to CNE. */ + $snippetSettings = new stdclass; + $snippetSettings->name = 'snippet-smtp-proxy'; + $snippetSettings->namespace = $space->k8space; + $snippetSettings->auto_import = false; + + $snippetSettings->values = new stdclass; + $snippetSettings->values->mail = new stdclass; + $snippetSettings->values->mail->enabled = true; + $snippetSettings->values->mail->smtp = new stdclass; + $snippetSettings->values->mail->smtp->host = "{$instance->k8name}.{$snippetSettings->namespace}.svc"; + $snippetSettings->values->mail->smtp->port = '1025'; + $snippetSettings->values->mail->smtp->user = 'smtp-bot@quickon.local'; // This is fake value. + $snippetSettings->values->mail->smtp->pass = $settingsMap->env->AUTHENTICATE_CODE; // This is fake value. + + $snippetResult = $this->loadModel('cne')->addSnippet($snippetSettings); + if($snippetResult->code != 200) + { + dao::$errors[] = $this->lang->system->errors->failToInstallSMTP; + $this->uninstall($instance); + return false; + } + + /* Save SMTP settings. */ + $secretKey = helper::readKey(); + $settingsMap->env->SMTP_PASS = openssl_encrypt($settingsMap->env->SMTP_PASS, 'DES-ECB', $secretKey); + $settingsMap->env->AUTHENTICATE_CODE = openssl_encrypt($settingsMap->env->AUTHENTICATE_CODE, 'DES-ECB', $secretKey); + + $snippetSettings->values->mail->smtp->pass = $settingsMap->env->AUTHENTICATE_CODE; + + $this->loadModel('setting'); + $this->setting->setItem('system.common.smtp.enabled', true); + $this->setting->setItem('system.common.smtp.instanceID', $instance->id); + $this->setting->setItem('system.common.smtp.snippetName', $snippetSettings->name); + $this->setting->setItem('system.common.smtp.settingsMap', json_encode($settingsMap)); + $this->setting->setItem('system.common.smtp.snippetSettings', json_encode($snippetSettings)); + + return $instance; + } + + /** + * Install LDAP. + * + * @param object $app + * @param string $thirdDomain + * @param string $instanceName + * @param string $channel + * @access public + * @return bool|object + */ + public function installLDAP($app, $thirdDomain = '', $instanceName = '', $k8name = '', $channel = 'stable') + { + $this->app->loadLang('system'); + + $space = $this->loadModel('space')->getSystemSpace($this->app->user->account); + + $customData = new stdclass; + $customData->dbType = null; + $customData->customDomain = $thirdDomain ? $thirdDomain : $this->randThirdDomain(); + + $instance = $this->createInstance($app, $space, $customData->customDomain, $instanceName, $k8name, $channel); + if(!$instance) + { + dao::$errors[] = $this->lang->system->errors->failToInstallLDAP; + return false; + } + + $settingMap = $this->installationSettingsMap($customData, array(), $instance); + + $settingMap->auth = new stdclass; + $settingMap->auth->username = $app->account ? $app->account->username : 'admin'; + $settingMap->auth->password = helper::randStr(16); + $settingMap->auth->root = 'dc=quickon,dc=org'; + + $instance = $this->doCneInstall($instance, $space, $settingMap); + if(!$instance) + { + dao::$errors[] = $this->lang->system->errors->failToInstallLDAP; + return false; + } + + /* Post snippet to CNE. */ + $snippetSettings = new stdclass; + $snippetSettings->name = 'snippet-qucheng-ldap'; + $snippetSettings->namespace = $space->k8space; + $snippetSettings->auto_import = false; + + $snippetSettings->values = new stdclass; + $snippetSettings->values->auth = new stdclass; + $snippetSettings->values->auth->ldap = new stdclass; + $snippetSettings->values->auth->ldap->enabled = true; + $snippetSettings->values->auth->ldap->type = 'ldap'; + $snippetSettings->values->auth->ldap->host = "{$instance->k8name}.{$snippetSettings->namespace}.svc"; + $snippetSettings->values->auth->ldap->port = '1389'; + $snippetSettings->values->auth->ldap->bindDN = "cn={$settingMap->auth->username},dc=quickon,dc=org"; + $snippetSettings->values->auth->ldap->bindPass = $settingMap->auth->password; + $snippetSettings->values->auth->ldap->baseDN = $settingMap->auth->root; + $snippetSettings->values->auth->ldap->filter = "&(objectClass=posixAccount)(cn=%s)"; + $snippetSettings->values->auth->ldap->attrUser = 'uid'; + $snippetSettings->values->auth->ldap->attrEmail = 'mail'; + + $snippetResult = $this->loadModel('cne')->addSnippet($snippetSettings); + if($snippetResult->code != 200) + { + dao::$errors[] = $this->lang->system->errors->failToInstallLDAP; + $this->uninstall($instance); + return false; + } + + /* Save LDAP account. */ + $secretKey = helper::readKey(); + $settingMap->auth->password = openssl_encrypt($settingMap->auth->password, 'DES-ECB', $secretKey); + $this->dao->update(TABLE_INSTANCE)->set('ldapSettings')->eq(json_encode($settingMap))->where('id')->eq($instance->id)->exec(); + $this->loadModel('setting')->setItem('system.common.ldap.active', 'qucheng'); + $this->loadModel('setting')->setItem('system.common.ldap.instanceID', $instance->id); + $this->loadModel('setting')->setItem('system.common.ldap.snippetName', $snippetSettings->name); // Parameter for App installation API. + + return $instance; + } + + /** + * Install app through API. + * + * @param object $cloudApp + * @param string $thirdDomain + * @param string $name + * @param string $channel + * @access public + * @return bool|object + */ + public function apiInstall($cloudApp, $thirdDomain = '', $name = '', $k8name = '', $channel = 'stable') + { + $this->loadModel('space'); + $space = $this->space->defaultSpace($this->app->user->account); + + $customData = new stdclass; + $customData->dbType = null; + $customData->customDomain = $thirdDomain ? $thirdDomain : $this->randThirdDomain(); + + $dbInfo = new stdclass; + $dbList = $this->cne->sharedDBList(); + if(count($dbList) > 0) + { + $dbInfo = reset($dbList); + + $customData->dbType = 'sharedDB'; + $customData->dbService = $dbInfo->name; // Use first shared database. + } + + $instance = $this->createInstance($cloudApp, $space, $customData->customDomain, $name, $k8name, $channel); + if(!$instance) return false; + + $settingMap = $this->installationSettingsMap($customData, $dbInfo, $instance); + return $this->doCneInstall($instance, $space, $settingMap); + } + + /** + * Create instance recorder for installation. + * + * @param object $app + * @param object $space + * @param object $thirdDomain + * @param string $name + * @param string $channel + * @param array $snippets + * @access public + * @return bool|object + */ + public function createInstance($app, $space, $thirdDomain, $name = '', $k8name = '', $channel = 'stable', $snippets = array()) + { + if(empty($k8name)) $k8name = "{$app->chart}-{$this->app->user->account}-" . date('YmdHis'); //name rule: chartName-userAccount-YmdHis; + + $instanceData = new stdclass; + $instanceData->appId = $app->id; + $instanceData->appName = $app->alias; + $instanceData->name = !empty($name) ? $name : $app->alias; + $instanceData->domain = !empty($thirdDomain) ? $this->fullDomain($thirdDomain) : ''; + $instanceData->logo = $app->logo; + $instanceData->desc = $app->desc; + $instanceData->introduction = isset($app->introduction) ? $app->introduction : $app->desc; + $instanceData->source = 'cloud'; + $instanceData->channel = $channel; + $instanceData->chart = $app->chart; + $instanceData->appVersion = $app->app_version; + $instanceData->version = $app->version; + $instanceData->space = $space->id; + $instanceData->k8name = $k8name; + $instanceData->status = 'creating'; + $instanceData->createdBy = $this->app->user->account; + $instanceData->createdAt = date('Y-m-d H:i:s'); + + foreach($snippets as $fieldName => $snippetName) $instanceData->$fieldName = $snippetName; + + $this->dao->insert(TABLE_INSTANCE)->data($instanceData)->autoCheck()->exec(); + if(dao::isError()) return false; + + return $this->getByID($this->dao->lastInsertID()); + } + + /** + * Create app instance on CNE platform. + * + * @param object $instance + * @param object $space + * @param object $settingsMap + * @param array $snippets + * @param object $app + * @access private + * @return object|bool + */ + private function doCneInstall($instance, $space, $settingsMap, $snippets = array(), $settings = array()) + { + $apiParams = new stdclass; + $apiParams->userame = $instance->createdBy; + $apiParams->cluser = ''; + $apiParams->namespace = $space->k8space; + $apiParams->name = $instance->k8name; + $apiParams->chart = $instance->chart; + $apiParams->version = $instance->version; + $apiParams->channel = $instance->channel; + $apiParams->settings_map = $settingsMap; + $apiParams->settings = $settings; + $apiParams->settings_snippets = array_values($snippets); + + if(strtolower($this->config->CNE->app->domain) == 'demo.haogs.cn') $apiParams->settings_snippets = array('quickon_saas'); // Only for demo enviroment. + + $result = $this->cne->installApp($apiParams); + if($result->code != 200) + { + $this->dao->delete()->from(TABLE_INSTANCE)->where('id')->eq($instance->id)->exec(); + return false; + } + + $this->action->create('instance', $instance->id, 'install', '', json_encode(array('result' => $result, 'apiParams' => $apiParams))); + + $instance->status = 'initializing'; + $instance->dbSettings = json_encode($apiParams->settings_map); + + $this->updateByID($instance->id, array('status' => $instance->status, 'dbSettings' => $instance->dbSettings)); + + return $instance; + } + + /* + * Uninstall app instance. + * + * @param object $instance + * @access public + * @return bool + */ + public function uninstall($instance) + { + if($instance->source == 'external') + { + $this->dao->update(TABLE_INSTANCE)->set('deleted')->eq(1)->where('id')->eq($instance->id)->exec(); + return true; + } + + $apiParams = new stdclass; + $apiParams->cluster = '';// Multiple cluster should set this field. + $apiParams->name = $instance->k8name; + $apiParams->channel = $instance->channel; + $apiParams->namespace = $instance->spaceData->k8space; + + $result = $this->cne->uninstallApp($apiParams); + $success = $result->code == 200 || $result->code == 404; + if($success) $this->dao->update(TABLE_INSTANCE)->set('deleted')->eq(1)->where('id')->eq($instance->id)->exec(); + + return $success; + } + + /* + * Start app instance. + * + * @param object $instance + * @access public + * @return object + */ + public function start($instance) + { + $apiParams = new stdclass; + $apiParams->cluster = ''; + $apiParams->name = $instance->k8name; + $apiParams->chart = $instance->chart; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->channel = $instance->channel; + + $result = $this->cne->startApp($apiParams); + if($result->code == 200) $this->dao->update(TABLE_INSTANCE)->set('status')->eq('starting')->where('id')->eq($instance->id)->exec(); + + return $result; + } + + /* + * Stop app instance. + * + * @param object $instance + * @access public + * @return object + */ + public function stop($instance) + { + $apiParams = new stdclass; + $apiParams->cluster = '';// Mulit cluster should set this field. + $apiParams->name = $instance->k8name; + $apiParams->chart = $instance->chart; + $apiParams->namespace = $instance->spaceData->k8space; + $apiParams->channel = $instance->channel; + + $result = $this->cne->stopApp($apiParams); + if($result->code == 200) $this->dao->update(TABLE_INSTANCE)->set('status')->eq('stopping')->where('id')->eq($instance->id)->exec(); + + return $result; + } + + /** + * Upgrade app instance to higher version. + * + * @param object $instance + * @param string $toVersion + * @param string $appVersion + * @access public + * @return bool + */ + public function upgrade($instance, $toVersion, $appVersion) + { + $success = $this->cne->upgradeToVersion($instance, $toVersion); + if(!$success) return false; + + $instanceData = new stdclass; + $instanceData->version = $toVersion; + $instanceData->appVersion = $appVersion; + $this->updateByID($instance->id, $instanceData); + + return true; + } + + /** + * Upgrade to senior App. + * + * @param object $instance + * @param object $seniorApp + * @access public + * @return bool + */ + public function upgradeToSenior($instance, $seniorApp) + { + $instance->chart = $seniorApp->chart; + $instance->version = $seniorApp->version; + + $settings = new stdclass; + $settings->settings_map = new stdclass; + $settings->settings_map->nameOverride = $this->parseK8Name($instance->k8name)->chart; + + $settings->settings_map->global = new stdclass; + $settings->settings_map->global->stopped = false; + + if(!$this->cne->updateConfig($instance, $settings)) + { + dao::$errors[] = $this->lang->instance->errors->failToSenior; + return false; + } + + $seniorData = new stdclass; + $seniorData->chart = $seniorApp->chart; + $seniorData->version = $seniorApp->version; + $seniorData->appVersion = $seniorApp->app_version; + $seniorData->appID = $seniorApp->id; + $seniorData->appName = $seniorApp->alias; + $seniorData->name = $instance->name . "($seniorApp->alias)"; + + $this->dao->update(TABLE_INSTANCE)->data($seniorData)->where('id')->eq($instance->id)->exec(); + + $logExtra = new stdclass; + $logExtra->result = 'success'; + $logExtra->data = new stdclass; + $logExtra->data->oldAppName = $instance->appName; + $logExtra->data->newAppName = $seniorApp->alias; + + $this->action->create('instance', $instance->id, 'tosenior', '', json_encode($logExtra)); + + return true; + } + + /* + * Query and update instances status. + * + * @param array $instances + * @access public + * @return array new status list [{id:xx, status: xx, changed: true/false}] + */ + public function batchFresh(&$instances) + { + $statusList = $this->cne->batchQueryStatus($instances); + + $newStatusList = array(); + + foreach($instances as $instance) + { + $statusData = zget($statusList, $instance->k8name, ''); + if($statusData && ($instance->status != $statusData->status || $instance->version != $statusData->version)) + { + $this->dao->update(TABLE_INSTANCE) + ->set('status')->eq($statusData->status) + ->beginIF($statusData->version)->set('version')->eq($statusData->version)->fi() + ->where('id')->eq($instance->id) + ->autoCheck() + ->exec(); + $instance->status = $statusData->status; + } + + $status = new stdclass; + $status->id = $instance->id; + $status->status = $instance->status; + + $newStatusList[] = $status; + } + + return $newStatusList; + } + + /* + * Query and update instance status. + * + * @param object $instance + * @access public + * @return object + */ + public function freshStatus($instance) + { + $instance->runDuration = 0; + $statusResponse = $this->cne->queryStatus($instance); + if($statusResponse->code != 200) return $instance; + + $statusData = $statusResponse->data; + $instance->runDuration = intval($statusData->age); // Run duration used in view page. + + if($instance->status != $statusData->status || $instance->version != $statusData->version) + { + $instance->status = $statusData->status; + + $this->dao->update(TABLE_INSTANCE) + ->set('status')->eq($statusData->status) + ->beginIF($statusData->version)->set('version')->eq($statusData->version)->fi() + ->where('id')->eq($instance->id) + ->autoCheck() + ->exec(); + } + + return $instance; + } + + /** + * Save auto backup settings. + * + * @param object $instance + * @access public + * @return bool + */ + public function saveAutoBackupSettings($instance) + { + /* Cycle days is always is 1 at present. */ + $settings = fixer::input('post') + ->setDefault('backupTime', '1:00') + ->setDefault('cycleDays', '1') + ->setDefault('keepDays', '10') + ->setDefault('autoBackup', array()) + ->get(); + $settings->autoBackup = in_array('true', $settings->autoBackup); + + if(!preg_match("/^([0-2][0-9]):([0-5][0-9])$/", $settings->backupTime, $parts)) + { + dao::$errors[] = $this->lang->instance->backup->invalidTime; + return false; + } + + /* Save cron task. */ + list($hour, $minute) = explode(':', $settings->backupTime); + $cronData = new stdclass; + $cronData->m = intval($minute); + $cronData->h = intval($hour); + //$cronData['dom'] = intval($settings->cycleDays); + $cronData->dom = '*'; + $cronData->mon = '*'; + $cronData->dow = '*'; + + $cron = $this->dao->select('*')->from(TABLE_CRON)->where('objectID')->eq($instance->id)->fetch(); + if($cron) + { + $this->dao->update(TABLE_CRON)->autoCheck()->data($cronData)->where('id')->eq($cron->id)->exec(); + } + else + { + $cronData->objectID = $instance->id; + $this->dao->insert(TABLE_CRON)->data($cronData)->exec(); + } + if($this->dao->isError()) return false; + + /* Save instance backup settings. */ + $this->dao->update(TABLE_INSTANCE)->set('autoBackup')->eq($settings->autoBackup)->set('backupKeepDays')->eq($settings->keepDays)->where('id')->eq($instance->id)->exec(); + if($this->dao->isError()) return false; + + $this->action->create('instance', $instance->id, 'saveAutoBackupSettings', '', json_encode(array('data' => $settings))); + return true; + } + + /** + * Get auto backup settings. + * + * @param int $instnaceID + * @access public + * @return object + */ + public function getAutoBackupSettings($instanceID) + { + $instance = $this->getByID($instanceID); + + $cron = $this->dao->select('*')->from(TABLE_CRON)->where('objectID')->eq($instanceID)->limit(1)->fetch(); + if(!$cron) $cron = new stdclass; + + $hour = substr('0' . zget($cron, 'h', '1'), -2, 2); + $minute = substr('0' . zget($cron, 'm', '00'), -2, 2); + + $settings = new stdclass; + $settings->backupTime = "{$hour}:{$minute}"; + $settings->autoBackup = boolval(zget($instance, 'autoBackup', false)); + $settings->keepDays = $settings->autoBackup ? intval(zget($instance, 'backupKeepDays', 7)) : 7; // Set default value to 7 days. + $settings->cycleDays = 1; // Cycle days is always is 1 at present. + + return $settings; + } + + /** + * Save auto backup settings. + * + * @param object $instance + * @access public + * @return bool + */ + public function saveAutoRestoreSettings($instance) + { + /* Cycle days is always is 1 at present. */ + $settings = fixer::input('post') + ->setDefault('restoreTime', '1:00') + ->setDefault('cycleDays', '1') + ->setDefault('autoRestore', array()) + ->get(); + $settings->autoRestore = in_array('true', $settings->autoRestore); + + if(!preg_match("/^([0-2][0-9]):([0-5][0-9])$/", $settings->restoreTime, $parts)) + { + dao::$errors[] = $this->lang->instance->restore->invalidTime; + return false; + } + + /* Save cron task. */ + list($hour, $minute) = explode(':', $settings->restoreTime); + $cronData = new stdclass; + $cronData->m = intval($minute); + $cronData->h = intval($hour); + //$cronData['dom'] = intval($settings->cycleDays); + $cronData->dom = '*'; + $cronData->mon = '*'; + $cronData->dow = '*'; + + $cron = $this->dao->select('*')->from(TABLE_CRON)->where('objectID')->eq($instance->id)->fetch(); + if($cron) + { + $this->dao->update(TABLE_CRON)->autoCheck()->data($cronData)->where('id')->eq($cron->id)->exec(); + } + else + { + $cronData->objectID = $instance->id; + $this->dao->insert(TABLE_CRON)->data($cronData)->exec(); + } + if($this->dao->isError()) return false; + + /* Save instance restore settings. */ + $this->dao->update(TABLE_INSTANCE)->set('autoRestore')->eq($settings->autoRestore)->where('id')->eq($instance->id)->exec(); + if($this->dao->isError()) return false; + + $this->action->create('instance', $instance->id, 'saveAutoRestoreSettings', '', json_encode(array('data' => $settings))); + return true; + } + + /** + * Get auto restore settings. + * + * @param int $instnaceID + * @access public + * @return object + */ + public function getAutoRestoreSettings($instanceID) + { + $instance = $this->getByID($instanceID); + + $cron = $this->dao->select('*')->from(TABLE_CRON)->where('objectID')->eq($instanceID)->limit(1)->fetch(); + if(!$cron) $cron = new stdclass; + + $hour = substr('0' . zget($cron, 'h', '1'), -2, 2); + $minute = substr('0' . zget($cron, 'm', '00'), -2, 2); + + $settings = new stdclass; + $settings->restoreTime = "{$hour}:{$minute}"; + $settings->autoRestore = boolval(zget($instance, 'autoRestore', false)); + $settings->cycleDays = 1; // Cycle days is always is 1 at present. + + return $settings; + } + + /** + * Do auto backup cron task. + * + * @access public + * @return void + */ + public function autoBackup() + { + $instanceList = $this->dao->select('*')->from(TABLE_INSTANCE) + ->where('deleted')->eq(0) + ->andWhere('autoBackup')->eq(true) + ->andWhere('status')->eq('running') + ->fetchAll('id'); + + /* Load all crons that instance is enable auto backup. */ + $crons = $this->dao->select('*')->from(TABLE_CRON)->where('objectID')->in(array_keys($instanceList))->fetchAll(); + + $nowHour = intval(date('H')); + $nowMiute = intval(date('i')); + foreach($crons as $cron) + { + $hour = intval($cron->h); + $minute = intval($cron->m); + if(!($nowHour == $hour and $nowMiute == $minute)) continue; + + /* 1. create new backup of instance. */ + $system = new stdclass; + $system->account = 'auto'; // The string 'auto' should be kept for System. + + $instance = zget($instanceList, $cron->objectID); + $instance->spaceData = $this->dao->select('*')->from(TABLE_SPACE)->where('id')->eq($instance->space)->fetch(); + $this->backup($instance, $system); + + $this->action->create('instance', $instance->id, 'autoBackup'); + + /* 2. Pick latest successful backup recorder. */ + $latestBackup = null; + $backupList = $this->backupList($instance); + foreach($backupList as $backup) + { + if(empty($latestBackup) or $backup->status == 'completed' && $backup->create_time > $latestBackup->create_time) + { + $latestBackup = $backup; + } + } + + /* 3. delete expired backup. Get backup list of instance, then check every backup is expired or not.*/ + // foreach($backupList as $backup) + // { + // if($backup->creator != 'auto') continue; // Only delete data madde by auto backup. + // if($latestBackup && $latestBackup->name == $backup->name) continue; // Keep latest successful backup. + // } + } + } + + /** + * Delete backup. + * + * @param object $instance + * @param string $backupName + * @access public + * @return bool + */ + public function deleteBackup($instance, $backupName) + { + return $this->cne->deleteBackup($instance, $backupName); + } + + /** + * Backup instance. + * + * @param object $instance + * @param object $user + * @access public + * @return bool + */ + public function backup($instance, $user) + { + $result = $this->cne->backup($instance, $user->account); + if($result->code != 200) return false; + + return true; + } + + /** + * Restore instance. + * + * @param object $instance + * @param object $user + * @param string $backupName + * @access public + * @return bool + */ + public function restore($instance, $user, $backupName) + { + $result = $this->cne->restore($instance, $backupName, $user->account); + if($result->code != 200) return false; + + return true; + } + + /* Run restore latest successful manual backup recorder automatically. + * + * @access public + * @return void + */ + public function autoRestore() + { + $instanceList = $this->dao->select('*')->from(TABLE_INSTANCE) + ->where('deleted')->eq(0) + ->andWhere('autoRestore')->eq(true) + ->andWhere('status')->eq('running') + ->fetchAll('id'); + + /* Load all crons that instance is enable auto restore. */ + $crons = $this->dao->select('*')->from(TABLE_CRON)->where('objectID')->in(array_keys($instanceList))->fetchAll(); + + $nowHour = intval(date('H')); + $nowMiute = intval(date('i')); + foreach($crons as $cron) + { + $hour = intval($cron->h); + $minute = intval($cron->m); + if(!($nowHour == $hour and $nowMiute == $minute)) continue; + + /* Pick latest successful manual backup recorder. */ + $latestBackup = null; + $instance = $this->getByID($cron->objectID); + $backupList = $this->backupList($instance); + foreach($backupList as $backup) + { + if($backup->username == 'auto') continue; // Only restore manual backup. + if(empty($latestBackup) or $backup->status == 'completed' && $backup->create_time > $latestBackup->create_time) + { + $latestBackup = $backup; + } + } + + if($latestBackup) + { + $sysUser = new stdclass; + $sysUser->account = 'auto'; + $this->restore($instance, $sysUser, $latestBackup->name); + + $this->action->create('instance', $instance->id, 'autoRestore'); + } + } + } + + /** + * Get backup list of instance. + * + * @param object $instance + * @access public + * @return array + */ + public function backupList($instance) + { + $result = $this->cne->backupList($instance); + if(empty($result) || $result->code != 200 || empty($result->data)) return array(); + + $backupList = $result->data; + usort($backupList, function($backup1, $backup2){ return $backup1->create_time < $backup2->create_time; }); + + $accounts = array_column($backupList, 'creator'); + foreach($backupList as $backup) $accounts = array_merge($accounts, array_column($backup->restores, 'creator')); + + $accounts = array_unique($accounts); + + $users = $this->dao->select('account,realname')->from(TABLE_USER)->where('account')->in($accounts)->fetchPairs('account', 'realname'); + + foreach($backupList as &$backup) + { + $backup->latest_restore_time = 0; + $backup->latest_restore_status = ''; + $backup->username = zget($users, $backup->creator); + /* Mount backup operator info and latest restore info. */ + foreach($backup->restores as &$restore) + { + $restore->username = zget($users, $restore->creator); + + if($restore->create_time > $backup->latest_restore_time) + { + $backup->latest_restore_time = $restore->create_time; + $backup->latest_restore_status = $restore->status; + } + } + } + + return $backupList; + } + + /** + * Mount DB name and alias to array for select options. + * + * @param array $databases + * @access public + * @return array + */ + public function dbListToOptions($databases) + { + $dbList = array(); + foreach($databases as $database) $dbList[$database->name] = zget($database, 'alias', $database->name); + + return $dbList; + } + + /** + * Restore instance by data from k8s cluster. + * + * @access public + * @return void + */ + public function restoreInstanceList() + { + $k8AppList = $this->cne->instancelist(); + $k8NameList = array_keys($k8AppList); + + //软删除不存在的数据 + $this->dao->update(TABLE_INSTANCE)->set('deleted')->eq(1)->where('k8name')->notIn($k8NameList)->exec(); + foreach($k8AppList as $k8App) + { + $existInstance = $this->dao->select('id')->from(TABLE_INSTANCE)->where('k8name')->eq($k8App->name)->fetch(); + if($existInstance) continue; + + $this->loadModel('store'); + $marketApp = $this->store->getAppInfo(0, false, $k8App->chart, $k8App->version, $k8App->channel); + if(empty($marketApp)) continue; + + $instanceData = new stdclass; + $instanceData->appId = $marketApp->id; + $instanceData->appName = $marketApp->alias; + $instanceData->name = $marketApp->alias; + $instanceData->logo = $marketApp->logo; + $instanceData->desc = $marketApp->desc; + $instanceData->introduction = isset($marketApp->introduction) ? $marketApp->introduction : $marketApp->desc; + $instanceData->source = 'cloud'; + $instanceData->channel = $k8App->channel; + $instanceData->chart = $k8App->chart; + $instanceData->appVersion = $marketApp->app_version; + $instanceData->version = $k8App->version; + $instanceData->k8name = $k8App->name; + $instanceData->status = 'stopped'; + + $parsedK8Name = $this->parseK8Name($k8App->name); + + $instanceData->createdBy = $k8App->username ? $k8App->username : $parsedK8Name->createdBy; + $instanceData->createdAt = $parsedK8Name->createdAt; + + $space = $this->dao->select('id,k8space')->from(TABLE_SPACE)->where('k8space')->eq($k8App->namespace)->fetch(); + if(empty($space)) $space = $this->loadModel('space')->defaultSpace($instanceData->createdBy); + + $instanceData->space = $space->id; + + $this->dao->insert(TABLE_INSTANCE)->data($instanceData)->exec(); + } + } + + /** + * Parse K8Name to get more data: chart, created time, user name. + * + * @param string $k8Name + * @access public + * @return object + */ + public function parseK8Name($k8Name) + { + $datePosition = strripos($k8Name, '-'); + $createdAt = trim(substr($k8Name, $datePosition), '-'); + + $createdBy = trim(substr($k8Name, 0, $datePosition), '-'); + $accountPosition = strripos($createdBy, '-'); + $createdBy = trim(substr($createdBy, $accountPosition), '-'); + + $parsedData = new stdclass; + $parsedData->chart = trim(substr($k8Name, 0, $accountPosition)); + $parsedData->createdBy = trim($createdBy, '-'); + $parsedData->createdAt = date('Y-m-d H:i:s', strtotime($createdAt)); + + return $parsedData; + } + + /** + * Delete expired instances if run in demo mode. + * + * @access public + * @return void + */ + public function deleteExpiredDemoInstance() + { + $demoAccounts = array_filter(explode(',', $this->config->demoAccounts)); + + $instanceList = $this->dao->select('*')->from(TABLE_INSTANCE) + ->where('deleted')->eq(0) + ->andWhere('createdBy')->in($demoAccounts) + ->fetchAll(); + if(empty($instanceList)) return; + + $spaceList = $this->dao->select('*')->from(TABLE_SPACE)->where('id')->in(array_column($instanceList, 'space'))->fetchAll('id'); + + foreach($instanceList as $instance) + { + $instance->spaceData = zget($spaceList, $instance->space); + if(empty($instance->spaceData)) continue; + + $this->uninstall($instance); + } + } + + /** + * Filter memory options that smaller then current memory size. + * + * @param object $resources + * @access public + * @return array + */ + public function filterMemOptions($resources) + { + $currentMemory = intval($resources->min->memory / 1024); + + $options = []; + foreach($this->lang->instance->memOptions as $size => $text) + { + if($size >= $currentMemory) $options[$size] = $text; + } + + return $options; + } + + /** + * Print suggested memory size by current memory usage. Show suggested messge when memory usage more than 90%. + * + * @param object $memUsage + * @param array $memoryOptions + * @access public + * @return void + */ + public function printSuggestedMemory($memUsage, $memoryOptions) + { + if($memUsage->rate < 90) return; + + foreach($memoryOptions as $size => $memText) + { + if($size > ($memUsage->limit / 1024)) + { + printf($this->lang->instance->adjustMemorySize, $memText); + return ; + } + } + } + + /** + * Print CPU usage. + * + * @param object $instance + * @param object $metrics + * @param string $type 'bar' is progress bar, 'pie' is progress pie. + * @static + * @access public + * @return mixed + */ + public static function printCpuUsage($instance, $metrics, $type = 'bar') + { + $rate = $instance->status == 'stopped' ? 0 : $metrics->rate; + $tip = "{$rate}% = {$metrics->usage} / {$metrics->limit}"; + + if(empty($color) && $rate == 0) $color = 'gray'; + if(empty($color) && $rate > 0 && $rate < 60) $color = 'primary'; + if(empty($color) && $rate >= 0 && $rate < 80) $color = 'warning'; + if(empty($color) && $rate >= 80) $color = 'danger'; + + if($type == 'array') return array('color' => $color, 'tip' => $tip, 'rate' => $rate . '%'); + + if(strtolower($type) == 'pie') commonModel::printProgressPie($rate, '', $tip); + + $valueType = 'percent'; + if($instance->status == 'stopped') $valueType = ''; + + commonModel::printProgressBar($rate, '', $tip, $valueType); + } + + /** + * Print memory usage. + * + * @param object $instance + * @param object $metrics + * @param string $type 'bar' is progress bar, 'pie' is progress pie. + * @static + * @access public + * @return mixed + */ + public static function printMemUsage($instance, $metrics, $type = 'bar') + { + $rate = $instance->status == 'stopped' ? 0 : $metrics->rate; + $tip = "{$rate}% = " . helper::formatKB($metrics->usage) . ' / ' . helper::formatKB($metrics->limit); + + if(empty($color) && $rate == 0) $color = 'gray'; + if(empty($color) && $rate > 0 && $rate < 60) $color = 'primary'; + if(empty($color) && $rate >= 0 && $rate < 80) $color = 'warning'; + if(empty($color) && $rate >= 80) $color = 'danger'; + + if($type == 'array') return array('color' => $color, 'tip' => $tip, 'rate' => $rate . '%'); + + if(strtolower($type) == 'pie') commonModel::printProgressPie($rate, '', $tip); + + $valueType = 'tip'; + if($instance->status == 'stopped') $valueType = ''; + + commonModel::printProgressBar($rate, '', $tip, $valueType); + } + + /* + * Print action buttons with icon. + * + * @param object $instance + * @access public + * @return void + */ + public function printIconActions($instance) + { + $actionHtml = ''; + + $disableStart = !$this->canDo('start', $instance); + $actionHtml .= html::commonButton("", "instance-id='{$instance->id}' title='{$this->lang->instance->start}'" . ($disableStart ? ' disabled ' : ''), "btn-start btn btn-lg btn-action"); + + $disableStop = !$this->canDo('stop', $instance); + $actionHtml .= html::commonButton('', "instance-id='{$instance->id}' title='{$this->lang->instance->stop}'" . ($disableStop ? ' disabled ' : ''), 'btn-stop btn btn-lg btn-action'); + + if(empty($instance->solution)) + { + $disableUninstall = !$this->canDo('uninstall', $instance); + $actionHtml .= html::commonButton('', "instance-id='{$instance->id}' title='{$this->lang->instance->uninstall}'" . ($disableUninstall ? ' disabled ' : ''), 'btn-uninstall btn btn-lg btn-action'); + } + + if($instance->domain) + { + $disableVisit = !$this->canDo('visit', $instance); + $actionHtml .= html::a($this->url($instance), '', '_blank', "title='{$this->lang->instance->visit}' class='btn btn-lg btn-action btn-link'" . ($disableVisit ? ' disabled style="pointer-events: none;"' : '')); + } + + echo $actionHtml; + } + + /* + * Print action buttons with text. + * + * @param object $instance + * @access public + * @return void + */ + public function printTextActions($instance) + { + $actionHtml = ''; + + $disableStart = !$this->canDo('start', $instance); + $actionHtml .= html::commonButton($this->lang->instance->start, "instance-id='{$instance->id}' title='{$this->lang->instance->start}'" . ($disableStart ? ' disabled ' : ''), "btn-start btn label label-outline label-primary label-lg"); + + $disableStop = !$this->canDo('stop', $instance); + $actionHtml .= html::commonButton($this->lang->instance->stop, "instance-id='{$instance->id}' title='{$this->lang->instance->stop}'" . ($disableStop ? ' disabled ' : ''), 'btn-stop btn label label-outline label-warning label-lg'); + + if(empty($instance->solution)) + { + $disableUninstall = !$this->canDo('uninstall', $instance); + $actionHtml .= html::commonButton($this->lang->instance->uninstall, "instance-id='{$instance->id}' title='{$this->lang->instance->uninstall}'" . ($disableUninstall ? ' disabled ' : ''), 'btn-uninstall btn label label-outline label-danger label-lg'); + } + + if($instance->domain) + { + $disableVisit = !$this->canDo('visit', $instance); + $actionHtml .= html::a($this->url($instance), $this->lang->instance->visit, '_blank', "title='{$this->lang->instance->visit}' class='btn btn-primary label-lg'" . ($disableVisit ? ' disabled style="pointer-events: none;"' : '')); + } + + echo $actionHtml; + } + + /** + * Print backup button of instance. + * + * @param object $instance + * @access public + * @return void + */ + public function printBackupBtn($instance) + { + $disabled = $instance->status == 'running' ? '' : 'disabled'; + $title = empty($disabled) ? $this->lang->instance->backup->create : $this->lang->instance->backupOnlyRunning; + $btn = html::commonButton($this->lang->instance->backup->create, "instance-id='{$instance->id}' title='{$title}' {$disabled}", "btn-backup btn btn-primary"); + + echo $btn; + } + + /** + * Print restore button of instance. + * + * @param object $instance + * @param object $backup + * @access public + * @return void + */ + public function printRestoreBtn($instance, $backup) + { + $disabled = $instance->status == 'running' && strtolower($backup->status) == 'completed' ? '' : 'disabled'; + $title = empty($disabled) ? $this->lang->instance->backup->restore : $this->lang->instance->restoreOnlyRunning; + $btn = html::commonButton($this->lang->instance->backup->restore, "instance-id='{$instance->id}' title='{$title}' {$disabled} backup-name='{$backup->name}'", "btn-restore btn btn-info"); + + echo $btn; + } + + /** + * Print button for managing database. + * + * @param object $db + * @param object $instance + * @access public + * @return void + */ + public function printDBAction($db, $instance) + { + $disabled = $db->ready ? '' : 'disabled'; + $btnHtml = html::commonButton($this->lang->instance->management, "{$disabled} data-db-name='{$db->name}' data-db-type='{$db->db_type}' data-id='{$instance->id}'", 'db-login btn btn-primary'); + + echo $btnHtml; + } + + /** + * Print message of action log of instance. + * + * @param object $instance + * @param object $log + * @access public + * @return string + */ + public function printLog($instance, $log) + { + if(empty($this->userPairs)) $this->userPairs = $this->loadModel('user')->getPairs('noclosed|noletter'); + $action = zget($this->lang->instance->actionList, $log->action, $this->lang->actions); + $logText = zget($this->userPairs, $log->actor) . ' ' . sprintf($action, $instance->name, $log->comment); + + $extra = json_decode($log->extra); + if(empty($extra) or !isset($extra->data)) + { + return $logText; + } + + switch($log->action) + { + case 'editname': + $oldName = zget($extra->data, 'oldName', ''); + $newName = zget($extra->data, 'newName', ''); + $logText .= ', ' . sprintf($this->lang->instance->nameChangeTo, $oldName, $newName); + break; + case 'upgrade': + $oldVersion = zget($extra->data, 'oldVersion', ''); + $newVersion = zget($extra->data, 'newVersion', ''); + $logText .= ', ' . sprintf($this->lang->instance->versionChangeTo, $oldVersion, $newVersion); + break; + case 'tosenior': + $oldAppName = zget($extra->data, 'oldAppName', ''); + $newAppName = zget($extra->data, 'newAppName', ''); + $logText .= ', ' . sprintf($this->lang->instance->toSeniorSerial, $oldAppName, $newAppName); + break; + case 'saveautobackupsettings': + $enableAutoBackup = zget($extra->data, 'autoBackup', 0); + $logText .= ': ' . ($enableAutoBackup ? $this->lang->instance->enableAutoBackup : $this->lang->instance->disableAutoBackup); + break; + default: + } + + return $logText; + } + + /* + * Convert CPU digital to readable format. + * + * @param array $cpuList + * @access public + * @return array + */ + public function getCpuOptions($cpuList) + { + $newList = array(); + foreach($cpuList as $cpuValue) $newList[$cpuValue] = $cpuValue . $this->lang->instance->cpuCore; + return $newList; + } + + /* + * Convert memory digital to readable format. + * + * @param array $memList + * @access public + * @return array + */ + public function getMemOptions($memList) + { + $newList = array(); + foreach($memList as $memValue) $newList[$memValue] = helper::formatKB(intval($memValue)); + return $newList; + } + + /* + * Get instance switcher. + * + * @param object $instance + * @access public + * @return string + */ + public function getSwitcher($instance) + { + $instanceLink = helper::createLink('instance', 'view', "id=$instance->id"); + + $output = "
"; + $output .= html::a($instanceLink, $instance->appName, '', 'class="btn"'); + $output .= "
"; + + return $output; + } + + /** + * Get switcher of custom installation page of store. + * + * @param object $app + * @access public + * @return array + */ + public function getInstallSwitcher($app) + { + $output = $this->loadModel('store')->getAppViewSwitcher($app); + $output .= "
"; + $output .= html::a(helper::createLink('instance', 'install', "id=$app->id"), $this->lang->instance->installApp, '', 'class="btn"'); + $output .= "
"; + + return $output; + } + + /** + * Get switcher of custom installation page of store. + * + * @param object $app + * @access public + * @return array + */ + public function getCustomInstallSwitcher($app) + { + $output = $this->loadModel('store')->getAppViewSwitcher($app); + $output .= "
"; + $output .= html::a(helper::createLink('instance', 'custominstall', "id=$app->id"), $this->lang->instance->customInstall, '', 'class="btn"'); + $output .= "
"; + + return $output; + } + + /** + * Get senior app list. The instance can be switched to senior App. + * + * @param object $instance + * @param string $channel + * @access public + * @return array + */ + public function seniorAppList($instance, $channel) + { + $appList = array(); + foreach(zget($this->config->instance->seniorChartList, $instance->chart, array()) as $chart) + { + $cloudApp = $this->loadModel('store')->getAppInfoByChart($chart, $channel, false); + if($cloudApp) $appList[] = $cloudApp; + } + + return $appList; + } + + /** + * Check the free memory of cluster is enough to install app. + * + * @param object $cloudApp + * @access public + * @return bool + */ + public function enoughMemory($cloudApp) + { + $clusterResource = $this->cne->cneMetrics(); + $freeMemory = intval($clusterResource->metrics->memory->allocatable * 0.9); // Remain 10% memory for system. + + return $freeMemory >= $cloudApp->memory; + } + + /** + * 判断按钮是否可点击。 + * Adjust the action clickable. + * + * @param object $instance + * @param string $action + * @access public + * @return bool + */ + public function isClickable(object $instance, string $action): bool + { + if($action == 'start') return $instance->type != 'external' ? $this->canDo('start', $instance) : false; + if($action == 'stop') return $instance->type != 'external' ? $this->canDo('stop', $instance) : false; + if($action == 'uninstall') return $instance->type != 'external' && $this->canDo('uninstall', $instance); + if($action == 'visit') return $instance->type != 'external' ? ($instance->domain && $this->canDo('visit', $instance)) : true; + if($action == 'upgrade') return !empty($instance->latestVersion); + if($action == 'bindUser') return ($instance->type == 'external' && in_array($instance->appName, array('Gitlab', 'Gitea', 'Gogs'))) ? true : false; + if($action == 'edit') return $instance->type != 'external' ? false : true; + + return true; + } +} diff --git a/module/instance/ui/setting.html.php b/module/instance/ui/setting.html.php new file mode 100644 index 0000000000..d8b099bb3e --- /dev/null +++ b/module/instance/ui/setting.html.php @@ -0,0 +1,43 @@ + + * @package instance + * @link https://www.zentao.net + */ + +namespace zin; + +formPanel +( + set::id('instanceSettingForm'), + set::title($lang->instance->setting), + set::submitBtnText($lang->save), + set::actions(array('submit')), + formRow + ( + formGroup + ( + set::name('name'), + set::width('500px'), + set::label($lang->instance->name), + set::value($instance->name), + ) + ), + formRow + ( + formGroup + ( + set::name('memory_kb'), + set::width('250px'), + set::control('picker'), + set::label($lang->instance->adjustMem), + set::value(intval($currentResource->max->memory / 1024)), + set::items($this->instance->filterMemOptions($currentResource)), + ) + ), +); diff --git a/module/instance/ui/upgrade.html.php b/module/instance/ui/upgrade.html.php new file mode 100644 index 0000000000..ad0b2cf909 --- /dev/null +++ b/module/instance/ui/upgrade.html.php @@ -0,0 +1,39 @@ + + * @package instance + * @link https://www.zentao.net + */ + +namespace zin; + +formPanel +( + set::id('instanceSettingForm'), + set::title(''), + set::submitBtnText($lang->confirm), + set::actions(array('submit', array('text' => $lang->cancel, 'data-type' => 'submit', 'data-dismiss' => 'modal'))), + span + ( + setClass('p-4 font-semibold'), + icon('exclamation-sign', setClass('text-warning icon-2x align-middle m-2')), + $lang->instance->notices['confirmUpgrade'], + empty($instance->latestVersion->change_log_url) ? null: a + ( + setClass('ml-5'), + set::target('_blank'), + set::href($instance->latestVersion->change_log_url), + setStyle('color', 'initial'), + setStyle('font-weight', 'initial'), + setStyle('text-decoration', 'underline'), + $lang->instance->updateLog, + ), + ), + div(setStyle('height', '20px')), + input(set::type('hidden'), set::name('confirm'), set::value('yes')) +); diff --git a/module/instance/ui/view.html.php b/module/instance/ui/view.html.php new file mode 100644 index 0000000000..ea3dad97e9 --- /dev/null +++ b/module/instance/ui/view.html.php @@ -0,0 +1,203 @@ + + * @package instance + * @link http://www.zentao.net + */ + +namespace zin; + +jsVar('instanceID', $instance->id); + +$setting = usePager('pager'); +$cpuInfo = $this->instance->printCpuUsage($instance, $instanceMetric->cpu, 'array'); +$memoryInfo = $this->instance->printMemUsage($instance, $instanceMetric->memory, 'array'); +$actions = $this->loadModel('common')->buildOperateMenu($instance); + +$dbListWg = array(); +foreach($dbList as $db) +{ + $disabledClass = $db->ready && commonModel::hasPriv('instance', 'ajaxDBAuthUrl') ? '' : 'disabled'; + $dbListWg[] = h::tr + ( + h::td($db->db_name), + h::td($db->db_type), + h::td + ( + $db->ready ? $lang->instance->dbReady : $lang->instance->dbWaiting, + setClass('text-' . ($db->ready ? 'success' : 'danger')) + ), + + h::td + ( + btn + ( + $lang->instance->management, + setClass('btn text-primary ghost ' . $disabledClass), + setData('dbname', $db->name), + setData('dbtype', $db->db_type), + setData('id', $instance->id), + on::click('openAdminer'), + ) + ), + ); +} + +detailHeader( + to::prefix(''), + to::title(''), +); +div +( + setClass('flex flex-normal gap-x-5'), + div + ( + setClass('basis-2/3'), + setID('instanceInfoContainer'), + detailBody + ( + sectionList + ( + /* 应用名称信息图标区块 */ + section + ( + div + ( + setClass('flex justify-between'), + div + ( + setClass('flex basis-full'), + img(set::src($instance->logo), setStyle(array('width' => '50px', 'height' => '50px'))), + div + ( + setClass('ml-3 flex col gap-y-1 basis-full'), + div + ( + $instance->name, setClass('text-xl'), + span($cloudApp->app_version, setClass('ml-3 label lighter rounded-full')) + ), + div + ( + setClass('flex progress-container'), + set::title($cpuInfo['tip']), + icon('cog-outline text-' . $cpuInfo['color']), + $lang->instance->cpuUsage, + div + ( + setClass('progress rounded-lg'), + setStyle('background', "var(--color-{$cpuInfo['color']}-50)"), + div + ( + setClass('progress-bar ' . $cpuInfo['color']), + set::role('progressbar'), + setStyle('width', $cpuInfo['rate']) + ) + ), + icon('desktop text-' . $memoryInfo['color']), + $lang->instance->memUsage, + span + ( + setClass('text-gray'), + sprintf($lang->instance->memTotal, helper::formatKB($instanceMetric->memory->limit)), + ), + div + ( + setClass('progress rounded-lg'), + set::title($memoryInfo['tip']), + setStyle('background', "var(--color-{$memoryInfo['color']}-50)"), + div + ( + setID('memoryRate'), + set::role('progressbar'), + setData('load', $instance->status == 'running' && $memoryInfo['rate'] == '0%'), + setClass('progress-bar ' . $memoryInfo['color']), + setStyle('width', $memoryInfo['rate']) + ) + ) + ), + ), + ), + btn($lang->instance->setting, setClass('btn ghost'), set::icon('backend'), setData('toggle', 'modal'), setData('size', 'sm'), set::url(createLink('instance', 'setting', "id={$instance->id}"))) + ), + ), + /* 基本信息区块 */ + section + ( + set::title($lang->instance->baseInfo), + h::table + ( + setStyle('min-width', '700px'), + setClass('table w-auto max-w-full bordered mt-4'), + h::tr + ( + h::th($lang->instance->status), + h::th($lang->instance->source), + // h::th($lang->instance->appTemplate), + h::th($lang->instance->installBy), + h::th($lang->instance->installAt), + h::th($lang->instance->runDuration), + $defaultAccount ? h::th($lang->instance->defaultAccount) : null, + $defaultAccount ? h::th($lang->instance->defaultPassword) : null, + ), + h::tr + ( + h::td + ( + setID('statusTD'), + setData('reload', in_array($instance->status, array('creating', 'initializing', 'pulling', 'startup', 'starting', 'suspending', 'installing', 'uninstalling', 'stopping', 'destroying', 'upgrading'))), + span + ( + setClass('label label-dot mr-1 ' . zget($this->lang->instance->htmlStatusesClass, $instance->status, '')) + ), + zget($this->lang->instance->statusList, $instance->status, ''), setClass('text-' . zget($this->lang->instance->htmlStatusesClass, $instance->status, '')) + ), + h::td(zget($lang->instance->sourceList, $instance->source, '')), + // h::td(a(set::href($this->createLink('store', 'appView', "id=$instance->appID")), $instance->appName)), + h::td(zget($users, $instance->createdBy, '')), + h::td(substr($instance->createdAt, 0, 16)), + h::td(common::printDuration($instance->runDuration)), + $defaultAccount ? h::td($defaultAccount->username) : null, + $defaultAccount ? h::td($defaultAccount->password) : null, + ) + ) + ), + /* 数据库区块 */ + empty($dbList) ? null : section + ( + set::title($lang->instance->dbList), + h::table + ( + setStyle('min-width', '700px'), + setClass('table w-auto max-w-full bordered mt-4'), + h::tr + ( + h::th($lang->instance->dbName), + h::th($lang->instance->dbType, setStyle('width', '100px')), + h::th($lang->instance->status, setStyle('width', '100px')), + h::th($lang->instance->action, setStyle('width', '100px')), + ), + $dbListWg + ) + ), + ), + floatToolbar + ( + set::object($instance), + isAjaxRequest('modal') ? null : to::prefix(backBtn(set::icon('back'), set::class('ghost text-white'), $lang->goback)), + set::main($actions['mainActions']), + set::suffix($actions['suffixActions']) + ), + ) + ), + div + ( + setClass('basis-auto'), + history() + ) +); \ No newline at end of file diff --git a/module/instance/view/advance.html.php b/module/instance/view/advance.html.php new file mode 100644 index 0000000000..f7b3c2d37d --- /dev/null +++ b/module/instance/view/advance.html.php @@ -0,0 +1,192 @@ +
+ + +
+
instance->visitIP;?>
+
+
+ + $value):?> + + + + + +
+
+ +
+
instance->mem;?>
+
+
+
+ + + + + + + + + + + + + + + + + +
+ instance->currentMemory;?>:resources->memory / 1024);?> + instance->printSuggestedMemory($instanceMetric->memory, $lang->instance->memOptions);?> +
instance->adjustMem;?>instance->filterMemOptions($currentResource), '', "class='form-control'");?>
+ instance->saveSetting, "id='memBtn' instance-id='$instance->id'", 'btn btn-primary'); ?> +
+
+
+ features->ldap)):?> +
+
system->ldapManagement;?>
+
+
+
+ + + system->hasSystemLDAP();?> + ldapSnippetName ? 'true' : '' ;?> + + + + + + + + + + +
$lang->instance->enableLDAP), $enableLDAP, ($LDAPInstalled ? '' : 'disabled'));?> + + instance->systemLDAPInactive;?> + instance->toSystemLDAP, '', "class='btn btn-default'");?> + +
+ instance->saveSetting, "id='ldapBtn' instance-id='$instance->id'" . ($LDAPInstalled ? '' : 'disabled'), 'btn btn-primary');?> +
+
+
+ + features->mail)):?> +
+
system->SMTP->common;?>
+
+
+
+ + + system->isSMTPEnabled();?> + smtpSnippetName ? 'true' : '' ;?> + + + + + + + + + + +
$lang->instance->enableSMTP), $enableSMTP, ($SMTPInstalled ? '' : 'disabled'));?> + + instance->systemSMTPInactive;?> + instance->toSystemSMTP, '', "class='btn btn-default'");?> + +
+ instance->saveSetting, "id='smtpBtn' instance-id='$instance->id'" . ($SMTPInstalled ? '' : 'disabled'), 'btn btn-primary');?> +
+
+
+ + + scalable) && $config->edition == 'biz'):?> +
+
instance->scalable;?>
+
+
+
+ + + + + + + +
instance->componentFields['replicas'];?> + replicas;?> + 1, 2=>2, 3=>3, 4=>4), intval($currentResource->replicas) ? : 1, " id='replicas-input' max='4' min='1' class='form-control scalable-input hide'");?> + + instance->change, 'id="replicas-edit"', 'btn btn-primary ' . (in_array($instance->status, array('uninstalling', 'destroying', 'destroyed', 'unknown', 'abnormal')) ? 'disabled' : ''));?> + instance->saveSetting, "id='replicas-save' instance-id='$instance->id'", 'btn btn-primary hide');?> +
+
+
+ + + +
+
instance->customSetting;?>
+
+
+
+ + + + + + + + + + + + + + + +
label;?> + name, $item->default, "class='form-control' placeholder='{$item->label}'");?> +
+ instance->saveSetting, "id='customBtn' instance-id='$instance->id'", 'btn btn-primary'); ?> +
+
+
+ + +
+
+
instance->dbList;?>
+
+
+ + + + + + + + + + + + + + + + + + + +
instance->dbName;?>instance->dbType;?>instance->dbStatus;?>instance->action;?>
db_name;?>db_type;?>ready ? $lang->instance->dbReady : $lang->instance->dbWaiting;?>instance->printDBAction($db, $instance);?>
+
+ +
diff --git a/module/instance/view/backup.html.php b/module/instance/view/backup.html.php new file mode 100644 index 0000000000..9e16c19fdf --- /dev/null +++ b/module/instance/view/backup.html.php @@ -0,0 +1,80 @@ +
+
+ instance->backup->keepBackupBySystem;?> +
+ refresh, '', "class='btn'");?> + id}", '', true), $lang->instance->backup->autoBackup, '', "class='iframe btn btn-primary' title='{$lang->instance->backup->autoBackup}' data-width='520' data-app='space'");?> + id}", '', true), $lang->instance->restore->autoRestore, '', "class='iframe btn btn-primary' title='{$lang->instance->restore->autoRestore}' data-width='520' data-app='space'");?> + config->instance->enableAutoRestore ? $autoRestoreBtn : $autoBackupBtn;?> + instance->printBackupBtn($instance);?> +
+
+ +
+

+ instance->errors->noBackup;?> + instance->printBackupBtn($instance);?> +

+
+ + + + + + + + + + + + + + + + + + + + + + backup_details->db[0]) ? intval(zget($backup->backup_details->db[0], 'size', 0)) : 0;?> + backup_details->volume[0]) ? intval(zget($backup->backup_details->volume[0], 'doneBytes', 0)) : 0;?> + + + + + + + + + +
instance->backup->date;?>instance->backup->operator;?>instance->backup->dbStatus;?>instance->backup->dbSize;?>instance->backup->restoreDate;?>instance->backup->dbStatus;?> + instance->backup->action;?> +
create_time);?>'>create_time);?>instance->backup->operators, $backup->username, $backup->username);?>instance->backup->statusList, strtolower($backup->status));?> + latest_restore_time) : '';?>'> + latest_restore_time ? date('Y-m-d', $backup->latest_restore_time) : '';?> + + latest_restore_status ? zget($lang->instance->restore->statusList, $backup->latest_restore_status, '') : '';?>instance->printRestoreBtn($instance, $backup);?>
+ + +
diff --git a/module/instance/view/backupsettings.html.php b/module/instance/view/backupsettings.html.php new file mode 100644 index 0000000000..674aa4ed2d --- /dev/null +++ b/module/instance/view/backupsettings.html.php @@ -0,0 +1,59 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.cn + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +app->getModuleRoot() . '/common/view/datepicker.html.php';?> +instance->notices);?> +id);?> +
+
+
+

instance->backup->autoBackup;?>

+
+
+
+

$lang->instance->backup->enableAutoBackup), $backupSettings->autoBackup ? 'true' : '');?>

+ + + + + + + + + + + + + + + + + + +
instance->backup->backupTime;?> +
+
+ backupTime, "class='form-control form-time' data-picker-position='bottom-right' maxlength='20'");?> +
+
+
instance->backup->cycleDays;?> +
instance->backup->cycleList, $backupSettings->cycleDays, "class='form-control' maxlength='20'");?>
+
instance->backup->keepDays;?> +
keepDays, "class='form-control' maxlength='2' min='1' max='30'");?>
+ instance->backup->keepDayRange;?> +
+
save, "id='saveSetting'", 'btn btn-primary');?>
+
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/baseinfo.html.php b/module/instance/view/baseinfo.html.php new file mode 100644 index 0000000000..bcdf7c9640 --- /dev/null +++ b/module/instance/view/baseinfo.html.php @@ -0,0 +1,115 @@ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + status == 'running'):?> + + + + + + + + + + + + + + + + +
instance->status;?> + instance->printStatus($instance, false);?> +
instance->source;?> + instance->sourceList, $instance->source, '');?> +
instance->appTemplate;?>createLink('store', 'appView', "id=$instance->appID"), $instance->appName);?>
instance->installAt;?>createdAt, 0, 16);?>
instance->runDuration;?>runDuration);?>
instance->defaultAccount;?>username;?>
instance->defaultPassword;?>password;?>
+
+
+
+
instance->cpuUsage;?>
+ instance->printCpuUsage($instance, $instanceMetric->cpu, 'pie');?> +
+
+
instance->memUsage;?>
+ instance->printMemUsage($instance, $instanceMetric->memory, 'pie');?> +
+
+
+
+
+
+solution) && array_key_exists(strtolower($instance->chart), $this->config->instance->seniorChartList)):?> +
+
+
instance->upgradeToSenior;?>
+
+
+

+ + $seniorApp):?> + 0) $seniorAppHtml .= $lang->instance->or;?> + id"), $seniorApp->alias, '_blank');?> + + appID"), $instance->appName, '_blank');?> + instance->descOfSwitchSerial, $currentAppUrl, $seniorAppHtml);?> + instance->serialDiff, '_blank', "id='serialDiff' class=''");?> +

+
+ status == 'stopped' ? '' : 'disabled';?> + + inLink('toSenior', "id={$instance->id}&seniorAppID={$seniorApp->id}", '', true);?> + instance->switchTo . $seniorApp->alias, '', $disabled . " class='iframe btn btn-primary' title='{$lang->instance->upgrade}' data-width='520' data-app='space'");?> + + + instance->stopInstanceTips;?> + +
+
+
+ +
+
+
instance->operationLog;?>
+
+
+ + + + + + + + + + + + + + + + + +
instance->log->date;?>instance->log->message;?>
date;?>instance->printLog($instance, $log);?>
+ +
+
diff --git a/module/instance/view/custominstall.html.php b/module/instance/view/custominstall.html.php new file mode 100644 index 0000000000..6571490d43 --- /dev/null +++ b/module/instance/view/custominstall.html.php @@ -0,0 +1,69 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> + +
+
+

instance->customInstall;?>

+ +
+

createLink('store', 'appView', "id={$appID}"), $lang->instance->noComponent . ' ' . $lang->goback, '', "class='btn btn-info'");?>

+
+ + +
+ +
+ + + settings as $setting):?> + type == 'choice') + { + $selectOptions = array_combine($setting->options, $setting->options); + if(strpos($setting->field, 'resources_cpu') !== false) $selectOptions = $this->instance->getCpuOptions($setting->options); + if(strpos($setting->field, 'resources_memory') !== false) $selectOptions = $this->instance->getMemOptions($setting->options); + } + ?> + + + + + + + +
lang->instance->componentFields, $setting->name, $setting->name);?> +
+ type == 'choice') echo html::select($setting->field, $selectOptions, $setting->value, "class='form-control chosen'");?> + type == 'int') echo html::number($setting->field, $setting->value, "class='form-control'", "require");?> +
+
+
+ + +
instance->install);?>
+ +
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/editname.html.php b/module/instance/view/editname.html.php new file mode 100644 index 0000000000..63fe7dde60 --- /dev/null +++ b/module/instance/view/editname.html.php @@ -0,0 +1,28 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +
+
+

instance->editName;?>

+
+
+ + + + + +
instance->name;?>name, "class='form-control' maxlength='50'");?>
+
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/install.html.php b/module/instance/view/install.html.php new file mode 100644 index 0000000000..f187c398cf --- /dev/null +++ b/module/instance/view/install.html.php @@ -0,0 +1,118 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.cn + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +instance->notices);?> +
+
+
+

instance->installApp . $cloudApp->alias . $cloudApp->app_version;?>

+
+
' class="cell not-watch load-indicator main-form"> + + + + + + + + + + + + + +
instance->name;?> +
+ alias, "class='form-control' maxlength='20'");?> +
+
instance->domain;?> +
+ + cne->sysDomain();?> +
+
+
instance->advanceOption . "", '', "data-toggle='collapse'");?>
+ + + + + + + + + + features->ldap) && $this->loadModel('system')->hasSystemLDAP()):?> + + + + + + + features->mail) && $this->loadModel('system')->isSMTPEnabled()):?> + + + + + + + dependencies->mysql) and $mysqlList or isset($cloudApp->dependencies->postgresql) and $pgList):?> + + + + + + + dependencies->mysql) and $mysqlList):?> + + + + + + dependencies->postgresql) and $pgList):?> + + + + + + + +
instance->version;?> +
+ +
+
+
+ system->ldapSnippetName() => $lang->instance->enableLDAP));?> +
+
+
+ system->smtpSnippetName() => $lang->instance->enableSMTP));?> +
+
instance->dbType;?> +
+ instance->dbTypes, 'sharedDB');?> +
+
instance->howToSelectDB, '_blank');?>
+
+ +
+
+
+ +
+
+
instance->install);?>
+
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/resourceerror.html.php b/module/instance/view/resourceerror.html.php new file mode 100644 index 0000000000..42334cf10d --- /dev/null +++ b/module/instance/view/resourceerror.html.php @@ -0,0 +1,22 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.cn + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +
+
+
+

instance->errors->notEnoughResource;?>

+
+

instance->errors->notEnoughMemory, $cloudApp->alias, $requiredMemory, $freeMemory, $gapMemory);?>

+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/restoresettings.html.php b/module/instance/view/restoresettings.html.php new file mode 100644 index 0000000000..02412b1f31 --- /dev/null +++ b/module/instance/view/restoresettings.html.php @@ -0,0 +1,55 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.cn + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +app->getModuleRoot() . '/common/view/datepicker.html.php';?> +instance->notices);?> +id);?> +
+
+
+

instance->restore->autoRestore;?>

+
+
+
+

+ $lang->instance->restore->enableAutoRestore), $restoreSettings->autoRestore ? 'true' : '');?> +

+ + + + + + + + + + + + + +
instance->restore->restoreTime;?> +
+
+ restoreTime, "class='form-control form-time' data-picker-position='bottom-right' maxlength='20'");?> +
+
+
instance->restore->cycleDays;?> +
+ instance->restore->cycleList, $restoreSettings->cycleDays, "class='form-control' maxlength='20'");?> +
+
+
save, "id='saveSetting'", 'btn btn-primary');?>
+
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/tosenior.html.php b/module/instance/view/tosenior.html.php new file mode 100644 index 0000000000..349ca35546 --- /dev/null +++ b/module/instance/view/tosenior.html.php @@ -0,0 +1,28 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.cn + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +
+
+

instance->toSeniorAttention;?>

+
+
+
id}&seniorAppID={$cloudApp->id}&confirm=yes");?>" class="cell not-watch load-indicator main-form form-ajax"> +
instance->toSeniorTips;?>
+
+ $lang->instance->hasRead));?> +
+
instance->upgrade, 'disabled');?>
+
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/upgrade.html.php b/module/instance/view/upgrade.html.php new file mode 100644 index 0000000000..0aa14ee7f5 --- /dev/null +++ b/module/instance/view/upgrade.html.php @@ -0,0 +1,32 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +instance->notices);?> +
+
+

instance->upgrade} : {$instance->name}";?>

+
+
+ latestVersion)):?> + + + + +
+ + instance->upgrade);?> +
+ +
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/instance/view/view.html.php b/module/instance/view/view.html.php new file mode 100644 index 0000000000..83511316be --- /dev/null +++ b/module/instance/view/view.html.php @@ -0,0 +1,50 @@ + + * @package instance + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +instance->notices);?> +id));?> +demoAppLife);?> + + +app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/system/config/dtable.php b/module/system/config/dtable.php new file mode 100644 index 0000000000..6a1d9c9d77 --- /dev/null +++ b/module/system/config/dtable.php @@ -0,0 +1,50 @@ +loadLang('instance'); + +$config->system->dtable = new stdclass(); + +$config->system->dtable->dbList = new stdclass(); + +$config->system->dtable->dbList->fieldList['name']['title'] = $lang->system->dbName; +$config->system->dtable->dbList->fieldList['name']['group'] = 1; + +$config->system->dtable->dbList->fieldList['dbType']['title'] = $lang->system->dbType; +$config->system->dtable->dbList->fieldList['dbType']['name'] = 'db_type'; +$config->system->dtable->dbList->fieldList['dbType']['group'] = 2; + +$config->system->dtable->dbList->fieldList['dbStatus']['title'] = $lang->system->dbStatus; +$config->system->dtable->dbList->fieldList['dbStatus']['name'] = 'status'; +$config->system->dtable->dbList->fieldList['dbStatus']['map'] = $lang->instance->statusList; + +$config->system->dtable->dbList->fieldList['actions']['name'] = 'actions'; +$config->system->dtable->dbList->fieldList['actions']['title'] = $lang->actions; +$config->system->dtable->dbList->fieldList['actions']['type'] = 'actions'; +$config->system->dtable->dbList->fieldList['actions']['menu'] = array('management'); + +$config->system->dtable->dbList->fieldList['actions']['list']['management']['icon'] = 'cog-outline'; +$config->system->dtable->dbList->fieldList['actions']['list']['management']['hint'] = $lang->system->management; + +$config->system->dtable->backup = new stdclass(); + +$config->system->dtable->backup->fieldList['time']['title'] = $lang->system->backup->time; +$config->system->dtable->backup->fieldList['time']['type'] = 'datetime'; + +$config->system->dtable->backup->fieldList['backupPerson']['title'] = $lang->system->backup->backupPerson; + +$config->system->dtable->backup->fieldList['type']['title'] = $lang->system->backup->type; + +$config->system->dtable->backup->fieldList['actions']['name'] = 'actions'; +$config->system->dtable->backup->fieldList['actions']['title'] = $lang->actions; +$config->system->dtable->backup->fieldList['actions']['type'] = 'actions'; +$config->system->dtable->backup->fieldList['actions']['menu'] = array('restore', 'delete'); + +$config->system->dtable->backup->fieldList['actions']['list']['restore']['icon'] = 'history'; +$config->system->dtable->backup->fieldList['actions']['list']['restore']['hint'] = $lang->system->backup->restore; +$config->system->dtable->backup->fieldList['actions']['list']['restore']['url'] = array('module' => 'backup', 'method' => 'restore', 'params' => 'time={time}'); +$config->system->dtable->backup->fieldList['actions']['list']['restore']['data-confirm'] = $lang->system->backup->confirmDelete; + +$config->system->dtable->backup->fieldList['actions']['list']['delete']['icon'] = 'trash'; +$config->system->dtable->backup->fieldList['actions']['list']['delete']['hint'] = $lang->delete; +$config->system->dtable->backup->fieldList['actions']['list']['delete']['url'] = array('module' => 'backup', 'method' => 'delete', 'params' => 'time={time}'); +$config->system->dtable->backup->fieldList['actions']['list']['delete']['data-confirm'] = $lang->system->backup->confirmRestore; diff --git a/module/system/control.php b/module/system/control.php new file mode 100644 index 0000000000..9ffe0cb106 --- /dev/null +++ b/module/system/control.php @@ -0,0 +1,699 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +class system extends control +{ + + /** + * __construct + * + * @access public + * @return void + */ + public function __construct($moduleName = '', $methodName = '') + { + parent::__construct($moduleName, $methodName); + + $this->backupPath = $this->loadModel('backup')->getBackupPath(); + if(!is_dir($this->backupPath)) + { + if(!mkdir($this->backupPath, 0755, true)) $this->view->error = sprintf($this->lang->system->backup->error->noWritable, $this->backupPath); + } + else + { + if(!is_writable($this->backupPath)) $this->view->error = sprintf($this->lang->system->backup->error->noWritable, $this->backupPath); + } + if(!is_writable($this->app->getTmpRoot())) $this->view->error = sprintf($this->lang->system->backup->error->noWritable, $this->app->getTmpRoot()); + + $this->loadModel('action'); + $this->loadModel('setting'); + } + + /** + * System index. + * + * @access public + * @return void + */ + public function index() + { + $this->view->title = $this->lang->system->common; + + $this->display(); + } + + /** + * Dashboard page. + * + * @param int $total + * @param int $recPerPage + * @param int $pageID + * @access public + * @return void + */ + public function dashboard($total = 0, $recPerPage = 20, $pageID = 1) + { + $this->loadModel('cne'); + $this->app->loadClass('pager', true); + $pager = new pager($total, $recPerPage, $pageID); + + $instances = $this->loadModel('instance')->getByAccount($this->app->user->account, $pager, '', '', 'running'); + + $actions = $this->loadModel('action')->getDynamic('all', 'today'); + + $this->view->position[] = $this->lang->my->common; + + $this->view->title = $this->lang->my->common; + $this->view->instances = $instances; + $this->view->actions = $actions; + $this->view->cneMetrics = $this->cne->cneMetrics(); + $this->view->instancesMetrics = $this->cne->instancesMetrics($instances); + $this->view->pager = $pager; + + $this->display(); + } + + /** + * Show database list. + * + * @access public + * @return void + */ + public function dbList() + { + $this->app->loadLang('instance'); + + $this->view->title = $this->lang->system->dbManagement; + $this->view->dbList = $this->loadModel('cne')->allDBList(); + + $this->display(); + } + + /** + * Test the connection of LDAP by post parameters. + * + * @access public + * @return void + */ + public function testLDAPConnection() + { + $settings = fixer::input('post') + ->setDefault('host', '') + ->setDefault('port', '') + ->setDefault('bindDN', '') + ->setDefault('bindPass', '') + ->setDefault('baseDN', '') + ->get(); + + $success = $this->system->testLDAPConnection($settings); + if($success) return $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->verifyLDAPSuccess)); + + return $this->send(array('result' => 'fail', 'message' => $this->lang->system->errors->verifyLDAPFailed)); + } + + /** + * Install LDAP + * + * @access public + * @return void + */ + public function installLDAP() + { + if($this->system->hasSystemLDAP()) return print(js::locate($this->inLink('ldapView'))); + + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + $ldapApp = $this->loadModel('store')->getAppInfoByChart('openldap', $channel, false); + if($_POST) + { + $postData = fixer::input('post')->setDefault('source', 'qucheng')->get(); + if($postData->source == 'qucheng') + { + $this->system->installQuchengLDAP($ldapApp, $channel); + } + else if($postData->source == 'extra') + { + $this->system->configExtraLDAP((object)$postData->extra); + } + else + { + dao::$errors[] = $this->lang->system->notSupportedLDAP; + } + + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->ldapInstallSuccess, 'locate' => $this->inLink('ldapView'))); + } + + $this->lang->switcherMenu = $this->system->getLDAPSwitcher(); + + $this->view->title = $this->lang->system->ldapManagement; + $this->view->ldapApp = $ldapApp; + $this->view->activeLDAP = $this->system->getActiveLDAP(); + $this->view->ldapSettings = $this->system->getExtraLDAPSettings(); + + $this->display(); + } + + /** + * Edit extra LDAP. + * + * @access public + * @return void + */ + public function editLDAP() + { + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + $ldapApp = $this->loadModel('store')->getAppInfoByChart('openldap', $channel, false); + if($_POST) + { + session_write_close(); + $this->system->updateLDAP($ldapApp, $channel); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->ldapUpdateSuccess, 'locate' => $this->inLink('ldapView'))); + } + + $this->view->title = $this->lang->system->editLDAP; + + + $this->view->ldapApp = $ldapApp; + $this->view->activeLDAP = $this->system->getActiveLDAP(); + $this->view->ldapSettings = $this->system->getExtraLDAPSettings(); + $this->display(); + } + + /** + * ajaxUpdatingLDAPProgress + * + * @access public + * @return void + */ + public function ajaxUpdatingLDAPProgress() + { + session_write_close(); + + $number = $this->loadModel('setting')->getItem('owner=system&module=common§ion=ldap&key=updatingProgress'); + echo sprintf($this->lang->system->LDAP->updatingProgress, intval($number)); + } + + /** + * LDAP view. + * + * @access public + * @return void + */ + public function ldapView() + { + $this->loadModel('instance'); + $this->app->loadLang('instance'); + + $ldapInstance = new stdclass; + $ldapInstance->id = 0; + + $activeLDAP = $this->loadModel('setting')->getItem('owner=system&module=common§ion=ldap&key=active'); + if($activeLDAP == 'qucheng') + { + $instanceID = $this->loadModel('setting')->getItem('owner=system&module=common§ion=ldap&key=instanceID'); + $ldapInstance = $this->instance->getByID($instanceID); + if(empty($ldapInstance)) return print js::alert($this->lang->system->notices->noLDAP); + + $ldapInstance = $this->instance->freshStatus($ldapInstance); + $ldapSettings = json_decode($ldapInstance->ldapSettings); + } + else if($activeLDAP == 'extra') + { + $ldapSettings = $this->system->getExtraLDAPSettings(); + } + else + { + return print js::alert($this->lang->system->notices->noLDAP); + } + + $this->lang->switcherMenu = $this->system->getLDAPSwitcher(); + + $this->view->title = $this->lang->system->ldapManagement; + + $this->view->activeLDAP = $activeLDAP; + $this->view->instanceID = $ldapInstance->id; + $this->view->ldapInstance = $ldapInstance; + $this->view->ldapSettings = $ldapSettings; + + $this->display(); + } + + /** + * Uninstall all LDAP in system. (This function is only for debug and test.) + * + * @access public + * @return void + */ + public function uninstallLDAP() + { + if(!$this->config->debug) return; // Only run in debug mode. + + /* 1. uninstall QuCheng LDAP. */ + if($this->system->uninstallQuChengLDAP()) + { + echo date('Y-m-d H:i:s') . ": Uninstall QuCheng LDAP success.
"; + } + else + { + echo date('Y-m-d H:i:s') . ": Uninstall QuCheng LDAP fail.
"; + $errors = dao::getError(); + foreach($errors as $error) echo $error . '
'; + } + + /* 2. uninstall extra LDAP. */ + if($this->system->uninstallExtraLDAP()) + { + echo date('Y-m-d H:i:s') . ": Uninstall extra LDAP success.
"; + } + else{ + echo date('Y-m-d H:i:s') . ": Uninstall extra LDAP fail.
"; + $errors = dao::getError(); + foreach($errors as $error) echo $error . '
'; + } + } + + /** + * OSS view. + * + * @access public + * @return void + */ + public function ossView() + { + $this->loadModel('cne'); + + $minioInstance = new stdclass; + $minioInstance->k8name = 'cne-operator'; + $minioInstance->spaceData = new stdclass; + $minioInstance->spaceData->k8space = $this->config->k8space; + + $ossAccount = $this->cne->getDefaultAccount($minioInstance, '', 'minio'); + $ossDomain = $this->cne->getDomain($minioInstance, '', 'minio'); + $this->lang->switcherMenu = $this->system->getOssSwitcher(); + + $this->view->title = $this->lang->system->oss->common; + $this->view->ossAccount = $ossAccount; + $this->view->ossDomain = $ossDomain; + + $this->display(); + } + + /** + * Install SMTP. + * + * @access public + * @return void + */ + public function installSMTP() + { + if($this->system->smtpSnippetName()) return print(js::locate($this->inLink('smtpView'))); + + if($_POST) + { + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + $this->system->installSysSMTP($channel); + + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->smtpInstallSuccess, 'locate' => $this->inLink('smtpView'))); + } + + $this->lang->switcherMenu = $this->system->getSMTPSwitcher(); + + $this->view->title = $this->lang->system->SMTP->common; + $this->view->smtpSettings = $this->system->getSMTPSettings(); + $this->view->smtpLinked = false; + $this->view->activeSMTP = false; + + $this->display(); + } + + /** + * Edit SMTP. + * + * @access public + * @return void + */ + public function editSMTP() + { + $this->loadModel('instance'); + $this->app->loadLang('instance'); + + if($_POST) + { + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + $postData = fixer::input('post')->setDefault('source', 'qucheng')->get(); + $this->system->updateSMTPSettings(); + + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::getError())); + + $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->smtpUpdateSuccess, 'locate' => $this->inLink('smtpView'))); + } + + $this->lang->switcherMenu = $this->system->getSMTPSwitcher(); + + $this->view->title = $this->lang->system->SMTP->editSMTP; + $this->view->smtpSettings = $this->system->getSMTPSettings(); + $this->view->smtpLinked = $this->instance->countSMTP(); + $this->view->activeSMTP = zget($this->view->smtpSettings, 'enabled', false); + + $this->display(); + } + + /** + * Show SMTP setting detail. + * + * @access public + * @return void + */ + public function smtpView() + { + $this->loadModel('instance'); + $this->app->loadLang('instance'); + + $instanceID = $this->loadModel('setting')->getItem('owner=system&module=common§ion=smtp&key=instanceID'); + $smtpInstance = $this->instance->getByID($instanceID); + if(empty($smtpInstance)) return print js::alert($this->lang->system->notices->notFoundSMTPService); + + + $this->lang->switcherMenu = $this->system->getSMTPSwitcher(); + + $this->view->title = $this->lang->system->SMTP->common; + $this->view->smtpSettings = $this->system->getSMTPSettings(); + $this->view->smtpInstance = $smtpInstance; + + $this->display(); + } + + /** + * Uninstall SMTP. (This function is only for debug and test.) + * + * @access public + * @return void + */ + public function uninstallSMTP() + { + if(!$this->config->debug) return; // Only run in debug mode. + + /* Uninstall system SMTP proxy APP. */ + if($this->system->uninstallSysSMTP()) + { + echo date('Y-m-d H:i:s') . ": Uninstall system SMTP success.
"; + } + else + { + echo date('Y-m-d H:i:s') . ": Uninstall system SMTP fail.
"; + $errors = dao::getError(); + foreach($errors as $error) echo $error . '
'; + } + } + + /** + * Config customer's domain. + * + * @access public + * @return void + */ + public function configDomain() + { + $domainSettings = $this->system->getDomainSettings(); + if($domainSettings->customDomain) $this->locate($this->inLink('domainView')); + $this->locate($this->inLink('editDomain')); + } + + /** + * Edit customer's domain. + * + * @access public + * @return void + */ + public function editDomain() + { + $this->loadModel('instance'); + + if($_POST) + { + session_write_close(); + $this->system->saveDomainSettings(); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::$errors)); + + return $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->updateDomainSuccess, 'locate' => $this->inlink('domainView'))); + } + + $this->view->title = $this->lang->system->domain->common; + $this->view->domainSettings = $this->system->getDomainSettings(); + + $this->display(); + } + + /** + * AjaxValidCert + * + * @access public + * @return void + */ + public function ajaxValidateCert() + { + $certData = fixer::input('post')->get(); + + $this->dao->select('*')->from('system')->data($certData) + ->batchCheck('customDomain,certPem,certKey', 'notempty'); + if(dao::isError()) return $this->send(array('result' => 'fail', 'message' => dao::$errors)); + + if(!validater::checkREG($certData->customDomain, '/^((?!-)[a-z0-9-]{1,63}(?send(array('result' => 'fail', 'message' => $this->lang->system->errors->invalidDomain)); + } + + $certName = 'tls-' . str_replace('.', '-',$certData->customDomain); + $result = $this->loadModel('cne')->validateCert($certName, $certData->certPem, $certData->certKey, $certData->customDomain); + if($result->code == 200) return $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->validCert)); + + return $this->send(array('result' => 'fail', 'message' => $result->message)); + } + + /** + * Show progress of updating domains. + * + * @access public + * @return void + */ + public function ajaxUpdatingDomainProgress() + { + session_write_close(); + + $oldDomainQty = $this->loadModel('instance')->countOldDomain(); + return print(sprintf($this->lang->system->domain->updatingProgress, $oldDomainQty)); + } + + /** + * Domain settings view. + * + * @access public + * @return void + */ + public function domainView() + { + $domainSettings = $this->system->getDomainSettings(); + $certName = 'tls-' . str_replace('.', '-', $domainSettings->customDomain); + $cert = $this->loadModel('cne')->certInfo($certName); + + $notAfter = zget($cert, 'not_after', ''); + if($notAfter) $cert->expiredDate = date('Y-m-d H:i:s', $notAfter); + + $this->view->title = $this->lang->system->domain->common; + $this->view->domainSettings = $domainSettings; + $this->view->cert = $cert; + + $this->display(); + } + + /** + * Config Server Load Balancing. + * + * @access public + * @return void + */ + public function configSLB() + { + $slbSettings = $this->system->getSLBSettings(); + if($slbSettings->name) return print(js::locate($this->inLink('slbView'))); + + return print(js::locate($this->inLink('editSLB'))); + } + + /** + * Edit SLB. + * + * @access public + * @return void + */ + public function editSLB() + { + if($_POST) + { + $this->system->saveSLBSettings(); + if(dao::isError()) + { + return $this->send(array('result' => 'fail', 'message' => dao::getError())); + } + + return $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->configSLBSuccess, 'locate' => $this->inLink('SLBView'))); + } + + $this->view->title = $this->lang->system->SLB->common; + $this->view->SLBSettings = $this->system->getSLBSettings(); + + $this->display(); + } + + /** + * SLB View. + * + * @access public + * @return void + */ + public function SLBView() + { + $this->view->title = $this->lang->system->SLB->common; + $this->view->slbSettings = $this->system->getSLBSettings(); + + $this->display(); + } + + public function browseBackup() + { + $backups = array(); + if(empty($this->view->error)) + { + $sqlFiles = glob("{$this->backupPath}*.sql*"); + if(!empty($sqlFiles)) + { + foreach($sqlFiles as $file) + { + $fileName = basename($file); + $backupFile = new stdclass(); + $backupFile->time = filemtime($file); + $backupFile->name = substr($fileName, 0, strpos($fileName, '.')); + $backupFile->sqlSummary = $this->system->getSQLSummary($file); + $backupFile->files[$file] = $this->backup->getBackupSummary($file); + + $fileBackup = $this->backup->getBackupFile($backupFile->name, 'file'); + if($fileBackup) $backupFile->files[$fileBackup] = $this->backup->getBackupSummary($fileBackup); + + $codeBackup = $this->backup->getBackupFile($backupFile->name, 'code'); + if($codeBackup) $backupFile->files[$codeBackup] = $this->backup->getBackupSummary($codeBackup); + + $backups[$backupFile->name] = $backupFile; + } + } + } + krsort($backups); + + $this->view->title = $this->lang->backup->common; + $this->view->position[] = $this->lang->backup->common; + $this->view->backups = $backups; + $this->display(); + } + + /** + * Verify SMTP account by ajax. + * + * @access public + * @return void + */ + public function ajaxVerifySMTPAccount() + { + $accountInfo= fixer::input('post')->get(); + + $passed = $this->loadModel('cne')->validateSMTP($accountInfo); + if($passed) $this->send(array('result' => 'success', 'message' => $this->lang->system->notices->verifySMTPSuccess)); + + $this->send(array('result' => 'fail', 'message' => $this->lang->system->errors->verifySMTPFailed)); + } + + /** + * Generate database auth parameters and jump to login page. + * + * @access public + * @return void + */ + public function ajaxDBAuthUrl() + { + $post = fixer::input('post') + ->setDefault('namespace', 'default') + ->get(); + if(empty($post->dbName)) return $this->send(array('result' => 'fail', 'message' => $this->lang->system->errors->dbNameIsEmpty)); + + $detail = $this->loadModel('cne')->dbDetail($post->dbName, $post->namespace); + if(empty($detail)) return $this->send(array('result' => 'fail', 'message' => $this->lang->system->errors->notFoundDB)); + + $this->app->loadConfig('instance'); + $dbAuth = array(); + $dbAuth['driver'] = zget($this->config->instance->adminer->dbTypes, $post->dbType, ''); + $dbAuth['server'] = $detail->host . ':' . $detail->port; + $dbAuth['username'] = $detail->username; + $dbAuth['db'] = $detail->database; + $dbAuth['password'] = $detail->password; + + $url = '/adminer?'. http_build_query($dbAuth); + $this->send(array('result' => 'success', 'message' => '', 'data' => array('url' => $url))); + } + + /** + * Get LDAP info by ajax. + * + * @access public + * @return void + */ + public function ajaxLdapInfo() + { + $instanceID = $this->loadModel('setting')->getItem('owner=system&module=common§ion=ldap&key=instanceID'); + $ldapInstance = $this->loadModel('instance')->getByID($instanceID); + if(empty($ldapInstance)) return $this->send(array('result' => 'fail', 'message' => $this->lang->system->errors->notFoundLDAP)); + + $ldapSetting = json_decode($ldapInstance->ldapSettings); + + $secretKey = helper::readKey(); + $password = openssl_decrypt($ldapSetting->auth->password, 'DES-ECB', $secretKey); + if(!$password) $password = openssl_decrypt($ldapSetting->auth->password, 'DES-ECB', $ldapInstance->createdAt); // Secret key was createdAt field value less v2.2. + + $this->send(array('result' => 'success', 'message' => '', 'data' => array('domain' => $this->instance->url($ldapInstance), 'account' => $ldapSetting->auth->username, 'pass' => $password))); + } + + /** + * Get oss account and domain by ajax. + * + * @access public + * @return void + */ + public function ajaxOssInfo() + { + $minioInstance = new stdclass; + $minioInstance->k8name = 'cne-operator'; + $minioInstance->spaceData = new stdclass; + $minioInstance->spaceData->k8space = $this->config->k8space; + + $ossAccount = $this->loadModel('cne')->getDefaultAccount($minioInstance, '', 'minio'); + + $ossDomain = $this->cne->getDomain($minioInstance, '', 'minio'); + $ossDomain->domain = $ossDomain->access_host; + + $url = $this->loadModel('instance')->url($ossDomain); + + if($ossAccount and $ossDomain) return $this->send(array('result' => 'success', 'message' => '', 'data' => array('account' => $ossAccount, 'url' => $url))); + + $this->send(array('result' => 'fail', 'message' => $this->lang->system->errors->failGetOssAccount)); + } +} diff --git a/module/system/css/dblist.css b/module/system/css/dblist.css new file mode 100644 index 0000000000..f6f3b660e3 --- /dev/null +++ b/module/system/css/dblist.css @@ -0,0 +1,3 @@ +#mainContent .table {margin: auto;width: 60%; min-width:400px; max-width: 800px;} +#mainContent .main-header {margin-bottom: 20px;} +#mainContent .main-header h2{font-size: 18px;} diff --git a/module/system/css/editdomain.css b/module/system/css/editdomain.css new file mode 100644 index 0000000000..1f7c85f630 --- /dev/null +++ b/module/system/css/editdomain.css @@ -0,0 +1 @@ +table tr th.new-domain-label{vertical-align: top; padding-top: 14px;} diff --git a/module/system/css/editldap.css b/module/system/css/editldap.css new file mode 100644 index 0000000000..d1fa0d1d8b --- /dev/null +++ b/module/system/css/editldap.css @@ -0,0 +1 @@ +.advanced {padding:5px 0 5px 150px;} diff --git a/module/system/css/index.css b/module/system/css/index.css new file mode 100644 index 0000000000..bb3b7523c8 --- /dev/null +++ b/module/system/css/index.css @@ -0,0 +1,7 @@ +.plug-container > div{margin-bottom: 20px;} +.plug-container a {display: inline-block; width: 150px;} +.plug-container a:hover { text-decoration: none; box-shadow: 0 1px 5px rgb(0 0 0 / 18%);} +.logo { display: inline-block; padding: 10px;} +.logo .icon {font-size: 100px; color: #70c0fb;} +.logo img {width: 100px; height:100px;} +.plug-title {display: inline-block; padding-top: 10px; text-align: center; font-size: 16px; font-weight: 500;} diff --git a/module/system/css/installldap.css b/module/system/css/installldap.css new file mode 100644 index 0000000000..d1fa0d1d8b --- /dev/null +++ b/module/system/css/installldap.css @@ -0,0 +1 @@ +.advanced {padding:5px 0 5px 150px;} diff --git a/module/system/css/installsmtp.css b/module/system/css/installsmtp.css new file mode 100644 index 0000000000..dde26ea91f --- /dev/null +++ b/module/system/css/installsmtp.css @@ -0,0 +1 @@ +#mainContent .main-col {display: block;} diff --git a/module/system/css/ldapview.css b/module/system/css/ldapview.css new file mode 100644 index 0000000000..b449e569c2 --- /dev/null +++ b/module/system/css/ldapview.css @@ -0,0 +1,7 @@ +.btn.label-lg.label-primary.btn-edit {color: #61be68;} +.btn.label-lg.label-outline {background-color: #ffffff!important;} +.btn.label-lg.label-outline:hover, .btn-visit.btn:focus {background-color: #e5fbf3!important;} +.edit-tools-tips {display: inline-block; margin: 0 10px;} +#ldapAccountModal .modal-content {padding-bottom: 20px;} +input#ldapPassword {border:none;} +#ldapPassBtn {border:none;} diff --git a/module/system/css/ossview.css b/module/system/css/ossview.css new file mode 100644 index 0000000000..5c80d0a5d5 --- /dev/null +++ b/module/system/css/ossview.css @@ -0,0 +1,5 @@ +.btn.label-lg.label-outline {background-color: #ffffff!important;} +.btn.label-lg.label-outline:hover, .btn-visit.btn:focus {background-color: #e5fbf3!important;} +input#ossPassword {border:none;} +input#ossSK{border:none;} + diff --git a/module/system/css/smtpview.css b/module/system/css/smtpview.css new file mode 100644 index 0000000000..899c01945b --- /dev/null +++ b/module/system/css/smtpview.css @@ -0,0 +1 @@ +#smtpPassBtn{border: 0;} diff --git a/module/system/js/dblist.js b/module/system/js/dblist.js new file mode 100644 index 0000000000..3b5f658b31 --- /dev/null +++ b/module/system/js/dblist.js @@ -0,0 +1,22 @@ +$(function() +{ + $('button.db-login').on('click', function(event) + { + var dbName = $(event.target).data('db-name'); + var dbType = $(event.target).data('db-type'); + var namespace = $(event.target).data('namespace'); + + $.post(createLink('system', 'ajaxDBAuthUrl'), {dbName, namespace, dbType}).done(function(res) + { + var response = JSON.parse(res); + if(response.result == 'success') + { + window.parent.open(response.data.url, 'Adminer'); + } + else + { + bootbox.alert(response.message); + } + }); + }); +}); diff --git a/module/system/js/dblist.ui.js b/module/system/js/dblist.ui.js new file mode 100644 index 0000000000..6039bf2e9a --- /dev/null +++ b/module/system/js/dblist.ui.js @@ -0,0 +1,27 @@ +window.manageDb = function(dbName, dbType, namespace) +{ + $.post($.createLink('system', 'ajaxDBAuthUrl'), {dbName, namespace, dbType}, function(res) + { + var response = JSON.parse(res); + if(response.result == 'success') + { + window.parent.open(response.data.url, 'Adminer'); + } + else + { + zui.Modal.alert(response.message); + } + }); +} + +window.renderDbList = function (result, {col, row, value}) +{ + if(col.name === 'status') + { + var statusClass = value == 'running' ? 'text-success' : ''; + result[0] = {html: '' + result[0] + ''}; + return result; + } + + return result; +} diff --git a/module/system/js/editdomain.js b/module/system/js/editdomain.js new file mode 100644 index 0000000000..39651e41bc --- /dev/null +++ b/module/system/js/editdomain.js @@ -0,0 +1,143 @@ +$(function() +{ + /** + * Fresh submitBtn status. + * + * @access public + * @return void + */ + function freshSubmitBtn() + { + if($('#httpstrue:checked').length == 0) + { + $('#submitBtn').attr('disabled', false); + return; + } + var pass = $('#validateCertBtn').attr('pass') == 'true'; + if(pass) + { + $('#submitBtn').attr('disabled', false); + } + else + { + $('#submitBtn').attr('disabled', true); + } + } + + freshSubmitBtn(); + + /** + * Toggle certificate textarea. + * + * @access public + * @return void + */ + function toggleCertBox() + { + $showCert = $("#httpstrue[type=checkbox]:checked").length > 0; + if($showCert) + { + $('#cert-box').show(); + } + else + { + $('#cert-box').hide(); + } + + freshSubmitBtn(); + } + + toggleCertBox(); + + $("#httpstrue[type=checkbox]").on('change', function() + { + toggleCertBox(); + }); + + $('#validateCertBtn').on('click', function() + { + var certData = {}; + certData.customDomain = $('#customDomain').val(); + certData.certPem = $('#certPem').val(); + certData.certKey = $('#certKey').val(); + $.post(createLink('system', 'ajaxValidateCert'), certData).done(function(response) + { + var res = JSON.parse(response); + if(res.result == 'success') + { + $('#validateCertBtn').attr('pass', 'true'); + $('#validateMsg').removeClass('text-red').addClass('text-green'); + $('#validateMsg').html(res.message); + } + else + { + $('#validateCertBtn').attr('pass', 'false'); + $('#validateMsg').removeClass('text-green').addClass('text-red'); + var errMessage = res.message; + if(res.message instanceof Array) errMessage = res.message.join(' '); + if(res.message instanceof Object) errMessage = Object.values(res.message).join(' '); + + $('#validateMsg').html(errMessage); + } + freshSubmitBtn(); + }); + }); + + var timerID = 0; + + /** + * Show progress modal and fresh progress of updating domain by ajax. + * + * @access public + * @return void + */ + function showProgressModal() + { + $('#waiting').modal('show'); + timerID = setInterval(function() + { + $.get(createLink('system', 'ajaxUpdatingDomainProgress'), function(data) + { + $('#waiting #message').html(data); + }); + }, 1000); + }; + + $('#submitBtn').on('click', function() + { + bootbox.confirm(notices.confirmUpdateDomain, function(result) + { + if(!result) return; + + showProgressModal(); + $('#submitBtn').attr('disabled', true); + + var domainData = $('#domainForm').serializeArray(); + $.post(createLink('system', 'editDomain'), domainData).done(function(response) + { + $('#submitBtn').attr('disabled', false); + + var res = JSON.parse(response); + if(res.result == 'success') + { + parent.window.location.href = res.locate; + } + else + { + $('#waiting').modal('hide'); + clearInterval(timerID); + + var errMessage = res.message; + if(res.message instanceof Array) errMessage = res.message.join('
'); + if(res.message instanceof Object) errMessage = Object.values(res.message).join('
'); + + bootbox.alert( + { + title: notices.fail, + message: errMessage, + }); + } + }); + }); + }); +}); diff --git a/module/system/js/editdomain.ui.js b/module/system/js/editdomain.ui.js new file mode 100644 index 0000000000..85d9c76f89 --- /dev/null +++ b/module/system/js/editdomain.ui.js @@ -0,0 +1,52 @@ +function onHttpsChange(event) +{ + const $https = $(event.target); + + if($https.prop('checked')) + { + $('.cert').removeClass('hidden'); + + var pass = $('#validateCertBtn').attr('pass') == 'true'; + if(!pass) $('#domainForm button[type=submit]').prop('disabled', true); + } + else + { + $('.cert').addClass('hidden'); + $('#domainForm button[type=submit]').prop('disabled', false); + } +} + +function checkCert() +{ + var certData = {}; + certData.customDomain = $('#customDomain').val(); + certData.certPem = $('#certPem').val(); + certData.certKey = $('#certKey').val(); + + $.ajaxSubmit( + { + url: $.createLink('system', 'ajaxValidateCert'), + data: certData, + onComplete: function(res) + { + if(res.result == 'success') + { + $('#validateCertBtn').attr('pass', 'true'); + $('#validateMsg').removeClass('text-danger').addClass('text-success'); + $('#validateMsg').html(res.message); + $('#domainForm button[type=submit]').prop('disabled', false); + } + else + { + $('#validateCertBtn').attr('pass', 'false'); + $('#validateMsg').removeClass('text-success').addClass('text-danger'); + var errMessage = res.message; + if(res.message instanceof Array) errMessage = res.message.join(' '); + if(res.message instanceof Object) errMessage = Object.values(res.message).join(' '); + + $('#validateMsg').html(errMessage); + } + }, + }); + +} diff --git a/module/system/js/editldap.js b/module/system/js/editldap.js new file mode 100644 index 0000000000..583bd742b6 --- /dev/null +++ b/module/system/js/editldap.js @@ -0,0 +1,155 @@ +$(function() +{ + /** + * Fresh submit button. + * + * @access public + * @return void + */ + function freshSubmitBtn() + { + var enableLDAP = $('#LDAPForm input[type=checkbox]:checked').length > 0; + var ldapCheckPass = true; + if($('#LDAPForm select[name=source]').val() == 'extra') + { + ldapCheckPass = $('#testConnectBtn').attr('pass') == 'true'; + } + + if(enableLDAP && ldapCheckPass) + { + $('#LDAPForm #submitBtn').attr('disabled', false); + } + else + { + $('#LDAPForm #submitBtn').attr('disabled', true); + } + } + + freshSubmitBtn(); + + $('#LDAPForm input[type=checkbox]').on('change', function(event) + { + freshSubmitBtn(); + }); + + $('#LDAPForm input[type=checkbox]').change(); + + $('select[name=source]').on('change', function(event) + { + if($(event.target).val() == 'qucheng') + { + $('#quchengLDAP').show(); + $('#extraLDAP').hide(); + } + else + { + $('#quchengLDAP').hide(); + $('#extraLDAP').show(); + } + + freshSubmitBtn(); + }); + $('select[name=source]').change(); + + $('#testConnectBtn').on('click', function(event) + { + var settings = {}; + settings.host = $('input[name="extra[host]"]').val(); + settings.port = $('input[name="extra[port]"]').val(); + settings.bindDN = $('input[name="extra[bindDN]"]').val(); + settings.bindPass = $('input[name="extra[bindPass]"]').val(); + settings.baseDN = $('input[name="extra[baseDN]"]').val(); + if(!settings.host || !settings.port || !settings.bindDN || !settings.bindPass || !settings.baseDN) + { + bootbox.alert( + { + title: notices.attention, + message: notices.fillAllRequiredFields, + }); + return; + } + + $.post(createLink('system', 'testLDAPConnection'), settings).done(function(response) + { + try + { + var res = JSON.parse(response); + } + catch(error) + { + var res = {result: 'fail', message: errors.verifyLDAPFailed,}; + } + $('#connectResult').html(res.message); + if(res.result == 'success') + { + $('#testConnectBtn').attr('pass', 'true'); + $('#connectResult').removeClass('text-red').addClass('text-success'); + freshSubmitBtn(); + } + else + { + $('#testConnectBtn').attr('pass', 'false'); + $('#connectResult').removeClass('text-success').addClass('text-red'); + freshSubmitBtn(); + } + }); + }); + + var timerID = 0; + + /** + * Show updating LDAP progress modal. + * + * @access public + * @return void + */ + function showProgressModal() + { + $('#waiting').modal('show'); + timerID = setInterval(function() + { + $.get(createLink('system', 'ajaxUpdatingLDAPProgress'), function(data) + { + $('#waiting #message').html(data); + }); + }, 1000); + }; + + $('#submitBtn').on('click', function() + { + bootbox.confirm(notices.confirmUpdateLDAP, function(result) + { + if(!result) return; + + showProgressModal(); + $('#submitBtn').attr('disabled', true); + + var ldapData = $('#LDAPForm').serializeArray(); + $.post(createLink('system', 'editLDAP'), ldapData).done(function(response) + { + $('#submitBtn').attr('disabled', false); + + var res = JSON.parse(response); + if(res.result == 'success') + { + parent.window.location.href = res.locate; + } + else + { + $('#waiting').modal('hide'); + clearInterval(timerID); + + var errMessage = res.message; + if(res.message instanceof Array) errMessage = res.message.join('
'); + if(res.message instanceof Object) errMessage = Object.values(res.message).join('
'); + + bootbox.alert( + { + title: notices.fail, + message: errMessage, + }); + } + }); + }); + }); +}); diff --git a/module/system/js/editslb.js b/module/system/js/editslb.js new file mode 100644 index 0000000000..c1e98ad428 --- /dev/null +++ b/module/system/js/editslb.js @@ -0,0 +1,34 @@ +$(function() +{ + var overtime = 30; + var timerID = 0; + $("#submitBtn").on('click', function() + { + $('#waiting').modal('show'); + timerID = setInterval(function() + { + $('#timer').text(overtime); + overtime--; + }, 1000); + + var slbData = {}; + slbData.ippool = $('#ippool').val(); + $.post(createLink('system', 'editSLB'), slbData).done(function(response) + { + $('#waiting').modal('hide'); + clearInterval(timerID); + overtime = 30; + + var res = JSON.parse(response); + if(res.result == 'success'){ + parent.window.location.href = res.locate; + }else{ + bootbox.alert( + { + title: notices.fail, + message: res.message, + }); + } + }); + }); +}); diff --git a/module/system/js/editsmtp.js b/module/system/js/editsmtp.js new file mode 100644 index 0000000000..664f09ff5a --- /dev/null +++ b/module/system/js/editsmtp.js @@ -0,0 +1,73 @@ +$(function() +{ + /** + * Fresh submit btton by enable SMTP checkboc value and verified result of SMTP account. + * + * @access public + * @return void + */ + function freshSubmitBtn() + { + var enableSMTP = $('#smtpForm input[type=checkbox]:checked').length > 0; + var accountRight = $('#verifyAccountBtn').attr('pass') == 'true'; + + if(enableSMTP && accountRight) + { + $('#smtpForm button[type=submit]').attr('disabled', false); + } + else + { + $('#smtpForm button[type=submit]').attr('disabled', true); + } + } + + $('#smtpForm input[type=checkbox]').on('change', function(event) + { + freshSubmitBtn(); + }); + + $('#smtpForm input[type=checkbox]').change(); + + $('#verifyAccountBtn').on('click', function(event) + { + var settings = {}; + settings.host = $("input[name='host']").val(); + settings.port = $("input[name='port']").val(); + settings.user = $("input[name='user']").val(); + settings.pass = $("input[name='pass']").val(); + if(!settings.host || !settings.port || !settings.user || !settings.pass) + { + bootbox.alert( + { + title: notices.attention, + message: notices.fillAllRequiredFields, + }); + return; + } + + $.post(createLink('system', 'ajaxVerifySMTPAccount'), settings).done(function(response) + { + try + { + var res = JSON.parse(response); + } + catch(error) + { + var res = {result: 'fail', message: errors.verifySMTPFailed,}; + } + $('#verifyResult').html(res.message); + if(res.result == 'success') + { + $('#verifyAccountBtn').attr('pass', 'true'); + $('#verifyResult').removeClass('text-red').addClass('text-success'); + freshSubmitBtn(); + } + else + { + $('#verifyAccountBtn').attr('pass', 'false'); + $('#verifyResult').removeClass('text-success').addClass('text-red'); + freshSubmitBtn(); + } + }); + }); +}); diff --git a/module/system/js/installldap.js b/module/system/js/installldap.js new file mode 100644 index 0000000000..fe5297b8e1 --- /dev/null +++ b/module/system/js/installldap.js @@ -0,0 +1,129 @@ +$(function() +{ + /** + * Fresh submit button. + * + * @access public + * @return void + */ + function freshSubmitBtn() + { + var enableLDAP = $('#LDAPForm input[type=checkbox]:checked').length > 0; + var ldapCheckPass = true; + if($('#LDAPForm select[name=source]').val() == 'extra') + { + ldapCheckPass = $('#testConnectBtn').attr('pass') == 'true'; + } + + if(enableLDAP && ldapCheckPass) + { + $('#LDAPForm #submitBtn').attr('disabled', false); + } + else + { + $('#LDAPForm #submitBtn').attr('disabled', true); + } + } + + freshSubmitBtn(); + + $('#LDAPForm input[type=checkbox]').on('change', function(event) + { + freshSubmitBtn(); + }); + + $('#LDAPForm input[type=checkbox]').change(); + + $('select[name=source]').on('change', function(event) + { + if($(event.target).val() == 'qucheng') + { + $('#quchengLDAP').show(); + $('#extraLDAP').hide(); + } + else + { + $('#quchengLDAP').hide(); + $('#extraLDAP').show(); + } + + freshSubmitBtn(); + }); + $('select[name=source]').change(); + + $('#testConnectBtn').on('click', function(event) + { + var settings = {}; + settings.host = $('input[name="extra[host]"]').val(); + settings.port = $('input[name="extra[port]"]').val(); + settings.bindDN = $('input[name="extra[bindDN]"]').val(); + settings.bindPass = $('input[name="extra[bindPass]"]').val(); + settings.baseDN = $('input[name="extra[baseDN]"]').val(); + if(!settings.host || !settings.port || !settings.bindDN || !settings.bindPass || !settings.baseDN) + { + bootbox.alert( + { + title: notices.attention, + message: notices.fillAllRequiredFields, + }); + return; + } + + $.post(createLink('system', 'testLDAPConnection'), settings).done(function(response) + { + try + { + var res = JSON.parse(response); + } + catch(error) + { + var res = {result: 'fail', message: errors.verifyLDAPFailed,}; + } + $('#connectResult').html(res.message); + if(res.result == 'success') + { + $('#testConnectBtn').attr('pass', 'true'); + $('#connectResult').removeClass('text-red').addClass('text-success'); + freshSubmitBtn(); + } + else + { + $('#testConnectBtn').attr('pass', 'false'); + $('#connectResult').removeClass('text-success').addClass('text-red'); + freshSubmitBtn(); + } + }); + }); + + $('#submitBtn').on('click', function() + { + $('#submitBtn').attr('disabled', true); + + var ldapData = $('#LDAPForm').serializeArray(); + $.post(createLink('system', 'installLDAP'), ldapData).done(function(response) + { + $('#submitBtn').attr('disabled', false); + + var res = JSON.parse(response); + if(res.result == 'success') + { + parent.window.location.href = res.locate; + } + else + { + $('#waiting').modal('hide'); + clearInterval(timerID); + + var errMessage = res.message; + if(res.message instanceof Array) errMessage = res.message.join('
'); + if(res.message instanceof Object) errMessage = Object.values(res.message).join('
'); + + bootbox.alert( + { + title: notices.fail, + message: errMessage, + }); + } + }); + }); +}); diff --git a/module/system/js/installsmtp.js b/module/system/js/installsmtp.js new file mode 100644 index 0000000000..a18e743352 --- /dev/null +++ b/module/system/js/installsmtp.js @@ -0,0 +1,91 @@ +$(function() +{ + /** + * Fresh submit btton by enable SMTP checkboc value and verified result of SMTP account. + * + * @access public + * @return void + */ + function freshSubmitBtn() + { + var enableSMTP = $('#smtpForm input[type=checkbox]:checked').length > 0; + var accountRight = $('#verifyAccountBtn').attr('pass') == 'true'; + + if(enableSMTP) + { + $('#smtpForm').find('table').show(); + $('#smtpForm').find('button[type=submit]').show(); + } + else + { + $('#smtpForm').find('table').hide(); + $('#smtpForm').find('button[type=submit]').hide(); + } + + if(enableSMTP && accountRight) + { + $('#smtpForm button[type=submit]').attr('disabled', false); + } + else + { + $('#smtpForm button[type=submit]').attr('disabled', true); + } + } + + $('#smtpForm input[type=checkbox]').on('change', function(event) + { + freshSubmitBtn(); + }); + + $('#smtpForm input[type=checkbox]').change(); + + $('#smtpForm input[type=text]').on('change', function(event) + { + $('#verifyAccountBtn').attr('pass', 'false'); + $('#verifyResult').text(''); + freshSubmitBtn(); + }); + + $('#verifyAccountBtn').on('click', function(event) + { + var settings = {}; + settings.host = $("input[name='host']").val(); + settings.port = $("input[name='port']").val(); + settings.user = $("input[name='user']").val(); + settings.pass = $("input[name='pass']").val(); + if(!settings.host || !settings.port || !settings.user || !settings.pass) + { + bootbox.alert( + { + title: notices.attention, + message: notices.fillAllRequiredFields, + }); + return; + } + + $.post(createLink('system', 'ajaxVerifySMTPAccount'), settings).done(function(response) + { + try + { + var res = JSON.parse(response); + } + catch(error) + { + var res = {result: 'fail', message: errors.verifySMTPFailed,}; + } + $('#verifyResult').html(res.message); + if(res.result == 'success') + { + $('#verifyAccountBtn').attr('pass', 'true'); + $('#verifyResult').removeClass('text-red').addClass('text-success'); + freshSubmitBtn(); + } + else + { + $('#verifyAccountBtn').attr('pass', 'false'); + $('#verifyResult').removeClass('text-success').addClass('text-red'); + freshSubmitBtn(); + } + }); + }); +}); diff --git a/module/system/js/ldapview.js b/module/system/js/ldapview.js new file mode 100644 index 0000000000..113ad5f7db --- /dev/null +++ b/module/system/js/ldapview.js @@ -0,0 +1,127 @@ +$(function() +{ + $('.btn-visit').on('click', function(event) + { + $.get(createLink('system', 'ajaxLdapInfo'), function(response) + { + let res = JSON.parse(response); + if(res.result == 'success') + { + $('#ldapAccount').text(res.data.account); + $('#ldapPassword').val(res.data.pass); + $('#ldapVisitUrl').attr('href', '//' + res.data.domain); + $('#ldapAccountModal').modal('show'); + } + }); + }); + + $('#ldapPassBtn').on('click', function(event){ + var inputTyep = $('#ldap_password').attr('type'); + if(inputTyep == 'text'){ + $('#ldap_password').attr('type', 'password'); + $('#ldapPassBtn').find('.icon').removeClass('icon-eye').addClass('icon-eye-off'); + } + else + { + $('#ldap_password').attr('type', 'text'); + $('#ldapPassBtn').find('.icon').addClass('icon-eye').removeClass('icon-eye-off'); + } + }); + + $('#copyPassBtn').on('click', function(event) + { + $('#ldapPassword').select(); + document.execCommand('copy'); + bootbox.alert(copySuccess); + }); + + $('.btn-start').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmStart, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.starting + '
', + }); + + let id = $(event.target).closest('button').attr('instance-id'); + let url = createLink('instance', 'ajaxStart', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + let res = JSON.parse(response); + if(res.result == 'success') + { + window.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('.btn-stop').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmStop, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.stopping + '
', + }); + + let id = $(event.target).closest('button').attr('instance-id'); + let url = createLink('instance', 'ajaxStop', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + let res = JSON.parse(response); + if(res.result == 'success') + { + window.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + setInterval(function() + { + var mainMenu = parent.window.$.apps.getLastApp(); + if(mainMenu.code != 'system') return; + + var statusURL = createLink('instance', 'ajaxStatus'); + $.post(statusURL, {idList: instanceIdList}).done(function(response) + { + let res = JSON.parse(response); + if(res.result == 'success' && res.data instanceof Array) + { + res.data.forEach(function(instance) + { + if($(".instance-status[instance-id=" + instance.id + "]").data('status') != instance.status) window.location.reload(); + }); + } + if(res.locate) window.parent.location.href = res.locate; + }); + }, 1000 * 5); + + $('[data-toggle="tooltip"]').tooltip(); +}); diff --git a/module/system/js/ossview.js b/module/system/js/ossview.js new file mode 100644 index 0000000000..66c388bc8c --- /dev/null +++ b/module/system/js/ossview.js @@ -0,0 +1,33 @@ +$(function() +{ + $('#ossManage').on('click', function(event) + { + $.get(createLink('system', 'ajaxOssInfo'), function(response) + { + let res = JSON.parse(response); + if(res.result == 'success') + { + $('#ossAdmin').text(res.data.account.username); + $('#ossPassword').val(res.data.account.password); + $('#ossVisitUrl').attr('href', res.data.url); + $('#ossAccountModal').modal('show'); + } + }); + }); + + $('#copySKBtn').on('click', function(event) + { + $('#ossSK').removeClass('hidden').select(); + document.execCommand('copy'); + $('#ossSK').addClass('hidden'); + bootbox.alert(copySuccess); + }); + + $('#copyPassBtn').on('click', function(event) + { + $('#ossPassword').removeClass('hidden').select(); + document.execCommand('copy'); + $('#ossPassword').addClass('hidden'); + bootbox.alert(copySuccess); + }); +}); diff --git a/module/system/js/ossview.ui.js b/module/system/js/ossview.ui.js new file mode 100644 index 0000000000..2010847a0f --- /dev/null +++ b/module/system/js/ossview.ui.js @@ -0,0 +1,34 @@ +$(function() +{ + $.get($.createLink('system', 'ajaxOssInfo'), function(response) + { + let res = JSON.parse(response); + if(res.result == 'success') + { + $('#ossAdmin').text(res.data.account.username); + $('#ossPassword').val(res.data.account.password); + $('#ossVisitUrl').attr('href', res.data.url); + $('#ossAccountModal').modal('show'); + } + }); +}); + +function copySK() +{ + var sk = document.getElementById('ossSK'); + $('#ossSK').removeClass('hidden'); + sk.select(); + document.execCommand('copy'); + $('#ossSK').addClass('hidden'); + zui.Modal.alert(copySuccess); +} + +function copyPassBtn() +{ + var ossPassword = document.getElementById('ossPassword'); + $('#ossPassword').removeClass('hidden'); + ossPassword.select(); + document.execCommand('copy'); + $('#ossPassword').addClass('hidden'); + zui.Modal.alert(copySuccess); +} diff --git a/module/system/js/smtpview.js b/module/system/js/smtpview.js new file mode 100644 index 0000000000..3b9eb96010 --- /dev/null +++ b/module/system/js/smtpview.js @@ -0,0 +1,110 @@ +$(function() +{ + $('#smtpPassBtn').on('click', function(event){ + var inputTyep = $('#smtp_password').attr('type'); + if(inputTyep == 'text'){ + $('#smtp_password').attr('type', 'password'); + $('#smtpPassBtn').find('.icon').removeClass('icon-eye').addClass('icon-eye-off'); + } + else + { + $('#smtp_password').attr('type', 'text'); + $('#smtpPassBtn').find('.icon').addClass('icon-eye').removeClass('icon-eye-off'); + } + }); + + $('.btn-start').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmStart, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.starting + '
', + }); + + let id = $(event.target).closest('button').attr('instance-id'); + let url = createLink('instance', 'ajaxStart', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + let res = JSON.parse(response); + if(res.result == 'success') + { + window.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + $('.btn-stop').on('click', function(event) + { + bootbox.confirm(instanceNotices.confirmStop, function(result) + { + if(!result) return; + + var loadingDialog = bootbox.dialog( + { + message: '
  ' + instanceNotices.stopping + '
', + }); + + let id = $(event.target).closest('button').attr('instance-id'); + let url = createLink('instance', 'ajaxStop', 'id=' + id, 'json'); + $.post(url).done(function(response) + { + loadingDialog.modal('hide'); + + let res = JSON.parse(response); + if(res.result == 'success') + { + window.location.reload(); + } + else + { + bootbox.alert( + { + title: instanceNotices.fail, + message: res.message, + }); + } + }); + }); + }); + + var enableTimer = true; + window.parent.$(window.parent.document).on('showapp', function(event, app) + { + enableTimer = app.code == 'space'; + }); + + setInterval(function() + { + if(!enableTimer) return; + + var statusURL = createLink('instance', 'ajaxStatus'); + $.post(statusURL, {idList: instanceIdList}).done(function(response) + { + let res = JSON.parse(response); + if(res.result == 'success' && res.data instanceof Array) + { + res.data.forEach(function(instance) + { + if($(".instance-status[instance-id=" + instance.id + "]").data('status') != instance.status) window.location.reload(); + }); + } + if(res.locate) window.parent.location.href = res.locate; + }); + }, 1000 * 5); + + $('[data-toggle="tooltip"]').tooltip(); +}); diff --git a/module/system/lang/en.php b/module/system/lang/en.php new file mode 100644 index 0000000000..4c5a8144b0 --- /dev/null +++ b/module/system/lang/en.php @@ -0,0 +1,268 @@ +system->dashboard = 'Dashboard'; +$lang->system->systemInfo = 'System information'; +$lang->system->dbManagement = 'Database management'; +$lang->system->ldapManagement = 'LDAP'; +$lang->system->dbList = 'Database list'; +$lang->system->dbName = 'Name'; +$lang->system->dbStatus = 'Status'; +$lang->system->dbType = 'Type'; +$lang->system->action = 'Action'; +$lang->system->management = 'Management'; +$lang->system->visit = 'Visit'; +$lang->system->close = 'Close'; +$lang->system->installLDAP = 'Install LDAP'; +$lang->system->editLDAP = 'Edit'; +$lang->system->LDAPInfo = 'LDAP information'; +$lang->system->accountInfo = 'Account information'; +$lang->system->advance = 'Senior'; +$lang->system->verify = 'Check'; +$lang->system->copy = 'Copy'; +$lang->system->copySuccess = '已复制到剪切板'; +$lang->system->cneStatus = '平台状态'; +$lang->system->cneStatistic = '资源统计'; +$lang->system->latestDynamic = '最新动态'; +$lang->system->nodeQuantity = '节点数'; +$lang->system->serviceQuantity = '服务数'; +$lang->system->cpuUsage = 'CPU(核)'; +$lang->system->memUsage = '内存(GB)'; + +/* LDAP */ +$lang->system->LDAP = new stdclass; +$lang->system->LDAP->info = 'LDAP信息'; +$lang->system->LDAP->ldapEnabled = '启用LDAP'; +$lang->system->LDAP->ldapQucheng = '渠成内置'; +$lang->system->LDAP->ldapSource = '来源'; +$lang->system->LDAP->ldapInstall = '安装并启用'; +$lang->system->LDAP->ldapUpdate = '更新'; +$lang->system->LDAP->accountInfo = '账号信息'; +$lang->system->LDAP->account = '账号'; +$lang->system->LDAP->password = '密码'; +$lang->system->LDAP->ldapUsername = '用户名'; +$lang->system->LDAP->ldapName = '名称'; +$lang->system->LDAP->host = '主机'; +$lang->system->LDAP->port = '端口'; +$lang->system->LDAP->account = '账号'; +$lang->system->LDAP->password = '密码'; +$lang->system->LDAP->ldapRoot = '根节点'; +$lang->system->LDAP->filterUser = '用户过滤'; +$lang->system->LDAP->email = '邮件字段'; +$lang->system->LDAP->extraAccount = '用户名字段'; +$lang->system->LDAP->ldapAdvance = '高级设置'; +$lang->system->LDAP->updateLDAP = '更新LDAP'; +$lang->system->LDAP->updateInstance = '更新已关联LDAP的服务'; +$lang->system->LDAP->updatingProgress = '更新中...剩余 %s 个服务。'; + +$lang->system->ldapTypeList = array(); +$lang->system->ldapTypeList['qucheng'] = '渠成内置'; +$lang->system->ldapTypeList['extra'] = '外部映射'; + +/* OSS */ +$lang->system->oss = new stdclass; +$lang->system->oss->common = 'Object Storage'; +$lang->system->oss->appURL = 'Application address'; +$lang->system->oss->user = 'User name'; +$lang->system->oss->password = 'Password'; +$lang->system->oss->manage = 'Manage'; +$lang->system->oss->apiURL = 'API address'; +$lang->system->oss->accessKey = 'Access Key'; +$lang->system->oss->secretKey = 'Secret Key'; + +/* SMTP */ +$lang->system->SMTP = new stdclass; +$lang->system->SMTP->common = '邮箱配置'; +$lang->system->SMTP->enabled = '启用SMTP'; +$lang->system->SMTP->install = '安装'; +$lang->system->SMTP->update = '更新'; +$lang->system->SMTP->edit = '编辑'; +$lang->system->SMTP->editSMTP = '编辑SMTP'; +$lang->system->SMTP->account = '发信邮箱'; +$lang->system->SMTP->password = '密码'; +$lang->system->SMTP->host = 'SMTP服务器'; +$lang->system->SMTP->port = 'SMTP端口'; +$lang->system->SMTP->save = '保存'; + +/* Domain */ +$lang->system->customDomain = '新域名'; +$lang->system->certPem = '公钥证书'; +$lang->system->certKey = '私钥'; + +$lang->system->domain = new stdclass; +$lang->system->domain->common = '域名管理'; +$lang->system->domain->editDomain = '修改域名配置'; +$lang->system->domain->config = '配置域名和证书'; +$lang->system->domain->currentDomain = '当前域名'; +$lang->system->domain->oldDomain = '旧域名'; +$lang->system->domain->newDomain = '新域名'; +$lang->system->domain->expiredDate = '证书过期时间'; +$lang->system->domain->uploadCert = '上传证书(仅支持泛域名证书)'; + +$lang->system->domain->notReuseOldDomain = '使用自定义域名后无法改回默认域名'; +$lang->system->domain->setDNS = '建议修改域名前请先进行DNS解析,'; +$lang->system->domain->dnsHelperLink = '点击查看帮助文档'; +$lang->system->domain->updateInstancesDomain = '更新已安装服务的域名'; +$lang->system->domain->totalOldDomain = '共 %s 个。'; +$lang->system->domain->updatingProgress = '更新中...,剩余 %s 个,'; +$lang->system->domain->updating = '更新中...'; + +$lang->system->SLB = new stdclass; +$lang->system->SLB->common = '负载均衡'; +$lang->system->SLB->config = '配置负载均衡'; +$lang->system->SLB->edit = '修改负载均衡'; +$lang->system->SLB->ipPool = 'IP段'; +$lang->system->SLB->ipPoolExample = '示例:192.168.10.0/24或者192.168.10.0-192.168.10.100'; +$lang->system->SLB->installing = '正在配置负载均衡'; +$lang->system->SLB->leftSeconds = '预计剩余'; +$lang->system->SLB->second = '秒'; + +$lang->system->notices = new stdclass; +$lang->system->notices->success = '成功'; +$lang->system->notices->fail = '失败'; +$lang->system->notices->attention = '注意'; +$lang->system->notices->noLDAP = '找不到LDAP配置数据'; +$lang->system->notices->ldapUsed = '%s个服务已关联了LDAP'; +$lang->system->notices->ldapInstallSuccess = 'LDAP安装成功'; +$lang->system->notices->ldapUpdateSuccess = 'LDAP更新成功'; +$lang->system->notices->confirmUpdateLDAP = '修改LDAP后,会自动更新并重启已关联的服务,确定要修改吗?'; +$lang->system->notices->verifyLDAPSuccess = '校验LDAP成功!'; +$lang->system->notices->fillAllRequiredFields = '请填写全部必填项!'; +$lang->system->notices->smtpInstallSuccess = 'LDAP安装成功'; +$lang->system->notices->smtpUpdateSuccess = 'LDAP更新成功'; +$lang->system->notices->smtpWhiteList = '为防止邮件被屏蔽,请在邮件服务器里面将发信邮箱设为白名单'; +$lang->system->notices->smtpAuthCode = '有些邮箱要填写单独申请的授权码,具体请到邮箱相关设置查询'; +$lang->system->notices->smtpUsed = '%s 个服务关联了SMTP'; +$lang->system->notices->verifySMTPSuccess = '校验成功!'; +$lang->system->notices->pleaseCheckSMTPInfo = '校验失败!请检查用户名和密码是否正确'; +$lang->system->notices->confirmUpdateDomain = '修改域名后,会自动更新已安装服务的域名,确定要修改吗?'; +$lang->system->notices->updateDomainSuccess = '域名修改成功。'; +$lang->system->notices->configSLBSuccess = '配置负载均衡成功。'; +$lang->system->notices->validCert = '校验成功'; + +$lang->system->errors = new stdclass; +$lang->system->errors->notFoundDB = '找不到该数据库'; +$lang->system->errors->notFoundLDAP = '找不到LDAP数据'; +$lang->system->errors->dbNameIsEmpty = '数据库名为空'; +$lang->system->errors->notSupportedLDAP = '暂不支持该类型的LDAP'; +$lang->system->errors->failToInstallLDAP = '安装内置LDAP失败'; +$lang->system->errors->failToInstallExtraLDAP = '对接外部LDAP失败'; +$lang->system->errors->failToUpdateExtraLDAP = '更新外部LDAP失败'; +$lang->system->errors->failToUninstallQuChengLDAP = '卸载渠成内部LDAP失败'; +$lang->system->errors->failToUninstallExtraLDAP = '卸载外部LDAP失败'; +$lang->system->errors->failToDeleteLDAPSnippet = '删除LDAP片段失败'; +$lang->system->errors->verifyLDAPFailed = '校验LDAP失败'; +$lang->system->errors->LDAPLinked = '有服务已经关联了LDAP'; +$lang->system->errors->SMTPLinked = '有服务已经关联了SMTP服务'; +$lang->system->errors->failGetOssAccount = '获取对象存储账号失败'; +$lang->system->errors->failToInstallSMTP = '安装SMTP失败'; +$lang->system->errors->failToUninstallSMTP = '卸载SMTP失败'; +$lang->system->errors->failToUpdateSMTP = '更新SMTP失败'; +$lang->system->errors->verifySMTPFailed = '校验SMTP失败'; +$lang->system->errors->notFoundSMTPApp = '找不到SMTP代理应用'; +$lang->system->errors->notFoundSMTPService = '找不到SMTP代理服务'; +$lang->system->errors->domainIsRequired = '必须填写域名'; +$lang->system->errors->invalidDomain = '无效的域名或格式错误。域名只允许小写字母、数字、点(.)和中横线(-)'; +$lang->system->errors->failToUpdateDomain = '更新域名失败'; +$lang->system->errors->forbiddenOriginalDomain = '不能修改为平台默认域名'; +$lang->system->errors->newDomainIsSameWithOld = '新域名不能与原域名相同'; +$lang->system->errors->failedToConfigSLB = '配置负载均衡失败'; +$lang->system->errors->wrongIPRange = 'IP段格式错误,请参照示例格式,' . $lang->system->SLB->ipPoolExample; +$lang->system->errors->ippoolRequired = 'IP段不能为空'; +$lang->system->errors->failedToInstallSLBComponent = '安装负载均衡组件失败'; +$lang->system->errors->tryReinstallSLB = '安装负载均衡组件超时,请重试。'; + +$lang->system->backup = new stdclass(); +$lang->system->backup->common = '系统备份'; +$lang->system->backup->shortCommon = '备份'; +$lang->system->backup->systemInfo = '系统信息'; +$lang->system->backup->index = '备份首页'; +$lang->system->backup->history = '备份记录'; +$lang->system->backup->delete = '删除备份'; +$lang->system->backup->backup = '备份'; +$lang->system->backup->restore = '回滚'; +$lang->system->backup->change = '保留时间'; +$lang->system->backup->changeAB = '修改'; +$lang->system->backup->rmPHPHeader = '去除安全设置'; +$lang->system->backup->setting = '设置'; +$lang->system->backup->backupPerson = '备份人'; +$lang->system->backup->type = '备份类型'; + +$lang->system->backup->settingAction = '备份设置'; + +$lang->system->backup->name = '名称'; +$lang->system->backup->currentVersion = '当前版本'; +$lang->system->backup->latestVersion = '最新版本'; + +$lang->system->backup->time = '备份时间'; +$lang->system->backup->files = '备份文件'; +$lang->system->backup->allCount = '总文件数'; +$lang->system->backup->count = '备份文件数'; +$lang->system->backup->size = '大小'; +$lang->system->backup->status = '状态'; +$lang->system->backup->running = '运行中'; +$lang->system->backup->done = '完成'; + +$lang->system->backup->backupName = '备份名称:'; +$lang->system->backup->backupSql = '备份数据库:'; +$lang->system->backup->backupFile = '备份附件:'; +$lang->system->backup->restoreImage = '回滚平台镜像:'; +$lang->system->backup->restoreSQL = '回滚数据库:'; +$lang->system->backup->restoreFile = '回滚附件:'; +$lang->system->backup->checkService = '检查服务:'; + +$lang->system->backup->upgrade = '升级'; +$lang->system->backup->rollback = '回滚'; +$lang->system->backup->restart = '重启'; +$lang->system->backup->delete = '删除'; + +$lang->system->backup->statusList['success'] = '成功'; +$lang->system->backup->statusList['fail'] = '失败'; + +$lang->system->backup->restoreProgress['doing'] = '进行中'; +$lang->system->backup->restoreProgress['done'] = '完成'; + +$lang->system->backup->typeList['manual'] = '手动备份'; +$lang->system->backup->typeList['upgrade'] = '升级前自动备份'; +$lang->system->backup->typeList['restore'] = '回滚前自动备份'; + +$lang->system->backup->settingDir = '备份目录'; +$lang->system->backup->settingList['nofile'] = '不备份附件和代码'; +$lang->system->backup->settingList['nosafe'] = '不需要防下载PHP文件头'; + +$lang->system->backup->waitting = '正在进行中,请稍候...'; +$lang->system->backup->progressSQL = '备份中,已备份%s'; +$lang->system->backup->progressAttach = '备份中,共有%s个文件,已经备份%s个'; +$lang->system->backup->progressCode = '代码备份中,共有%s个文件,已经备份%s个'; +$lang->system->backup->confirmDelete = '是否删除该备份?'; +$lang->system->backup->confirmRestore = '请确认是否回滚?'; +$lang->system->backup->holdDays = '备份保留最近 %s 天'; +$lang->system->backup->copiedFail = '复制失败的文件:'; +$lang->system->backup->restoreTip = '还原功能只还原数据库。'; +$lang->system->backup->versionInfo = '点击查看新版本介绍'; +$lang->system->backup->confirmUpgrade = '请确认是否升级渠成平台?'; +$lang->system->backup->upgrading = '升级中'; +$lang->system->backup->backupTitle = '正在备份 渠成平台...'; +$lang->system->backup->restoreTitle = '正在回滚 渠成平台...'; +$lang->system->backup->backingUp = '进行中'; +$lang->system->backup->restoring = '进行中'; + +$lang->system->backup->success = new stdclass(); +$lang->system->backup->success->backup = '备份成功!'; +$lang->system->backup->success->restore = '还原成功!'; +$lang->system->backup->success->upgrade = '升级成功!'; +$lang->system->backup->success->degrade = '降级成功!'; + +$lang->system->backup->error = new stdclass(); +$lang->system->backup->error->noCreateDir = '备份目录不存在,也无法创建该目录'; +$lang->system->backup->error->noWritable = "%s 不可写!请检查该目录权限,否则无法备份。"; +$lang->system->backup->error->noDelete = "文件 %s 无法删除,修改权限或手工删除。"; +$lang->system->backup->error->restoreSQL = "数据库还原失败,错误:%s"; +$lang->system->backup->error->restoreFile = "附件还原失败,错误:%s"; +$lang->system->backup->error->backupFile = "附件备份失败,错误:%s"; +$lang->system->backup->error->backupCode = "代码备份失败,错误:%s"; + +$lang->system->backup->error->upgradeFail = "升级失败!"; +$lang->system->backup->error->upgradeOvertime = "升级超时!"; +$lang->system->backup->error->degradeFail = "降级失败!"; +$lang->system->backup->error->beenLatestVersion = "已经是最新版,无需升级!"; +$lang->system->backup->error->requireVersion = "必须上传版本号!"; + diff --git a/module/system/lang/zh-cn.php b/module/system/lang/zh-cn.php new file mode 100644 index 0000000000..3f20251bee --- /dev/null +++ b/module/system/lang/zh-cn.php @@ -0,0 +1,268 @@ +system->dashboard = '仪表盘'; +$lang->system->systemInfo = '系统信息'; +$lang->system->dbManagement = '数据库管理'; +$lang->system->ldapManagement = 'LDAP'; +$lang->system->dbList = '数据库列表'; +$lang->system->dbName = '名称'; +$lang->system->dbStatus = '状态'; +$lang->system->dbType = '类型'; +$lang->system->action = '操作'; +$lang->system->management = '管理'; +$lang->system->visit = '访问'; +$lang->system->close = '关闭'; +$lang->system->installLDAP = '安装LDAP'; +$lang->system->editLDAP = '编辑'; +$lang->system->LDAPInfo = 'LDAP信息'; +$lang->system->accountInfo = '账号信息'; +$lang->system->advance = '高级'; +$lang->system->verify = '校验'; +$lang->system->copy = '复制'; +$lang->system->copySuccess = '已复制到剪切板'; +$lang->system->cneStatus = '平台状态'; +$lang->system->cneStatistic = '资源统计'; +$lang->system->latestDynamic = '最新动态'; +$lang->system->nodeQuantity = '节点数'; +$lang->system->serviceQuantity = '服务数'; +$lang->system->cpuUsage = 'CPU(核)'; +$lang->system->memUsage = '内存(GB)'; + +/* LDAP */ +$lang->system->LDAP = new stdclass; +$lang->system->LDAP->info = 'LDAP信息'; +$lang->system->LDAP->ldapEnabled = '启用LDAP'; +$lang->system->LDAP->ldapQucheng = '渠成内置'; +$lang->system->LDAP->ldapSource = '来源'; +$lang->system->LDAP->ldapInstall = '安装并启用'; +$lang->system->LDAP->ldapUpdate = '更新'; +$lang->system->LDAP->accountInfo = '账号信息'; +$lang->system->LDAP->account = '账号'; +$lang->system->LDAP->password = '密码'; +$lang->system->LDAP->ldapUsername = '用户名'; +$lang->system->LDAP->ldapName = '名称'; +$lang->system->LDAP->host = '主机'; +$lang->system->LDAP->port = '端口'; +$lang->system->LDAP->account = '账号'; +$lang->system->LDAP->password = '密码'; +$lang->system->LDAP->ldapRoot = '根节点'; +$lang->system->LDAP->filterUser = '用户过滤'; +$lang->system->LDAP->email = '邮件字段'; +$lang->system->LDAP->extraAccount = '用户名字段'; +$lang->system->LDAP->ldapAdvance = '高级设置'; +$lang->system->LDAP->updateLDAP = '更新LDAP'; +$lang->system->LDAP->updateInstance = '更新已关联LDAP的服务'; +$lang->system->LDAP->updatingProgress = '更新中...剩余 %s 个服务。'; + +$lang->system->ldapTypeList = array(); +$lang->system->ldapTypeList['qucheng'] = '渠成内置'; +$lang->system->ldapTypeList['extra'] = '外部映射'; + +/* OSS */ +$lang->system->oss = new stdclass; +$lang->system->oss->common = '对象存储'; +$lang->system->oss->appURL = '应用地址'; +$lang->system->oss->user = '用户名'; +$lang->system->oss->password = '密码'; +$lang->system->oss->manage = '管理'; +$lang->system->oss->apiURL = 'API地址'; +$lang->system->oss->accessKey = 'Access Key'; +$lang->system->oss->secretKey = 'Secret Key'; + +/* SMTP */ +$lang->system->SMTP = new stdclass; +$lang->system->SMTP->common = '邮箱配置'; +$lang->system->SMTP->enabled = '启用SMTP'; +$lang->system->SMTP->install = '安装'; +$lang->system->SMTP->update = '更新'; +$lang->system->SMTP->edit = '编辑'; +$lang->system->SMTP->editSMTP = '编辑SMTP'; +$lang->system->SMTP->account = '发信邮箱'; +$lang->system->SMTP->password = '密码'; +$lang->system->SMTP->host = 'SMTP服务器'; +$lang->system->SMTP->port = 'SMTP端口'; +$lang->system->SMTP->save = '保存'; + +/* Domain */ +$lang->system->customDomain = '新域名'; +$lang->system->certPem = '公钥证书'; +$lang->system->certKey = '私钥'; + +$lang->system->domain = new stdclass; +$lang->system->domain->common = '域名管理'; +$lang->system->domain->editDomain = '修改域名配置'; +$lang->system->domain->config = '配置域名和证书'; +$lang->system->domain->currentDomain = '当前域名'; +$lang->system->domain->oldDomain = '旧域名'; +$lang->system->domain->newDomain = '新域名'; +$lang->system->domain->expiredDate = '证书过期时间'; +$lang->system->domain->uploadCert = '上传证书(仅支持泛域名证书)'; + +$lang->system->domain->notReuseOldDomain = '使用自定义域名后无法改回默认域名'; +$lang->system->domain->setDNS = '建议修改域名前请先进行DNS解析,'; +$lang->system->domain->dnsHelperLink = '点击查看帮助文档'; +$lang->system->domain->updateInstancesDomain = '更新已安装服务的域名'; +$lang->system->domain->totalOldDomain = '共 %s 个。'; +$lang->system->domain->updatingProgress = '更新中...,剩余 %s 个,'; +$lang->system->domain->updating = '更新中...'; + +$lang->system->SLB = new stdclass; +$lang->system->SLB->common = '负载均衡'; +$lang->system->SLB->config = '配置负载均衡'; +$lang->system->SLB->edit = '修改负载均衡'; +$lang->system->SLB->ipPool = 'IP段'; +$lang->system->SLB->ipPoolExample = '示例:192.168.10.0/24或者192.168.10.0-192.168.10.100'; +$lang->system->SLB->installing = '正在配置负载均衡'; +$lang->system->SLB->leftSeconds = '预计剩余'; +$lang->system->SLB->second = '秒'; + +$lang->system->notices = new stdclass; +$lang->system->notices->success = '成功'; +$lang->system->notices->fail = '失败'; +$lang->system->notices->attention = '注意'; +$lang->system->notices->noLDAP = '找不到LDAP配置数据'; +$lang->system->notices->ldapUsed = '%s个服务已关联了LDAP'; +$lang->system->notices->ldapInstallSuccess = 'LDAP安装成功'; +$lang->system->notices->ldapUpdateSuccess = 'LDAP更新成功'; +$lang->system->notices->confirmUpdateLDAP = '修改LDAP后,会自动更新并重启已关联的服务,确定要修改吗?'; +$lang->system->notices->verifyLDAPSuccess = '校验LDAP成功!'; +$lang->system->notices->fillAllRequiredFields = '请填写全部必填项!'; +$lang->system->notices->smtpInstallSuccess = 'LDAP安装成功'; +$lang->system->notices->smtpUpdateSuccess = 'LDAP更新成功'; +$lang->system->notices->smtpWhiteList = '为防止邮件被屏蔽,请在邮件服务器里面将发信邮箱设为白名单'; +$lang->system->notices->smtpAuthCode = '有些邮箱要填写单独申请的授权码,具体请到邮箱相关设置查询'; +$lang->system->notices->smtpUsed = '%s 个服务关联了SMTP'; +$lang->system->notices->verifySMTPSuccess = '校验成功!'; +$lang->system->notices->pleaseCheckSMTPInfo = '校验失败!请检查用户名和密码是否正确'; +$lang->system->notices->confirmUpdateDomain = '修改域名后,会自动更新已安装服务的域名,确定要修改吗?'; +$lang->system->notices->updateDomainSuccess = '域名修改成功。'; +$lang->system->notices->configSLBSuccess = '配置负载均衡成功。'; +$lang->system->notices->validCert = '校验成功'; + +$lang->system->errors = new stdclass; +$lang->system->errors->notFoundDB = '找不到该数据库'; +$lang->system->errors->notFoundLDAP = '找不到LDAP数据'; +$lang->system->errors->dbNameIsEmpty = '数据库名为空'; +$lang->system->errors->notSupportedLDAP = '暂不支持该类型的LDAP'; +$lang->system->errors->failToInstallLDAP = '安装内置LDAP失败'; +$lang->system->errors->failToInstallExtraLDAP = '对接外部LDAP失败'; +$lang->system->errors->failToUpdateExtraLDAP = '更新外部LDAP失败'; +$lang->system->errors->failToUninstallQuChengLDAP = '卸载渠成内部LDAP失败'; +$lang->system->errors->failToUninstallExtraLDAP = '卸载外部LDAP失败'; +$lang->system->errors->failToDeleteLDAPSnippet = '删除LDAP片段失败'; +$lang->system->errors->verifyLDAPFailed = '校验LDAP失败'; +$lang->system->errors->LDAPLinked = '有服务已经关联了LDAP'; +$lang->system->errors->SMTPLinked = '有服务已经关联了SMTP服务'; +$lang->system->errors->failGetOssAccount = '获取对象存储账号失败'; +$lang->system->errors->failToInstallSMTP = '安装SMTP失败'; +$lang->system->errors->failToUninstallSMTP = '卸载SMTP失败'; +$lang->system->errors->failToUpdateSMTP = '更新SMTP失败'; +$lang->system->errors->verifySMTPFailed = '校验SMTP失败'; +$lang->system->errors->notFoundSMTPApp = '找不到SMTP代理应用'; +$lang->system->errors->notFoundSMTPService = '找不到SMTP代理服务'; +$lang->system->errors->domainIsRequired = '必须填写域名'; +$lang->system->errors->invalidDomain = '无效的域名或格式错误。域名只允许小写字母、数字、点(.)和中横线(-)'; +$lang->system->errors->failToUpdateDomain = '更新域名失败'; +$lang->system->errors->forbiddenOriginalDomain = '不能修改为平台默认域名'; +$lang->system->errors->newDomainIsSameWithOld = '新域名不能与原域名相同'; +$lang->system->errors->failedToConfigSLB = '配置负载均衡失败'; +$lang->system->errors->wrongIPRange = 'IP段格式错误,请参照示例格式,' . $lang->system->SLB->ipPoolExample; +$lang->system->errors->ippoolRequired = 'IP段不能为空'; +$lang->system->errors->failedToInstallSLBComponent = '安装负载均衡组件失败'; +$lang->system->errors->tryReinstallSLB = '安装负载均衡组件超时,请重试。'; + +$lang->system->backup = new stdclass(); +$lang->system->backup->common = '系统备份'; +$lang->system->backup->shortCommon = '备份'; +$lang->system->backup->systemInfo = '系统信息'; +$lang->system->backup->index = '备份首页'; +$lang->system->backup->history = '备份记录'; +$lang->system->backup->delete = '删除备份'; +$lang->system->backup->backup = '备份'; +$lang->system->backup->restore = '回滚'; +$lang->system->backup->change = '保留时间'; +$lang->system->backup->changeAB = '修改'; +$lang->system->backup->rmPHPHeader = '去除安全设置'; +$lang->system->backup->setting = '设置'; +$lang->system->backup->backupPerson = '备份人'; +$lang->system->backup->type = '备份类型'; + +$lang->system->backup->settingAction = '备份设置'; + +$lang->system->backup->name = '名称'; +$lang->system->backup->currentVersion = '当前版本'; +$lang->system->backup->latestVersion = '最新版本'; + +$lang->system->backup->time = '备份时间'; +$lang->system->backup->files = '备份文件'; +$lang->system->backup->allCount = '总文件数'; +$lang->system->backup->count = '备份文件数'; +$lang->system->backup->size = '大小'; +$lang->system->backup->status = '状态'; +$lang->system->backup->running = '运行中'; +$lang->system->backup->done = '完成'; + +$lang->system->backup->backupName = '备份名称:'; +$lang->system->backup->backupSql = '备份数据库:'; +$lang->system->backup->backupFile = '备份附件:'; +$lang->system->backup->restoreImage = '回滚平台镜像:'; +$lang->system->backup->restoreSQL = '回滚数据库:'; +$lang->system->backup->restoreFile = '回滚附件:'; +$lang->system->backup->checkService = '检查服务:'; + +$lang->system->backup->upgrade = '升级'; +$lang->system->backup->rollback = '回滚'; +$lang->system->backup->restart = '重启'; +$lang->system->backup->delete = '删除'; + +$lang->system->backup->statusList['success'] = '成功'; +$lang->system->backup->statusList['fail'] = '失败'; + +$lang->system->backup->restoreProgress['doing'] = '进行中'; +$lang->system->backup->restoreProgress['done'] = '完成'; + +$lang->system->backup->typeList['manual'] = '手动备份'; +$lang->system->backup->typeList['upgrade'] = '升级前自动备份'; +$lang->system->backup->typeList['restore'] = '回滚前自动备份'; + +$lang->system->backup->settingDir = '备份目录'; +$lang->system->backup->settingList['nofile'] = '不备份附件和代码'; +$lang->system->backup->settingList['nosafe'] = '不需要防下载PHP文件头'; + +$lang->system->backup->waitting = '正在进行中,请稍候...'; +$lang->system->backup->progressSQL = '备份中,已备份%s'; +$lang->system->backup->progressAttach = '备份中,共有%s个文件,已经备份%s个'; +$lang->system->backup->progressCode = '代码备份中,共有%s个文件,已经备份%s个'; +$lang->system->backup->confirmDelete = '是否删除该备份?'; +$lang->system->backup->confirmRestore = '请确认是否回滚?'; +$lang->system->backup->holdDays = '备份保留最近 %s 天'; +$lang->system->backup->copiedFail = '复制失败的文件:'; +$lang->system->backup->restoreTip = '还原功能只还原数据库。'; +$lang->system->backup->versionInfo = '点击查看新版本介绍'; +$lang->system->backup->confirmUpgrade = '请确认是否升级渠成平台?'; +$lang->system->backup->upgrading = '升级中'; +$lang->system->backup->backupTitle = '正在备份 渠成平台...'; +$lang->system->backup->restoreTitle = '正在回滚 渠成平台...'; +$lang->system->backup->backingUp = '进行中'; +$lang->system->backup->restoring = '进行中'; + +$lang->system->backup->success = new stdclass(); +$lang->system->backup->success->backup = '备份成功!'; +$lang->system->backup->success->restore = '还原成功!'; +$lang->system->backup->success->upgrade = '升级成功!'; +$lang->system->backup->success->degrade = '降级成功!'; + +$lang->system->backup->error = new stdclass(); +$lang->system->backup->error->noCreateDir = '备份目录不存在,也无法创建该目录'; +$lang->system->backup->error->noWritable = "%s 不可写!请检查该目录权限,否则无法备份。"; +$lang->system->backup->error->noDelete = "文件 %s 无法删除,修改权限或手工删除。"; +$lang->system->backup->error->restoreSQL = "数据库还原失败,错误:%s"; +$lang->system->backup->error->restoreFile = "附件还原失败,错误:%s"; +$lang->system->backup->error->backupFile = "附件备份失败,错误:%s"; +$lang->system->backup->error->backupCode = "代码备份失败,错误:%s"; + +$lang->system->backup->error->upgradeFail = "升级失败!"; +$lang->system->backup->error->upgradeOvertime = "升级超时!"; +$lang->system->backup->error->degradeFail = "降级失败!"; +$lang->system->backup->error->beenLatestVersion = "已经是最新版,无需升级!"; +$lang->system->backup->error->requireVersion = "必须上传版本号!"; + diff --git a/module/system/model.php b/module/system/model.php new file mode 100644 index 0000000000..e07310cf5f --- /dev/null +++ b/module/system/model.php @@ -0,0 +1,1128 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +class systemModel extends model +{ + /** + * Construct function: load setting model. + * + * @access public + * @return mixed + */ + public function __construct() + { + parent::__construct(); + $this->loadModel('setting'); + } + + /** + * Get LDAP menu switcher. + * + * @access public + * @return string + */ + public function getLDAPSwitcher() + { + $output = "
"; + $output .= "{$this->lang->system->ldapManagement}"; + $output .= "
"; + + return $output; + } + + /** + * Get Oss menu switcher. + * + * @access public + * @return string + */ + public function getOssSwitcher() + { + $output = "
"; + $output .= "{$this->lang->system->oss->common}"; + $output .= "
"; + + return $output; + } + + /** + * Get SMTP menu switcher. + * + * @access public + * @return string + */ + public function getSMTPSwitcher() + { + $output = "
"; + $output .= "{$this->lang->system->SMTP->common}"; + $output .= "
"; + + return $output; + } + + /** + * Print action buttons. + * + * @param object $db + * @access public + * @return void + */ + public function printDBAction($db) + { + $disabled = strtolower($db->status) == 'running' ? '' : 'disabled'; + $btnHtml = html::commonButton($this->lang->system->management, "{$disabled} data-db-name='{$db->name}' data-db-type='{$db->db_type}' data-namespace='{$db->namespace}'", 'db-login btn btn-primary'); + + echo $btnHtml; + } + + /** + * Print edit LDAP button. + * + * @access public + * @return void + */ + public function printEditLDAPBtn() + { + $this->loadModel('instance'); + + $title = $this->lang->system->editLDAP; + $toolTips = ''; + $count = $this->instance->countLDAP(); + if($count) + { + $title = sprintf($this->lang->system->notices->ldapUsed, $count); + $toolTips = "data-toggle='tooltip' data-placement='bottom'"; + } + + $buttonHtml = ''; + $buttonHtml .= ""; + $buttonHtml .= html::a(inLink('editLDAP'), $this->lang->system->editLDAP, '', "title='{$title}' class='btn-edit btn label label-outline label-primary label-lg'"); + $buttonHtml .= ""; + + echo $buttonHtml; + } + + /** + * Print LDAP buttons. + * + * @param objevt $ldapInstance + * @access public + * @return mixed + */ + public function printLDAPButtons($ldapInstance) + { + $this->loadModel('instance'); + $this->app->loadLang('instance'); + + $buttonHtml = ''; + + if($ldapInstance->domain) + { + $disableVisit = !$this->instance->canDo('visit', $ldapInstance); + $buttonHtml .= html::commonButton($this->lang->instance->visit, "instance-id='{$ldapInstance->id}' title='{$this->lang->instance->visit}'" . ($disableVisit ? ' disabled ' : ''), 'btn-visit btn label label-outline label-primary label-lg'); + } + + $disableStart = !$this->instance->canDo('start', $ldapInstance); + $buttonHtml .= html::commonButton($this->lang->instance->start, "instance-id='{$ldapInstance->id}' title='{$this->lang->instance->start}'" . ($disableStart ? ' disabled ' : ''), "btn-start btn label label-outline label-primary label-lg"); + + $title = $this->lang->instance->stop; + $toolTips = ''; + $count = $this->instance->countLDAP(); + if($count) + { + $title = $this->lang->system->notices->ldapUsed; + $toolTips = "data-toggle='tooltip' data-placement='bottom' runat='server'"; + } + + $disableStop = $count > 0 || !$this->instance->canDo('stop', $ldapInstance); + $buttonHtml .= ""; + $buttonHtml .= html::commonButton($this->lang->instance->stop, "instance-id='{$ldapInstance->id}' title='{$title}'" . ($disableStop ? ' disabled ' : ''), 'btn-stop btn label label-outline label-danger label-lg'); + $buttonHtml .= ""; + + echo $buttonHtml; + } + + /** + * Install QuCheng LDAP. + * + * @param string $channel + * @access public + * @return bool|object + */ + public function installQuchengLDAP($ldapApp, $channel) + { + return $this->loadModel('instance')->installLDAP($ldapApp, '', 'OpenLDAP', $k8name = '', $channel); + } + + /** + * Update Qucheng LDAP. + * + * @param object $ldapApp + * @param string $channel + * @access public + * @return string + */ + public function updateQuchengLDAP($ldapApp, $channel) + { + $instanceID = $this->setting->getItem('owner=system&module=common§ion=ldap&key=instanceID'); + if($instanceID) + { + /* If QuCheng internal LDAP has been installed. Set QuCheng LDAP to be active. */ + return $this->setting->setItem('system.common.ldap.active', 'qucheng'); + } + else + { + /* If QuCheng internal LDAP has not been installed. */ + return $this->installQuchengLDAP($ldapApp, $channel); + } + } + + /** + * Install or update extra LDAP: it is creating a snippet in k8s system in fact. + * + * @param object $settings + * @access protected + * @return bool + */ + public function configExtraLDAP($settings) + { + if(!$this->testLDAPConnection($settings)) + { + dao::$errors[] = $this->lang->system->notSupportedLDAP; + return false; + } + + $space = $this->loadModel('space')->getSystemSpace($this->app->user->account); + + $snippetSettings = new stdclass; + $snippetSettings->name = 'snippet-extra-ldap'; + $snippetSettings->namespace = $space->k8space; + $snippetSettings->auto_import = false; + + $snippetSettings->values = new stdclass; + $snippetSettings->values->auth = new stdclass; + $snippetSettings->values->auth->ldap = new stdclass; + $snippetSettings->values->auth->ldap->enabled = true; + $snippetSettings->values->auth->ldap->type = 'ldap'; + $snippetSettings->values->auth->ldap->host = $settings->host; + $snippetSettings->values->auth->ldap->port = strval($settings->port); + $snippetSettings->values->auth->ldap->bindDN = "cn={$settings->bindDN},{$settings->baseDN}"; + $snippetSettings->values->auth->ldap->bindPass = $settings->bindPass; + $snippetSettings->values->auth->ldap->baseDN = $settings->baseDN; + $snippetSettings->values->auth->ldap->filter = html_entity_decode($settings->filter); + $snippetSettings->values->auth->ldap->attrUser = $settings->attrUser; + $snippetSettings->values->auth->ldap->attrEmail = $settings->attrEmail; + + $exists = $this->getExtraLDAPSettings(); + if(empty($exists)) + { + $snippetResult = $this->loadModel('cne')->addSnippet($snippetSettings); + if($snippetResult->code != 200) + { + dao::$errors[] = $this->lang->system->errors->failToInstallExtraLDAP; + return false; + } + } + else + { + $snippetResult = $this->loadModel('cne')->updateSnippet($snippetSettings); + if($snippetResult->code != 200) + { + dao::$errors[] = $this->lang->system->errors->failToUpdateExtraLDAP; + return false; + } + } + + /* Save extra LDAP setting to database. */ + $secretKey = helper::readKey(); + $encryptedPassword = openssl_encrypt($snippetSettings->values->auth->ldap->bindPass, 'DES-ECB', $secretKey); + $settings->bindPass = $encryptedPassword; + + $this->setting->setItem('system.common.ldap.active', 'extra'); + $this->setting->setItem('system.common.ldap.extraSnippetName', $snippetSettings->name); // Parameter for App installation API. + $this->setting->setItem('system.common.ldap.extraSettings', json_encode($settings)); + + return true; + } + + /** + * Update LDAP config and update instance. + * + * @param object $ldapApp + * @param string $channel + * @access public + * @return void + */ + public function updateLDAP($ldapApp, $channel) + { + $postData = fixer::input('post')->setDefault('source', 'qucheng')->get(); + if($postData->source == 'qucheng') + { + $success = $this->updateQuchengLDAP($ldapApp, $channel); + } + else if($postData->source == 'extra') + { + $success = $this->configExtraLDAP((object)$postData->extra); + if($success) $this->uninstallQuChengLDAP(true); + } + + if(!$success) return false; + + /* Update instances that has been enabled LDAP. */ + $instanceList = $this->loadModel('instance')->getListEnabledLDAP(); + $counter = count($instanceList); + foreach($instanceList as $instance) + { + $this->loadModel('setting')->setItem('system.common.ldap.updatingProgress', $counter); + $this->instance->switchLDAP($instance, true); + $counter-- ; + } + + $this->loadModel('setting')->deleteItems('owner=system&module=common§ion=ldap&key=updatingProgress'); + return true; + } + + /** + * Uninstall QuCheng LDAP. + * + * @param bool $force + * @access public + * @return bool + */ + public function uninstallQuChengLDAP($force = false) + { + if(!$force) + { + $ldapLinked = $this->loadModel('instance')->countLDAP(); + if($ldapLinked) + { + dao::$errors[] = $this->lang->system->errors->LDAPLinked; + return false; + } + } + + $instanceID = $this->setting->getItem('owner=system&module=common§ion=ldap&key=instanceID'); + $instance = $this->loadModel('instance')->getByID($instanceID); + if($instance) + { + /* 1. Uninstall QuCheng LDAP service. */ + if(!$this->loadModel('instance')->uninstall($instance)) + { + dao::$errors[] = $this->lang->system->errors->failToUninstallQuChengLDAP; + return false; + } + + /* 2. Remove snippet config map from CNE. */ + $space = $this->loadModel('space')->getSystemSpace($this->app->user->account); + + $apiParams = new stdclass; + $apiParams->name = 'snippet-qucheng-ldap'; + $apiParams->namespace = $space->k8space; + + $result = $this->loadModel('cne')->removeSnippet($apiParams); + if($result->code != 200) + { + dao::$errors[] = $this->lang->system->errors->failToUninstallQuChengLDAP; + return false; + } + } + + /* 3. Delete LDAP settings in database. */ + $this->setting->deleteItems('owner=system&module=common§ion=ldap&key=instanceID'); + $this->setting->deleteItems('owner=system&module=common§ion=ldap&key=snippetName'); + if($this->getActiveLDAP() == 'qucheng') $this->setting->deleteItems('owner=system&module=common§ion=ldap&key=active'); + + return true; + } + + /** + * Uninstall extra LDAP. + * + * @access public + * @return bool + */ + public function uninstallExtraLDAP() + { + $ldapLinked = $this->loadModel('instance')->countLDAP(); + if($ldapLinked) + { + dao::$errors[] = $this->lang->system->errors->LDAPLinked; + return false; + } + + /* 1. Remove snippet config map from CNE. */ + $space = $this->loadModel('space')->getSystemSpace($this->app->user->account); + + $apiParams = new stdclass; + $apiParams->name = 'snippet-extra-ldap'; + $apiParams->namespace = $space->k8space; + + $result = $this->loadModel('cne')->removeSnippet($apiParams); + if($result->code != 200) + { + dao::$errors[] = $this->lang->system->errors->failToDeleteLDAPSnippet; + return false; + } + + /* 2. Delete extra LDAP settings in database. */ + $this->setting->deleteItems('owner=system&module=common§ion=ldap&key=extraSettings'); + $this->setting->deleteItems('owner=system&module=common§ion=ldap&key=extraSnippetName'); + if($this->getActiveLDAP() == 'extra') $this->setting->deleteItems('owner=system&module=common§ion=ldap&key=active'); + + return true; + } + + /** + * Get extra LDAP settings. + * + * @access public + * @return object|array + */ + public function getExtraLDAPSettings() + { + $settings = $this->setting->getItem('owner=system&module=common§ion=ldap&key=extraSettings'); + $settings = @json_decode($settings); + if(empty($settings)) return array(); + + $secretKey = helper::readKey(); + $settings->bindPass = openssl_decrypt($settings->bindPass, 'DES-ECB', $secretKey); + return $settings; + } + + /** + * Test LDAP Connection by post settings. + * + * @param object $settings + * @access public + * @return bool + */ + public function testLDAPConnection($settings) + { + $connectID = ldap_connect("ldap://{$settings->host}:{$settings->port}"); + + if(!ldap_set_option($connectID, LDAP_OPT_PROTOCOL_VERSION, 3)) return false; + + return ldap_bind($connectID, "cn={$settings->bindDN},{$settings->baseDN}", $settings->bindPass); + } + + /** + * Has installed global LDAP or not. + * + * @access public + * @return bool + */ + public function hasSystemLDAP() + { + $activeLDAP = $this->setting->getItem('owner=system&module=common§ion=ldap&key=active'); + return $activeLDAP == 'extra' or $activeLDAP == 'qucheng'; // LDAP has been installed. + } + + /** + * Get global LDAP snippet name. + * + * @access public + * @return string + */ + public function ldapSnippetName() + { + $activeLDAP = $this->setting->getItem('owner=system&module=common§ion=ldap&key=active'); + + if($activeLDAP == 'extra') return $this->setting->getItem('owner=system&module=common§ion=ldap&key=extraSnippetName'); + + return $this->setting->getItem('owner=system&module=common§ion=ldap&key=snippetName'); + } + + /** + * Get active LDAP type. + * + * @access public + * @return string|null + */ + public function getActiveLDAP() + { + return $this->setting->getItem('owner=system&module=common§ion=ldap&key=active'); + } + + /** + * Check global SMTP is enabled or not. + * + * @access public + * @return bool + */ + public function isSMTPEnabled() + { + $smtpSnippetName = $this->smtpSnippetName(); + $enabled = $this->setting->getItem('owner=system&module=common§ion=smtp&key=enabled'); + + return $smtpSnippetName && $enabled; + } + + /** + * Get SMTP snippet name. + * + * @access public + * @return string + */ + public function smtpSnippetName() + { + return $this->setting->getItem('owner=system&module=common§ion=smtp&key=snippetName'); + } + + /** + * Get SMTP settings. + * + * @access public + * @return object + */ + public function getSMTPSettings() + { + $settingMap = json_decode($this->setting->getItem('owner=system&module=common§ion=smtp&key=settingsMap')); + if(empty($settingMap)) return new stdclass; + + $settings = $settingMap->env; + $settings->SMTP_PASS = openssl_decrypt($settings->SMTP_PASS, 'DES-ECB', helper::readKey()); + + //$snippetSettings = json_decode($this->setting->getItem('owner=system&module=common§ion=smtp&key=snippetSettings')); + + return $settings; + } + + /** + * Update SMTP settings. + * + * @access public + * @return void + */ + public function updateSMTPSettings() + { + $this->loadModel('cne'); + $this->loadModel('instance'); + + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + + $smtpSettings = fixer::input('post')->get(); + + $instanceID = $this->setting->getItem('owner=system&module=common§ion=smtp&key=instanceID'); + $instance = $this->instance->getByID($instanceID); + if(!$instance) + { + dao::$errors[] = $this->lang->system->errors->notFoundSMTPService; + return false; + } + + $instance->version = 'latest'; // Update and upgrade SMTP proxy instance. + + /* 1. Update SMTP service settings. */ + $settingsMap = new stdclass; + $settingsMap->env = new stdclass; + $settingsMap->env->SMTP_HOST = $smtpSettings->host; + $settingsMap->env->SMTP_PORT = strval($smtpSettings->port); + $settingsMap->env->SMTP_USER = $smtpSettings->user; + $settingsMap->env->SMTP_PASS = $smtpSettings->pass; + + $settings = new stdclass; + $settings->settings_map = $settingsMap; + + $success = $this->loadModel('cne')->updateConfig($instance, $settings); + if(!$success) + { + dao::$errors[] = $this->lang->system->errors->failToUpdateSMTP; + return false; + } + + /* 2. Save SMTP account. */ + $secretKey = helper::readKey(); + $settingsMap->env->SMTP_PASS = openssl_encrypt($settingsMap->env->SMTP_PASS, 'DES-ECB', $secretKey); + + $this->loadModel('setting'); + $this->setting->setItem('system.common.smtp.settingsMap', json_encode($settingsMap)); + + return true; + } + + /** + * Install global SMTP service. + * + * @param string $channel + * @access public + * @return bool + */ + public function installSysSMTP($channel = 'stable') + { + $settings = fixer::input('post')->get(); + + $smtpApp = $this->loadModel('store')->getAppInfoByChart('cne-courier', $channel, false); + if(empty($smtpApp)) + { + dao::$errors[] = $this->lang->system->notFoundSMTPApp; + return false; + } + + $result = $this->loadModel('instance')->installSysSMTP($smtpApp, $settings, 'cne-courier', '', $channel); + return $result; + } + + /** + * Uninstall system SMTP. + * + * @access public + * @return bool + */ + public function uninstallSysSMTP() + { + $smtpLinked = $this->loadModel('instance')->countSMTP(); + if($smtpLinked) + { + dao::$errors[] = $this->lang->system->errors->SMTPLinked; + return false; + } + + $instanceID = $this->setting->getItem('owner=system&module=common§ion=smtp&key=instanceID'); + $instance = $this->instance->getByID($instanceID); + if($instance) + { + /* 1. Uninstall QuCheng LDAP service. */ + if(!$this->loadModel('instance')->uninstall($instance)) + { + dao::$errors[] = $this->lang->system->errors->failToUninstallSMTP; + return false; + } + + /* 2. Remove snippet config map from CNE. */ + $space = $this->loadModel('space')->getSystemSpace($this->app->user->account); + + $apiParams = new stdclass; + $apiParams->name = 'snippet-smtp-proxy'; + $apiParams->namespace = $space->k8space; + + $result = $this->loadModel('cne')->removeSnippet($apiParams); + if($result->code != 200) + { + dao::$errors[] = $this->lang->system->errors->failToUninstallQuChengLDAP; + return false; + } + } + + /* 3. Delete SMTP settings in database. */ + $this->setting->deleteItems('owner=system&module=common§ion=smtp&key=enabled'); + $this->setting->deleteItems('owner=system&module=common§ion=smtp&key=instanceID'); + $this->setting->deleteItems('owner=system&module=common§ion=smtp&key=snippetName'); + $this->setting->deleteItems('owner=system&module=common§ion=smtp&key=settingsMap'); + $this->setting->deleteItems('owner=system&module=common§ion=smtp&key=snippetSettings'); + + return true; + } + + /** + * Get customized domain settings. * + * @access public + * @return object + */ + public function getDomainSettings() + { + $settings = new stdclass; + $settings->customDomain = $this->setting->getItem('owner=system&module=common§ion=domain&key=customDomain'); + $settings->https = $this->setting->getItem('owner=system&module=common§ion=domain&key=https'); + $settings->certPem = ''; // + $settings->certKey = ''; // + + return $settings; + } + + /** + * Save customized somain settings. + * + * @access public + * @return void + */ + public function saveDomainSettings() + { + $settings = fixer::input('post') + ->setDefault('customDomain', '') + ->setDefault('https', 'false') + ->setIf(is_array($this->post->https) && in_array('true', $this->post->https), 'https', 'true') + ->setDefault('certPem', '') + ->setDefault('certKey', '') + ->get(); + + $this->dao->from('system')->data($settings) + ->check('customDomain', 'notempty') + ->checkIf($settings->https == 'true', 'certPem', 'notempty') + ->checkIf($settings->https == 'true', 'certKey', 'notempty'); + if(dao::isError()) return; + + if(!validater::checkREG($settings->customDomain, '/^((?!-)[a-z0-9-]{1,63}(?lang->system->errors->invalidDomain; + return; + } + + /* Upload Certificate to CNE. */ + if($settings->https == 'true') + { + $cert = new stdclass; + $cert->name = 'tls-' . str_replace('.', '-', $settings->customDomain); + $cert->certificate_pem = $settings->certPem; + $cert->private_key_pem = $settings->certKey; + $certResult = $this->loadModel('cne')->uploadCert($cert); + if($certResult->code != 200) + { + dao::$errors[] = $certResult->message; + return; + } + } + + $oldSettings = $this->getDomainSettings(); + if($settings->customDomain == $oldSettings->customDomain) + { + dao::$errors[] = $this->lang->system->errors->newDomainIsSameWithOld; + return; + } + + if(stripos($settings->customDomain, 'haogs.cn') !== false) + { + dao::$errors[] = $this->lang->system->errors->forbiddenOriginalDomain; + return; + } + + $expiredDomain = $this->setting->getItem('owner=system&module=common§ion=domain&key=expiredDomain'); + $expiredDomain = empty($expiredDomain ) ? array(getenv('APP_DOMAIN')) : json_decode($expiredDomain, true); + $expiredDomain[] = zget($settings, 'customDomain', ''); + $this->setting->setItem('system.common.domain.expiredDomain', json_encode($expiredDomain)); + + $this->setting->setItem('system.common.domain.customDomain', zget($settings, 'customDomain', '')); + $this->setting->setItem('system.common.domain.https', zget($settings, 'https', 'false')); + + $this->loadModel('instance')->updateInstancesDomain(); + + $this->updateMinioDomain(); + } + + /** + * Update minio domain. + * + * @access public + * @return void + */ + public function updateMinioDomain() + { + $this->loadModel('cne'); + $sysDomain = $this->cne->sysDomain(); + + $minioInstance = new stdclass; + $minioInstance->k8name = 'cne-operator'; + $minioInstance->chart = 'cne-operator'; + $minioInstance->spaceData = new stdclass; + $minioInstance->spaceData->k8space = $this->config->k8space; + + $settings = new stdclass; + $settings->settings_map = new stdclass; + $settings->settings_map->minio = new stdclass; + $settings->settings_map->minio->ingress = new stdclass; + $settings->settings_map->minio->ingress->enabled = true; + $settings->settings_map->minio->ingress->host = 's3.' . $sysDomain; + + $this->cne->updateConfig($minioInstance, $settings); + } + + /** + * Get SLB settings. + * + * @access public + * @return object + */ + public function getSLBSettings() + { + $settings = new stdclass; + $settings->instanceID = $this->setting->getItem('owner=system&module=common§ion=slb&key=instanceID'); + $settings->name = $this->setting->getItem('owner=system&module=common§ion=slb&key=name'); + $settings->ippool = ''; + + if($settings->instanceID) + { + $slbInstance = $this->loadModel('instance')->getByID($settings->instanceID); + + $qlbInfo = $this->loadmodel('cne')->getQLBinfo($settings->name, $slbInstance->spaceData->k8space); // QLB: Qucheng load balancer. + if($qlbInfo) $settings->ippool = $qlbInfo->ippool; + } + + return $settings; + } + + /** + * Save SLB settings. + * + * @access public + * @return void + */ + public function saveSLBSettings() + { + $settings = fixer::input('post') + ->setDefault('ippool', '') + ->get(); + if(empty($settings->ippool)) + { + dao::$errors[] = $this->lang->system->errors->ippoolRequired; + return; + } + + $reg1Result = validater::checkREG($settings->ippool, '/^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}\/\d{1,2}$/'); + $reg2Result = validater::checkREG($settings->ippool, '/^(((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3})-(((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3})$/'); + if($reg1Result === false && $reg2Result === false) + { + dao::$errors[] = $this->lang->system->errors->wrongIPRange; + return; + } + + $instanceID = $this->setting->getItem('owner=system&module=common§ion=slb&key=instanceID'); + if(!$instanceID) + { + /* 1. Install SLB component metallb at first time. */ + $slbInstance = $this->installSLBInstance(); + if(!$slbInstance) return; + + $instanceID = $slbInstance->id; + $this->setting->setItem('system.common.slb.instanceID', $slbInstance->id); + } + + $instance = $this->loadModel('instance')->getByID($instanceID); + $status = ''; + /* Wait 30 seconds at most for SLB instance ready. */ + for($times = 0; $times < 10; $times++) + { + sleep(3); + $statusResponse = $this->loadModel('cne')->queryStatus($instance); + if($statusResponse->code != 200) continue; + + $status = $statusResponse->data->status; + if($status == 'running') break; + } + + if($status != 'running') + { + dao::$errors[] = $this->lang->system->errors->tryReinstallSLB; + return; + } + + /* 2. Config SLB. */ + $settings->name = 'qlb-quickon'; + $settings->namespace = $this->config->k8space; + $success = $this->loadModel('cne')->configQLB($settings); + if(!$success) + { + dao::$errors[] = $this->lang->system->errors->failedToConfigSLB; + return; + } + + $this->setting->setItem('system.common.slb.name', zget($settings, 'name', '')); + } + + /** + * Install SLB instance. + * + * @access private + * @return object|null + */ + private function installSLBInstance() + { + $channel = $this->app->session->cloudChannel ? $this->app->session->cloudChannel : $this->config->cloud->api->channel; + + $slbApp = new stdclass; + $slbApp->name = 'metallb'; + $slbApp->alias = 'metallb'; + $slbApp->desc = 'metallb'; + $slbApp->chart = 'metallb'; + $slbApp->app_version = ''; + $slbApp->version = ''; + $slbApp->id = ''; + $slbApp->logo = ''; + + $slbInstance = $this->loadModel('instance')->installSysSLB($slbApp, 'cne-lb', $channel); + if(!$slbInstance) + { + dao::$errors[] = $this->lang->system->errors->failedToInstallSLBComponent; + return; + } + + return $slbInstance; + } + + /** + * Print SMTP buttons. + * + * @param objevt $smtpInstance + * @access public + * @return string + */ + public function printSMTPButtons($smtpInstance) + { + $this->loadModel('instance'); + $this->app->loadLang('instance'); + + $buttonHtml = ''; + + $disableStart = !$this->instance->canDo('start', $smtpInstance); + $buttonHtml .= html::commonButton($this->lang->instance->start, "instance-id='{$smtpInstance->id}' title='{$this->lang->instance->start}'" . ($disableStart ? ' disabled ' : ''), "btn-start btn label label-outline label-primary label-lg"); + + $title = $this->lang->instance->stop; + $toolTips = ''; + $count = $this->instance->countSMTP(); + if($count) + { + $title = sprintf($this->lang->system->notices->smtpUsed, $count); + $toolTips = "data-toggle='tooltip' data-placement='bottom' runat='server'"; + } + + $buttonHtml .= ""; + $buttonHtml .= html::a(inLink('editSMTP'), $this->lang->system->SMTP->edit, '', "title='{$title}' class='btn-edit btn label label-outline label-primary label-lg'"); + $buttonHtml .= ""; + + $disableStop = $count > 0 || !$this->instance->canDo('stop', $smtpInstance); + $buttonHtml .= ""; + $buttonHtml .= html::commonButton($this->lang->instance->stop, "instance-id='{$smtpInstance->id}' title='{$title}'" . ($disableStop ? ' disabled ' : ''), 'btn-stop btn label label-outline label-danger label-lg'); + $buttonHtml .= ""; + + echo $buttonHtml; + } + + public function getK8sTag() + { + $serviceAccount = '/var/run/secrets/kubernetes.io/serviceaccount'; + if(!is_dir($serviceAccount)) return "Fail: Please run script in k8s."; + + $nameSpace = file_get_contents($serviceAccount . '/namespace'); + + $apiServer = empty($_POST['apiServer']) ? 'https://kubernetes.default.svc' : $_POST['apiServer']; + $token = file_get_contents($serviceAccount . '/token'); + $cacert = $serviceAccount . '/ca.crt'; + + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, "$apiServer/api/v1/namespaces/{$nameSpace}/serviceaccounts/default"); + curl_setopt($curl, CURLOPT_HEADER, false); + curl_setopt($curl, CURLOPT_CAINFO, $cacert); + curl_setopt($curl, CURLOPT_HTTPHEADER, array("Authorization: Bearer $token")); + curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET'); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLINFO_HEADER_OUT, true); + $response = curl_exec($curl); + $errors = curl_error($curl); + curl_close($curl); + + if($errors) return "Fail: " . $errors; + + $serviceAccounts = json_decode($response); + if(empty($serviceAccounts->metadata->uid)) + { + if($serviceAccounts->code == '403') return $this->lang->misc->k8s->repairNotice; + return "Fail: " . $response; + } + + $infos = array(); + $infos['apiServer'] = $apiServer; + $infos['nameSpace'] = $nameSpace; + $infos['uid'] = ''; + if(isset($serviceAccounts->metadata->uid)) $infos['uid'] = $serviceAccounts->metadata->uid; + + if(empty($infos['uid'])) return "Fail: Can not find k8s uid information"; + + $md5 = md5($this->config->global->sn); + $systemDebug = $this->config->debug; + $this->config->debug = false; + $encrypted = @openssl_encrypt(json_encode($infos), 'DES-CBC', substr($md5, 0, 8)); + $this->config->debug = $systemDebug; + return $encrypted; + } + + /** + * Backup SQL. + * + * @param string $backupFile + * @access public + * @return object + */ + public function backSQL($backupFile, $backupType = 'manual') + { + $zdb = $this->app->loadClass('zdb'); + $dumpStatus = $zdb->dump($backupFile); + if($dumpStatus->result === true) $this->processSQLSummary($backupFile, $backupType); + return $dumpStatus; + } + + /** + * Restore SQL. + * + * @param string $backupFile + * @access public + * @return object + */ + public function restoreSQL($backupFile) + { + $zdb = $this->app->loadClass('zdb'); + $nosafe = strpos($this->config->backup->setting, 'nosafe') !== false; + + $backupDir = dirname($backupFile); + $fileName = date('YmdHis') . mt_rand(0, 9); + $backFileName = "{$backupDir}/{$fileName}.sql"; + if(!$nosafe) $backFileName .= '.php'; + + $result = $this->backSQL($backFileName, 'restore'); + if($result->result and !$nosafe) $this->addFileHeader($backFileName); + + $allTables = $zdb->getAllTables(); + foreach($allTables as $tableName => $tableType) + { + try + { + $this->dbh->query("DROP $tableType IF EXISTS `$tableName`"); + } + catch(PDOException $e){} + } + + $importResult = $zdb->import($backupFile); + + if($importResult && $importResult->result) + { + $this->loadModel('instance')->restoreInstanceList(); + $this->processRestoreSummary('sql', 'done'); + } + + return $importResult; + } + + /** + * Restore File. + * + * @param string $backupFile + * @access public + * @return object + */ + public function restoreFile($backupFile) + { + $return = new stdclass(); + $return->result = true; + $return->error = ''; + + if(is_file($backupFile)) + { + $oldDir = getcwd(); + chdir($this->app->getTmpRoot()); + $this->app->loadClass('pclzip', true); + $zip = new pclzip($backupFile); + if($zip->extract(PCLZIP_OPT_PATH, $this->app->getAppRoot() . 'www/data/', PCLZIP_OPT_TEMP_FILE_ON) == 0) + { + $return->result = false; + $return->error = $zip->errorInfo(); + } + chdir($oldDir); + } + elseif(is_dir($backupFile)) + { + $zfile = $this->app->loadClass('zfile'); + $zfile->copyDir($backupFile, $this->app->getAppRoot() . 'www/data/', $showDetails = false); + } + + $this->processRestoreSummary('file', 'done'); + + return $return; + } + + /** + * Get backup account and backup type. + * + * @param string $file + * @access public + * @return array + */ + public function getSQLSummary($file) + { + $summaryFile = $this->loadModel('backup')->getBackupPath() . DS . 'summary'; + $sqlSummary = json_decode(file_get_contents($summaryFile), true); + return isset($sqlSummary[basename($file)]) ? $sqlSummary[basename($file)] : array(); + } + + /** + * Process restore summay. + * + * @param string $restoreType + * @param string $status + * @param string $action + * @access public + * @return bool + */ + public function processRestoreSummary($restoreType = 'sql', $status = 'done', $action = 'add') + { + $summaryFile = $this->loadModel('backup')->getBackupPath() . DS . 'restoreSummary'; + if(!file_exists($summaryFile) and !touch($summaryFile)) return false; + + $summary = json_decode(file_get_contents($summaryFile), true); + if(empty($summary)) $summary = array(); + + if($action == 'add') + { + $summary[$restoreType] = $status; + } + else + { + $summary = array(); + } + if(file_put_contents($summaryFile, json_encode($summary))) return true; + return false; + + } + + /** + * Save backup account and backup type. + * + * @param string $file + * @param string $type + * @param string $action + * @access public + * @return bool + */ + public function processSQLSummary($file, $type = 'manual', $action = 'add') + { + $backupPath = dirname($file); + $fileName = basename($file); + + $summaryFile = $backupPath . DS . 'summary'; + if(!file_exists($summaryFile) and !touch($summaryFile)) return false; + + $summary = json_decode(file_get_contents($summaryFile), true); + if(empty($summary)) $summary = array(); + + if($action == 'add') + { + $summary[$fileName]['account'] = $this->app->user->account == 'guest' ? '' : $this->app->user->account; + $summary[$fileName]['backupType'] = $type; + } + else + { + unset($summary[$fileName]); + } + + if(file_put_contents($summaryFile, json_encode($summary))) return true; + return false; + } + + /** + * Check upgrade process is overtime (5 miniutes) or not. + * + * @access public + * @return mixed + */ + public function isGradeOvertime() + { + $upgradedAt = $this->loadModel('setting')->getItem('owner=system&module=backup§ion=global&key=upgradedAt'); + + return (time() - intval($upgradedAt)) > 300; + } + +} diff --git a/module/system/ui/browsebackup.html.php b/module/system/ui/browsebackup.html.php new file mode 100644 index 0000000000..ab7cf43811 --- /dev/null +++ b/module/system/ui/browsebackup.html.php @@ -0,0 +1,37 @@ + + * @package system + * @link https://www.zentao.net + */ +namespace zin; + +foreach($backups as $backup) +{ + $backup->backupPerson = isset($backup->sqlSummary['account']) ? $backup->sqlSummary['account'] : ''; + if(empty($backup->sqlSummary['backupType'])) $backup->sqlSummary['backupType'] = ''; + $backup->type = $backup->sqlSummary['backupType']; +} +$backups = initTableData($backups, $config->system->dtable->backup->fieldList, $this->system); + +panel +( + set::size('lg'), + set::title($lang->system->backup->systemInfo), + div + ( + setStyle('width', '66.6%'), + dtable + ( + set::cols($config->system->dtable->backup->fieldList), + set::data($backups), + ), + ), +); + +render(); + diff --git a/module/system/ui/dblist.html.php b/module/system/ui/dblist.html.php new file mode 100644 index 0000000000..173f2fa61d --- /dev/null +++ b/module/system/ui/dblist.html.php @@ -0,0 +1,34 @@ + + * @package system + * @link https://www.zentao.net + */ +namespace zin; + +$config->system->dtable->dbList->fieldList['actions']['list']['management']['url'] = 'javascript:manageDb("{name}", "{db_type}", "{namespace}")'; + +$dbList = initTableData($dbList, $config->system->dtable->dbList->fieldList, $this->system); + +panel +( + set::size('lg'), + set::title($lang->system->dbList), + div + ( + setStyle('width', '66.6%'), + dtable + ( + set::cols($config->system->dtable->dbList->fieldList), + set::data($dbList), + set::onRenderCell(jsRaw('window.renderDbList')) + ), + ), +); + +render(); + diff --git a/module/system/ui/domainview.html.php b/module/system/ui/domainview.html.php new file mode 100644 index 0000000000..c4f5ed8c74 --- /dev/null +++ b/module/system/ui/domainview.html.php @@ -0,0 +1,43 @@ + + * @package system + * @link https://www.zentao.net + */ +namespace zin; + +$expiredDate = !empty($cert) ? zget($cert, 'expiredDate', '') : ''; + +panel +( + set::size('lg'), + set::title($lang->system->domain->common), + to::headingActions + ( + btn + ( + setClass('primary'), + $lang->system->domain->editDomain, + set::url($this->createLink('system', 'editDomain')), + ), + ), + tableData + ( + item + ( + set::name($lang->system->domain->currentDomain), + zget($domainSettings, 'customDomain', ''), + ), + item + ( + set::name($lang->system->domain->expiredDate), + $expiredDate + ), + ), +); + +render(); diff --git a/module/system/ui/editdomain.html.php b/module/system/ui/editdomain.html.php new file mode 100644 index 0000000000..09dbf9d75f --- /dev/null +++ b/module/system/ui/editdomain.html.php @@ -0,0 +1,94 @@ + + * @package system + * @link https://www.zentao.net + */ +namespace zin; + +formPanel +( + set::title($lang->system->domain->config), + set::id('domainForm'), + formRow + ( + formGroup + ( + set::label($lang->system->domain->oldDomain), + set::control('static'), + set::value($this->loadModel('cne')->sysDomain()), + h::span + ( + setClass('tips-git leading-8 ml-2 text-danger'), + $lang->system->domain->notReuseOldDomain, + ), + ), + ), + formGroup + ( + set::label($lang->system->domain->newDomain), + set::name('customDomain'), + set::value(zget($domainSettings, 'customDomain', '')), + set::required(true), + h::span + ( + setClass('tips-git leading-8 ml-2'), + $lang->system->domain->setDNS, + ), + h::a + ( + setClass('leading-8'), + set::target('_blank'), + set::href('https://www.qucheng.com/book/Installation-manual/47.html'), + $lang->system->domain->dnsHelperLink + ), + ), + formGroup + ( + set::label(''), + set::name('https[]'), + set::control(array('type' => 'checkbox', 'text' => $lang->system->domain->uploadCert, 'value' => 'true')), + on::change('onHttpsChange'), + ), + formGroup + ( + setClass('cert hidden'), + set::label($lang->system->certPem), + set::name('certPem'), + set::control('textarea'), + set::value(zget($domainSettings, 'certPem', '')), + ), + formGroup + ( + setClass('cert hidden'), + set::label($lang->system->certKey), + set::name('certPem'), + set::control('textarea'), + set::value(zget($domainSettings, 'certKey', '')), + ), + formRow + ( + formGroup + ( + setClass('cert hidden'), + set::label(''), + btn + ( + $lang->system->verify, + set::id('validateCertBtn'), + on::click('checkCert'), + ), + h::span + ( + setClass('tips-git leading-8 ml-2'), + set::id('validateMsg'), + ), + ) + ), +); +render(); + diff --git a/module/system/ui/index.html.php b/module/system/ui/index.html.php new file mode 100644 index 0000000000..4373ed59e1 --- /dev/null +++ b/module/system/ui/index.html.php @@ -0,0 +1,14 @@ + + * @package system + * @link https://www.zentao.net + */ +namespace zin; + + +render(); diff --git a/module/system/ui/ossview.html.php b/module/system/ui/ossview.html.php new file mode 100644 index 0000000000..5326e74bb0 --- /dev/null +++ b/module/system/ui/ossview.html.php @@ -0,0 +1,85 @@ + + * @package system + * @link https://www.zentao.net + */ +namespace zin; + +jsVar('copySuccess', $lang->system->copySuccess); + +$apiURL = !empty($ossDomain->extra_hosts) ? zget($ossDomain->extra_hosts, 'api', '') : ''; + +panel +( + set::size('lg'), + set::title($lang->system->oss->common), + tableData + ( + item + ( + set::name($lang->system->oss->appURL), + a + ( + set::id('ossVisitUrl'), + set::target('_blank'), + $lang->system->visit, + ), + ), + item + ( + set::name($lang->system->oss->user), + span + ( + set::id('ossAdmin'), + ), + ), + item + ( + set::name($lang->system->oss->password), + h::input + ( + setClass('hidden'), + set::id('ossPassword'), + set::value($ossAccount->password), + ), + btn + ( + $lang->system->copy, + on::click('copyPassBtn'), + ) + ), + item + ( + set::name($lang->system->oss->apiURL), + $apiURL, + ), + item + ( + set::name($lang->system->oss->accessKey), + $ossAccount->username, + ), + item + ( + set::name($lang->system->oss->secretKey), + h::input + ( + setClass('hidden'), + set::id('ossSK'), + set::value($ossAccount->password), + ), + btn + ( + $lang->system->copy, + on::click('copySK'), + ) + ), + ), +); + +render(); + diff --git a/module/system/view/dashboard.html.php b/module/system/view/dashboard.html.php new file mode 100644 index 0000000000..0db1a8d76e --- /dev/null +++ b/module/system/view/dashboard.html.php @@ -0,0 +1,20 @@ + + * @package my + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +
+
+
+
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/system/view/dblist.html.php b/module/system/view/dblist.html.php new file mode 100644 index 0000000000..f5697c2293 --- /dev/null +++ b/module/system/view/dblist.html.php @@ -0,0 +1,43 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> + +
+
+
+

system->dbList;?>

+
+ + + + + + + + + + + + + + + +
system->dbName;?>system->dbType;?>system->dbStatus;?>system->action?>
name;?>db_type?>instance->statusList, $db->status);?>system->printDBAction($db);?>
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> + diff --git a/module/system/view/domainview.html.php b/module/system/view/domainview.html.php new file mode 100644 index 0000000000..dbf06aa211 --- /dev/null +++ b/module/system/view/domainview.html.php @@ -0,0 +1,43 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> + +
+
+
+

system->domain->common;?>

+
+ inLink('editDomain'), $lang->system->domain->editDomain, '', "class='btn btn-primary'");?> +
+
+ + + + + + + + + + + + + +
system->domain->currentDomain;?>
system->domain->expiredDate;?>
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/system/view/editdomain.html.php b/module/system/view/editdomain.html.php new file mode 100644 index 0000000000..03ad29d67c --- /dev/null +++ b/module/system/view/editdomain.html.php @@ -0,0 +1,97 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> + +system->errors);?> +system->notices);?> + +
+
+
+

system->domain->config;?>

+
+
+ + + + + + + + + + + + + + + + + + +
system->domain->oldDomain;?>loadModel('cne')->sysDomain();?> system->domain->notReuseOldDomain;?>
system->domain->newDomain;?> + +
+ system->domain->setDNS;?> + system->domain->dnsHelperLink, '_blank', "class='text-primary'");?> +
+
+

+ + $lang->system->domain->uploadCert), $domainSettings->https, ($domainUsed ? "onclick='return false;'" : ''));?> +

+
+ + + + + + + + + + + + + + + + + + +
system->certPem;?>
system->certKey;?>
+ system->verify, "id='validateCertBtn'");?> + +
+
save, "id='submitBtn'", "btn btn-primary");?>
+
+
+
+ +app->getModuleRoot() . '/common/view/footer.html.php';?> + diff --git a/module/system/view/editldap.html.php b/module/system/view/editldap.html.php new file mode 100644 index 0000000000..c3c0b16bcd --- /dev/null +++ b/module/system/view/editldap.html.php @@ -0,0 +1,32 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +loadModel('instance')->countLDAP();?> +system->errors);?> +system->notices);?> + + +
+
+
+

system->ldapManagement;?>

+
+ +
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> + diff --git a/module/system/view/editslb.html.php b/module/system/view/editslb.html.php new file mode 100644 index 0000000000..b31992d53d --- /dev/null +++ b/module/system/view/editslb.html.php @@ -0,0 +1,58 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> + +system->errors);?> +system->notices);?> + +
+
+
+

system->SLB->config;?>

+
+
+ + + + + + + + +
system->SLB->ipPool;?> + system->SLB->ipPoolExample}'");?> +
+
save, "id='submitBtn'", 'btn btn-wide btn-primary');?>
+
+
+
+ +app->getModuleRoot() . '/common/view/footer.html.php';?> + diff --git a/module/system/view/editsmtp.html.php b/module/system/view/editsmtp.html.php new file mode 100644 index 0000000000..edd8c1be2e --- /dev/null +++ b/module/system/view/editsmtp.html.php @@ -0,0 +1,32 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +loadModel('instance')->countSMTP();?> +system->errors);?> +system->notices);?> + + +
+
+
+

system->SMTP->editSMTP;?>

+
+ +
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> + diff --git a/module/system/view/index.html.php b/module/system/view/index.html.php new file mode 100644 index 0000000000..a76bff722e --- /dev/null +++ b/module/system/view/index.html.php @@ -0,0 +1,76 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> + +app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/system/view/installldap.html.php b/module/system/view/installldap.html.php new file mode 100644 index 0000000000..33272e3546 --- /dev/null +++ b/module/system/view/installldap.html.php @@ -0,0 +1,31 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +loadModel('instance')->countLDAP();?> +system->errors);?> +system->notices);?> + +
+
+
+

system->installLDAP;?>

+
+ +
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> + diff --git a/module/system/view/installsmtp.html.php b/module/system/view/installsmtp.html.php new file mode 100644 index 0000000000..3fc433875f --- /dev/null +++ b/module/system/view/installsmtp.html.php @@ -0,0 +1,31 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +loadModel('instance')->countSMTP();?> +system->errors);?> +system->notices);?> + +
+
+
+

system->SMTP->install;?>

+
+ +
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> + diff --git a/module/system/view/instancesblock.html.php b/module/system/view/instancesblock.html.php new file mode 100644 index 0000000000..eb6703a917 --- /dev/null +++ b/module/system/view/instancesblock.html.php @@ -0,0 +1,43 @@ +app->loadLang('instance');?> + +
+

instance->runningService;?>

+ +
+

instance->empty;?>

+
+ + + + + + + + + + + + + + + + + + id);?> + + + + + +
instance->name;?>instance->version;?>instance->status?>instance->cpu;?>instance->mem;?>
createLink('instance', 'view', "id=$instance->id"), $instance->name);?>appVersion;?> + instance->printStatus($instance, false);?> + + + instance->leftTime;?> + 00:00 + + + instance->printCpuUsage($instance, $metrics->cpu);?>instance->printMemUsage($instance, $metrics->memory);?>
+ + +
diff --git a/module/system/view/ldapform.html.php b/module/system/view/ldapform.html.php new file mode 100644 index 0000000000..9738a56434 --- /dev/null +++ b/module/system/view/ldapform.html.php @@ -0,0 +1,98 @@ +
+

+ system->hasSystemLDAP();?> + $lang->system->LDAP->ldapEnabled), $enableLDAP ? 'true' : '', (($ldapLinked or $activeLDAP) ? "onclick='return false;'" : '')); ?> +

+ + + + + + + + +
system->LDAP->ldapSource;?>system->ldapTypeList, $activeLDAP,"class='form-control'");?>
+ + + + + + + + + + + +
system->LDAP->ldapUsername?>account->username;?>
system->LDAP->ldapRoot;?>
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
system->LDAP->host;?>
system->LDAP->port;?>
system->LDAP->ldapUsername;?>
system->LDAP->password;?>
system->LDAP->ldapRoot;?>
+
system->LDAP->ldapAdvance . "", '', "data-toggle='collapse'");?>
+ + + + + + + + + + + + + + + + + + +
system->LDAP->filterUser;?>
system->LDAP->email;?>
system->LDAP->extraAccount;?>
+ + + + + + + + +
system->verify, "id='testConnectBtn'");?>
+
+
system->LDAP->ldapUpdate : $lang->system->LDAP->ldapInstall, "id='submitBtn'", "btn btn-primary btn-wide");?>
+
+ diff --git a/module/system/view/ldapview.html.php b/module/system/view/ldapview.html.php new file mode 100644 index 0000000000..048c902100 --- /dev/null +++ b/module/system/view/ldapview.html.php @@ -0,0 +1,132 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +instance->notices);?> + +system->copySuccess);?> + +
+
+
+

system->LDAP->info;?>

+
+ system->printEditLDAPBtn();?> + + system->printLDAPButtons($ldapInstance);?> + + system->visit, '', "disabled class='btn label label-outline label-primary label-lg'");?> + system->close, '', "disabled class='btn label label-outline label-primary label-lg'");?> + +
+
+ + + + + + + + + + + + + + + + + + + + + +
system->LDAP->ldapSource;?>system->ldapTypeList, $activeLDAP, '');?>
system->LDAP->ldapUsername;?>auth, 'username', '');?>
system->LDAP->ldapRoot;?>auth, 'root', '');?>
instance->status;?>instance->statusList, $ldapInstance->status, '');?>
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
system->LDAP->ldapSource;?>system->ldapTypeList, $activeLDAP, '');?>
system->LDAP->host;?>
system->LDAP->port;?>
system->LDAP->ldapUsername;?>
system->LDAP->password;?> +
+ + +
+
system->LDAP->ldapRoot;?>
system->LDAP->ldapAdvance;?>
system->LDAP->filterUser;?>
system->LDAP->email;?>
system->LDAP->extraAccount;?>
+ +
+
+ +app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/system/view/ossview.html.php b/module/system/view/ossview.html.php new file mode 100644 index 0000000000..f0a2822c97 --- /dev/null +++ b/module/system/view/ossview.html.php @@ -0,0 +1,69 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +system->copySuccess);?> + +
+
+
+

system->oss->common;?>

+
+ system->oss->manage, "id='ossManage'", "btn label label-outline label-primary label-lg");?> +
+
+ + + + + + + + + + + + + + + + +
system->oss->apiURL;?>extra_hosts, 'api', '');?>
system->oss->accessKey;?>username;?>
system->oss->secretKey;?> + + system->copy, "id='copySKBtn'");?> +
+
+
+ +app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/system/view/overviewblock.html.php b/module/system/view/overviewblock.html.php new file mode 100644 index 0000000000..5b868bb7b6 --- /dev/null +++ b/module/system/view/overviewblock.html.php @@ -0,0 +1,32 @@ +app->loadLang('cne');?> +
+
+
+

system->cneStatus;?>

+
lang->CNE->statusIcons, $cneMetrics->status, $this->lang->CNE->statusIcons['unknown']);?>
+
CNE->statusList, $cneMetrics->status, $lang->CNE->statusList['unknown']);?>
+
+
+

system->cneStatistic;?>

+
+
+
system->nodeQuantity;?>
+
node_count;?>
+
+
+
system->serviceQuantity;?>
+
instance->totalServices();?>
+
+
+
system->cpuUsage;?>
+ cne->printCpuUsage($cneMetrics->metrics->cpu);?> +
+
+
system->memUsage;?>
+ cne->printMemUsage($cneMetrics->metrics->memory);?> +
+
+
+
+
+ diff --git a/module/system/view/slbview.html.php b/module/system/view/slbview.html.php new file mode 100644 index 0000000000..28da2501da --- /dev/null +++ b/module/system/view/slbview.html.php @@ -0,0 +1,38 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> + +
+
+
+

system->SLB->common;?>

+
+ inLink('editSLB'), $lang->system->SLB->edit, '', "class='btn btn-primary'");?> +
+
+ + + + + + + + +
system->SLB->ipPool;?>
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/module/system/view/smtpform.html.php b/module/system/view/smtpform.html.php new file mode 100644 index 0000000000..a95ed09ae0 --- /dev/null +++ b/module/system/view/smtpform.html.php @@ -0,0 +1,36 @@ +
+

+ system->smtpSnippetName();?> + $lang->system->SMTP->enabled), $enableSMTP ? 'true' : '', (($smtpLinked or $activeSMTP) ? "onclick='return false;'" : ''));?> +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
system->SMTP->account;?>
system->SMTP->password;?>
system->SMTP->host;?>
system->SMTP->port;?>
system->verify, "id='verifyAccountBtn'");?>
+
system->SMTP->update : $lang->system->SMTP->install);?>
+
diff --git a/module/system/view/smtpview.html.php b/module/system/view/smtpview.html.php new file mode 100644 index 0000000000..1efc99e40e --- /dev/null +++ b/module/system/view/smtpview.html.php @@ -0,0 +1,60 @@ + + * @package system + * @version $Id$ + * @link https://www.qucheng.com + */ +?> +app->getModuleRoot() . '/common/view/header.html.php';?> +instance->notices);?> +id));?> + +
+
+
+

system->SMTP->common;?>

+
+ system->printSMTPButtons($smtpInstance);?> +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
system->SMTP->account;?>
system->SMTP->password;?> +
+ + +
+
system->SMTP->host;?>
system->SMTP->port;?>
instance->status;?>instance->statusList, $smtpInstance->status, '');?>
+
+
+app->getModuleRoot() . '/common/view/footer.html.php';?> diff --git a/www/static/svg/admin-platform.svg b/www/static/svg/admin-platform.svg new file mode 100644 index 0000000000..13f7ff96b5 --- /dev/null +++ b/www/static/svg/admin-platform.svg @@ -0,0 +1,4 @@ + + + +