dreamfactory/df-admin-app

DreamFactory™ 管理控制台

安装数量: 41,308

依赖项: 0

建议者: 0

安全性: 0

星星: 13

关注者: 17

分支: 21

开放问题: 24

语言:JavaScript

类型:dreamfactory-app

5.0.3 2023-11-17 11:15 UTC

This package is auto-updated.

Last update: 2024-09-02 22:52:12 UTC


README

适用于 DreamFactory v2.0 及以上版本的 AngularJS 管理应用程序。

使用此管理应用程序,您可以从任何地方管理 DreamFactory 实例。使用 Bootswatch 主题进行自定义或使用 SCSS/SASS 自定义。使用 Node.js、Grunt 以及包含的 grunt 脚本合并、压缩和丑化组件模块,以创建一个可部署的应用程序。

安装应用程序

克隆存储库。导航到您克隆存储库的顶级目录,并输入 bower install注意:您必须已安装 Node、Grunt 和 GruntCLI。

使用 Node 和 Grunt 构建应用程序

应用程序预装了 grunt 文件,该文件将源文件合并、压缩、丑化、压缩并重新组织为更“传输友好”的方式。它减少了加载时间,并自动清除客户端缓存,以便下次客户端使用时可以看到您的更改,无需手动清除缓存。此过程将创建一个名为 dist 的文件夹,其中包含处理后的应用程序。从现在开始,短语“构建应用程序”是指此过程。要在命令行中运行构建过程,请在应用程序的顶级目录中输入 grunt build注意:您必须已安装 Node、Grunt、GruntCLI 和 Bower。

以下是构建 dist 版本的方法。

一次性设置

install node and npm (downloadable installer)
sudo npm install -g bower
sudo npm install -g grunt-cli
cd ~/repos/df-admin-app (or wherever your repo is)
npm install
bower install (creates sym link app/bower_components)

应用程序使用 Sass/Scss 构建,并使用 Compass 编译。这需要 Ruby 和 Compass。按照此 指南 进行设置。

然后重建 dist 文件夹

grunt build

或者您可以直接使用 Docker 运行开发模式

docker-compose up

使用 Docker 构建

docker-compose run --rm web-dev bash -c "grunt build --force && chmod -R a+rwX ."

最终编译的 CSS 将写入 app/styles/styles.css

构建发布版本

这将推送到 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 2.12.2

app/scripts/app.js 中增加应用程序版本。

// Set application version number
.constant('APP_VERSION', '2.12.2')
grunt build
git add --all
git commit -m "Release 2.12.2"
git flow release finish 2.12.2
git push origin develop
git checkout master
git push origin master
git push --tags

从任何地方管理您的 DreamFactory 实例

该应用程序可以配置为从另一台远程服务器管理 DreamFactory 实例。只需打开 app/scripts 目录中包含的 app.js 文件,并将您的 DreamFactory 实例主机名添加到顶部的 INSTANCE_BASE_URL 常量中。现在您可以可选地构建应用程序并将 dist 目录部署。您必须在部署应用程序的 DreamFactory 实例中启用 CORS。

主题应用程序

app/styles/sass/partials 中,您可以找到应用程序所有自定义部分的样式表以及 themes 目录中的几个 Bootswatch 模板。所有这些都在 styles.scss 中按特定顺序添加。要更改到不同的 Bootswatch 主题,只需更改 styles.scss 中所有主题名称的出现即可。别忘了运行 grunt build 来编译样式表并构建应用程序。

应用程序架构

该应用程序被设计成具有可插拔的模块。每个模块都包含自己的路由、事件和逻辑,以便移除任何一个模块都不会停止应用程序的工作。这些模块存储在 app/admin_components 之下。为了提高速度,设计了一个模块作为应用程序中频繁使用的数据的中央存储库。许多其他模块依赖于这个模块来获取数据以执行其任务,但经过一点重构,它可以被移除以产生真正独立的模块。

主应用程序

主应用程序文件位于两个目录中。位于 app 目录下的 scriptsviewsscripts 目录包含您的 app.js 文件和一个名为 controllers 的子目录,其中包含 main.js。在上述 views 目录中可以找到 main.js 中定义的控制器对应的视图。app.js 文件包含一些常量。值得注意的是 INSTANCE_BASE_URLINSTANCE_API_PREFIXAPP_API_KEYINSTANCE_BASE_URL 允许设置一个主机,应用程序及其模块将引用该主机进行 API 调用。INSTANCE_API_PREFIX 可以更改以匹配服务器设置。APP_API_KEY 用于以下定义的配置选项中,该选项设置应用程序发出的所有调用的 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: {
                        checkUser:['checkUserService', function (checkUserService) {
                            return checkUserService.checkUser();
                        }]
                    }
                });
        }])

    .run([function () {
            
    }])
    
    // 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>
    </div>
</div>

如果您熟悉AngularJS,您会注意到大多数模块的工作是通过指令委派,而不是使用路由和控制器来完成的。虽然这不允许进行深度链接,但它确实提供了通过上下文处理数据的便利性。大多数模块将具有管理和详细上下文,这些上下文被建模为指令,并在与模块文件夹相关的本地存储模板。管理指令将从存储库中提取数据,基于该数据创建一个对象(称为“管理对象”),并以表格形式呈现。通常与“管理对象”关联的行为(例如,管理应用程序对象允许您从其管理对象状态启动托管应用程序)。选择管理对象后,它将被传递给详细指令,该指令将为该类型创建一个对象以进行编辑或创建。当详细指令检测到数据时,管理上下文关闭,详细上下文显示。这通常是一个用于编辑当前选中对象的表单。可以保存、更新或关闭对象,更改将被丢弃。基本的CRUD操作。一旦完成操作,详细上下文可以关闭,管理上下文将再次出现。我们将删除操作保留在管理上下文中,其中可以选择一个或多个对象进行删除。

所有指令(上下文)都以类似的方式工作,即首先将数据传递给它们,然后创建一个包含该数据的对象。传递给指令的数据将始终封装在创建的对象的“record”属性中。我们经常将其他与UI相关的属性附加到该对象上,这些属性可以在__dfUI属性下找到。以下是详细上下文中App对象的一个示例。

{
    __dfUI: {
        selected: false  
    },
    record: {
        // app data for editing
    },
    recordCopy: {
        // copied app data for comparison
    }
}

所有表单模型都与记录属性上存储的数据相关联。当我们保存/更新应用程序时,将创建一个新的App对象来替换旧的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();
        };
        
        
    }
}])