浏览代码

支持虚拟组织

组织分为:
1、实体
2、虚拟
MaxKey 2 年之前
父节点
当前提交
e01ff78c7a

+ 4 - 0
maxkey-core/src/main/java/org/maxkey/entity/Organizations.java

@@ -49,6 +49,10 @@ public class Organizations extends JpaBaseEntity implements Serializable {
     private String parentCode;
     @Column
     private String parentName;
+    /**
+     * 1. entity
+     * 2. virtual
+     */
     @Column
     private String type;
     @Column

+ 12 - 6
maxkey-web-frontend/maxkey-web-mgt-app/src/app/entity/TreeNodes.ts

@@ -1,19 +1,18 @@
 /*
  * Copyright [2022] [MaxKey of copyright http://www.maxkey.top]
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- 
 
 import { NzFormatEmitEvent, NzTreeNode, NzTreeNodeOptions } from 'ng-zorro-antd/tree';
 export class TreeNodes {
@@ -31,7 +30,13 @@ export class TreeNodes {
     }
 
     init(treeAttrs: any) {
-        this._rootNode = { title: treeAttrs.rootNode.title, key: treeAttrs.rootNode.key, expanded: true, isLeaf: false };
+        this._rootNode = {
+            title: treeAttrs.rootNode.title,
+            key: treeAttrs.rootNode.key,
+            type: treeAttrs.rootNode.attrs.type,
+            expanded: true,
+            isLeaf: false
+        };
         this.request = treeAttrs.nodes;
     }
 
@@ -43,9 +48,10 @@ export class TreeNodes {
         let treeNodes: any[] = [];
         for (let node of this.request) {
             if (node.key != rootNode.key && node.parentKey == rootNode.key) {
-                let treeNode = { title: node.title, key: node.key, expanded: false, isLeaf: true };
+                let treeNode = { title: node.title, key: node.key, type: node.attrs.type, expanded: false, isLeaf: true };
                 this.buildTree(treeNode);
                 treeNodes.push(treeNode);
+                console.log(treeNode);
                 rootNode.isLeaf = false;
             }
         }

+ 104 - 76
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/organizations/organization-editer/organization-editer.component.html

@@ -6,77 +6,87 @@
         <nz-form-item style="display: none">
           <nz-form-label [nzMd]="6" nzFor="id">{{ 'mxk.text.id' | i18n }}</nz-form-label>
           <nz-form-control [nzMd]="18" nzErrorTip="The input is not valid id!">
-            <input [(ngModel)]="form.model.id" disabled="{{ isEdit }}" [ngModelOptions]="{ standalone: true }" nz-input name="id" id="id" />
+            <input [(ngModel)]="form.model.id" disabled="{{ isEdit }}" [ngModelOptions]="{ standalone: true }" nz-input
+              name="id" id="id" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="code">{{ 'mxk.organizations.code' | i18n }}</nz-form-label>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="code">{{ 'mxk.organizations.code' | i18n }}
+          </nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid code!">
-            <input [(ngModel)]="form.model.code" [ngModelOptions]="{ standalone: true }" nz-input name="code" id="code" />
+            <input [(ngModel)]="form.model.code" [ngModelOptions]="{ standalone: true }" nz-input name="code"
+              id="code" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="name">{{ 'mxk.organizations.name' | i18n }}</nz-form-label>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="name">{{ 'mxk.organizations.name' | i18n }}
+          </nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid name!">
-            <input [(ngModel)]="form.model.name" [ngModelOptions]="{ standalone: true }" nz-input name="name" id="name" />
+            <input [(ngModel)]="form.model.name" [ngModelOptions]="{ standalone: true }" nz-input name="name"
+              id="name" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="fullName">{{ 'mxk.organizations.fullName' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid fullName!">
-            <input [(ngModel)]="form.model.fullName" [ngModelOptions]="{ standalone: true }" nz-input name="fullName" id="fullName" />
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="fullName">{{ 'mxk.organizations.fullName' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid fullName!">
+            <input [(ngModel)]="form.model.fullName" [ngModelOptions]="{ standalone: true }" nz-input name="fullName"
+              id="fullName" />
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="type">{{ 'mxk.organizations.type' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid type!">
+            <nz-select name="type" [(ngModel)]="form.model.type" [ngModelOptions]="{ standalone: true }">
+              <nz-option nzValue="entity" nzLabel="{{ 'mxk.organizations.type.entity' | i18n }}"></nz-option>
+              <nz-option nzValue="virtual" nzLabel="{{ 'mxk.organizations.type.virtual' | i18n }}"></nz-option>
+            </nz-select>
           </nz-form-control>
         </nz-form-item>
         <nz-form-item style="display: none">
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="parentId">{{ 'mxk.organizations.parentId' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid parentId!">
-            <input [(ngModel)]="form.model.parentId" [ngModelOptions]="{ standalone: true }" nz-input name="parentId" id="parentId" />
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="parentId">{{ 'mxk.organizations.parentId' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid parentId!">
+            <input [(ngModel)]="form.model.parentId" [ngModelOptions]="{ standalone: true }" nz-input name="parentId"
+              id="parentId" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item style="display: none">
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="parentCode">{{ 'mxk.organizations.parentCode' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid parentCode!">
-            <input
-              [(ngModel)]="form.model.parentCode"
-              readonly
-              [ngModelOptions]="{ standalone: true }"
-              nz-input
-              name="parentCode"
-              id="parentCode"
-            />
-          </nz-form-control>
-        </nz-form-item>
-        <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="parentName">{{ 'mxk.organizations.parentName' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid parentName!">
-            <input
-              [(ngModel)]="form.model.parentName"
-              readonly
-              [ngModelOptions]="{ standalone: true }"
-              nz-input
-              name="parentName"
-              id="parentName"
-            />
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="parentCode">{{ 'mxk.organizations.parentCode' | i18n
+            }}</nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid parentCode!">
+            <input [(ngModel)]="form.model.parentCode" readonly [ngModelOptions]="{ standalone: true }" nz-input
+              name="parentCode" id="parentCode" />
+          </nz-form-control>
+        </nz-form-item>
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="parentName">{{ 'mxk.organizations.parentName' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid parentName!">
+            <input [(ngModel)]="form.model.parentName" readonly [ngModelOptions]="{ standalone: true }" nz-input
+              name="parentName" id="parentName" />
           </nz-form-control>
         </nz-form-item>
 
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="sortIndex">{{ 'mxk.text.sortIndex' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid sortIndex!">
-            <input [(ngModel)]="form.model.sortIndex" [ngModelOptions]="{ standalone: true }" nz-input name="sortIndex" id="sortIndex" />
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid sortIndex!">
+            <input [(ngModel)]="form.model.sortIndex" [ngModelOptions]="{ standalone: true }" nz-input name="sortIndex"
+              id="sortIndex" />
           </nz-form-control>
         </nz-form-item>
 
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="status">{{ 'mxk.text.status' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="14" [nzXs]="24" nzErrorTip="The input is not valid status!">
-            <nz-switch
-              [(ngModel)]="form.model.switch_status"
-              [ngModelOptions]="{ standalone: true }"
-              name="status"
-              [nzCheckedChildren]="checkedTemplate"
-              [nzUnCheckedChildren]="unCheckedTemplate"
-            ></nz-switch>
+            <nz-switch [(ngModel)]="form.model.switch_status" [ngModelOptions]="{ standalone: true }" name="status"
+              [nzCheckedChildren]="checkedTemplate" [nzUnCheckedChildren]="unCheckedTemplate"></nz-switch>
             <ng-template #checkedTemplate><i nz-icon nzType="check"></i></ng-template>
             <ng-template #unCheckedTemplate><i nz-icon nzType="close"></i></ng-template>
           </nz-form-control>
@@ -84,33 +94,38 @@
       </nz-tab>
       <nz-tab nzTitle="{{ 'mxk.organizations.tab.extra' | i18n }}">
         <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="codePath">{{ 'mxk.organizations.codePath' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid codePath!">
-            <input [(ngModel)]="form.model.codePath" [ngModelOptions]="{ standalone: true }" nz-input name="codePath" id="codePath" />
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="codePath">{{ 'mxk.organizations.codePath' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid codePath!">
+            <input [(ngModel)]="form.model.codePath" [ngModelOptions]="{ standalone: true }" nz-input name="codePath"
+              id="codePath" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="namePath">{{ 'mxk.organizations.namePath' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid namePath!">
-            <input [(ngModel)]="form.model.namePath" [ngModelOptions]="{ standalone: true }" nz-input name="namePath" id="namePath" />
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="namePath">{{ 'mxk.organizations.namePath' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid namePath!">
+            <input [(ngModel)]="form.model.namePath" [ngModelOptions]="{ standalone: true }" nz-input name="namePath"
+              id="namePath" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="level">{{ 'mxk.organizations.level' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid level!">
-            <input [(ngModel)]="form.model.level" [ngModelOptions]="{ standalone: true }" nz-input name="level" id="level" />
-          </nz-form-control>
-        </nz-form-item>
-        <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="type">{{ 'mxk.organizations.type' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid type!">
-            <input [(ngModel)]="form.model.type" [ngModelOptions]="{ standalone: true }" nz-input name="type" id="type" />
+            <input [(ngModel)]="form.model.level" [ngModelOptions]="{ standalone: true }" nz-input name="level"
+              id="level" />
           </nz-form-control>
         </nz-form-item>
+
         <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="division">{{ 'mxk.organizations.division' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid division!">
-            <input [(ngModel)]="form.model.division" [ngModelOptions]="{ standalone: true }" nz-input name="division" id="division" />
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="division">{{ 'mxk.organizations.division' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid division!">
+            <input [(ngModel)]="form.model.division" [ngModelOptions]="{ standalone: true }" nz-input name="division"
+              id="division" />
           </nz-form-control>
         </nz-form-item>
       </nz-tab>
@@ -118,37 +133,38 @@
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="country">{{ 'mxk.organizations.country' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid country!">
-            <input [(ngModel)]="form.model.country" [ngModelOptions]="{ standalone: true }" nz-input name="country" id="country" />
+            <input [(ngModel)]="form.model.country" [ngModelOptions]="{ standalone: true }" nz-input name="country"
+              id="country" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="region">{{ 'mxk.organizations.region' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid region!">
-            <input [(ngModel)]="form.model.region" [ngModelOptions]="{ standalone: true }" nz-input name="region" id="region" />
+            <input [(ngModel)]="form.model.region" [ngModelOptions]="{ standalone: true }" nz-input name="region"
+              id="region" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="locality">{{ 'mxk.organizations.locality' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid locality!">
-            <input [(ngModel)]="form.model.locality" [ngModelOptions]="{ standalone: true }" nz-input name="locality" id="locality" />
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="locality">{{ 'mxk.organizations.locality' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid locality!">
+            <input [(ngModel)]="form.model.locality" [ngModelOptions]="{ standalone: true }" nz-input name="locality"
+              id="locality" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="street">{{ 'mxk.organizations.street' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid street!">
-            <input [(ngModel)]="form.model.street" [ngModelOptions]="{ standalone: true }" nz-input name="street" id="street" />
+            <input [(ngModel)]="form.model.street" [ngModelOptions]="{ standalone: true }" nz-input name="street"
+              id="street" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="address">{{ 'mxk.organizations.address' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid address!">
-            <input [(ngModel)]="form.model.address" [ngModelOptions]="{ standalone: true }" nz-input name="address" id="address" />
-          </nz-form-control>
-        </nz-form-item>
-        <nz-form-item>
-          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="postalCode">{{ 'mxk.organizations.postalCode' | i18n }}</nz-form-label>
-          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid postalCode!">
-            <input [(ngModel)]="form.model.postalCode" [ngModelOptions]="{ standalone: true }" nz-input name="postalCode" id="postalCode" />
+            <input [(ngModel)]="form.model.address" [ngModelOptions]="{ standalone: true }" nz-input name="address"
+              id="address" />
           </nz-form-control>
         </nz-form-item>
       </nz-tab>
@@ -156,19 +172,22 @@
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="contact">{{ 'mxk.organizations.contact' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid contact!">
-            <input [(ngModel)]="form.model.contact" [ngModelOptions]="{ standalone: true }" nz-input name="contact" id="contact" />
+            <input [(ngModel)]="form.model.contact" [ngModelOptions]="{ standalone: true }" nz-input name="contact"
+              id="contact" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="phone">{{ 'mxk.organizations.phone' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid phone!">
-            <input [(ngModel)]="form.model.phone" [ngModelOptions]="{ standalone: true }" nz-input name="phone" id="phone" />
+            <input [(ngModel)]="form.model.phone" [ngModelOptions]="{ standalone: true }" nz-input name="phone"
+              id="phone" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
           <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="email">{{ 'mxk.organizations.email' | i18n }}</nz-form-label>
           <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48" nzErrorTip="The input is not valid email!">
-            <input [(ngModel)]="form.model.email" [ngModelOptions]="{ standalone: true }" nz-input name="email" id="email" />
+            <input [(ngModel)]="form.model.email" [ngModelOptions]="{ standalone: true }" nz-input name="email"
+              id="email" />
           </nz-form-control>
         </nz-form-item>
         <nz-form-item>
@@ -177,6 +196,15 @@
             <input [(ngModel)]="form.model.fax" [ngModelOptions]="{ standalone: true }" nz-input name="fax" id="fax" />
           </nz-form-control>
         </nz-form-item>
+        <nz-form-item>
+          <nz-form-label [nzSm]="6" [nzXs]="24" nzFor="postalCode">{{ 'mxk.organizations.postalCode' | i18n }}
+          </nz-form-label>
+          <nz-form-control [nzSm]="18" [nzMd]="18" [nzXs]="36" [nzXl]="48"
+            nzErrorTip="The input is not valid postalCode!">
+            <input [(ngModel)]="form.model.postalCode" [ngModelOptions]="{ standalone: true }" nz-input
+              name="postalCode" id="postalCode" />
+          </nz-form-control>
+        </nz-form-item>
       </nz-tab>
     </nz-tabset>
   </form>
@@ -185,4 +213,4 @@
 <div *nzModalFooter>
   <button nz-button nzType="default" (click)="onClose($event)">{{ 'mxk.text.close' | i18n }}</button>
   <button nz-button nzType="primary" (click)="onSubmit($event)">{{ 'mxk.text.submit' | i18n }}</button>
-</div>
+</div>

+ 1 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/organizations/organization-editer/organization-editer.component.ts

@@ -72,6 +72,7 @@ export class OrganizationEditerComponent implements OnInit {
         this.cdr.detectChanges();
       });
     } else {
+      this.form.model.type = 'entity';
       if (this.parentNode) {
         this.form.model.parentId = this.parentNode?.key;
         this.form.model.parentName = this.parentNode?.title;

+ 6 - 2
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/organizations/organizations.component.html

@@ -38,11 +38,11 @@
         <span class="custom-node">
           <span *ngIf="!node.isLeaf" (contextmenu)="contextMenu($event, menu)">
             <i nz-icon [nzType]="node.isExpanded ? 'folder-open' : 'folder'" (click)="openFolder(node)"></i>
-            <span class="folder-name">{{ node.title }}</span>
+            <span class="folder-name">{{ node.title + (origin.type == 'virtual' ? '_v' : '') }}</span>
           </span>
           <span *ngIf="node.isLeaf" (contextmenu)="contextMenu($event, menu)">
             <i nz-icon nzType="file"></i>
-            <span class="file-name">{{ node.title }}</span>
+            <span class="file-name">{{ node.title + (origin.type == 'virtual' ? '_v' : '') }}</span>
           </span>
         </span>
       </ng-template>
@@ -64,6 +64,7 @@
               (nzCheckedChange)="onTableAllChecked($event)"></th>
             <th nzAlign="center">{{ 'mxk.organizations.code' | i18n }}</th>
             <th nzAlign="center">{{ 'mxk.organizations.name' | i18n }}</th>
+            <th nzAlign="center">{{ 'mxk.organizations.type' | i18n }}</th>
             <th nzAlign="center">{{ 'mxk.text.sortIndex' | i18n }}</th>
             <th nzAlign="center">{{ 'mxk.text.status' | i18n }}</th>
             <th nzAlign="center"><a>{{ 'mxk.text.action' | i18n }}</a></th>
@@ -77,6 +78,9 @@
               <span>{{ data.id }}</span>
             </td>
             <td nzAlign="left"> {{ data.name }}</td>
+            <td nzAlign="left">
+              {{ data.type == 'entity' ? ('mxk.organizations.type.entity' | i18n) : ('mxk.organizations.type.virtual' |
+              i18n) }}</td>
             <td nzAlign="center"> {{ data.sortIndex }}</td>
             <td nzAlign="center"> <i *ngIf="data.status == 1" nz-icon nzType="check-circle" nzTheme="fill"
                 style="color: green"></i></td>

+ 2 - 2
maxkey-web-frontend/maxkey-web-mgt-app/src/app/routes/users/users.component.html

@@ -43,11 +43,11 @@
         <span class="custom-node">
           <span *ngIf="!node.isLeaf" (contextmenu)="contextMenu($event, menu)">
             <i nz-icon [nzType]="node.isExpanded ? 'folder-open' : 'folder'" (click)="openFolder(node)"></i>
-            <span class="folder-name">{{ node.title }}</span>
+            <span class="folder-name">{{ node.title + (origin.type == 'virtual' ? '_v' : '') }}</span>
           </span>
           <span *ngIf="node.isLeaf" (contextmenu)="contextMenu($event, menu)">
             <i nz-icon nzType="file"></i>
-            <span class="file-name">{{ node.title }}</span>
+            <span class="file-name">{{ node.title + (origin.type == 'virtual' ? '_v' : '') }}</span>
           </span>
         </span>
       </ng-template>

+ 2 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/en-US.json

@@ -190,6 +190,8 @@
 			"parentCode": "Parent Code",
 			"parentName": "Parent Name",
 			"type": "Type",
+			"type.entity": "Entity",
+			"type.virtual": "Virtual",
 			"codePath": "Code Path",
 			"namePath": "NamePath",
 			"level": "Level",

+ 2 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-CN.json

@@ -191,6 +191,8 @@
 			"parentCode": "父级编码",
 			"parentName": "父级名称",
 			"type": "类型",
+			"type.entity": "实体",
+			"type.virtual": "虚拟",
 			"codePath": "编码路径",
 			"namePath": "名称路径",
 			"level": "级别",

+ 2 - 0
maxkey-web-frontend/maxkey-web-mgt-app/src/assets/i18n/zh-TW.json

@@ -192,6 +192,8 @@
 			"parentCode": "父級編碼",
 			"parentName": "父級名稱",
 			"type": "類型",
+			"type.entity": "實體",
+			"type.virtual": "虛擬",
 			"codePath": "編碼路徑",
 			"namePath": "名稱路徑",
 			"level": "級別",

+ 0 - 342
maxkey-webs/maxkey-web-maxkey/src/main/resources/application-https.properties

@@ -1,342 +0,0 @@
-############################################################################
-#  Copyright [2021] [MaxKey of copyright http://www.maxkey.top]
-#  
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#  
-#      http://www.apache.org/licenses/LICENSE-2.0
-#  
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
-############################################################################
-#spring.profiles.active=http                                               #
-############################################################################
-#server port
-server.port                                     =${SERVER_PORT:443}
-#session default 600
-#600s   =10m
-#1800s  =30m
-#3600s  =1h
-#28800s =8h
-server.servlet.session.timeout                  =${SERVLET_SESSION_TIMEOUT:600}
-#server context path
-server.servlet.context-path                     =/maxkey
-#nacos discovery
-spring.cloud.nacos.discovery.enabled            =${NACOS_DISCOVERY_ENABLED:false}
-spring.cloud.nacos.discovery.instance-enabled   =false
-spring.cloud.nacos.discovery.server-addr        =${NACOS_DISCOVERY_SERVER_ADDR:127.0.0.1:8848}
-############################################################################
-#domain name configuration                                                 #
-############################################################################
-maxkey.server.scheme                            =https
-maxkey.server.basedomain                        =${SERVER_DOMAIN:maxkey.top}
-maxkey.server.domain                            =sso.${maxkey.server.basedomain}
-maxkey.server.name                              =${maxkey.server.scheme}://${maxkey.server.domain}
-maxkey.server.uri                               =${maxkey.server.name}${server.servlet.context-path}
-#default.uri                
-maxkey.server.default.uri                       =${maxkey.server.uri}/appList
-maxkey.server.mgt.uri                           =${maxkey.server.name}:9527/maxkey-mgt/login
-maxkey.server.authz.uri                         =${maxkey.server.name}${server.servlet.context-path}
-#InMemory 0 , Redis 2               
-maxkey.server.persistence                       =${SERVER_PERSISTENCE:0}
-#identity none, Kafka ,RocketMQ
-maxkey.server.message.queue                     =${SERVER_MESSAGE_QUEUE:none}
-#issuer name                
-maxkey.app.issuer                               =CN=ConSec,CN=COM,CN=SH
-#must > jwt expire * 2    
-maxkey.session.timeout                          =${SERVER_SESSION_TIMEOUT:1800}
-
-maxkey.auth.jwt.issuer                          =${maxkey.server.uri}
-maxkey.auth.jwt.expires                         =600
-maxkey.auth.jwt.secret                          =7heM-14BtxjyKPuH3ITIm7q2-ps5MuBirWCsrrdbzzSAOuSPrbQYiaJ54AeA0uH2XdkYy3hHAkTFIsieGkyqxOJZ_dQzrCbaYISH9rhUZAKYx8tUY0wkE4ArOC6LqHDJarR6UIcMsARakK9U4dhoOPO1cj74XytemI-w6ACYfzRUn_Rn4e-CQMcnD1C56oNEukwalf06xVgXl41h6K8IBEzLVod58y_VfvFn-NGWpNG0fy_Qxng6dg8Dgva2DobvzMN2eejHGLGB-x809MvC4zbG7CKNVlcrzMYDt2Gt2sOVDrt2l9YqJNfgaLFjrOEVw5cuXemGkX1MvHj6TAsbLg
-maxkey.auth.jwt.refresh.secret                  =7heM-14BtxjyKPuH3ITIm7q2-ps5MuBirWCsrrdbzzSAOuSPrbQYiaJ54AeA0uH2XdkYy3hHAkTFIsieGkyqxOJZ_dQzrCbaYISH9rhUZAKYx8tUY0wkE4ArOC6LqHDJarR6UIcMsARakK9U4dhoOPO1cj74XytemI-w6ACYfzRUn_Rn4e-CQMcnD1C56oNEukwalf06xVgXl41h6K8IBEzLVod58y_VfvFn-NGWpNG0fy_Qxng6dg8Dgva2DobvzMN2eejHGLGB-x809MvC4zbG7CKNVlcrzMYDt2Gt2sOVDrt2l9YqJNfgaLFjrOEVw5cuXemGkX1MvHj6TAsbLg
-############################################################################
-#Login configuration                                                       #
-############################################################################
-#enable captcha
-maxkey.login.captcha                            =${LOGIN_CAPTCHA:false}
-#enable two factor,use one time password
-maxkey.login.mfa                                =${LOGIN_MFA_ENABLED:true}
-#TimeBasedOtpAuthn MailOtpAuthn SmsOtpAuthnYunxin SmsOtpAuthnAliyun SmsOtpAuthnTencentCloud
-maxkey.login.mfa.type                           =${LOGIN_MFA_TYPE:TimeBasedOtpAuthn}
-#enable social sign on          
-maxkey.login.socialsignon                       =${LOGIN_SOCIAL_ENABLED:true}
-#Enable kerberos/SPNEGO         
-maxkey.login.kerberos                           =false
-#wsFederation           
-maxkey.login.wsfederation                       =false
-#remeberme          
-maxkey.login.remeberme                          =${LOGIN_REMEBERME:true}
-#validity           
-maxkey.login.remeberme.validity                 =0
-#JWT support
-maxkey.login.jwt                                =${LOGIN_JWT:true}
-maxkey.login.jwt.issuer                         =${LOGIN_JWT_ISSUER:${maxkey.server.authz.uri}}
-#whitelist
-maxkey.ipaddress.whitelist                      =false
-#notices show
-maxkey.notices.visible                          =false
-
-############################################################################
-#ssl configuration                                                         #
-############################################################################
-server.ssl.key-store                            =${SSL_KEY_STORE:classpath:maxkeyserver.keystore}
-server.ssl.key-alias                            =${SSL_KEY_ALIAS:maxkey}
-server.ssl.enabled                              =${SSL_ENABLED:true}
-server.ssl.key-store-password                   =${SSL_KEY_PASSWORD:maxkey}
-server.ssl.key-store-type                       =${SSL_KEY_STORE_TYPE:JKS}
-
-############################################################################
-#database configuration 
-#   supported database
-#       mysql
-#       highgo
-#       postgresql
-############################################################################
-spring.datasource.type                          =com.alibaba.druid.pool.DruidDataSource
-#mysql
-spring.datasource.driver-class-name             =com.mysql.cj.jdbc.Driver
-spring.datasource.username                      =${DATABASE_USER:root}
-spring.datasource.password                      =${DATABASE_PWD:maxkey}
-spring.datasource.url                           =jdbc:mysql://${DATABASE_HOST:localhost}:${DATABASE_PORT:3306}/${DATABASE_NAME:maxkey}?autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
-#highgo
-#spring.datasource.driver-class-name=com.highgo.jdbc.Driver
-#spring.datasource.username=highgo
-#spring.datasource.password=High@123
-#spring.datasource.url=jdbc:highgo://192.168.56.107:5866/highgo?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
-#postgresql
-#spring.datasource.driver-class-name=org.postgresql.Driver
-#spring.datasource.username=root
-#spring.datasource.password=maxkey!
-#spring.datasource.url=jdbc:postgresql://localhost/maxkey?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
-#mybatis
-mybatis.dialect                                 =mysql
-mybatis.type-aliases-package                    =org.maxkey.entity,org.maxkey.entity.apps,
-mybatis.mapper-locations                        =classpath*:/org/maxkey/persistence/mapper/xml/${mybatis.dialect}/*.xml
-mybatis.table-column-snowflake-datacenter-id    =1
-mybatis.table-column-snowflake-machine-id       =1
-mybatis.table-column-escape                     =false
-mybatis.table-column-case                       =lowercase
-
-############################################################################
-#redis server  configuration                                               #
-############################################################################
-spring.redis.host                               =${REDIS_HOST:127.0.0.1}
-spring.redis.port                               =${REDIS_PORT:6379}
-spring.redis.password                           =${REDIS_PWD:password}
-spring.redis.timeout                            =10000
-spring.redis.jedis.pool.max-wait                =1000
-spring.redis.jedis.pool.max-idle                =200
-spring.redis.lettuce.pool.max-active            =-1
-spring.redis.lettuce.pool.min-idle              =0
-
-############################################################################
-#mail configuration                                                        #
-############################################################################
-spring.mail.default-encoding                    =utf-8
-spring.mail.host                                =${MAIL_HOST:smtp.163.com}
-spring.mail.port                                =${MAIL_PORT:465}
-spring.mail.username                            =${MAIL_USER:maxkey@163.com}
-spring.mail.password                            =${MAIL_PWD:password}
-spring.mail.protocol                            =smtp
-spring.mail.properties.ssl                      =true
-spring.mail.properties.sender                   =${MAIL_SENDER:maxkey@163.com}
-spring.mail.properties.mailotp.message.subject  =MaxKey One Time PassWord
-spring.mail.properties.mailotp.message.template ={0} You Token is {1} , it validity in {2}  minutes.
-spring.mail.properties.mailotp.message.type     =html
-spring.mail.properties.mailotp.message.validity =300
-
-############################################################################
-#Spring Session for Cluster configuration                                  #
-############################################################################
-# Session store type.
-spring.session.store-type                       =none
-#spring.session.store-type=redis
-# Session timeout. If a duration suffix is not specified, seconds is used.
-#server.servlet.session.timeout=1800
-# Sessions flush mode.
-#spring.session.redis.flush-mode=on_save 
-# Namespace for keys used to store sessions.
-#spring.session.redis.namespace=spring:session 
-
-############################################################################
-#Kafka for connectors configuration                                        #
-############################################################################
-spring.kafka.bootstrap-servers                  =${KAFKA_SERVERS:localhost:9092}
-# retries   
-spring.kafka.producer.retries                   =0
-# acks  
-spring.kafka.producer.acks                      =1
-# batch-size    
-spring.kafka.producer.batch-size                =16384
-# linger.ms 
-spring.kafka.producer.properties.linger.ms      =0
-# buffer-memory 
-spring.kafka.producer.buffer-memory             =33554432
-# serializer    
-spring.kafka.producer.key-serializer            =org.apache.kafka.common.serialization.StringSerializer
-spring.kafka.producer.value-serializer          =org.apache.kafka.common.serialization.StringSerializer
-# partitioner
-#spring.kafka.producer.properties.partitioner.class=com.felix.kafka.producer.CustomizePartitioner
-############################################################################
-#RocketMQ for connectors configuration                                        #
-############################################################################
-rocketmq.name-server                            =${ROCKETMQ_SERVERS:localhost:9876}
-rocketmq.producer.enable                        =true
-rocketmq.producer.group                         =maxkey_identity
-############################################################################ 
-#Time-based One-Time Password configuration                                #
-############################################################################
-maxkey.otp.policy.type                          =totp
-maxkey.otp.policy.digits                        =6
-maxkey.otp.policy.issuer                        =${OTP_POLICY_ISSUER:MaxKey}
-maxkey.otp.policy.domain                        =${maxkey.server.domain}
-maxkey.otp.policy.period                        =30
-
-############################################################################ 
-#Kerberos Login configuration                                              #
-#short name of user domain must be in upper case,eg:MAXKEY                 #
-############################################################################
-maxkey.login.kerberos.default.userdomain      =MAXKEY
-#short name of user domain must be in upper case,eg:MAXKEY.ORG
-maxkey.login.kerberos.default.fulluserdomain  =MAXKEY.ORG
-#last 8Bit crypto for Kerberos web Authentication 
-maxkey.login.kerberos.default.crypto          =846KZSzYq56M6d5o
-#Kerberos Authentication server RUL
-maxkey.login.kerberos.default.redirecturi     =http://sso.maxkey.top/kerberos/authn/
-
-############################################################################ 
-#HTTPHEADER Login configuration                                            #
-############################################################################
-maxkey.login.httpheader.enable                =false
-maxkey.login.httpheader.headername            =header-user
-# iv-user is for IBM Security Access Manager
-#config.httpheader.headername=iv-user
-
-############################################################################ 
-#BASIC Login support configuration                                         #
-############################################################################
-maxkey.login.basic.enable                     =false
-
-#############################################################################
-#WsFederation Login support configuration
-#identifier: the identifer for the ADFS server
-#url: the login url for ADFS
-#principal: the name of the attribute/assertion returned by ADFS that contains the principal's username.
-#relyingParty: the identifier of the CAS Server as it has been configured in ADFS.
-#tolerance: (optional) the amount of drift to allow when validating the timestamp on the token. Default: 10000 (ms)
-#attributeMutator: (optional) a class (defined by you) that can modify the attributes/assertions returned by the ADFS server
-#signingCertificate: ADFS's signing certificate used to validate the token/assertions issued by ADFS.
-############################################################################
-maxkey.login.wsfederation.identifier          =http://adfs.maxkey.top/adfs/services/trust
-maxkey.login.wsfederation.url                 =https://adfs.maxkey.top/adfs/ls/
-maxkey.login.wsfederation.principal           =upn
-maxkey.login.wsfederation.relyingParty        =urn:federation:connsec
-maxkey.login.wsfederation.signingCertificate  =adfs-signing.crt
-maxkey.login.wsfederation.tolerance           =10000
-maxkey.login.wsfederation.upn.suffix          =maxkey.org
-maxkey.login.wsfederation.logoutUrl           =https://adfs.maxkey.top/adfs/ls/?wa=wsignout1.0
-
-#############################################################################
-#OIDC V1.0 METADATA configuration                                           #
-#############################################################################
-maxkey.oidc.metadata.issuer                     =${maxkey.server.authz.uri}
-maxkey.oidc.metadata.authorizationEndpoint      =${maxkey.server.authz.uri}/authz/oauth/v20/authorize
-maxkey.oidc.metadata.tokenEndpoint              =${maxkey.server.authz.uri}/authz/oauth/v20/token
-maxkey.oidc.metadata.userinfoEndpoint           =${maxkey.server.authz.uri}/api/connect/userinfo
-
-#############################################################################
-#SAML V2.0 configuration                                                    #
-#############################################################################
-#saml common
-maxkey.saml.v20.max.parser.pool.size                            =2
-maxkey.saml.v20.assertion.validity.time.ins.seconds             =90
-maxkey.saml.v20.replay.cache.life.in.millis                     =14400000
-maxkey.saml.v20.issue.instant.check.clock.skew.in.seconds       =90
-maxkey.saml.v20.issue.instant.check.validity.time.in.seconds    =300
-#saml Identity Provider keystore
-maxkey.saml.v20.idp.keystore.password                           =maxkey
-maxkey.saml.v20.idp.keystore.private.key.password               =maxkey
-maxkey.saml.v20.idp.keystore                                    =classpath\:config/samlServerKeystore.jks
-#keystore Identity Provider for security
-maxkey.saml.v20.idp.issuing.entity.id                           =maxkey.top
-maxkey.saml.v20.idp.issuer                                      =${maxkey.server.authz.uri}/saml
-maxkey.saml.v20.idp.receiver.endpoint                           =https\://sso.maxkey.top/
-#Saml v20 Identity Provider METADATA
-maxkey.saml.v20.metadata.orgName                =MaxKeyTop
-maxkey.saml.v20.metadata.orgDisplayName         =MaxKeyTop
-maxkey.saml.v20.metadata.orgURL                 =https://www.maxkey.top
-maxkey.saml.v20.metadata.contactType            =technical
-maxkey.saml.v20.metadata.company                =MaxKeyTop
-maxkey.saml.v20.metadata.givenName              =maxkey
-maxkey.saml.v20.metadata.surName                =maxkey
-maxkey.saml.v20.metadata.emailAddress           =maxkeysupport@163.com
-maxkey.saml.v20.metadata.telephoneNumber        =4008981111
-
-#saml RelayParty keystore
-maxkey.saml.v20.sp.keystore.password                            =maxkey
-maxkey.saml.v20.sp.keystore.private.key.password                =maxkey
-maxkey.saml.v20.sp.keystore                                     =classpath\:config/samlClientKeystore.jks
-maxkey.saml.v20.sp.issuing.entity.id                            =client.maxkey.org
-
-############################################################################
-#Management endpoints configuration                                        #
-############################################################################
-management.security.enabled                     =false
-#management.endpoints.jmx.exposure.include=health,info
-#management.endpoints.web.exposure.include=metrics,health,info,env,prometheus
-management.endpoints.web.exposure.include       =*
-management.endpoint.health.show-details         =ALWAYS
-#Spring Boot Admin Client
-spring.boot.admin.client.url                    =${SPRING_BOOT_ADMIN_URL:http://127.0.0.1:9528}
-management.health.redis.enabled                 =false
-management.health.mail.enabled                  =false
-
-############################################################################
-#Do not modify the following configuration
-############################################################################
-#springfox.documentation.swagger.v2.path=/api-docs                         #
-#Swagger Configure Properties                                              #
-############################################################################
-maxkey.swagger.enable                           =true
-maxkey.swagger.title                            =MaxKey\u5355\u70b9\u767b\u5f55\u8ba4\u8bc1\u7cfb\u7edfAPI\u6587\u6863
-maxkey.swagger.description                      =MaxKey\u5355\u70b9\u767b\u5f55\u8ba4\u8bc1\u7cfb\u7edfAPI\u6587\u6863
-maxkey.swagger.version                          =${application.formatted-version}
-springdoc.packagesToScan                        =org.maxkey
-############################################################################
-#freemarker configuration                                                  #
-############################################################################
-spring.freemarker.template-loader-path          =classpath:/templates/views
-spring.freemarker.cache                         =false
-spring.freemarker.charset                       =UTF-8
-spring.freemarker.check-template-location       =true
-spring.freemarker.content-type                  =text/html
-spring.freemarker.expose-request-attributes     =false
-spring.freemarker.expose-session-attributes     =false
-spring.freemarker.request-context-attribute     =request
-spring.freemarker.suffix                        =.ftl
-
-############################################################################
-#static resources configuration                                            #
-############################################################################
-spring.mvc.static-path-pattern                  =/static/**
-spring.messages.basename                        =classpath:messages/message
-spring.messages.encoding                        =UTF-8
-
-############################################################################
-#server servlet encoding configuration                                     #
-############################################################################
-#encoding
-#server.servlet.encoding.charset=UTF-8
-#server.servlet.encoding.enabled=true
-#server.servlet.encoding.force=true
-
-############################################################################
-#Servlet multipart configuration                                           #
-############################################################################
-spring.servlet.multipart.enabled                =true
-spring.servlet.multipart.max-file-size          =4194304