dreamfactory / dsp-admin
DreamFactory 服务平台 (tm) 管理控制台
Requires
- dreamfactory/installer: dev-develop as dev-master
README
DSP 的 AngularJS 管理应用程序
使用管理应用程序 2 从任何地方管理您的 DSP。使用 Bootswatch 的主题进行自定义或使用 SCSS/SASS 自定义。使用 Node.js、Grunt 和包含的 grunt 脚本合并、压缩和丑化组件模块,以创建一个可用于部署的应用程序。
安装管理应用程序 2
克隆存储库。导航到已克隆存储库的顶级目录,然后输入 bower install
。 注意:您必须已安装 Node、Grunt 和 GruntCLI。
使用 Node 和 Grunt 构建应用程序
管理应用程序 2 预装了 grunt 文件,该文件将源文件合并、压缩、丑化、压缩并重新组织成更“适合传输”的方式。它减少了加载时间,并自动清除客户端缓存,以便下次客户端使用时可以看到您的更改,而无需手动清除缓存。此过程将创建一个名为 dist
的文件夹,其中包含处理后的应用程序。从现在起,“构建应用程序”这个短语将指代此过程。要在应用程序的顶级目录中运行构建过程,请在命令行中输入 grunt build
。 注意:您必须已安装 Node、Grunt、GruntCLI 和 Bower。
以下是构建 admin2 的 dist 版本的方法。
一次性设置
install node and npm (downloadable installer)
sudo npm install -g bower
sudo npm install grunt-cli
cd ~/repos/admin2 (or wherever your repo is)
npm install
bower install
然后重新构建 dist 文件夹
grunt build
在提交更改之前,应将 /dist/fonts 和 app/index.html 撤回。这些修改后的文件是构建过程中的不需要的副作用。
git checkout -- dist/fonts app/index.html
构建发布版本
这会将内容推送到 master。运行 composer update 将拉取新版本。使用 git-flow 进行发布的基步骤如下。在尝试此操作之前,您的本地 develop 和 master 应该是最新的。
git checkout master
git pull origin master
git checkout develop
git pull origin develop
git flow release start 1.0.7
在 app/scripts/app.js 中提高应用程序版本。
// Set application version number
.constant('APP_VERSION', '1.0.7')
grunt build
git checkout -- dist/fonts app/index.html
git add --all
git commit -m "Release 1.0.7"
git flow release finish 1.0.7
git push origin develop
git checkout master
git push origin master
git push --tags
从任何地方管理您的 DSP
管理应用程序 2 可以配置为从另一个远程服务器管理您的 DSP。只需打开位于 app\scripts
目录中的 app.js
文件,并将您的 DSP 主机名添加到顶部的 DSP_URL
常量中。现在,您可以可选地构建应用程序并将 dist
目录部署。您必须启用 DSP 中的 CORS,以便将应用程序部署到的服务器。
主题管理应用程序 2
Admin App 2使用Sass/Scss构建,并通过Compass编译。这需要Ruby和Compass。请遵循此指南来设置所有这些。在app/styles/sass/partials
中,您可以找到所有自定义管理应用部分的样式表以及一些Bootswatch模板(这些命名为variables(1-8).scss)。所有这些都在styles.scss
中以特定顺序添加。要切换到不同的Bootswatch主题,只需找到'@import variables(1-8).scss'行并更改数字。或者下载不同的Bootswatch主题,并用新主题的variables.scss替换当前的variables.scss。别忘了运行Compass来编译样式表,然后可选地构建应用并将dist目录部署。
Admin App 2 架构
Admin App 2的设计旨在具有可插拔的模块。每个模块都包含它自己的路由、事件和逻辑,以便移除一个模块不会停止应用工作。这些模块存储在app/admin_components
下。为了方便使用Admin App 2,设计了一个模块作为频繁使用的数据的中央仓库。许多其他模块依赖于这个模块来获取数据以完成其工作,但经过一点重构,它可以被移除以产生真正的无约束模块。
主应用
主应用文件位于两个目录下。位于app
目录下的scripts
和views
目录。scripts
目录包含您的app.js文件和一个名为controllers
的子目录,其中包含main.js
。在上述views
目录中可以找到在main.js
中定义的控制器对应的视图。app.js
文件包含一些常量。值得注意的是DSP_URL
和DSP_API_KEY
。DSP_URL
允许设置一个主机,应用及其模块将引用它进行API调用。DSP_API_KEY
是应用名称,用于以下定义API密钥的配置选项,该选项设置应用发出的所有调用中的API密钥。app.js
还定义了登录、登出、注册的标准路由。这些路由在main.js
中定义了相应的控制器。
main.js
定义了应用特定的控制器。MainCtrl充当顶级作用域,其他模块可以查询应用级数据。例如,我们的顶级导航和组件导航链接存储在这里的数组中,这些数组传递给渲染链接和控制活动链接高亮的指令。每当添加/移除模块时,它的链接都需要在这里处理。但您不应该经常(或根本不)遇到这种情况。
身份验证控制器提供身份验证/注册事件的附加点。它们在处理用户管理模块产生的身份验证/注册事件时实现稀疏逻辑。这为身份验证/注册的具体应用逻辑和实际验证/注册用户的业务逻辑之间提供了解耦。有关更多信息,请参阅main.js
注释。
数据仓库和实用模块
名为dfApplicationData
的数据仓库模块简化了常用应用数据的加载和管理。它在浏览器会话存储中创建一个名为dfApplicationObj
的对象。它包含通用的方法来访问、修改和删除应用和服务器中的数据。它还提供访问器方法来检索和保存实际的dfApplicationObj。虽然不建议直接与此对象交互,但有时这是必要的恶行。该模块还包含初始化代码,以检查是否需要构建新的应用对象或刷新屏幕以使用本地数据以及要加载的API。
工具模块提供了与模块操作相关的服务、工厂、指令和过滤器。例如我们的图标服务、导航、表格过滤/分页等,都存储在这里。基本上,这里存储的是多个模块可能需要访问且没有其他地方可去的东西。
模块设计
模块按照AngularJS的常规方式定义。angular.module(MODULE_NAME, [DEPENDENCIES])
。在这行代码下面,我们定义了一些常量和模块的配置。因为模块通常是小的SPA,所以我们只包含了一个主路由。下面展示的是dfApps
模块的一个子部分,以说明这一点。
// Module definition angular.module('dfApps', ['ngRoute', 'dfUtility', 'dfApplication', 'dfHelp', 'dfTable']) // Path constants are defined to facilitate ease of reorganization .constant('MOD_APPS_ROUTER_PATH', '/apps') .constant('MOD_APPS_ASSET_PATH', 'admin_components/adf-apps/') // A Route for the module is configured and a bit of access logic is included .config(['$routeProvider', 'MOD_APPS_ROUTER_PATH', 'MOD_APPS_ASSET_PATH', function ($routeProvider, MOD_APPS_ROUTER_PATH, MOD_APPS_ASSET_PATH) { $routeProvider .when(MOD_APPS_ROUTER_PATH, { templateUrl: MOD_APPS_ASSET_PATH + 'views/main.html', controller: 'AppsCtrl', resolve: { checkAppObj:['dfApplicationData', function (dfApplicationData) { // is the app in init if (dfApplicationData.initInProgress) { // don't load controller until it is finished return dfApplicationData.initDeferred.promise; } }], checkCurrentUser: ['UserDataService', '$location', function (UserDataService, $location) { var currentUser = UserDataService.getCurrentUser(); // If there is no currentUser and we don't allow guest users if (!currentUser) { $location.url('/login') } // There is a currentUser but they are not an admin else if (currentUser && !currentUser.is_sys_admin) { $location.url('/launchpad') } }] } }); }]) .run(['DSP_URL', '$templateCache', function (DSP_URL, $templateCache) { }]) // More module code
每个组件模块都是以这种方式设计的。模块通常会有一个控制器,其中存储着模块的导航(侧边栏链接)和模块标题。
// Module config/routes/constants .controller('AppsCtrl', ['$scope', function($scope) { // Set Title in parent $scope.$parent.title = 'Apps'; // Set module links $scope.links = [ { name: 'manage-apps', label: 'Manage', path: 'manage-apps' }, { name: 'create-app', label: 'Create', path: 'create-app' }, { name: 'import-app', label: 'Import', path: 'import-app' }, { name: 'app-groups', label: 'Groups', path: 'app-groups' } ]; // Set empty section options // additional logic if there are no apps present $scope.emptySectionOptions = { title: 'You have no Apps!', text: 'Click the button below to get started building your first application. You can always create new applications by clicking the tab located in the section menu to the left.', buttonText: 'Create An App!', viewLink: $scope.links[1] }; $scope.$on('$destroy', function (e) { }); }])
每个模块都有一个main.html
。在main.html
中,将有与模块功能相关的指令。有一个侧边栏导航指令,它接受links
数组并生成导航。一些ng-if语句渲染正确选定的视图。以下是dfApps
模块的main.html
文件。
<div> <div class="col-md-2 df-sidebar-nav"> <df-sidebar-nav></df-sidebar-nav> </div> <div class="col-md-10 df-section" df-fs-height > <df-manage-apps data-ng-if="activeView.path === 'manage-apps'"></df-manage-apps> <df-app-details data-ng-if="activeView.path === 'create-app'" data-new-app="true"></df-app-details> <df-import-app data-ng-if="activeView.path === 'import-app'"></df-import-app> <df-app-groups data-ng-if="activeView.path === 'app-groups'"></df-app-groups> </div> </div>
如果你对AngularJS有经验,你会看到大多数模块的工作都委托给了指令,而不是使用路由和控制器。虽然这不允许进行深度链接,但它提供了通过上下文处理数据的便利。大多数模块将具有管理和详情上下文,这些上下文被建模为指令,并且与模块文件夹相关的模板存储在本地。管理指令将从存储库中获取数据,基于这些数据创建一个对象(称为“管理对象”),并以表格、列表或缩略图的形式呈现它。通常与管理对象关联的行为(例如,管理应用程序对象允许您从其管理对象状态启动托管应用程序)。选择管理对象后,它将被传递给一个详情指令,该指令将为该类型创建一个对象以进行编辑或创建。当详情指令检测到数据时,管理上下文关闭,详情上下文显示。这通常是一个用于编辑当前选定对象的表单。对象可以保存、更新或关闭更改。基本的CRUD操作。一旦完成操作,详情上下文可以关闭,管理上下文将重新出现。我们将在管理上下文中保存删除操作,其中可以选中一个或多个对象进行删除。
所有的指令(上下文)都是以类似的方式工作的,即接收数据,然后创建一个包含该数据的对象。传递给指令的数据将始终封装在创建对象的“record”属性中。我们经常将其他UI特定属性附加到该对象上,这些属性可以在__dfUI
属性下找到。以下是一个示例,说明了详情上下文中App对象的样子。
{ __dfUI: { selected: false }, record: { // app data for editing }, recordCopy: { // copied app data for comparison } }
所有表单模型都与记录属性上存储的数据相关联。当我们保存/更新应用程序时,将创建一个新的App对象来替换旧的一个。然后你可以自由地关闭详情视图或继续编辑。如果你已经做了更改并尝试不保存就关闭,那么记录和recordCopy的比较将会失败,并提示你“你确定要关闭吗?”这就是它的基本工作方式。很简单。获取数据并创建对象。选择要编辑的对象。显示表单以编辑对象。在保存和关闭时进行比较。
上下文组织(我们如何设置指令)
每个指令都是以类似的方式组织的。请参见下面的示例。
.directive(DIRECTIVE_NAME, [function() { return { restrict: 'E', scope: { thingData: '=' }, templateUrl: CONSTANT_PATH + 'views/TEMPLATE.html', link: function (scope, elem, attrs) { // LOCAL FUNCTIONS: // things pertaining to this directive that won't be shared or stored on scope // Object constructors usually var Thing = function (thingData) { var thingModel = { thingName: 'My Awesome thing name' } thingData = thingData || thingModel return { __dfUI: { selected: false, hasError: false }, record: thingData, recordCopy: angular.copy(thingData); } } scope.theThing = null; // PUBLIC API // Scope functions that attach to our UI // we do preliminary checking here scope.saveThing = function () { if (scope.theThing.__dfUI.hasError) { alert('Thing has error'); return; } scope._saveThing() } // PRIVATE API // functions stored on/off scope that provide // targeted functionality scope._myPrivFuncOne = function () { // Do someting } scope._saveThingToServer = function () { // Save thing to server. Return promise } // COMPLEX IMPLEMENTATION // These scope functions generally are called from the public api // Their names usually correspond with a preceding underscore // We call private api functions targeted for specific tasks // and build our...COMPLEX IMPLEMENTATION of the public function. scope._saveThing = function () { // private func to do someting scope._myPrivFuncOne(); // save thing to server scope._saveThingToServer(scope.theThing).then( function (result) { scope.theThing = new Thing(result.data) }, function (reject) { //report error } ); } // WATCHERS // place any watchers here var watchThing = scope.$watch('thingData', function (newValue, oldValue) { scope.theThing = new Thing(newValue); } // MESSAGES // Handle messaging/events here scope.$on('$destroy', function (e) { watchThing(); } } }])