mirror of https://gitee.com/maxjhandsome/pig
🔖 Releasing / Version tags. 2.7.2
This commit is contained in:
parent
2057ce35ee
commit
b5dcbc68e7
|
@ -1,34 +1,32 @@
|
|||
DROP DATABASE IF EXISTS `pig_config`;
|
||||
|
||||
CREATE DATABASE `pig_config` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
|
||||
USE pig_config;
|
||||
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
USE `pig_config`;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for config_info
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `config_info`;
|
||||
CREATE TABLE `config_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`data_id` varchar(255) DEFAULT NULL,
|
||||
`group_id` varchar(255) DEFAULT NULL,
|
||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||
`md5` varchar(32) DEFAULT NULL,
|
||||
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||
`group_id` varchar(255) COLLATE utf8_bin DEFAULT NULL,
|
||||
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
|
||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||
`src_user` mediumtext,
|
||||
`src_ip` varchar(20) DEFAULT NULL,
|
||||
`app_name` varchar(128) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
`c_desc` varchar(256) DEFAULT NULL,
|
||||
`c_use` varchar(64) DEFAULT NULL,
|
||||
`effect` varchar(64) DEFAULT NULL,
|
||||
`type` varchar(64) DEFAULT NULL,
|
||||
`c_schema` mediumtext,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COMMENT='config_info';
|
||||
`src_user` text COLLATE utf8_bin COMMENT 'source user',
|
||||
`src_ip` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
|
||||
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL,
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
|
||||
`c_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL,
|
||||
`c_use` varchar(64) COLLATE utf8_bin DEFAULT NULL,
|
||||
`effect` varchar(64) COLLATE utf8_bin DEFAULT NULL,
|
||||
`type` varchar(64) COLLATE utf8_bin DEFAULT NULL,
|
||||
`c_schema` text COLLATE utf8_bin,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of config_info
|
||||
|
@ -48,16 +46,16 @@ COMMIT;
|
|||
DROP TABLE IF EXISTS `config_info_aggr`;
|
||||
CREATE TABLE `config_info_aggr` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`data_id` varchar(255) DEFAULT NULL,
|
||||
`group_id` varchar(128) DEFAULT NULL,
|
||||
`datum_id` varchar(255) DEFAULT NULL,
|
||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT '内容',
|
||||
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||
`group_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||
`datum_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'datum_id',
|
||||
`content` longtext COLLATE utf8_bin NOT NULL COMMENT '内容',
|
||||
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
|
||||
`app_name` varchar(128) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='增加租户字段';
|
||||
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL,
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for config_info_beta
|
||||
|
@ -65,20 +63,20 @@ CREATE TABLE `config_info_aggr` (
|
|||
DROP TABLE IF EXISTS `config_info_beta`;
|
||||
CREATE TABLE `config_info_beta` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`data_id` varchar(255) DEFAULT NULL,
|
||||
`group_id` varchar(128) DEFAULT NULL,
|
||||
`app_name` varchar(128) DEFAULT NULL,
|
||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||
`beta_ips` varchar(1024) DEFAULT NULL,
|
||||
`md5` varchar(32) DEFAULT NULL,
|
||||
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
|
||||
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||
`beta_ips` varchar(1024) COLLATE utf8_bin DEFAULT NULL COMMENT 'betaIps',
|
||||
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
|
||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||
`src_user` mediumtext,
|
||||
`src_ip` varchar(20) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='config_info_beta';
|
||||
`src_user` text COLLATE utf8_bin COMMENT 'source user',
|
||||
`src_ip` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for config_info_tag
|
||||
|
@ -86,20 +84,20 @@ CREATE TABLE `config_info_beta` (
|
|||
DROP TABLE IF EXISTS `config_info_tag`;
|
||||
CREATE TABLE `config_info_tag` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`data_id` varchar(255) DEFAULT NULL,
|
||||
`group_id` varchar(128) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
`tag_id` varchar(128) DEFAULT NULL,
|
||||
`app_name` varchar(128) DEFAULT NULL,
|
||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||
`md5` varchar(32) DEFAULT NULL,
|
||||
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
|
||||
`tag_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_id',
|
||||
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
|
||||
`content` longtext COLLATE utf8_bin NOT NULL COMMENT 'content',
|
||||
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'md5',
|
||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||
`src_user` mediumtext,
|
||||
`src_ip` varchar(20) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='config_info_tag';
|
||||
`src_user` text COLLATE utf8_bin COMMENT 'source user',
|
||||
`src_ip` varchar(20) COLLATE utf8_bin DEFAULT NULL COMMENT 'source ip',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for config_tags_relation
|
||||
|
@ -107,16 +105,16 @@ CREATE TABLE `config_info_tag` (
|
|||
DROP TABLE IF EXISTS `config_tags_relation`;
|
||||
CREATE TABLE `config_tags_relation` (
|
||||
`id` bigint(20) NOT NULL COMMENT 'id',
|
||||
`tag_name` varchar(128) DEFAULT NULL,
|
||||
`tag_type` varchar(64) DEFAULT NULL,
|
||||
`data_id` varchar(255) DEFAULT NULL,
|
||||
`group_id` varchar(128) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
`tag_name` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'tag_name',
|
||||
`tag_type` varchar(64) COLLATE utf8_bin DEFAULT NULL COMMENT 'tag_type',
|
||||
`data_id` varchar(255) COLLATE utf8_bin NOT NULL COMMENT 'data_id',
|
||||
`group_id` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'group_id',
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
|
||||
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
|
||||
PRIMARY KEY (`nid`) USING BTREE,
|
||||
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='config_tag_relation';
|
||||
PRIMARY KEY (`nid`),
|
||||
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for group_capacity
|
||||
|
@ -124,7 +122,7 @@ CREATE TABLE `config_tags_relation` (
|
|||
DROP TABLE IF EXISTS `group_capacity`;
|
||||
CREATE TABLE `group_capacity` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`group_id` varchar(128) DEFAULT NULL,
|
||||
`group_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
|
||||
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
|
||||
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
|
||||
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
||||
|
@ -133,9 +131,9 @@ CREATE TABLE `group_capacity` (
|
|||
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
|
||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_group_id` (`group_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='集群、各Group容量信息表';
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_group_id` (`group_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for his_config_info
|
||||
|
@ -144,39 +142,58 @@ DROP TABLE IF EXISTS `his_config_info`;
|
|||
CREATE TABLE `his_config_info` (
|
||||
`id` bigint(64) unsigned NOT NULL,
|
||||
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`data_id` varchar(255) DEFAULT NULL,
|
||||
`group_id` varchar(128) DEFAULT NULL,
|
||||
`app_name` varchar(128) DEFAULT NULL,
|
||||
`content` longtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
||||
`md5` varchar(32) DEFAULT NULL,
|
||||
`data_id` varchar(255) COLLATE utf8_bin NOT NULL,
|
||||
`group_id` varchar(128) COLLATE utf8_bin NOT NULL,
|
||||
`app_name` varchar(128) COLLATE utf8_bin DEFAULT NULL COMMENT 'app_name',
|
||||
`content` longtext COLLATE utf8_bin NOT NULL,
|
||||
`md5` varchar(32) COLLATE utf8_bin DEFAULT NULL,
|
||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00',
|
||||
`src_user` mediumtext,
|
||||
`src_ip` varchar(20) DEFAULT NULL,
|
||||
`op_type` char(10) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
PRIMARY KEY (`nid`) USING BTREE,
|
||||
KEY `idx_gmt_create` (`gmt_create`) USING BTREE,
|
||||
KEY `idx_gmt_modified` (`gmt_modified`) USING BTREE,
|
||||
KEY `idx_did` (`data_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='多租户改造';
|
||||
`src_user` text COLLATE utf8_bin,
|
||||
`src_ip` varchar(20) COLLATE utf8_bin DEFAULT NULL,
|
||||
`op_type` char(10) COLLATE utf8_bin DEFAULT NULL,
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT '租户字段',
|
||||
PRIMARY KEY (`nid`),
|
||||
KEY `idx_gmt_create` (`gmt_create`),
|
||||
KEY `idx_gmt_modified` (`gmt_modified`),
|
||||
KEY `idx_did` (`data_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for permissions
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `permissions`;
|
||||
CREATE TABLE `permissions` (
|
||||
`role` varchar(50) NOT NULL,
|
||||
`resource` varchar(512) NOT NULL,
|
||||
`action` varchar(8) NOT NULL,
|
||||
UNIQUE KEY `uk_role_permission` (`role`,`resource`,`action`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for roles
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `roles`;
|
||||
CREATE TABLE `roles` (
|
||||
`username` varchar(50) DEFAULT NULL,
|
||||
`role` varchar(50) DEFAULT NULL
|
||||
`username` varchar(50) NOT NULL,
|
||||
`role` varchar(50) NOT NULL,
|
||||
UNIQUE KEY `uk_username_role` (`username`,`role`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of roles
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `roles` VALUES ('nacos', 'ROLE_ADMIN');
|
||||
COMMIT;
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for tenant_capacity
|
||||
-- ----------------------------
|
||||
DROP TABLE IF EXISTS `tenant_capacity`;
|
||||
CREATE TABLE `tenant_capacity` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'Tenant ID',
|
||||
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
|
||||
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
|
||||
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
|
||||
|
@ -185,9 +202,9 @@ CREATE TABLE `tenant_capacity` (
|
|||
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
|
||||
`gmt_create` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '创建时间',
|
||||
`gmt_modified` datetime NOT NULL DEFAULT '2010-05-05 00:00:00' COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_tenant_id` (`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户容量信息表';
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for tenant_info
|
||||
|
@ -195,17 +212,17 @@ CREATE TABLE `tenant_capacity` (
|
|||
DROP TABLE IF EXISTS `tenant_info`;
|
||||
CREATE TABLE `tenant_info` (
|
||||
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`kp` varchar(128) DEFAULT NULL,
|
||||
`tenant_id` varchar(128) DEFAULT NULL,
|
||||
`tenant_name` varchar(128) DEFAULT NULL,
|
||||
`tenant_desc` varchar(256) DEFAULT NULL,
|
||||
`create_source` varchar(32) DEFAULT NULL,
|
||||
`kp` varchar(128) COLLATE utf8_bin NOT NULL COMMENT 'kp',
|
||||
`tenant_id` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_id',
|
||||
`tenant_name` varchar(128) COLLATE utf8_bin DEFAULT '' COMMENT 'tenant_name',
|
||||
`tenant_desc` varchar(256) COLLATE utf8_bin DEFAULT NULL COMMENT 'tenant_desc',
|
||||
`create_source` varchar(32) COLLATE utf8_bin DEFAULT NULL COMMENT 'create_source',
|
||||
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
|
||||
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`) USING BTREE,
|
||||
KEY `idx_tenant_id` (`tenant_id`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='tenant_info';
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
|
||||
KEY `idx_tenant_id` (`tenant_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
|
||||
|
||||
-- ----------------------------
|
||||
-- Table structure for users
|
||||
|
@ -213,16 +230,16 @@ CREATE TABLE `tenant_info` (
|
|||
DROP TABLE IF EXISTS `users`;
|
||||
CREATE TABLE `users` (
|
||||
`username` varchar(50) NOT NULL,
|
||||
`password` varchar(500) DEFAULT NULL,
|
||||
`password` varchar(500) NOT NULL,
|
||||
`enabled` tinyint(1) NOT NULL,
|
||||
PRIMARY KEY (`username`) USING BTREE
|
||||
PRIMARY KEY (`username`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ----------------------------
|
||||
-- Records of users
|
||||
-- ----------------------------
|
||||
BEGIN;
|
||||
INSERT INTO `users` VALUES ('nacos', '$2a$10$1fXDf9q5CKAA.Fe4rjTzzONGDI4cXFvMfPx9Yribr9OQC2.JDe/wK', 1);
|
||||
INSERT INTO `users` VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', 1);
|
||||
COMMIT;
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-auth</artifactId>
|
||||
|
@ -46,13 +46,13 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-upms-api</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--security-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-security</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--JDBC相关-->
|
||||
<dependency>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-codegen</artifactId>
|
||||
|
@ -37,7 +37,7 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-swagger</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--注册中心客户端-->
|
||||
<dependency>
|
||||
|
@ -53,30 +53,30 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-datasource</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-mybatis</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--common-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-core</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--日志处理-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-log</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--安全模块-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-security</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--代码生成模板引擎-->
|
||||
<dependency>
|
||||
|
|
|
@ -118,6 +118,7 @@
|
|||
},
|
||||
searchChange(form, done) {
|
||||
this.searchForm = form
|
||||
this.page.currentPage = 1
|
||||
this.getList(this.page, form)
|
||||
done()
|
||||
},
|
||||
|
@ -126,4 +127,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-common-core</artifactId>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<artifactId>pig-common</artifactId>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-common-log</artifactId>
|
||||
|
@ -37,13 +37,13 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-core</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--UPMS接口模块-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-upms-api</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--安全依赖获取上下文信息-->
|
||||
<dependency>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-common-mybatis</artifactId>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-common-security</artifactId>
|
||||
|
@ -37,7 +37,7 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-core</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--安全模块-->
|
||||
<dependency>
|
||||
|
@ -52,7 +52,7 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-upms-api</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-common-swagger</artifactId>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-common</artifactId>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-gateway</artifactId>
|
||||
|
@ -61,7 +61,7 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-core</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--接口文档-->
|
||||
<dependency>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-monitor</artifactId>
|
||||
|
|
|
@ -1,22 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ /*
|
||||
~ * Copyright (c) 2019-2020, 冷冷 (wangiegie@gmail.com).
|
||||
~ * <p>
|
||||
~ * Licensed under the GNU Lesser General Public License 3.0 (the "License");
|
||||
~ * you may not use this file except in compliance with the License.
|
||||
~ * You may obtain a copy of the License at
|
||||
~ * <p>
|
||||
~ * https://www.gnu.org/licenses/lgpl.html
|
||||
~ * <p>
|
||||
~ * 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.
|
||||
~ */
|
||||
-->
|
||||
|
||||
Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
@ -24,26 +18,37 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-register</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>pig-register</name>
|
||||
<description>nacos 注册配置中心</description>
|
||||
<description>nacos 注册配置中</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud.nacos</groupId>
|
||||
<artifactId>nacos-config</artifactId>
|
||||
<version>1.1.4</version>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat.embed</groupId>
|
||||
<artifactId>tomcat-embed-jasper</artifactId>
|
||||
<version>7.0.59</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud.nacos</groupId>
|
||||
<artifactId>nacos-naming</artifactId>
|
||||
<version>1.1.4</version>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
<!-- log -->
|
||||
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud.nacos</groupId>
|
||||
<artifactId>nacos-istio</artifactId>
|
||||
<version>1.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
|
@ -65,10 +70,6 @@
|
|||
<version>0.10.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -32,9 +32,8 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
|||
public class PigNacosApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty(ConfigConstants.TOMCAT_DIR, "logs");
|
||||
System.setProperty(ConfigConstants.TOMCAT_ACCESS_LOG, "false");
|
||||
System.setProperty(ConfigConstants.STANDALONE_MODE, "true");
|
||||
System.setProperty(ConfigConstants.AUTH_ENABLED, "false");
|
||||
SpringApplication.run(PigNacosApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,7 @@ public interface ConfigConstants {
|
|||
String STANDALONE_MODE = "nacos.standalone";
|
||||
|
||||
/**
|
||||
* tomcat 目录
|
||||
* 是否开启认证
|
||||
*/
|
||||
String TOMCAT_DIR = "server.tomcat.basedir";
|
||||
|
||||
/**
|
||||
* tomcat 日志配置
|
||||
*/
|
||||
String TOMCAT_ACCESS_LOG = "server.tomcat.accesslog.enabled";
|
||||
String AUTH_ENABLED = "nacos.core.auth.enabled";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.config;
|
||||
|
||||
|
||||
import com.alibaba.nacos.core.code.ControllerMethodsCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
/**
|
||||
* @author yshen
|
||||
* @author nkorange
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Component
|
||||
@EnableScheduling
|
||||
@PropertySource("/application.properties")
|
||||
public class ConsoleConfig {
|
||||
|
||||
@Autowired
|
||||
private ControllerMethodsCache methodsCache;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers");
|
||||
methodsCache.initClassMethod("com.alibaba.nacos.console.controller");
|
||||
methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
config.addAllowedOrigin("*");
|
||||
config.addAllowedHeader("*");
|
||||
config.setMaxAge(18000L);
|
||||
config.addAllowedMethod("*");
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.controller;
|
||||
|
||||
import com.alibaba.nacos.config.server.model.RestResult;
|
||||
import com.alibaba.nacos.console.config.WebSecurityConfig;
|
||||
import com.alibaba.nacos.console.security.CustomUserDetailsServiceImpl;
|
||||
import com.alibaba.nacos.console.utils.JwtTokenUtils;
|
||||
import com.alibaba.nacos.console.utils.PasswordEncoderUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* auth
|
||||
*
|
||||
* @author wfnuser
|
||||
*/
|
||||
@RestController("auth")
|
||||
@RequestMapping("/v1/auth")
|
||||
public class AuthController {
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtils jwtTokenUtils;
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
@Autowired
|
||||
private CustomUserDetailsServiceImpl userDetailsService;
|
||||
|
||||
/**
|
||||
* Whether the Nacos is in broken states or not, and cannot recover except by being restarted
|
||||
*
|
||||
* @return HTTP code equal to 200 indicates that Nacos is in right states. HTTP code equal to 500 indicates that
|
||||
* Nacos is in broken states.
|
||||
*/
|
||||
|
||||
@PostMapping("login")
|
||||
public RestResult<String> login(@RequestParam String username, @RequestParam String password, HttpServletResponse response) {
|
||||
|
||||
// 通过用户名和密码创建一个 Authentication 认证对象,实现类为 UsernamePasswordAuthenticationToken
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
RestResult<String> rr = new RestResult<String>();
|
||||
|
||||
try {
|
||||
//通过 AuthenticationManager(默认实现为ProviderManager)的authenticate方法验证 Authentication 对象
|
||||
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
||||
//将 Authentication 绑定到 SecurityContext
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
//生成Token
|
||||
String token = jwtTokenUtils.createToken(authentication);
|
||||
//将Token写入到Http头部
|
||||
response.addHeader(WebSecurityConfig.AUTHORIZATION_HEADER, "Bearer " + token);
|
||||
rr.setCode(200);
|
||||
rr.setData("Bearer " + token);
|
||||
return rr;
|
||||
} catch (BadCredentialsException authentication) {
|
||||
rr.setCode(401);
|
||||
rr.setMessage("Login failed");
|
||||
return rr;
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("password")
|
||||
public RestResult<String> updatePassword(@RequestParam(value = "oldPassword") String oldPassword,
|
||||
@RequestParam(value = "newPassword") String newPassword) {
|
||||
|
||||
RestResult<String> rr = new RestResult<String>();
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
String username = ((UserDetails) principal).getUsername();
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
String password = userDetails.getPassword();
|
||||
|
||||
// TODO: throw out more fine grained exceptions
|
||||
try {
|
||||
if (PasswordEncoderUtil.matches(oldPassword, password)) {
|
||||
userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword));
|
||||
rr.setCode(200);
|
||||
rr.setMessage("Update password success");
|
||||
} else {
|
||||
rr.setCode(401);
|
||||
rr.setMessage("Old password is invalid");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
rr.setCode(500);
|
||||
rr.setMessage("Update userpassword failed");
|
||||
}
|
||||
return rr;
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@ public class HealthController {
|
|||
* @return HTTP code equal to 200 indicates that Nacos is in right states. HTTP code equal to 500 indicates that
|
||||
* Nacos is in broken states.
|
||||
*/
|
||||
@GetMapping("liveness")
|
||||
@GetMapping("/liveness")
|
||||
public ResponseEntity liveness() {
|
||||
return ResponseEntity.ok().body("OK");
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public class HealthController {
|
|||
* @return HTTP code equal to 200 indicates that Nacos is ready. HTTP code equal to 500 indicates that Nacos is not
|
||||
* ready.
|
||||
*/
|
||||
@GetMapping("readiness")
|
||||
@GetMapping("/readiness")
|
||||
public ResponseEntity readiness(HttpServletRequest request) {
|
||||
boolean isConfigReadiness = isConfigReadiness();
|
||||
boolean isNamingReadiness = isNamingReadiness(request);
|
||||
|
|
|
@ -20,6 +20,9 @@ import com.alibaba.nacos.config.server.model.TenantInfo;
|
|||
import com.alibaba.nacos.config.server.service.PersistService;
|
||||
import com.alibaba.nacos.console.model.Namespace;
|
||||
import com.alibaba.nacos.console.model.NamespaceAllInfo;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import com.alibaba.nacos.core.auth.ActionTypes;
|
||||
import com.alibaba.nacos.core.auth.Secured;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -29,6 +32,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* namespace service
|
||||
|
@ -42,6 +46,10 @@ public class NamespaceController {
|
|||
@Autowired
|
||||
private PersistService persistService;
|
||||
|
||||
private Pattern namespaceIdCheckPattern = Pattern.compile("^[\\w-]+");
|
||||
|
||||
private static final int NAMESPACE_ID_MAX_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* Get namespace list
|
||||
*
|
||||
|
@ -101,16 +109,46 @@ public class NamespaceController {
|
|||
* @return whether create ok
|
||||
*/
|
||||
@PostMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE)
|
||||
public Boolean createNamespace(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam("customNamespaceId") String namespaceId,
|
||||
@RequestParam("namespaceName") String namespaceName,
|
||||
@RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) {
|
||||
// TODO 获取用kp
|
||||
String namespaceId = UUID.randomUUID().toString();
|
||||
if(StringUtils.isBlank(namespaceId)){
|
||||
namespaceId = UUID.randomUUID().toString();
|
||||
} else {
|
||||
namespaceId = namespaceId.trim();
|
||||
if (!namespaceIdCheckPattern.matcher(namespaceId).matches()) {
|
||||
return false;
|
||||
}
|
||||
if (namespaceId.length() > NAMESPACE_ID_MAX_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
if(persistService.tenantInfoCountByTenantId(namespaceId) > 0){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
persistService.insertTenantInfoAtomic("1", namespaceId, namespaceName, namespaceDesc, "nacos",
|
||||
System.currentTimeMillis());
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author klw(213539@qq.com)
|
||||
* @Description: check namespaceId exist
|
||||
* @Date 2019/12/10 21:41
|
||||
* @param: namespaceId
|
||||
* @return java.lang.Boolean
|
||||
*/
|
||||
@GetMapping(params = "checkNamespaceIdExist=true")
|
||||
public Boolean checkNamespaceIdExist(@RequestParam("customNamespaceId") String namespaceId){
|
||||
if(StringUtils.isBlank(namespaceId)){
|
||||
return false;
|
||||
}
|
||||
return (persistService.tenantInfoCountByTenantId(namespaceId) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* edit namespace
|
||||
*
|
||||
|
@ -120,6 +158,7 @@ public class NamespaceController {
|
|||
* @return whether edit ok
|
||||
*/
|
||||
@PutMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE)
|
||||
public Boolean editNamespace(@RequestParam("namespace") String namespace,
|
||||
@RequestParam("namespaceShowName") String namespaceShowName,
|
||||
@RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) {
|
||||
|
@ -137,6 +176,7 @@ public class NamespaceController {
|
|||
* @return whether del ok
|
||||
*/
|
||||
@DeleteMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE)
|
||||
public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam("namespaceId") String namespaceId) {
|
||||
persistService.removeTenantInfoAtomic("1", namespaceId);
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.controller;
|
||||
|
||||
|
||||
import com.alibaba.nacos.config.server.model.RestResult;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
|
||||
import com.alibaba.nacos.core.auth.ActionTypes;
|
||||
import com.alibaba.nacos.core.auth.Secured;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* Permission operation controller
|
||||
*
|
||||
* @author nkorange
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/v1/auth/permissions")
|
||||
public class PermissionController {
|
||||
|
||||
@Autowired
|
||||
private NacosRoleServiceImpl nacosRoleService;
|
||||
|
||||
/**
|
||||
* Query permissions of a role
|
||||
*
|
||||
* @param role the role
|
||||
* @param pageNo page index
|
||||
* @param pageSize page size
|
||||
* @return permission of a role
|
||||
*/
|
||||
@GetMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ)
|
||||
public Object getPermissions(@RequestParam int pageNo, @RequestParam int pageSize,
|
||||
@RequestParam(name = "role", defaultValue = StringUtils.EMPTY) String role) {
|
||||
return nacosRoleService.getPermissionsFromDatabase(role, pageNo, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a permission to a role
|
||||
*
|
||||
* @param role the role
|
||||
* @param resource the related resource
|
||||
* @param action the related action
|
||||
* @return ok if succeed
|
||||
*/
|
||||
@PostMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE)
|
||||
public Object addPermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) {
|
||||
nacosRoleService.addPermission(role, resource, action);
|
||||
return new RestResult<>(200, "add permission ok!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a permission from a role
|
||||
*
|
||||
* @param role the role
|
||||
* @param resource the related resource
|
||||
* @param action the related action
|
||||
* @return ok if succeed
|
||||
*/
|
||||
@DeleteMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE)
|
||||
public Object deletePermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) {
|
||||
nacosRoleService.deletePermission(role, resource, action);
|
||||
return new RestResult<>(200, "delete permission ok!");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.controller;
|
||||
|
||||
|
||||
import com.alibaba.nacos.config.server.model.RestResult;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
|
||||
import com.alibaba.nacos.core.auth.ActionTypes;
|
||||
import com.alibaba.nacos.core.auth.Secured;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Role operation controller
|
||||
*
|
||||
* @author nkorange
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/v1/auth/roles")
|
||||
public class RoleController {
|
||||
|
||||
@Autowired
|
||||
private NacosRoleServiceImpl roleService;
|
||||
|
||||
/**
|
||||
* Get roles list
|
||||
*
|
||||
* @param pageNo number index of page
|
||||
* @param pageSize page size
|
||||
* @param username optional, username of user
|
||||
* @return role list
|
||||
*/
|
||||
@GetMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ)
|
||||
public Object getRoles(@RequestParam int pageNo, @RequestParam int pageSize,
|
||||
@RequestParam(name = "username", defaultValue = "") String username) {
|
||||
return roleService.getRolesFromDatabase(username, pageNo, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a role to a user
|
||||
* <p>
|
||||
* This method is used for 2 functions:
|
||||
* 1. create a role and bind it to GLOBAL_ADMIN.
|
||||
* 2. bind a role to an user.
|
||||
*
|
||||
* @param role
|
||||
* @param username
|
||||
* @return
|
||||
*/
|
||||
@PostMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE)
|
||||
public Object addRole(@RequestParam String role, @RequestParam String username) {
|
||||
roleService.addRole(role, username);
|
||||
return new RestResult<>(200, "add role ok!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a role. If no username is specified, all users under this role are deleted
|
||||
*
|
||||
* @param role role
|
||||
* @param username username
|
||||
* @return ok if succeed
|
||||
*/
|
||||
@DeleteMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE)
|
||||
public Object deleteRole(@RequestParam String role,
|
||||
@RequestParam(name = "username", defaultValue = StringUtils.EMPTY) String username) {
|
||||
if (StringUtils.isBlank(username)) {
|
||||
roleService.deleteRole(role);
|
||||
} else {
|
||||
roleService.deleteRole(role, username);
|
||||
}
|
||||
return new RestResult<>(200, "delete role of user " + username + " ok!");
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
package com.alibaba.nacos.console.controller;
|
||||
|
||||
|
||||
import com.alibaba.nacos.common.util.VersionUtils;
|
||||
import com.alibaba.nacos.common.utils.VersionUtils;
|
||||
import com.alibaba.nacos.core.utils.SystemUtils;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
@ -34,7 +34,7 @@ import java.util.Map;
|
|||
@RequestMapping("/v1/console/server")
|
||||
public class ServerStateController {
|
||||
|
||||
@GetMapping("state")
|
||||
@GetMapping("/state")
|
||||
public ResponseEntity serverState() {
|
||||
Map<String,String> serverState = new HashMap<>(3);
|
||||
serverState.put("standalone_mode",SystemUtils.STANDALONE_MODE ?
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.controller;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.alibaba.nacos.api.common.Constants;
|
||||
import com.alibaba.nacos.config.server.auth.RoleInfo;
|
||||
import com.alibaba.nacos.config.server.model.RestResult;
|
||||
import com.alibaba.nacos.config.server.model.User;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthManager;
|
||||
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUser;
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
|
||||
import com.alibaba.nacos.console.utils.JwtTokenUtils;
|
||||
import com.alibaba.nacos.console.utils.PasswordEncoderUtil;
|
||||
import com.alibaba.nacos.core.auth.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* User related methods entry
|
||||
*
|
||||
* @author wfnuser
|
||||
* @author nkorange
|
||||
*/
|
||||
@RestController("user")
|
||||
@RequestMapping({"/v1/auth", "/v1/auth/users"})
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtils jwtTokenUtils;
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private NacosUserDetailsServiceImpl userDetailsService;
|
||||
|
||||
@Autowired
|
||||
private NacosRoleServiceImpl roleService;
|
||||
|
||||
@Autowired
|
||||
private AuthConfigs authConfigs;
|
||||
|
||||
@Autowired
|
||||
private NacosAuthManager authManager;
|
||||
|
||||
/**
|
||||
* Create a new user
|
||||
*
|
||||
* @param username username
|
||||
* @param password password
|
||||
* @return ok if create succeed
|
||||
* @throws IllegalArgumentException if user already exist
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE)
|
||||
@PostMapping
|
||||
public Object createUser(@RequestParam String username, @RequestParam String password) {
|
||||
|
||||
User user = userDetailsService.getUserFromDatabase(username);
|
||||
if (user != null) {
|
||||
throw new IllegalArgumentException("user '" + username + "' already exist!");
|
||||
}
|
||||
userDetailsService.createUser(username, PasswordEncoderUtil.encode(password));
|
||||
return new RestResult<>(200, "create user ok!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an existed user
|
||||
*
|
||||
* @param username username of user
|
||||
* @return ok if deleted succeed, keep silent if user not exist
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@DeleteMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE)
|
||||
public Object deleteUser(@RequestParam String username) {
|
||||
List<RoleInfo> roleInfoList = roleService.getRoles(username);
|
||||
if (roleInfoList != null) {
|
||||
for (RoleInfo roleInfo : roleInfoList) {
|
||||
if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
|
||||
throw new IllegalArgumentException("cannot delete admin: " + username);
|
||||
}
|
||||
}
|
||||
}
|
||||
userDetailsService.deleteUser(username);
|
||||
return new RestResult<>(200, "delete user ok!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an user
|
||||
*
|
||||
* @param username username of user
|
||||
* @param newPassword new password of user
|
||||
* @return ok if update succeed
|
||||
* @throws IllegalArgumentException if user not exist or oldPassword is incorrect
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@PutMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE)
|
||||
public Object updateUser(@RequestParam String username, @RequestParam String newPassword) {
|
||||
|
||||
User user = userDetailsService.getUserFromDatabase(username);
|
||||
if (user == null) {
|
||||
throw new IllegalArgumentException("user " + username + " not exist!");
|
||||
}
|
||||
|
||||
userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword));
|
||||
|
||||
return new RestResult<>(200, "update user ok!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paged users
|
||||
*
|
||||
* @param pageNo number index of page
|
||||
* @param pageSize size of page
|
||||
* @return A collection of users, empty set if no user is found
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@GetMapping
|
||||
@Secured(resource = NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.READ)
|
||||
public Object getUsers(@RequestParam int pageNo, @RequestParam int pageSize) {
|
||||
return userDetailsService.getUsersFromDatabase(pageNo, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to Nacos
|
||||
* <p>
|
||||
* This methods uses username and password to require a new token.
|
||||
*
|
||||
* @param username username of user
|
||||
* @param password password
|
||||
* @param response http response
|
||||
* @param request http request
|
||||
* @return new token of the user
|
||||
* @throws AccessException if user info is incorrect
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Object login(@RequestParam String username, @RequestParam String password,
|
||||
HttpServletResponse response, HttpServletRequest request) throws AccessException {
|
||||
|
||||
|
||||
if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
|
||||
NacosUser user = (NacosUser) authManager.login(request);
|
||||
|
||||
response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER,
|
||||
NacosAuthConfig.TOKEN_PREFIX + user.getToken());
|
||||
|
||||
JSONObject result = new JSONObject();
|
||||
result.put(Constants.ACCESS_TOKEN, user.getToken());
|
||||
result.put(Constants.TOKEN_TTL, authConfigs.getTokenValidityInSeconds());
|
||||
result.put(Constants.GLOBAL_ADMIN, user.isGlobalAdmin());
|
||||
return result;
|
||||
}
|
||||
|
||||
// 通过用户名和密码创建一个 Authentication 认证对象,实现类为 UsernamePasswordAuthenticationToken
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
|
||||
RestResult<String> rr = new RestResult<String>();
|
||||
try {
|
||||
//通过 AuthenticationManager(默认实现为ProviderManager)的authenticate方法验证 Authentication 对象
|
||||
Authentication authentication = authenticationManager.authenticate(authenticationToken);
|
||||
//将 Authentication 绑定到 SecurityContext
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
//生成Token
|
||||
String token = jwtTokenUtils.createToken(authentication);
|
||||
//将Token写入到Http头部
|
||||
response.addHeader(NacosAuthConfig.AUTHORIZATION_HEADER, "Bearer " + token);
|
||||
rr.setCode(200);
|
||||
rr.setData("Bearer " + token);
|
||||
return rr;
|
||||
} catch (BadCredentialsException authentication) {
|
||||
rr.setCode(401);
|
||||
rr.setMessage("Login failed");
|
||||
return rr;
|
||||
}
|
||||
}
|
||||
|
||||
@PutMapping("/password")
|
||||
@Deprecated
|
||||
public RestResult<String> updatePassword(@RequestParam(value = "oldPassword") String oldPassword,
|
||||
@RequestParam(value = "newPassword") String newPassword) {
|
||||
|
||||
RestResult<String> rr = new RestResult<String>();
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
String username = ((UserDetails) principal).getUsername();
|
||||
User user = userDetailsService.getUserFromDatabase(username);
|
||||
String password = user.getPassword();
|
||||
|
||||
// TODO: throw out more fine grained exceptions
|
||||
try {
|
||||
if (PasswordEncoderUtil.matches(oldPassword, password)) {
|
||||
userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword));
|
||||
rr.setCode(200);
|
||||
rr.setMessage("Update password success");
|
||||
} else {
|
||||
rr.setCode(401);
|
||||
rr.setMessage("Old password is invalid");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
rr.setCode(500);
|
||||
rr.setMessage("Update userpassword failed");
|
||||
}
|
||||
return rr;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.exception;
|
||||
|
||||
import com.alibaba.nacos.core.auth.AccessException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
|
||||
/**
|
||||
* Exception handler for console module
|
||||
*
|
||||
* @author nkorange
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class ConsoleExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ConsoleExceptionHandler.class);
|
||||
|
||||
@ExceptionHandler(AccessException.class)
|
||||
private ResponseEntity<String> handleAccessException(AccessException e) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg());
|
||||
}
|
||||
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
private ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.toString());
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
private ResponseEntity<String> handleException(Exception e) {
|
||||
logger.error("CONSOLE", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.toString());
|
||||
}
|
||||
}
|
|
@ -15,11 +15,12 @@
|
|||
*/
|
||||
package com.alibaba.nacos.console.filter;
|
||||
|
||||
import com.alibaba.nacos.console.config.WebSecurityConfig;
|
||||
import com.alibaba.nacos.console.utils.JwtTokenUtils;
|
||||
import com.alibaba.nacos.api.common.Constants;
|
||||
import com.alibaba.nacos.console.security.nacos.JwtTokenManager;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
|
@ -37,29 +38,23 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
|||
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
private JwtTokenUtils tokenProvider;
|
||||
private JwtTokenManager tokenManager;
|
||||
|
||||
public JwtAuthenticationTokenFilter(JwtTokenUtils tokenProvider) {
|
||||
this.tokenProvider = tokenProvider;
|
||||
public JwtAuthenticationTokenFilter(JwtTokenManager tokenManager) {
|
||||
this.tokenManager = tokenManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
|
||||
String jwt = resolveToken(request);
|
||||
|
||||
if (jwt != null && !"".equals(jwt.trim()) && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
if (this.tokenProvider.validateToken(jwt)) {
|
||||
/**
|
||||
* get auth info
|
||||
*/
|
||||
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
|
||||
/**
|
||||
* save user info to securityContext
|
||||
*/
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
if (StringUtils.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
this.tokenManager.validateToken(jwt);
|
||||
Authentication authentication = this.tokenManager.getAuthentication(jwt);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
|
@ -67,12 +62,12 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
|||
* Get token from header
|
||||
*/
|
||||
private String resolveToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader(WebSecurityConfig.AUTHORIZATION_HEADER);
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
|
||||
return bearerToken.substring(7, bearerToken.length());
|
||||
String bearerToken = request.getHeader(NacosAuthConfig.AUTHORIZATION_HEADER);
|
||||
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
String jwt = request.getParameter(WebSecurityConfig.AUTHORIZATION_TOKEN);
|
||||
if (StringUtils.hasText(jwt)) {
|
||||
String jwt = request.getParameter(Constants.ACCESS_TOKEN);
|
||||
if (StringUtils.isNotBlank(jwt)) {
|
||||
return jwt;
|
||||
}
|
||||
return null;
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security;
|
||||
|
||||
|
||||
import com.alibaba.nacos.config.server.model.User;
|
||||
import com.alibaba.nacos.config.server.service.PersistService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* Custem user service
|
||||
*
|
||||
* @author wfnuser
|
||||
*/
|
||||
@Service
|
||||
public class CustomUserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private transient PersistService persistService;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
|
||||
User user = persistService.findUserByUsername(userName);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException(userName);
|
||||
}
|
||||
return new CustomUserDetails(user);
|
||||
}
|
||||
|
||||
public void updateUserPassword(String username, String password) throws Exception {
|
||||
persistService.updateUserPassword(username, password);
|
||||
}
|
||||
}
|
|
@ -13,8 +13,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security;
|
||||
package com.alibaba.nacos.console.security.nacos;
|
||||
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
|
@ -32,7 +33,7 @@ import org.springframework.stereotype.Component;
|
|||
public class CustomAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
@Autowired
|
||||
private CustomUserDetailsServiceImpl userDetailsService;
|
||||
private NacosUserDetailsServiceImpl userDetailsService;
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security;
|
||||
package com.alibaba.nacos.console.security.nacos;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -37,10 +37,10 @@ public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
|
|||
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationEntryPoint.class);
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest httpServletRequest,
|
||||
HttpServletResponse httpServletResponse,
|
||||
public void commence(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AuthenticationException e) throws IOException, ServletException {
|
||||
logger.error("Responding with unauthorized error. Message - {}", e.getMessage());
|
||||
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
||||
logger.error("Responding with unauthorized error. Message:{}, url:{}", e.getMessage(), request.getRequestURI());
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security.nacos;
|
||||
|
||||
import com.alibaba.nacos.core.auth.AuthConfigs;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* JWT token manager
|
||||
*
|
||||
* @author wfnuser
|
||||
* @author nkorange
|
||||
*/
|
||||
@Component
|
||||
public class JwtTokenManager {
|
||||
|
||||
private static final String AUTHORITIES_KEY = "auth";
|
||||
|
||||
@Autowired
|
||||
private AuthConfigs authConfigs;
|
||||
|
||||
/**
|
||||
* Create token
|
||||
*
|
||||
* @param authentication auth info
|
||||
* @return token
|
||||
*/
|
||||
public String createToken(Authentication authentication) {
|
||||
return createToken(authentication.getName());
|
||||
}
|
||||
|
||||
public String createToken(String userName) {
|
||||
|
||||
long now = (new Date()).getTime();
|
||||
|
||||
Date validity;
|
||||
validity = new Date(now + authConfigs.getTokenValidityInSeconds() * 1000L);
|
||||
|
||||
Claims claims = Jwts.claims().setSubject(userName);
|
||||
|
||||
return Jwts.builder()
|
||||
.setClaims(claims)
|
||||
.setExpiration(validity)
|
||||
.signWith(SignatureAlgorithm.HS256, authConfigs.getSecretKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get auth Info
|
||||
*
|
||||
* @param token token
|
||||
* @return auth info
|
||||
*/
|
||||
public Authentication getAuthentication(String token) {
|
||||
/**
|
||||
* parse the payload of token
|
||||
*/
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(authConfigs.getSecretKey())
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));
|
||||
|
||||
User principal = new User(claims.getSubject(), "", authorities);
|
||||
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate token
|
||||
*
|
||||
* @param token token
|
||||
* @return whether valid
|
||||
*/
|
||||
public void validateToken(String token) {
|
||||
Jwts.parser().setSigningKey(authConfigs.getSecretKey()).parseClaimsJws(token);
|
||||
}
|
||||
}
|
|
@ -13,13 +13,16 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacos.console.config;
|
||||
package com.alibaba.nacos.console.security.nacos;
|
||||
|
||||
|
||||
import com.alibaba.nacos.console.filter.JwtAuthenticationTokenFilter;
|
||||
import com.alibaba.nacos.console.security.CustomUserDetailsServiceImpl;
|
||||
import com.alibaba.nacos.console.security.JwtAuthenticationEntryPoint;
|
||||
import com.alibaba.nacos.console.utils.JwtTokenUtils;
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
|
||||
import com.alibaba.nacos.core.auth.AuthConfigs;
|
||||
import com.alibaba.nacos.core.auth.AuthSystemTypes;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
@ -34,6 +37,7 @@ import org.springframework.security.config.http.SessionCreationPolicy;
|
|||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsUtils;
|
||||
|
||||
/**
|
||||
* Spring security config
|
||||
|
@ -42,61 +46,96 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
|
|||
*/
|
||||
@Configuration
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
public class NacosAuthConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
public static final String AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
public static final String AUTHORIZATION_TOKEN = "access_token";
|
||||
|
||||
public static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ",";
|
||||
|
||||
@Autowired
|
||||
private CustomUserDetailsServiceImpl userDetailsService;
|
||||
public static final String LOGIN_ENTRY_POINT = "/v1/auth/login";
|
||||
|
||||
@Autowired
|
||||
private JwtAuthenticationEntryPoint unauthorizedHandler;
|
||||
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/v1/auth/**";
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtils tokenProvider;
|
||||
public static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
public static final String CONSOLE_RESOURCE_NAME_PREFIX = "console/";
|
||||
|
||||
@Autowired
|
||||
private Environment env;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenManager tokenProvider;
|
||||
|
||||
@Autowired
|
||||
private AuthConfigs authConfigs;
|
||||
|
||||
@Autowired
|
||||
private NacosUserDetailsServiceImpl userDetailsService;
|
||||
|
||||
@Value("${nacos.security.ignore.urls}")
|
||||
private String ignoreURLs;
|
||||
|
||||
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
|
||||
@Override
|
||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
||||
return super.authenticationManagerBean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) {
|
||||
|
||||
if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) {
|
||||
ignoreURLs = "/**";
|
||||
}
|
||||
//
|
||||
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
|
||||
ignoreURLs = env.getProperty("nacos.security.ignore.urls", "/**");
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(ignoreURLs)) {
|
||||
for (String ignoreURL : ignoreURLs.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
|
||||
web.ignoring().antMatchers(ignoreURL.trim());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) {
|
||||
String ignoreUrls = env.getProperty("nacos.security.ignore.urls", "/**");
|
||||
for (String url : ignoreUrls.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) {
|
||||
web.ignoring().antMatchers(url.trim());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated().and()
|
||||
// custom token authorize exception handler
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(unauthorizedHandler).and()
|
||||
// since we use jwt, session is not necessary
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
// since we use jwt, csrf is not necessary
|
||||
.csrf().disable();
|
||||
http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
// disable cache
|
||||
http.headers().cacheControl();
|
||||
if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) {
|
||||
http
|
||||
|
||||
.csrf().disable()
|
||||
.cors() // We don't need CSRF for JWT based authentication
|
||||
|
||||
.and()
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
|
||||
.antMatchers(LOGIN_ENTRY_POINT).permitAll()
|
||||
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
|
||||
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(new JwtAuthenticationEntryPoint());
|
||||
|
||||
// disable cache
|
||||
http.headers().cacheControl();
|
||||
|
||||
http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Bean
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security.nacos;
|
||||
|
||||
import com.alibaba.nacos.api.common.Constants;
|
||||
import com.alibaba.nacos.config.server.auth.RoleInfo;
|
||||
import com.alibaba.nacos.console.security.nacos.roles.NacosRoleServiceImpl;
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUser;
|
||||
import com.alibaba.nacos.core.auth.AccessException;
|
||||
import com.alibaba.nacos.core.auth.AuthManager;
|
||||
import com.alibaba.nacos.core.auth.Permission;
|
||||
import com.alibaba.nacos.core.auth.User;
|
||||
import com.alibaba.nacos.core.utils.Loggers;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builtin access control entry of Nacos
|
||||
*
|
||||
* @author nkorange
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Component
|
||||
public class NacosAuthManager implements AuthManager {
|
||||
|
||||
private static final String TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
@Autowired
|
||||
private JwtTokenManager tokenManager;
|
||||
|
||||
@Autowired
|
||||
private AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private NacosRoleServiceImpl roleService;
|
||||
|
||||
@Override
|
||||
public User login(Object request) throws AccessException {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
String token = resolveToken(req);
|
||||
if (StringUtils.isBlank(token)) {
|
||||
throw new AccessException("user not found!");
|
||||
}
|
||||
|
||||
try {
|
||||
tokenManager.validateToken(token);
|
||||
} catch (ExpiredJwtException e) {
|
||||
throw new AccessException("token expired!");
|
||||
} catch (Exception e) {
|
||||
throw new AccessException("token invalid!");
|
||||
}
|
||||
|
||||
Authentication authentication = tokenManager.getAuthentication(token);
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
String username = authentication.getName();
|
||||
NacosUser user = new NacosUser();
|
||||
user.setUserName(username);
|
||||
user.setToken(token);
|
||||
List<RoleInfo> roleInfoList = roleService.getRoles(username);
|
||||
if (roleInfoList != null) {
|
||||
for (RoleInfo roleInfo : roleInfoList) {
|
||||
if (roleInfo.getRole().equals(NacosRoleServiceImpl.GLOBAL_ADMIN_ROLE)) {
|
||||
user.setGlobalAdmin(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void auth(Permission permission, User user) throws AccessException {
|
||||
if (Loggers.AUTH.isDebugEnabled()) {
|
||||
Loggers.AUTH.debug("auth permission: {}, user: {}", permission, user);
|
||||
}
|
||||
|
||||
if (!roleService.hasPermission(user.getUserName(), permission)) {
|
||||
throw new AccessException("authorization failed!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token from header
|
||||
*/
|
||||
private String resolveToken(HttpServletRequest request) throws AccessException {
|
||||
String bearerToken = request.getHeader(NacosAuthConfig.AUTHORIZATION_HEADER);
|
||||
if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
bearerToken = request.getParameter(Constants.ACCESS_TOKEN);
|
||||
if (StringUtils.isBlank(bearerToken)) {
|
||||
String userName = request.getParameter("username");
|
||||
String password = request.getParameter("password");
|
||||
bearerToken = resolveTokenFromUser(userName, password);
|
||||
}
|
||||
|
||||
return bearerToken;
|
||||
}
|
||||
|
||||
private String resolveTokenFromUser(String userName, String rawPassword) throws AccessException {
|
||||
|
||||
try {
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, rawPassword);
|
||||
authenticationManager.authenticate(authenticationToken);
|
||||
} catch (AuthenticationException e) {
|
||||
throw new AccessException("unknown user!");
|
||||
}
|
||||
|
||||
return tokenManager.createToken(userName);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security.nacos.roles;
|
||||
|
||||
|
||||
import com.alibaba.nacos.config.server.auth.PermissionInfo;
|
||||
import com.alibaba.nacos.config.server.auth.PermissionPersistService;
|
||||
import com.alibaba.nacos.config.server.auth.RoleInfo;
|
||||
import com.alibaba.nacos.config.server.auth.RolePersistService;
|
||||
import com.alibaba.nacos.config.server.model.Page;
|
||||
import com.alibaba.nacos.console.security.nacos.NacosAuthConfig;
|
||||
import com.alibaba.nacos.console.security.nacos.users.NacosUserDetailsServiceImpl;
|
||||
import com.alibaba.nacos.core.auth.AuthConfigs;
|
||||
import com.alibaba.nacos.core.auth.Permission;
|
||||
import com.alibaba.nacos.core.utils.Loggers;
|
||||
import io.jsonwebtoken.lang.Collections;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.mina.util.ConcurrentHashSet;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Nacos builtin role service.
|
||||
*
|
||||
* @author nkorange
|
||||
* @since 1.2.0
|
||||
*/
|
||||
@Service
|
||||
public class NacosRoleServiceImpl {
|
||||
|
||||
public static final String GLOBAL_ADMIN_ROLE = "ROLE_ADMIN";
|
||||
|
||||
@Autowired
|
||||
private AuthConfigs authConfigs;
|
||||
|
||||
@Autowired
|
||||
private RolePersistService rolePersistService;
|
||||
|
||||
@Autowired
|
||||
private NacosUserDetailsServiceImpl userDetailsService;
|
||||
|
||||
@Autowired
|
||||
private PermissionPersistService permissionPersistService;
|
||||
|
||||
private Set<String> roleSet = new ConcurrentHashSet<>();
|
||||
|
||||
private Map<String, List<RoleInfo>> roleInfoMap = new ConcurrentHashMap<>();
|
||||
|
||||
private Map<String, List<PermissionInfo>> permissionInfoMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
|
||||
private void reload() {
|
||||
try {
|
||||
Page<RoleInfo> roleInfoPage = rolePersistService.getRolesByUserName(StringUtils.EMPTY, 1, Integer.MAX_VALUE);
|
||||
if (roleInfoPage == null) {
|
||||
return;
|
||||
}
|
||||
Set<String> tmpRoleSet = new HashSet<>(16);
|
||||
Map<String, List<RoleInfo>> tmpRoleInfoMap = new ConcurrentHashMap<>(16);
|
||||
for (RoleInfo roleInfo : roleInfoPage.getPageItems()) {
|
||||
if (!tmpRoleInfoMap.containsKey(roleInfo.getUsername())) {
|
||||
tmpRoleInfoMap.put(roleInfo.getUsername(), new ArrayList<>());
|
||||
}
|
||||
tmpRoleInfoMap.get(roleInfo.getUsername()).add(roleInfo);
|
||||
tmpRoleSet.add(roleInfo.getRole());
|
||||
}
|
||||
|
||||
Map<String, List<PermissionInfo>> tmpPermissionInfoMap = new ConcurrentHashMap<>(16);
|
||||
for (String role : tmpRoleSet) {
|
||||
Page<PermissionInfo> permissionInfoPage = permissionPersistService.getPermissions(role, 1, Integer.MAX_VALUE);
|
||||
tmpPermissionInfoMap.put(role, permissionInfoPage.getPageItems());
|
||||
}
|
||||
|
||||
roleSet = tmpRoleSet;
|
||||
roleInfoMap = tmpRoleInfoMap;
|
||||
permissionInfoMap = tmpPermissionInfoMap;
|
||||
} catch (Exception e) {
|
||||
Loggers.AUTH.warn("[LOAD-ROLES] load failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user has permission of the resource.
|
||||
* <p>
|
||||
* Note if the user has many roles, this method returns true if any one role of the user has the
|
||||
* desired permission.
|
||||
*
|
||||
* @param username user info
|
||||
* @param permission permission to auth
|
||||
* @return true if granted, false otherwise
|
||||
*/
|
||||
public boolean hasPermission(String username, Permission permission) {
|
||||
|
||||
List<RoleInfo> roleInfoList = getRoles(username);
|
||||
if (Collections.isEmpty(roleInfoList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Global admin pass:
|
||||
for (RoleInfo roleInfo : roleInfoList) {
|
||||
if (GLOBAL_ADMIN_ROLE.equals(roleInfo.getRole())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Old global admin can pass resource 'console/':
|
||||
if (permission.getResource().startsWith(NacosAuthConfig.CONSOLE_RESOURCE_NAME_PREFIX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For other roles, use a pattern match to decide if pass or not.
|
||||
for (RoleInfo roleInfo : roleInfoList) {
|
||||
List<PermissionInfo> permissionInfoList = getPermissions(roleInfo.getRole());
|
||||
if (Collections.isEmpty(permissionInfoList)) {
|
||||
continue;
|
||||
}
|
||||
for (PermissionInfo permissionInfo : permissionInfoList) {
|
||||
String permissionResource = permissionInfo.getResource().replaceAll("\\*", ".*");
|
||||
String permissionAction = permissionInfo.getAction();
|
||||
if (permissionAction.contains(permission.getAction()) &&
|
||||
Pattern.matches(permissionResource, permission.getResource())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<RoleInfo> getRoles(String username) {
|
||||
List<RoleInfo> roleInfoList = roleInfoMap.get(username);
|
||||
if (!authConfigs.isCachingEnabled()) {
|
||||
Page<RoleInfo> roleInfoPage = getRolesFromDatabase(username, 1, Integer.MAX_VALUE);
|
||||
if (roleInfoPage != null) {
|
||||
roleInfoList = roleInfoPage.getPageItems();
|
||||
}
|
||||
}
|
||||
return roleInfoList;
|
||||
}
|
||||
|
||||
public Page<RoleInfo> getRolesFromDatabase(String userName, int pageNo, int pageSize) {
|
||||
Page<RoleInfo> roles = rolePersistService.getRolesByUserName(userName, pageNo, pageSize);
|
||||
if (roles == null) {
|
||||
return new Page<>();
|
||||
}
|
||||
return roles;
|
||||
}
|
||||
|
||||
public List<PermissionInfo> getPermissions(String role) {
|
||||
List<PermissionInfo> permissionInfoList = permissionInfoMap.get(role);
|
||||
if (!authConfigs.isCachingEnabled()) {
|
||||
Page<PermissionInfo> permissionInfoPage = getPermissionsFromDatabase(role, 1, Integer.MAX_VALUE);
|
||||
if (permissionInfoPage != null) {
|
||||
permissionInfoList = permissionInfoPage.getPageItems();
|
||||
}
|
||||
}
|
||||
return permissionInfoList;
|
||||
}
|
||||
|
||||
public Page<PermissionInfo> getPermissionsByRoleFromDatabase(String role, int pageNo, int pageSize) {
|
||||
return permissionPersistService.getPermissions(role, pageNo, pageSize);
|
||||
}
|
||||
|
||||
public void addRole(String role, String username) {
|
||||
if (userDetailsService.getUser(username) == null) {
|
||||
throw new IllegalArgumentException("user '" + username + "' not found!");
|
||||
}
|
||||
if (GLOBAL_ADMIN_ROLE.equals(role)) {
|
||||
throw new IllegalArgumentException("role '" + GLOBAL_ADMIN_ROLE + "' is not permitted to create!");
|
||||
}
|
||||
rolePersistService.addRole(role, username);
|
||||
roleSet.add(role);
|
||||
}
|
||||
|
||||
public void deleteRole(String role, String userName) {
|
||||
rolePersistService.deleteRole(role, userName);
|
||||
}
|
||||
|
||||
public void deleteRole(String role) {
|
||||
rolePersistService.deleteRole(role);
|
||||
roleSet.remove(role);
|
||||
}
|
||||
|
||||
public Page<PermissionInfo> getPermissionsFromDatabase(String role, int pageNo, int pageSize) {
|
||||
Page<PermissionInfo> pageInfo = permissionPersistService.getPermissions(role, pageNo, pageSize);
|
||||
if (pageInfo == null) {
|
||||
return new Page<>();
|
||||
}
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
public void addPermission(String role, String resource, String action) {
|
||||
if (!roleSet.contains(role)) {
|
||||
throw new IllegalArgumentException("role " + role + " not found!");
|
||||
}
|
||||
permissionPersistService.addPermission(role, resource, action);
|
||||
}
|
||||
|
||||
public void deletePermission(String role, String resource, String action) {
|
||||
permissionPersistService.deletePermission(role, resource, action);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package com.alibaba.nacos.console.security.nacos.users;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.nacos.core.auth.User;
|
||||
|
||||
/**
|
||||
* @author nkorange
|
||||
* @since 1.2.0
|
||||
*/
|
||||
public class NacosUser extends User {
|
||||
|
||||
private String token;
|
||||
|
||||
private boolean globalAdmin = false;
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public boolean isGlobalAdmin() {
|
||||
return globalAdmin;
|
||||
}
|
||||
|
||||
public void setGlobalAdmin(boolean globalAdmin) {
|
||||
this.globalAdmin = globalAdmin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.toJSONString(this);
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security;
|
||||
package com.alibaba.nacos.console.security.nacos.users;
|
||||
|
||||
import com.alibaba.nacos.config.server.model.User;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
@ -27,11 +27,11 @@ import java.util.Collection;
|
|||
*
|
||||
* @author wfnuser
|
||||
*/
|
||||
public class CustomUserDetails implements UserDetails {
|
||||
public class NacosUserDetails implements UserDetails {
|
||||
|
||||
private User user;
|
||||
|
||||
public CustomUserDetails(User user) {
|
||||
public NacosUserDetails(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
package com.alibaba.nacos.console.security.nacos.users;
|
||||
|
||||
|
||||
import com.alibaba.nacos.config.server.auth.UserPersistService;
|
||||
import com.alibaba.nacos.config.server.model.Page;
|
||||
import com.alibaba.nacos.config.server.model.User;
|
||||
import com.alibaba.nacos.core.auth.AuthConfigs;
|
||||
import com.alibaba.nacos.core.utils.Loggers;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Custem user service
|
||||
*
|
||||
* @author wfnuser
|
||||
* @author nkorange
|
||||
*/
|
||||
@Service
|
||||
public class NacosUserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
private Map<String, User> userMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private UserPersistService userPersistService;
|
||||
|
||||
@Autowired
|
||||
private AuthConfigs authConfigs;
|
||||
|
||||
@Scheduled(initialDelay = 5000, fixedDelay = 15000)
|
||||
private void reload() {
|
||||
try {
|
||||
Page<User> users = getUsersFromDatabase(1, Integer.MAX_VALUE);
|
||||
if (users == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, User> map = new ConcurrentHashMap<>(16);
|
||||
for (User user : users.getPageItems()) {
|
||||
map.put(user.getUsername(), user);
|
||||
}
|
||||
userMap = map;
|
||||
} catch (Exception e) {
|
||||
Loggers.AUTH.warn("[LOAD-USERS] load failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
|
||||
User user = userMap.get(username);
|
||||
if (!authConfigs.isCachingEnabled()) {
|
||||
user = userPersistService.findUserByUsername(username);
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
return new NacosUserDetails(user);
|
||||
}
|
||||
|
||||
public void updateUserPassword(String username, String password) {
|
||||
userPersistService.updateUserPassword(username, password);
|
||||
}
|
||||
|
||||
public Page<User> getUsersFromDatabase(int pageNo, int pageSize) {
|
||||
return userPersistService.getUsers(pageNo, pageSize);
|
||||
}
|
||||
|
||||
public User getUser(String username) {
|
||||
User user = userMap.get(username);
|
||||
if (!authConfigs.isCachingEnabled()) {
|
||||
user = getUserFromDatabase(username);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
public User getUserFromDatabase(String username) {
|
||||
return userPersistService.findUserByUsername(username);
|
||||
}
|
||||
|
||||
public void createUser(String username, String password) {
|
||||
userPersistService.createUser(username, password);
|
||||
}
|
||||
|
||||
public void deleteUser(String username) {
|
||||
userPersistService.deleteUser(username);
|
||||
}
|
||||
}
|
|
@ -16,8 +16,10 @@
|
|||
package com.alibaba.nacos.console.utils;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
@ -26,6 +28,7 @@ import org.springframework.security.core.userdetails.User;
|
|||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -38,90 +41,135 @@ import java.util.List;
|
|||
@Component
|
||||
public class JwtTokenUtils {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(JwtTokenUtils.class);
|
||||
private final Logger log = LoggerFactory.getLogger(JwtTokenUtils.class);
|
||||
|
||||
private static final String AUTHORITIES_KEY = "auth";
|
||||
private static final String AUTHORITIES_KEY = "auth";
|
||||
|
||||
/**
|
||||
* secret key
|
||||
*/
|
||||
private String secretKey;
|
||||
/**
|
||||
* minimum SHA_256 secretKey string length
|
||||
*/
|
||||
private static final int SHA_256_SECRET_CHAR_SIZE = 256 / 8;
|
||||
|
||||
/**
|
||||
* Token validity time(ms)
|
||||
*/
|
||||
private long tokenValidityInMilliseconds;
|
||||
/**
|
||||
* default SHA_256 secretKey flag
|
||||
*/
|
||||
private static final String DEFAULT_SECRET_FLAG = "default";
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.secretKey = "SecretKey012345678901234567890123456789012345678901234567890123456789";
|
||||
this.tokenValidityInMilliseconds = 1000 * 60 * 30L;
|
||||
}
|
||||
/**
|
||||
* custom SHA_256 secretKey from config property
|
||||
*/
|
||||
@Value("${nacos.security.token.secret-key:default}")
|
||||
private String customSecretKeyStr;
|
||||
|
||||
/**
|
||||
* Create token
|
||||
*
|
||||
* @param authentication auth info
|
||||
* @return token
|
||||
*/
|
||||
public String createToken(Authentication authentication) {
|
||||
/**
|
||||
* Current time
|
||||
*/
|
||||
long now = (new Date()).getTime();
|
||||
/**
|
||||
* Validity date
|
||||
*/
|
||||
Date validity;
|
||||
validity = new Date(now + this.tokenValidityInMilliseconds);
|
||||
/**
|
||||
* secret key
|
||||
*/
|
||||
private SecretKey secretKey;
|
||||
|
||||
/**
|
||||
* create token
|
||||
*/
|
||||
return Jwts.builder()
|
||||
.setSubject(authentication.getName())
|
||||
.claim(AUTHORITIES_KEY, "")
|
||||
.setExpiration(validity)
|
||||
.signWith(SignatureAlgorithm.HS256, secretKey)
|
||||
.compact();
|
||||
}
|
||||
/**
|
||||
* Token validity time(ms)
|
||||
*/
|
||||
private long tokenValidityInMilliseconds;
|
||||
|
||||
/**
|
||||
* Get auth Info
|
||||
*
|
||||
* @param token token
|
||||
* @return auth info
|
||||
*/
|
||||
public Authentication getAuthentication(String token) {
|
||||
/**
|
||||
* parse the payload of token
|
||||
*/
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
//use default secretKey for SHA-256
|
||||
if (customSecretKeyStr == null || DEFAULT_SECRET_FLAG.equals(customSecretKeyStr)) {
|
||||
this.secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
|
||||
} else {
|
||||
//use custom secretKey
|
||||
int size = customSecretKeyStr.length();
|
||||
int left = SHA_256_SECRET_CHAR_SIZE - size;
|
||||
if (left > 0) {
|
||||
//character for padding
|
||||
StringBuilder stringBuilder = new StringBuilder(customSecretKeyStr);
|
||||
for (int i = 0 ;i < left ; i ++){
|
||||
stringBuilder.append(i%10);
|
||||
}
|
||||
this.secretKey = Keys.hmacShaKeyFor(stringBuilder.toString().getBytes());
|
||||
}else {
|
||||
this.secretKey = Keys.hmacShaKeyFor(customSecretKeyStr.getBytes());
|
||||
}
|
||||
}
|
||||
this.tokenValidityInMilliseconds = 1000 * 60 * 30L;
|
||||
}
|
||||
|
||||
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));
|
||||
/**
|
||||
* Create token
|
||||
*
|
||||
* @param authentication auth info
|
||||
* @return token
|
||||
*/
|
||||
public String createToken(Authentication authentication) {
|
||||
/**
|
||||
* Current time
|
||||
*/
|
||||
long now = (new Date()).getTime();
|
||||
/**
|
||||
* Validity date
|
||||
*/
|
||||
Date validity;
|
||||
validity = new Date(now + this.tokenValidityInMilliseconds);
|
||||
|
||||
/**
|
||||
* create token
|
||||
*/
|
||||
return Jwts.builder()
|
||||
.setSubject(authentication.getName())
|
||||
.claim(AUTHORITIES_KEY, "")
|
||||
.setExpiration(validity)
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get auth Info
|
||||
*
|
||||
* @param token token
|
||||
* @return auth info
|
||||
*/
|
||||
public Authentication getAuthentication(String token) {
|
||||
/**
|
||||
* parse the payload of token
|
||||
*/
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList((String) claims.get(AUTHORITIES_KEY));
|
||||
|
||||
|
||||
User principal = new User(claims.getSubject(), "", authorities);
|
||||
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
|
||||
}
|
||||
User principal = new User(claims.getSubject(), "", authorities);
|
||||
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
|
||||
}
|
||||
|
||||
/**
|
||||
* validate token
|
||||
*
|
||||
* @param token token
|
||||
* @return whether valid
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.info("Invalid JWT signature.");
|
||||
log.trace("Invalid JWT signature trace: {}", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* validate token
|
||||
*
|
||||
* @param token token
|
||||
* @return whether valid
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (SignatureException e) {
|
||||
log.info("Invalid JWT signature.");
|
||||
log.trace("Invalid JWT signature trace: {}", e);
|
||||
} catch (MalformedJwtException e) {
|
||||
log.info("Invalid JWT token.");
|
||||
log.trace("Invalid JWT token trace: {}", e);
|
||||
} catch (ExpiredJwtException e) {
|
||||
log.info("Expired JWT token.");
|
||||
log.trace("Expired JWT token trace: {}", e);
|
||||
} catch (UnsupportedJwtException e) {
|
||||
log.info("Unsupported JWT token.");
|
||||
log.trace("Unsupported JWT token trace: {}", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.info("JWT token compact of handler are invalid.");
|
||||
log.trace("JWT token compact of handler are invalid trace: {}", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
nacos:
|
||||
cmdb:
|
||||
dumpTaskInterval: 3600
|
||||
eventTaskInterval: 10
|
||||
labelTaskInterval: 300
|
||||
loadDataAtStart: false
|
||||
security: # 删除此配置无需登录验证
|
||||
ignore:
|
||||
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**
|
||||
server:
|
||||
port: 8848
|
||||
tomcat:
|
||||
basedir: logs
|
||||
|
||||
db:
|
||||
num: 1
|
||||
|
@ -15,13 +10,20 @@ db:
|
|||
url:
|
||||
0: jdbc:mysql://${MYSQL-HOST:pig-mysql}:${MYSQL-PORT:3306}/${MYSQL-DB:pig_config}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
|
||||
|
||||
nacos:
|
||||
core:
|
||||
auth:
|
||||
system.type: nacos
|
||||
default.token.secret.key: SecretKey012345678901234567890123456789012345678901234567890123456789
|
||||
security:
|
||||
ignore:
|
||||
urls: /,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/**
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
platform: mysql
|
||||
security:
|
||||
enabled: true
|
||||
|
||||
server:
|
||||
tomcat:
|
||||
basedir: logs
|
||||
useAddressServer: true
|
||||
|
|
@ -30,6 +30,7 @@
|
|||
"generator-star-spacing": "off",
|
||||
"wrap-iife": "off",
|
||||
"arrow-parens": "off",
|
||||
"indent": "off"
|
||||
"indent": "off",
|
||||
"comma-dangle": "off"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,55 +27,56 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@alifd/next-theme-loader": "^1.3.1",
|
||||
"@babel/cli": "^7.2.3",
|
||||
"@babel/core": "^7.2.2",
|
||||
"@babel/plugin-proposal-decorators": "^7.2.3",
|
||||
"@babel/preset-env": "^7.2.3",
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"@babel/cli": "^7.7.7",
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
||||
"@babel/preset-env": "^7.7.7",
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.4",
|
||||
"babel-plugin-import": "^1.12.0",
|
||||
"babel-preset-react-app": "^6.1.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^2.0.2",
|
||||
"eslint": "^5.11.0",
|
||||
"eslint-config-ali": "^4.1.0",
|
||||
"eslint-config-prettier": "^3.3.0",
|
||||
"eslint-loader": "^2.1.1",
|
||||
"babel-plugin-import": "^1.13.0",
|
||||
"babel-preset-react-app": "^9.1.0",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"copy-webpack-plugin": "^5.1.1 ",
|
||||
"cross-env": "^6.0.3",
|
||||
"css-loader": "^3.4.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-ali": "^9.0.2",
|
||||
"eslint-config-prettier": "^6.8.0",
|
||||
"eslint-loader": "^3.0.3",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-prettier": "^3.0.0",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"file-loader": "^2.0.0",
|
||||
"eslint-plugin-react": "^7.17.0",
|
||||
"eslint-plugin-react-hooks": "^2.3.0",
|
||||
"file-loader": "^5.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"husky": "^1.1.4",
|
||||
"lint-staged": "^8.0.4",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"prettier": "1.15.2",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"uglifyjs-webpack-plugin": "^2.1.0",
|
||||
"url-loader": "^1.1.2",
|
||||
"webpack": "^4.28.2",
|
||||
"webpack-cli": "^3.1.2",
|
||||
"webpack-dev-server": "^3.1.13"
|
||||
"husky": "^3.1.0",
|
||||
"lint-staged": "^9.5.0",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"prettier": "1.19.1",
|
||||
"sass-loader": "^8.0.0",
|
||||
"style-loader": "^1.1.2",
|
||||
"uglifyjs-webpack-plugin": "^2.2.0",
|
||||
"url-loader": "^3.0.0",
|
||||
"webpack": "^4.41.4",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-dev-server": "^3.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alifd/next": "^1.15.12",
|
||||
"@alifd/next": "^1.17.4",
|
||||
"axios": "^0.18.0",
|
||||
"jquery": "^3.3.1",
|
||||
"moment": "^2.23.0",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-redux": "^5.1.1",
|
||||
"react-router": "^4.3.1",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-redux": "^7.1.3",
|
||||
"react-router": "^5.1.2",
|
||||
"react-router-dom": "^5.1.2",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^4.0.1",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"yamljs": "^0.3.0"
|
||||
}
|
||||
|
|
|
@ -109,14 +109,17 @@ class NewNameSpace extends React.Component {
|
|||
this.setState({
|
||||
disabled: true,
|
||||
});
|
||||
let { customNamespaceId } = values;
|
||||
if (!customNamespaceId) {
|
||||
customNamespaceId = '';
|
||||
}
|
||||
request({
|
||||
type: 'post',
|
||||
url: 'v1/console/namespaces',
|
||||
type: 'get',
|
||||
url: 'v1/console/namespaces?checkNamespaceIdExist=true',
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
beforeSend: () => this.openLoading(),
|
||||
data: {
|
||||
namespaceName: values.namespaceShowName,
|
||||
namespaceDesc: values.namespaceDesc,
|
||||
customNamespaceId,
|
||||
},
|
||||
success: res => {
|
||||
this.disabled = false;
|
||||
|
@ -124,13 +127,38 @@ class NewNameSpace extends React.Component {
|
|||
disabled: false,
|
||||
});
|
||||
if (res === true) {
|
||||
this.closeDialog();
|
||||
this.props.getNameSpaces();
|
||||
this.refreshNameSpace(); // 刷新全局namespace
|
||||
} else {
|
||||
Dialog.alert({
|
||||
title: locale.notice,
|
||||
content: res.message,
|
||||
content: locale.namespaceIdAlreadyExist,
|
||||
});
|
||||
} else {
|
||||
request({
|
||||
type: 'post',
|
||||
url: 'v1/console/namespaces',
|
||||
contentType: 'application/x-www-form-urlencoded',
|
||||
beforeSend: () => this.openLoading(),
|
||||
data: {
|
||||
customNamespaceId,
|
||||
namespaceName: values.namespaceShowName,
|
||||
namespaceDesc: values.namespaceDesc,
|
||||
},
|
||||
success: res => {
|
||||
this.disabled = false;
|
||||
this.setState({
|
||||
disabled: false,
|
||||
});
|
||||
if (res === true) {
|
||||
this.closeDialog();
|
||||
this.props.getNameSpaces();
|
||||
this.refreshNameSpace(); // 刷新全局namespace
|
||||
} else {
|
||||
Dialog.alert({
|
||||
title: locale.notice,
|
||||
content: locale.newnamespceFailedMessage,
|
||||
});
|
||||
}
|
||||
},
|
||||
complete: () => this.closeLoading(),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -164,6 +192,32 @@ class NewNameSpace extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
validateNamespzecId(rule, value, callback) {
|
||||
if (!value || value.trim() === '') {
|
||||
callback();
|
||||
} else {
|
||||
const { locale = {} } = this.props;
|
||||
if (value.length > 128) {
|
||||
callback(locale.namespaceIdTooLong);
|
||||
}
|
||||
const chartReg = /^[\w-]+/g;
|
||||
const matchResult = value.match(chartReg);
|
||||
if (matchResult) {
|
||||
if (matchResult.length > 1) {
|
||||
callback(locale.input);
|
||||
} else {
|
||||
if (value.length !== matchResult[0].length) {
|
||||
callback(locale.input);
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
} else {
|
||||
callback(locale.input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale = {} } = this.props;
|
||||
const footer = (
|
||||
|
@ -193,6 +247,14 @@ class NewNameSpace extends React.Component {
|
|||
style={{ width: '100%', position: 'relative' }}
|
||||
visible={this.state.loading}
|
||||
>
|
||||
<FormItem label={locale.namespaceId} {...formItemLayout}>
|
||||
<Input
|
||||
{...this.field.init('customNamespaceId', {
|
||||
rules: [{ validator: this.validateNamespzecId.bind(this) }],
|
||||
})}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label={locale.name} required {...formItemLayout}>
|
||||
<Input
|
||||
{...this.field.init('namespaceShowName', {
|
||||
|
|
|
@ -125,9 +125,7 @@ class RegionGroup extends React.Component {
|
|||
? false
|
||||
: window.location.search.indexOf('hideTopbar=') === -1,
|
||||
},
|
||||
() => {
|
||||
this.setRegionWidth();
|
||||
}
|
||||
() => this.setRegionWidth()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,3 +23,15 @@ export const GET_STATE = 'GET_STATE';
|
|||
|
||||
export const GET_SUBSCRIBERS = 'GET_SUBSCRIBERS';
|
||||
export const REMOVE_SUBSCRIBERS = 'REMOVE_SUBSCRIBERS';
|
||||
|
||||
export const UPDATE_USER = 'UPDATE_USER';
|
||||
export const SIGN_IN = 'SIGN_IN';
|
||||
export const USER_LIST = 'USER_LIST';
|
||||
|
||||
export const ROLE_LIST = 'ROLE_LIST';
|
||||
|
||||
export const PERMISSIONS_LIST = 'PERMISSIONS_LIST';
|
||||
|
||||
export const GET_NAMESPACES = 'GET_NAMESPACES';
|
||||
|
||||
export const GET_CONFIGURATION = 'GET_CONFIGURATION';
|
||||
|
|
|
@ -12,9 +12,16 @@
|
|||
*/
|
||||
|
||||
import projectConfig from './config';
|
||||
import moment from 'moment';
|
||||
import $ from 'jquery';
|
||||
import i18DocObj from './i18ndoc';
|
||||
import { Message } from '@alifd/next';
|
||||
|
||||
function goLogin() {
|
||||
const url = window.location.href;
|
||||
localStorage.removeItem('token');
|
||||
const base_url = url.split('#')[0];
|
||||
console.log('base_url', base_url);
|
||||
window.location = `${base_url}#/login`;
|
||||
}
|
||||
|
||||
const global = window;
|
||||
|
||||
|
@ -205,120 +212,6 @@ const nacosUtils = (function(_global) {
|
|||
};
|
||||
})(global);
|
||||
|
||||
const aliwareIntl = (function(_global) {
|
||||
/**
|
||||
* 国际化构造方法
|
||||
* @param {Object} options 配置信息
|
||||
*/
|
||||
function AliwareI18n(options) {
|
||||
// let currentLocal = options.currentLocal || navigator.language || navigator.userLanguage;
|
||||
|
||||
const nowData = options.locals;
|
||||
this.nowData = nowData;
|
||||
this.setMomentLocale(this.currentLanguageCode);
|
||||
}
|
||||
|
||||
let aliwareLocal = aliwareGetCookieByKeyName('aliyun_lang') || 'zh';
|
||||
let aliwareLocalSite = aliwareGetCookieByKeyName('aliyun_country') || 'cn';
|
||||
aliwareLocal = aliwareLocal.toLowerCase();
|
||||
aliwareLocalSite = aliwareLocalSite.toLowerCase();
|
||||
// 当前语言
|
||||
AliwareI18n.prototype.currentLocal = aliwareLocal;
|
||||
// 当前地区
|
||||
AliwareI18n.prototype.currentSite = aliwareLocalSite;
|
||||
// 当前语言-地区
|
||||
AliwareI18n.prototype.currentLanguageCode =
|
||||
aliwareGetCookieByKeyName('docsite_language') || `${aliwareLocal}-${aliwareLocalSite}`;
|
||||
/**
|
||||
* 通过key获取对应国际化文案
|
||||
* @param {String} key 国际化key
|
||||
*/
|
||||
AliwareI18n.prototype.get = function(key) {
|
||||
return this.nowData[key];
|
||||
};
|
||||
/**
|
||||
* 修改国际化文案数据
|
||||
* @param {String} local 语言信息
|
||||
*/
|
||||
AliwareI18n.prototype.changeLanguage = function(local) {
|
||||
this.nowData = i18DocObj[local] || {};
|
||||
};
|
||||
/**
|
||||
* 数字国际化
|
||||
* @param {Number} num 数字
|
||||
*/
|
||||
AliwareI18n.prototype.intlNumberFormat = function(num) {
|
||||
if (typeof Intl !== 'object' || typeof Intl.NumberFormat !== 'function') {
|
||||
return num;
|
||||
}
|
||||
try {
|
||||
return new Intl.NumberFormat(this.currentLanguageCode).format(num || 0);
|
||||
} catch (error) {
|
||||
return num;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 时间戳格式化
|
||||
* @param {Number} num 时间戳
|
||||
* @param {Object} initOption 配置信息
|
||||
*/
|
||||
AliwareI18n.prototype.intlTimeFormat = function(num = Date.now(), initOption = {}) {
|
||||
try {
|
||||
const date = Object.prototype.toString.call(num) === '[object Date]' ? num : new Date(num);
|
||||
const options = Object.assign(
|
||||
{},
|
||||
{
|
||||
// weekday: "short",
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
},
|
||||
initOption
|
||||
);
|
||||
return date.toLocaleDateString(this.currentLanguageCode, options);
|
||||
} catch (error) {
|
||||
return typeof moment === 'function' ? moment(num).format() : '--';
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 获取当前时间格式
|
||||
* @param {String} language 语言信息: zh/en
|
||||
*/
|
||||
AliwareI18n.prototype.getIntlTimeFormat = function(_language) {
|
||||
const language = _language || aliwareLocal;
|
||||
const langObj = {
|
||||
zh: 'YYYY年M月D日 HH:mm:ss',
|
||||
en: 'MMM D, YYYY, h:mm:ss A',
|
||||
default: 'YYYY-MM-DD HH:mm:ss',
|
||||
};
|
||||
return langObj[language] ? langObj[language] : langObj.default;
|
||||
};
|
||||
/**
|
||||
* 设置moment的locale
|
||||
* @param {String} languageCode 语言信息: zh-ch/en-us
|
||||
*/
|
||||
AliwareI18n.prototype.setMomentLocale = function(languageCode) {
|
||||
if (Object.prototype.toString.call(moment) === '[object Function]') {
|
||||
moment.locale(languageCode || this.currentLanguageCode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return new AliwareI18n({
|
||||
currentLocal: `${aliwareLocal}`,
|
||||
locals:
|
||||
i18DocObj[AliwareI18n.prototype.currentLanguageCode] ||
|
||||
i18DocObj['en-us'] ||
|
||||
i18DocObj['zh-cn'] ||
|
||||
{},
|
||||
});
|
||||
})(global);
|
||||
|
||||
/**
|
||||
* 获取url中的参数
|
||||
*/
|
||||
|
@ -561,7 +454,7 @@ const request = (function(_global) {
|
|||
} catch (e) {}
|
||||
// 设置自动loading效果
|
||||
if (serviceObj.autoLoading) {
|
||||
nacosUtils.openLoading();
|
||||
// nacosUtils.openLoading();
|
||||
const prevComplete = config.complete;
|
||||
config.complete = function() {
|
||||
nacosUtils.closeLoading();
|
||||
|
@ -597,11 +490,22 @@ const request = (function(_global) {
|
|||
|
||||
// 处理后置中间件
|
||||
config = handleMiddleWare.apply(this, [config, ...args, middlewareBackList]);
|
||||
let token = {};
|
||||
try {
|
||||
token = JSON.parse(localStorage.token);
|
||||
} catch (e) {
|
||||
console.log('Token Erro', localStorage.token, e);
|
||||
goLogin();
|
||||
}
|
||||
const { accessToken = '' } = token;
|
||||
const [url, paramsStr = ''] = config.url.split('?');
|
||||
const params = paramsStr.split('&');
|
||||
params.push(`accessToken=${accessToken}`);
|
||||
|
||||
return $.ajax(
|
||||
Object.assign({}, config, {
|
||||
type: config.type,
|
||||
url: config.url,
|
||||
url: [url, params.join('&')].join('?'),
|
||||
data: config.data || '',
|
||||
dataType: config.dataType || 'json',
|
||||
beforeSend(xhr) {
|
||||
|
@ -615,17 +519,17 @@ const request = (function(_global) {
|
|||
success => {},
|
||||
error => {
|
||||
// 处理403 forbidden
|
||||
if (error && (error.status === 403 || error.status === 401)) {
|
||||
// 跳转至login页
|
||||
// TODO: 用 react-router 重写,改造成本比较高,这里先hack
|
||||
const url = window.location.href;
|
||||
// TODO: 后端返回细致的错误码,如果原始密码不对 不应该直接跳到登陆页
|
||||
if (url.includes('password')) {
|
||||
return;
|
||||
}
|
||||
const base_url = url.split('#')[0];
|
||||
window.location = `${base_url}#/login`;
|
||||
const { status, responseJSON = {} } = error || {};
|
||||
if (responseJSON.message) {
|
||||
Message.error(responseJSON.message);
|
||||
}
|
||||
if (
|
||||
[401, 403].includes(status) &&
|
||||
['unknown user!', 'token invalid', 'token expired!'].includes(responseJSON.message)
|
||||
) {
|
||||
goLogin();
|
||||
}
|
||||
return error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -645,10 +549,9 @@ export {
|
|||
nacosEvent,
|
||||
nacosUtils,
|
||||
aliwareGetCookieByKeyName,
|
||||
aliwareIntl,
|
||||
removeParams,
|
||||
getParams,
|
||||
setParam,
|
||||
setParams,
|
||||
removeParams,
|
||||
request,
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -30,7 +30,6 @@ import { LANGUAGE_KEY, REDUX_DEVTOOLS } from './constants';
|
|||
|
||||
import Login from './pages/Login';
|
||||
import Namespace from './pages/NameSpace';
|
||||
import Password from './pages/Password';
|
||||
import Newconfig from './pages/ConfigurationManagement/NewConfig';
|
||||
import Configsync from './pages/ConfigurationManagement/ConfigSync';
|
||||
import Configdetail from './pages/ConfigurationManagement/ConfigDetail';
|
||||
|
@ -44,6 +43,9 @@ import ServiceList from './pages/ServiceManagement/ServiceList';
|
|||
import ServiceDetail from './pages/ServiceManagement/ServiceDetail';
|
||||
import SubscriberList from './pages/ServiceManagement/SubscriberList';
|
||||
import ClusterNodeList from './pages/ClusterManagement/ClusterNodeList';
|
||||
import UserManagement from './pages/AuthorityControl/UserManagement';
|
||||
import PermissionsManagement from './pages/AuthorityControl/PermissionsManagement';
|
||||
import RolesManagement from './pages/AuthorityControl/RolesManagement';
|
||||
import Welcome from './pages/Welcome/Welcome';
|
||||
|
||||
import reducers from './reducers';
|
||||
|
@ -65,17 +67,13 @@ const reducer = combineReducers({
|
|||
|
||||
const store = createStore(
|
||||
reducer,
|
||||
compose(
|
||||
applyMiddleware(thunk),
|
||||
window[REDUX_DEVTOOLS] ? window[REDUX_DEVTOOLS]() : f => f
|
||||
)
|
||||
compose(applyMiddleware(thunk), window[REDUX_DEVTOOLS] ? window[REDUX_DEVTOOLS]() : f => f)
|
||||
);
|
||||
|
||||
const MENU = [
|
||||
{ path: '/', exact: true, render: () => <Redirect to="/welcome" /> },
|
||||
{ path: '/welcome', component: Welcome },
|
||||
{ path: '/namespace', component: Namespace },
|
||||
{ path: '/password', component: Password },
|
||||
{ path: '/newconfig', component: Newconfig },
|
||||
{ path: '/configsync', component: Configsync },
|
||||
{ path: '/configdetail', component: Configdetail },
|
||||
|
@ -89,12 +87,12 @@ const MENU = [
|
|||
{ path: '/serviceDetail', component: ServiceDetail },
|
||||
{ path: '/subscriberList', component: SubscriberList },
|
||||
{ path: '/clusterManagement', component: ClusterNodeList },
|
||||
{ path: '/userManagement', component: UserManagement },
|
||||
{ path: '/rolesManagement', component: RolesManagement },
|
||||
{ path: '/permissionsManagement', component: PermissionsManagement },
|
||||
];
|
||||
|
||||
@connect(
|
||||
state => ({ ...state.locale }),
|
||||
{ changeLanguage }
|
||||
)
|
||||
@connect(state => ({ ...state.locale }), { changeLanguage })
|
||||
class App extends React.Component {
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
|
|
|
@ -1217,10 +1217,6 @@ form.vertical-margin-lg .form-group {
|
|||
border-color: #e0e0e0 !important;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.row-bg-green {
|
||||
background-color: #e4fdda;
|
||||
}
|
||||
|
|
|
@ -18,14 +18,13 @@ import { connect } from 'react-redux';
|
|||
import { ConfigProvider, Dropdown, Menu } from '@alifd/next';
|
||||
import siteConfig from '../config';
|
||||
import { changeLanguage } from '@/reducers/locale';
|
||||
import PasswordReset from '../pages/AuthorityControl/UserManagement/PasswordReset';
|
||||
import { passwordReset } from '../reducers/authority';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@withRouter
|
||||
@connect(
|
||||
state => ({ ...state.locale }),
|
||||
{ changeLanguage }
|
||||
)
|
||||
@connect(state => ({ ...state.locale }), { changeLanguage })
|
||||
@ConfigProvider.config
|
||||
class Header extends React.Component {
|
||||
static displayName = 'Header';
|
||||
|
@ -38,6 +37,8 @@ class Header extends React.Component {
|
|||
changeLanguage: PropTypes.func,
|
||||
};
|
||||
|
||||
state = { passwordResetUser: '' };
|
||||
|
||||
switchLang = () => {
|
||||
const { language = 'en-US', changeLanguage } = this.props;
|
||||
const currentLanguage = language === 'en-US' ? 'zh-CN' : 'en-US';
|
||||
|
@ -50,16 +51,23 @@ class Header extends React.Component {
|
|||
};
|
||||
|
||||
changePassword = () => {
|
||||
this.props.history.push('/password');
|
||||
this.setState({
|
||||
passwordResetUser: this.getUsername(),
|
||||
});
|
||||
};
|
||||
|
||||
getUsername = () => {
|
||||
const token = window.localStorage.getItem('token');
|
||||
if (token) {
|
||||
const base64Url = token.split('.')[1];
|
||||
const [, base64Url = ''] = token.split('.');
|
||||
const base64 = base64Url.replace('-', '+').replace('_', '/');
|
||||
const parsedToken = JSON.parse(window.atob(base64));
|
||||
return parsedToken.sub;
|
||||
try {
|
||||
const parsedToken = JSON.parse(window.atob(base64));
|
||||
return parsedToken.sub;
|
||||
} catch (e) {
|
||||
delete localStorage.token;
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
@ -71,6 +79,7 @@ class Header extends React.Component {
|
|||
location: { pathname },
|
||||
} = this.props;
|
||||
const { home, docs, blog, community, languageSwitchButton } = locale;
|
||||
const { passwordResetUser = '' } = this.state;
|
||||
const BASE_URL = `https://nacos.io/${language.toLocaleLowerCase()}/`;
|
||||
const NAV_MENU = [
|
||||
{ id: 1, title: home, link: BASE_URL },
|
||||
|
@ -79,45 +88,56 @@ class Header extends React.Component {
|
|||
{ id: 4, title: community, link: `${BASE_URL}community/index.html` },
|
||||
];
|
||||
return (
|
||||
<header className="header-container header-container-primary">
|
||||
<div className="header-body">
|
||||
<a
|
||||
href={`https://nacos.io/${language.toLocaleLowerCase()}/`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src="img/logo-2000-390.svg"
|
||||
className="logo"
|
||||
alt={siteConfig.name}
|
||||
title={siteConfig.name}
|
||||
/>
|
||||
</a>
|
||||
{/* if is login page, we will show logout */}
|
||||
{pathname !== '/login' && (
|
||||
<Dropdown trigger={<div className="logout">{this.getUsername()}</div>}>
|
||||
<Menu>
|
||||
<Menu.Item onClick={this.logout}>{locale.logout}</Menu.Item>
|
||||
<Menu.Item onClick={this.changePassword}>{locale.changePassword}</Menu.Item>
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
)}
|
||||
<span className="language-switch language-switch-primary" onClick={this.switchLang}>
|
||||
{languageSwitchButton}
|
||||
</span>
|
||||
<div className="header-menu header-menu-open">
|
||||
<ul>
|
||||
{NAV_MENU.map(item => (
|
||||
<li key={item.id} className="menu-item menu-item-primary">
|
||||
<a href={item.link} target="_blank" rel="noopener noreferrer">
|
||||
{item.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<>
|
||||
<header className="header-container header-container-primary">
|
||||
<div className="header-body">
|
||||
<a
|
||||
href={`https://nacos.io/${language.toLocaleLowerCase()}/`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<img
|
||||
src="img/logo-2000-390.svg"
|
||||
className="logo"
|
||||
alt={siteConfig.name}
|
||||
title={siteConfig.name}
|
||||
/>
|
||||
</a>
|
||||
{/* if is login page, we will show logout */}
|
||||
{pathname !== '/login' && (
|
||||
<Dropdown trigger={<div className="logout">{this.getUsername()}</div>}>
|
||||
<Menu>
|
||||
<Menu.Item onClick={this.logout}>{locale.logout}</Menu.Item>
|
||||
<Menu.Item onClick={this.changePassword}>{locale.changePassword}</Menu.Item>
|
||||
</Menu>
|
||||
</Dropdown>
|
||||
)}
|
||||
<span className="language-switch language-switch-primary" onClick={this.switchLang}>
|
||||
{languageSwitchButton}
|
||||
</span>
|
||||
<div className="header-menu header-menu-open">
|
||||
<ul>
|
||||
{NAV_MENU.map(item => (
|
||||
<li key={item.id} className="menu-item menu-item-primary">
|
||||
<a href={item.link} target="_blank" rel="noopener noreferrer">
|
||||
{item.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
<PasswordReset
|
||||
username={passwordResetUser}
|
||||
onOk={user =>
|
||||
passwordReset(user).then(res => {
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.setState({ passwordResetUser: undefined })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,506 +13,127 @@
|
|||
|
||||
import React from 'react';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ConfigProvider, Icon } from '@alifd/next';
|
||||
import Header from './Header';
|
||||
import $ from 'jquery';
|
||||
import { connect } from 'react-redux';
|
||||
import { setParams } from '../globalLib';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ConfigProvider, Icon, Menu } from '@alifd/next';
|
||||
import Header from './Header';
|
||||
import { getState } from '../reducers/base';
|
||||
import _menu from '../menu';
|
||||
import getMenuData from './menu';
|
||||
|
||||
import './index.scss';
|
||||
const { SubMenu, Item } = Menu;
|
||||
|
||||
@withRouter
|
||||
@connect(
|
||||
state => ({ ...state.locale, ...state.base }),
|
||||
{ getState }
|
||||
)
|
||||
@connect(state => ({ ...state.locale, ...state.base }), { getState })
|
||||
@ConfigProvider.config
|
||||
class MainLayout extends React.Component {
|
||||
static displayName = 'MainLayout';
|
||||
|
||||
static propTypes = {
|
||||
history: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
locale: PropTypes.object,
|
||||
children: PropTypes.any,
|
||||
location: PropTypes.object,
|
||||
history: PropTypes.object,
|
||||
version: PropTypes.any,
|
||||
functionMode: PropTypes.any,
|
||||
getState: PropTypes.func,
|
||||
functionMode: PropTypes.string,
|
||||
children: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.deepNav = [];
|
||||
this.oneLevelNavArr = {}; // 平行导航map
|
||||
this.state = {
|
||||
navList: [..._menu.data],
|
||||
leftBarClose: false,
|
||||
showLink: null,
|
||||
navRow: [],
|
||||
noChild: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getState();
|
||||
this.refreshNav();
|
||||
}
|
||||
|
||||
goBack() {
|
||||
this.props.history.goBack();
|
||||
}
|
||||
|
||||
nacosToggleNav(id, event) {
|
||||
event.preventDefault();
|
||||
const nowNav = document.getElementById(id);
|
||||
const iconClass = nowNav.querySelector('.iconshow');
|
||||
const subNav = nowNav.querySelector('.subnavlist');
|
||||
const { classList } = iconClass;
|
||||
let tmpClassName = 'iconshow ';
|
||||
for (let i = 0; i < classList.length; i++) {
|
||||
if (classList[i] === 'icon-arrow-down') {
|
||||
subNav.style.display = 'none';
|
||||
subNav.className += ' hidden';
|
||||
tmpClassName += 'icon-arrow-right';
|
||||
}
|
||||
if (classList[i] === 'icon-arrow-right') {
|
||||
tmpClassName += 'icon-arrow-down';
|
||||
subNav.className = subNav.className.replace(/hidden/g, '');
|
||||
subNav.style.display = 'block';
|
||||
}
|
||||
}
|
||||
iconClass.className = tmpClassName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the back button
|
||||
* TODO: this.props.history.goBack(); ???
|
||||
* @param url
|
||||
*/
|
||||
nacosGoBack(url) {
|
||||
const params = window.location.hash.split('?')[1];
|
||||
const urlArr = params.split('&') || [];
|
||||
const queryParams = [];
|
||||
for (let i = 0; i < urlArr.length; i++) {
|
||||
if (
|
||||
urlArr[i].split('=')[0] !== '_k' &&
|
||||
urlArr[i].split('=')[0] !== 'dataId' &&
|
||||
urlArr[i].split('=')[0] !== 'group'
|
||||
) {
|
||||
if (urlArr[i].split('=')[0] === 'searchDataId') {
|
||||
queryParams.push(`dataId=${urlArr[i].split('=')[1]}`);
|
||||
} else if (urlArr[i].split('=')[0] === 'searchGroup') {
|
||||
queryParams.push(`group=${urlArr[i].split('=')[1]}`);
|
||||
} else {
|
||||
queryParams.push(urlArr[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (localStorage.getItem('namespace')) {
|
||||
queryParams.push(`namespace=${localStorage.getItem('namespace')}`);
|
||||
}
|
||||
this.props.history.push(`/${url}?${queryParams.join('&')}`);
|
||||
}
|
||||
|
||||
nacosEnterBack() {
|
||||
document.getElementById('backarrow').style.color = '#09c';
|
||||
}
|
||||
|
||||
nacosOutBack() {
|
||||
document.getElementById('backarrow').style.color = '#546478';
|
||||
}
|
||||
|
||||
nacosToggleLeftBar() {
|
||||
if (!this.nacosOutDom) return;
|
||||
if (!this.state.leftBarClose) {
|
||||
// 关闭
|
||||
this.nacosOutDom.className = 'viewFramework-product';
|
||||
this.nacosLeftBarDom.style.width = 0;
|
||||
this.nacosBodyDom.style.left = 0;
|
||||
this.nacosToggleIconDom.style.left = 0;
|
||||
} else {
|
||||
this.nacosOutDom.className = 'viewFramework-product viewFramework-product-col-1';
|
||||
this.nacosLeftBarDom.style.width = '180px';
|
||||
this.nacosBodyDom.style.left = '180px';
|
||||
this.nacosToggleIconDom.style.left = '160px';
|
||||
}
|
||||
|
||||
this.setState({
|
||||
leftBarClose: !this.state.leftBarClose,
|
||||
});
|
||||
}
|
||||
|
||||
navTo(url) {
|
||||
if (url !== '/configdetail' && url !== '/configeditor') {
|
||||
// 二级菜单不清空
|
||||
setParams({
|
||||
dataId: '',
|
||||
group: '',
|
||||
});
|
||||
}
|
||||
|
||||
const params = window.location.hash.split('?')[1];
|
||||
const urlArr = params.split('&') || [];
|
||||
const queryParams = [];
|
||||
for (let i = 0; i < urlArr.length; i++) {
|
||||
if (urlArr[i].split('=')[0] !== '_k') {
|
||||
queryParams.push(urlArr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
this.props.history.push(`${url}?${queryParams.join('&')}`);
|
||||
const { search } = this.props.location;
|
||||
this.props.history.push([url, search].join(''));
|
||||
}
|
||||
|
||||
nacosSetSpecialNav(item) {
|
||||
item.children.forEach(_item => {
|
||||
const obj = _item;
|
||||
isCurrentPath(url) {
|
||||
const { location } = this.props;
|
||||
return url === location.pathname ? 'current-path' : undefined;
|
||||
}
|
||||
|
||||
if (obj.dontUseChild === true) {
|
||||
obj.parentName = item.title;
|
||||
obj.parentId = item.id;
|
||||
obj.parentPath = `/${item.id}`;
|
||||
this.deepNav.push(obj);
|
||||
}
|
||||
if (_item.children) {
|
||||
this.nacosSetSpecialNav(_item);
|
||||
defaultOpenKeys() {
|
||||
const MenuData = getMenuData(this.props.functionMode);
|
||||
for (let i = 0, len = MenuData.length; i < len; i++) {
|
||||
const { children } = MenuData[i];
|
||||
if (children && children.filter(({ url }) => url === this.props.location.pathname).length) {
|
||||
return String(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isShowGoBack() {
|
||||
const urls = [];
|
||||
const MenuData = getMenuData(this.props.functionMode);
|
||||
MenuData.forEach(item => {
|
||||
if (item.url) urls.push(item.url);
|
||||
if (item.children) item.children.forEach(({ url }) => urls.push(url));
|
||||
});
|
||||
}
|
||||
|
||||
nacosNavAct(serviceName, match, location) {
|
||||
if (!match) {
|
||||
const formatpath = location.pathname.substr(1); // 得到当前路径
|
||||
const nowpathobj = this.oneLevelNavArr[formatpath]; // 根据平行导航匹配父类
|
||||
if (nowpathobj) {
|
||||
if (nowpathobj.parent === serviceName) {
|
||||
// 如果父类等于当前的导航则高亮
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
nacosLoopNavDeeply(data, parentServiceName) {
|
||||
// 深度遍历获取所有的导航数据
|
||||
data.forEach(item => {
|
||||
if (item) {
|
||||
const navObj = item;
|
||||
|
||||
const _parentServiceName = item.serviceName;
|
||||
navObj.parentServiceName = parentServiceName;
|
||||
this.oneLevelNavArr[item.serviceName] = navObj; // 得到每一个层级的导航映射
|
||||
if (item.children && item.children.length > 0) {
|
||||
this.nacosLoopNavDeeply(item.children, _parentServiceName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
activeNav(id) {
|
||||
if (this.preActNav) {
|
||||
this.preActNav.removeClass('active');
|
||||
}
|
||||
const nowNav = $(`#${id}`);
|
||||
nowNav.addClass('active');
|
||||
this.preActNav = nowNav;
|
||||
}
|
||||
|
||||
nacosLoopNav(data, _index = 0, parent) {
|
||||
const { locale = {}, location = {} } = this.props;
|
||||
const { pathname } = location;
|
||||
let index = _index;
|
||||
// 遍历导航,只显示2级
|
||||
const self = this;
|
||||
return data.map(item => {
|
||||
if (!item) return '';
|
||||
index++;
|
||||
if (item.dontUseChild === true) return '';
|
||||
if (item.children && item.children.length > 0) {
|
||||
if (item.isVirtual) {
|
||||
// 如果是虚拟菜单需要增加展开箭头
|
||||
const icon = item.isExtend ? (
|
||||
<span className="icon-arrow-down iconshow" />
|
||||
) : (
|
||||
<span className="icon-arrow-right iconshow" />
|
||||
);
|
||||
const hiddenClass = item.isExtend ? '' : 'hidden';
|
||||
return (
|
||||
<li
|
||||
style={{ display: item.enable ? 'block' : 'none' }}
|
||||
key={`${item.serviceName}`}
|
||||
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
|
||||
id={`${item.serviceName}`}
|
||||
>
|
||||
<div>
|
||||
<a href="" onClick={this.nacosToggleNav.bind(this, item.serviceName)}>
|
||||
<div className="nav-icon">{icon}</div>
|
||||
<div className="nav-title">{locale[item.serviceName]}</div>
|
||||
</a>
|
||||
</div>
|
||||
<ul className={`subnavlist ${hiddenClass}`}>
|
||||
{self.nacosLoopNav(item.children, index)}
|
||||
</ul>
|
||||
</li>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<li
|
||||
className={pathname === `/${item.serviceName}` ? 'selected' : ''}
|
||||
key={`${item.serviceName}`}
|
||||
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
|
||||
onClick={this.navTo.bind(this, `/${item.serviceName}`)}
|
||||
>
|
||||
<a
|
||||
href="javascript:;"
|
||||
id={`${item.serviceName}`}
|
||||
onClick={this.activeNav.bind(this, `nav${index}`)}
|
||||
>
|
||||
<div className="nav-icon" />
|
||||
<div className="nav-title">{locale[item.serviceName]}</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<li
|
||||
className={pathname === `/${item.serviceName}` ? 'selected' : ''}
|
||||
key={`${item.serviceName}`}
|
||||
data-spm-click={`gostr=/aliyun;locaid=${item.serviceName}`}
|
||||
onClick={this.navTo.bind(this, `/${item.serviceName}`)}
|
||||
>
|
||||
<a
|
||||
href={'javascript:;'}
|
||||
id={`${item.serviceName}`}
|
||||
onClick={this.activeNav.bind(this, `nav${index}`)}
|
||||
>
|
||||
<div className="nav-icon" />
|
||||
<div className="nav-title">{locale[item.serviceName]}</div>
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
nacosGetNav(navList) {
|
||||
let navRow = ''; // 导航
|
||||
if (navList.length > 0) {
|
||||
navRow = <ul>{this.nacosLoopNav(navList)}</ul>;
|
||||
this.nacosLoopNavDeeply(navList); // 深度遍历导航树获得平行map
|
||||
}
|
||||
return navRow;
|
||||
}
|
||||
|
||||
renderNav() {
|
||||
const { navList } = this.state;
|
||||
this.nacosLeftBarDom = document.getElementById('viewFramework-product-navbar');
|
||||
this.nacosBodyDom = document.getElementById('viewFramework-product-body');
|
||||
this.nacosToggleIconDom = document.getElementById('viewFramework-product-navbar-collapse');
|
||||
this.nacosOutDom = document.getElementById('page-header-mask');
|
||||
const defaultNav = '/configurationManagement';
|
||||
this.props.history.listen(location => {
|
||||
if (this.preSimplePath && this.preSimplePath !== '/') {
|
||||
if (location.pathname.indexOf(this.preSimplePath) !== -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const simplePath = window.location.hash.split('?')[0];
|
||||
const navName = simplePath.substr('2');
|
||||
this.preSimplePath = simplePath;
|
||||
|
||||
if (navName === '') {
|
||||
this.props.history.push(defaultNav);
|
||||
setTimeout(() => {
|
||||
this.activeNav('configurationManagement');
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const nowNavObj = this.oneLevelNavArr[navName];
|
||||
if (!nowNavObj) {
|
||||
this.setState({
|
||||
noChild: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { parentServiceName } = nowNavObj;
|
||||
|
||||
const parentNav = this.oneLevelNavArr[parentServiceName];
|
||||
if (simplePath !== '/' && nowNavObj && parentNav && !parentNav.isVirtual) {
|
||||
this.setState({
|
||||
showLink: (
|
||||
<div>
|
||||
<Icon
|
||||
type="arrow-left"
|
||||
onClick={this.nacosGoBack.bind(this, parentServiceName)}
|
||||
id={'backarrow'}
|
||||
onMouseOver={this.nacosEnterBack.bind(this)}
|
||||
onMouseLeave={this.nacosOutBack.bind(this)}
|
||||
style={{
|
||||
marginLeft: 77,
|
||||
marginTop: 0,
|
||||
fontWeight: 'bold',
|
||||
cursor: 'pointer',
|
||||
color: '#546478',
|
||||
fontSize: '20px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
||||
navRow: <ul>{this.nacosLoopNav([nowNavObj])}</ul>,
|
||||
});
|
||||
setTimeout(() => {
|
||||
const navid = navName;
|
||||
this.activeNav(navid);
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showLink: null,
|
||||
navRow: <ul>{this.nacosLoopNav(navList)}</ul>,
|
||||
});
|
||||
setTimeout(() => {
|
||||
const navid = navName;
|
||||
this.activeNav(navid);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
refreshNav() {
|
||||
const { navList } = this.state;
|
||||
const { location, history, functionMode } = this.props;
|
||||
const [configUrl, serviceUrl, clusterUrl] = [
|
||||
'/configurationManagement',
|
||||
'/serviceManagement',
|
||||
'/clusterManagement',
|
||||
];
|
||||
this.setState(
|
||||
{
|
||||
navList: navList.map(item => {
|
||||
if (
|
||||
item.serviceName === 'configurationManagementVirtual' &&
|
||||
(functionMode === null || functionMode === 'config')
|
||||
) {
|
||||
item.enable = true;
|
||||
}
|
||||
if (
|
||||
item.serviceName === 'serviceManagementVirtual' &&
|
||||
(functionMode === null || functionMode === 'naming')
|
||||
) {
|
||||
item.enable = true;
|
||||
}
|
||||
if (
|
||||
item.serviceName === 'clusterManagementVirtual' &&
|
||||
(functionMode === null || functionMode === 'cluster')
|
||||
) {
|
||||
item.enable = true;
|
||||
}
|
||||
return item;
|
||||
}),
|
||||
},
|
||||
() => this.setState({ navRow: this.nacosGetNav(navList) }, () => this.renderNav())
|
||||
);
|
||||
if (functionMode === 'config' && location.pathname === serviceUrl) {
|
||||
history.push(configUrl);
|
||||
}
|
||||
if (functionMode === 'naming' && location.pathname === configUrl) {
|
||||
history.push(serviceUrl);
|
||||
}
|
||||
if (functionMode === 'cluster' && location.pathname === clusterUrl) {
|
||||
history.push(clusterUrl);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps() {
|
||||
setTimeout(() => this.refreshNav());
|
||||
return !urls.includes(this.props.location.pathname);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale = {}, version } = this.props;
|
||||
const { nacosName, doesNotExist } = locale;
|
||||
const { showLink, navRow, leftBarClose, noChild } = this.state;
|
||||
const { locale = {}, version, functionMode } = this.props;
|
||||
const MenuData = getMenuData(functionMode);
|
||||
return (
|
||||
<div className="viewFramework-product" style={{ top: 66 }}>
|
||||
<>
|
||||
<Header />
|
||||
<div
|
||||
className="viewFramework-product-navbar"
|
||||
style={{ width: 180, marginLeft: 0 }}
|
||||
id="viewFramework-product-navbar"
|
||||
data-spm="acm_nav"
|
||||
>
|
||||
<div className="viewFramework-product-navbar-removed">
|
||||
<div>
|
||||
<div className="product-nav-scene product-nav-main-scene">
|
||||
{showLink ? (
|
||||
<div className="product-nav-icon env" style={{ height: 80, paddingTop: 25 }}>
|
||||
{showLink}
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{ textIndent: 0, display: !version ? 'none' : 'block' }}
|
||||
className="product-nav-title"
|
||||
title={nacosName}
|
||||
>
|
||||
<span>{nacosName}</span>
|
||||
<span style={{ marginLeft: 5 }}>{version}</span>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="product-nav-list"
|
||||
style={{ position: 'relative', top: 0, height: '100%' }}
|
||||
>
|
||||
{navRow}
|
||||
</div>
|
||||
<div className="main-container">
|
||||
<div className="left-panel">
|
||||
{this.isShowGoBack() ? (
|
||||
<div className="go-back" onClick={() => this.goBack()}>
|
||||
<Icon type="arrow-left" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="viewFramework-product-navbar-collapse"
|
||||
id="viewFramework-product-navbar-collapse"
|
||||
onClick={this.nacosToggleLeftBar.bind(this)}
|
||||
>
|
||||
<div className="product-navbar-collapse-inner">
|
||||
<div className="product-navbar-collapse-bg" />
|
||||
<div className="product-navbar-collapse">
|
||||
{leftBarClose ? (
|
||||
<span className="icon-collapse-right" style={{ display: 'block' }} />
|
||||
) : (
|
||||
<span className="icon-collapse-left" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="viewFramework-product-body"
|
||||
style={{ marginLeft: 180 }}
|
||||
id="viewFramework-product-body"
|
||||
>
|
||||
<div>
|
||||
{!noChild ? (
|
||||
<div>{this.props.children}</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
height: 300,
|
||||
lineHeight: 300,
|
||||
textAlign: 'center',
|
||||
fontSize: 18,
|
||||
}}
|
||||
>
|
||||
{doesNotExist}
|
||||
</div>
|
||||
<>
|
||||
<h1 className="nav-title">
|
||||
{locale.nacosName}
|
||||
<span>{version}</span>
|
||||
</h1>
|
||||
<Menu
|
||||
defaultOpenKeys={this.defaultOpenKeys()}
|
||||
className="nav-menu"
|
||||
openMode="single"
|
||||
>
|
||||
{MenuData.map((subMenu, idx) => {
|
||||
if (subMenu.children) {
|
||||
return (
|
||||
<SubMenu key={String(idx)} label={locale[subMenu.key]}>
|
||||
{subMenu.children.map((item, i) => (
|
||||
<Item
|
||||
key={[idx, i].join('-')}
|
||||
onClick={() => this.navTo(item.url)}
|
||||
className={this.isCurrentPath(item.url)}
|
||||
>
|
||||
{locale[item.key]}
|
||||
</Item>
|
||||
))}
|
||||
</SubMenu>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Item
|
||||
key={idx}
|
||||
className={['first-menu', this.isCurrentPath(subMenu.url)]
|
||||
.filter(c => c)
|
||||
.join(' ')}
|
||||
onClick={() => this.navTo(subMenu.url)}
|
||||
>
|
||||
{locale[subMenu.key]}
|
||||
</Item>
|
||||
);
|
||||
})}
|
||||
</Menu>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="right-panel">{this.props.children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.header-container {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
background-color: #fff;
|
||||
}
|
||||
.header-container-primary {
|
||||
background: #252a2f;
|
||||
}
|
||||
|
@ -1396,5 +1388,63 @@ h6 {
|
|||
}
|
||||
|
||||
.product-nav-list li.selected a {
|
||||
background-color: #F4F6F8;
|
||||
background-color: #f4f6f8;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
height: calc(100vh - 66px);
|
||||
.left-panel,
|
||||
.right-panel {
|
||||
float: left;
|
||||
height: 100%;
|
||||
}
|
||||
.left-panel {
|
||||
width: 180px;
|
||||
background-color: #eaedf1;
|
||||
}
|
||||
.right-panel {
|
||||
width: calc(100% - 180px);
|
||||
padding: 10px;
|
||||
overflow: scroll;
|
||||
}
|
||||
.nav-title {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
line-height: 70px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
background-color: #d9dee4;
|
||||
span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
.nav-menu {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
line-height: 40px;
|
||||
div.next-menu-item,
|
||||
.first-menu > .next-menu-item-inner {
|
||||
color: #333;
|
||||
}
|
||||
.next-menu-item-inner {
|
||||
height: 40px;
|
||||
color: #666;
|
||||
}
|
||||
.current-path {
|
||||
background-color: #f2f3f7;
|
||||
}
|
||||
}
|
||||
.go-back {
|
||||
text-align: center;
|
||||
color: rgb(84, 100, 120);
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
padding: 10px 0;
|
||||
margin-top: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import { isJsonString } from '../utils/nacosutil';
|
||||
|
||||
const configurationMenu = {
|
||||
key: 'configurationManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
key: 'configurationManagement',
|
||||
url: '/configurationManagement',
|
||||
},
|
||||
{
|
||||
key: 'historyRollback',
|
||||
url: '/historyRollback',
|
||||
},
|
||||
{
|
||||
key: 'listeningToQuery',
|
||||
url: '/listeningToQuery',
|
||||
},
|
||||
],
|
||||
};
|
||||
/**
|
||||
* 权限控制相关
|
||||
*/
|
||||
const authorityControlMenu = {
|
||||
key: 'authorityControl',
|
||||
children: [
|
||||
{
|
||||
key: 'userList',
|
||||
url: '/userManagement',
|
||||
},
|
||||
{
|
||||
key: 'roleManagement',
|
||||
url: '/rolesManagement',
|
||||
},
|
||||
{
|
||||
key: 'privilegeManagement',
|
||||
url: '/permissionsManagement',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default function(model) {
|
||||
const { token = '{}' } = localStorage;
|
||||
const { globalAdmin } = isJsonString(token) ? JSON.parse(token) || {} : {};
|
||||
|
||||
return [
|
||||
model === 'naming' ? undefined : configurationMenu,
|
||||
{
|
||||
key: 'serviceManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
key: 'serviceManagement',
|
||||
url: '/serviceManagement',
|
||||
},
|
||||
{
|
||||
key: 'subscriberList',
|
||||
url: '/subscriberList',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'clusterManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
key: 'clusterManagement',
|
||||
url: '/clusterManagement',
|
||||
},
|
||||
],
|
||||
},
|
||||
globalAdmin ? authorityControlMenu : undefined,
|
||||
{
|
||||
key: 'namespace',
|
||||
url: '/namespace',
|
||||
},
|
||||
].filter(item => item);
|
||||
}
|
|
@ -50,6 +50,10 @@ const I18N_CONF = {
|
|||
namespace: 'Namespace',
|
||||
clusterManagementVirtual: 'ClusterManagement',
|
||||
clusterManagement: 'Cluster Node List',
|
||||
authorityControl: 'Authority Control',
|
||||
userList: 'User List',
|
||||
roleManagement: 'Role Management',
|
||||
privilegeManagement: 'Privilege Management',
|
||||
},
|
||||
Password: {
|
||||
passwordNotConsistent: 'The passwords are not consistent',
|
||||
|
@ -277,6 +281,7 @@ const I18N_CONF = {
|
|||
importSuccBegin: 'The import was successful,with ',
|
||||
importSuccEnd: 'configuration items imported',
|
||||
importFail: 'Import failed',
|
||||
importFail403: 'Unauthorized!',
|
||||
importDataValidationError: 'No legitimate data was read, please check the imported data file.',
|
||||
metadataIllegal: 'The imported metadata file is illegal',
|
||||
namespaceNotExist: 'namespace does not exist',
|
||||
|
@ -313,6 +318,7 @@ const I18N_CONF = {
|
|||
delSelectedAlertTitle: 'Delete config',
|
||||
delSelectedAlertContent: 'please select the configuration to delete',
|
||||
delSuccessMsg: 'delete successful',
|
||||
cloneEditableTitle: 'Modify Data Id and Group (optional)',
|
||||
},
|
||||
NewConfig: {
|
||||
newListingMain: 'Create Configuration',
|
||||
|
@ -463,9 +469,14 @@ const I18N_CONF = {
|
|||
newnamespce: 'Create Namespace',
|
||||
loading: 'Loading...',
|
||||
name: 'Namespace:',
|
||||
namespaceId: 'Namespace ID(automatically generated if not filled):',
|
||||
namespaceIdTooLong: 'The namespace ID length cannot exceed 128',
|
||||
namespacenotnull: 'Namespace cannot be empty',
|
||||
namespacedescnotnull: 'Namespace description cannot be empty',
|
||||
description: 'Description:',
|
||||
namespaceIdAlreadyExist: 'namespaceId already exist',
|
||||
newnamespceFailedMessage:
|
||||
'namespaceId format is incorrect/namespaceId length greater than 128/namespaceId already exist',
|
||||
},
|
||||
NameSpaceList: {
|
||||
notice: 'Notice',
|
||||
|
@ -499,6 +510,86 @@ const I18N_CONF = {
|
|||
update: 'Update',
|
||||
insert: 'Insert',
|
||||
},
|
||||
UserManagement: {
|
||||
userManagement: 'User Management',
|
||||
createUser: 'Create user',
|
||||
resetPassword: 'Edit',
|
||||
deleteUser: 'Delete',
|
||||
deleteUserTip: 'Do you want to delete this user?',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
operation: 'Operation',
|
||||
},
|
||||
NewUser: {
|
||||
createUser: 'Create user',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
rePassword: 'Repeat',
|
||||
usernamePlaceholder: 'Please Enter Username',
|
||||
passwordPlaceholder: 'Please Enter Password',
|
||||
rePasswordPlaceholder: 'Please Enter Repeat Password',
|
||||
usernameError: 'User name cannot be empty!',
|
||||
passwordError: 'Password cannot be empty!',
|
||||
rePasswordError: 'Repeat Password cannot be empty!',
|
||||
rePasswordError2: 'Passwords are inconsistent!',
|
||||
},
|
||||
PasswordReset: {
|
||||
resetPassword: 'Password Reset',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
rePassword: 'Repeat',
|
||||
passwordPlaceholder: 'Please Enter Password',
|
||||
rePasswordPlaceholder: 'Please Enter Repeat Password',
|
||||
passwordError: 'Password cannot be empty!',
|
||||
rePasswordError: 'Repeat Password cannot be empty!',
|
||||
rePasswordError2: 'Passwords are inconsistent!',
|
||||
},
|
||||
RolesManagement: {
|
||||
roleManagement: 'Role management',
|
||||
bindingRoles: 'Binding roles',
|
||||
role: 'Role',
|
||||
username: 'Username',
|
||||
operation: 'Operation',
|
||||
deleteRole: 'Delete',
|
||||
deleteRoleTip: 'Do you want to delete this role?',
|
||||
},
|
||||
NewRole: {
|
||||
bindingRoles: 'Binding roles',
|
||||
username: 'Username',
|
||||
role: 'Role',
|
||||
usernamePlaceholder: 'Please Enter Username',
|
||||
rolePlaceholder: 'Please Enter Role',
|
||||
usernameError: 'User name cannot be empty!',
|
||||
roleError: 'Role cannot be empty!',
|
||||
},
|
||||
PermissionsManagement: {
|
||||
privilegeManagement: 'Permissions Management',
|
||||
addPermission: 'Add Permission',
|
||||
role: 'Role',
|
||||
resource: 'Resource',
|
||||
action: 'Action',
|
||||
operation: 'Operation',
|
||||
deletePermission: 'Delete',
|
||||
deletePermissionTip: 'Do you want to delete this permission?',
|
||||
readOnly: 'read only',
|
||||
writeOnly: 'write only',
|
||||
readWrite: 'Read and write',
|
||||
},
|
||||
NewPermissions: {
|
||||
addPermission: 'Add Permission',
|
||||
role: 'Role',
|
||||
resource: 'Resource',
|
||||
action: 'Action',
|
||||
resourcePlaceholder: 'Please select resources',
|
||||
rolePlaceholder: 'Please enter Role',
|
||||
actionPlaceholder: 'Please select Action',
|
||||
resourceError: 'Resource cannot be empty!',
|
||||
roleError: 'Role cannot be empty!',
|
||||
actionError: 'Action cannot be empty!',
|
||||
readOnly: 'read only',
|
||||
writeOnly: 'write only',
|
||||
readWrite: 'Read and write',
|
||||
},
|
||||
};
|
||||
|
||||
export default I18N_CONF;
|
||||
|
|
|
@ -50,6 +50,10 @@ const I18N_CONF = {
|
|||
namespace: '命名空间',
|
||||
clusterManagementVirtual: '集群管理',
|
||||
clusterManagement: '节点列表',
|
||||
authorityControl: '权限控制',
|
||||
userList: '用户列表',
|
||||
roleManagement: '角色管理',
|
||||
privilegeManagement: '权限管理',
|
||||
},
|
||||
Password: {
|
||||
passwordNotConsistent: '两次输入密码不一致',
|
||||
|
@ -276,6 +280,7 @@ const I18N_CONF = {
|
|||
importSuccBegin: '导入成功,导入了',
|
||||
importSuccEnd: '项配置',
|
||||
importFail: '导入失败',
|
||||
importFail403: '没有权限!',
|
||||
importDataValidationError: '未读取到合法数据,请检查导入的数据文件。',
|
||||
metadataIllegal: '导入的元数据文件非法',
|
||||
namespaceNotExist: 'namespace 不存在',
|
||||
|
@ -311,6 +316,7 @@ const I18N_CONF = {
|
|||
delSelectedAlertTitle: '配置删除',
|
||||
delSelectedAlertContent: '请选择要删除的配置',
|
||||
delSuccessMsg: '删除成功',
|
||||
cloneEditableTitle: '修改 Data Id 和 Group (可选操作)',
|
||||
},
|
||||
NewConfig: {
|
||||
newListingMain: '新建配置',
|
||||
|
@ -461,9 +467,13 @@ const I18N_CONF = {
|
|||
newnamespce: '新建命名空间',
|
||||
loading: '加载中...',
|
||||
name: '命名空间名:',
|
||||
namespaceId: '命名空间ID(不填则自动生成):',
|
||||
namespaceIdTooLong: '命名空间ID长度不能超过128',
|
||||
namespacenotnull: '命名空间不能为空',
|
||||
namespacedescnotnull: '命名空间描述不能为空',
|
||||
description: '描述:',
|
||||
namespaceIdAlreadyExist: 'namespaceId已存在',
|
||||
newnamespceFailedMessage: 'namespaceId格式不正确/namespaceId长度大于128/namespaceId已存在',
|
||||
},
|
||||
NameSpaceList: {
|
||||
notice: '提示',
|
||||
|
@ -497,6 +507,86 @@ const I18N_CONF = {
|
|||
update: '更新',
|
||||
insert: '插入',
|
||||
},
|
||||
UserManagement: {
|
||||
userManagement: '用户管理',
|
||||
createUser: '创建用户',
|
||||
resetPassword: '修改',
|
||||
deleteUser: '删除',
|
||||
deleteUserTip: '是否要删除该用户?',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
operation: '操作',
|
||||
},
|
||||
NewUser: {
|
||||
createUser: '创建用户',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
rePassword: '确认密码',
|
||||
usernamePlaceholder: '请输入用户名',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
rePasswordPlaceholder: '请输入确认密码',
|
||||
usernameError: '用户名不能为空!',
|
||||
passwordError: '密码不能为空!',
|
||||
rePasswordError: '确认密码不能为空!',
|
||||
rePasswordError2: '两次输入密码不一致!',
|
||||
},
|
||||
PasswordReset: {
|
||||
resetPassword: '密码重置',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
rePassword: '确认密码',
|
||||
passwordError: '密码不能为空!',
|
||||
passwordPlaceholder: '请输入密码',
|
||||
rePasswordPlaceholder: '请输入确认密码',
|
||||
rePasswordError: '确认密码不能为空!',
|
||||
rePasswordError2: '两次输入密码不一致!',
|
||||
},
|
||||
RolesManagement: {
|
||||
roleManagement: '角色管理',
|
||||
bindingRoles: '绑定角色',
|
||||
role: '角色名',
|
||||
username: '用户名',
|
||||
operation: '操作',
|
||||
deleteRole: '删除',
|
||||
deleteRoleTip: '是否要删除该角色?',
|
||||
},
|
||||
NewRole: {
|
||||
bindingRoles: '绑定角色',
|
||||
username: '用户名',
|
||||
role: '角色名',
|
||||
usernamePlaceholder: '请输入用户名',
|
||||
rolePlaceholder: '请输入角色名',
|
||||
usernameError: '用户名不能为空!',
|
||||
roleError: '角色名不能为空!',
|
||||
},
|
||||
PermissionsManagement: {
|
||||
privilegeManagement: '权限管理',
|
||||
addPermission: '添加权限',
|
||||
role: '角色名',
|
||||
resource: '资源',
|
||||
action: '动作',
|
||||
operation: '操作',
|
||||
deletePermission: '删除',
|
||||
deletePermissionTip: '是否要删除该权限?',
|
||||
readOnly: '只读',
|
||||
writeOnly: '只写',
|
||||
readWrite: '读写',
|
||||
},
|
||||
NewPermissions: {
|
||||
addPermission: '添加权限',
|
||||
role: '角色名',
|
||||
resource: '资源',
|
||||
action: '动作',
|
||||
resourcePlaceholder: '请选择资源',
|
||||
rolePlaceholder: '请输入角色名',
|
||||
actionPlaceholder: '请选择动作',
|
||||
resourceError: '资源不能为空!',
|
||||
roleError: '角色名不能为空!',
|
||||
actionError: '动作不能为空!',
|
||||
readOnly: '只读',
|
||||
writeOnly: '只写',
|
||||
readWrite: '读写',
|
||||
},
|
||||
};
|
||||
|
||||
export default I18N_CONF;
|
||||
|
|
|
@ -1,295 +0,0 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
data: [
|
||||
{
|
||||
enable: false,
|
||||
isExtend: true,
|
||||
name: '配置管理',
|
||||
title: '配置管理',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configurationManagementVirtual',
|
||||
link: 'configurationManagementVirtual',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.configurationManagementVirtual',
|
||||
useRouter: false,
|
||||
id: 'com.alibaba.nacos.page.configurationManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置列表',
|
||||
title: '配置列表',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configurationManagement',
|
||||
link: 'configurationManagement',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.configurationManagement',
|
||||
useRouter: false,
|
||||
id: 'configurationManagement',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置详情',
|
||||
title: '配置详情',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configdetail',
|
||||
link: 'Configdetail',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.configdetail',
|
||||
useRouter: false,
|
||||
id: 'configdetail',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '同步配置',
|
||||
title: '同步配置',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configsync',
|
||||
link: 'configsync',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: true,
|
||||
registerName: 'com.alibaba.nacos.page.configsync',
|
||||
useRouter: false,
|
||||
id: 'configsync',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置编辑',
|
||||
title: '配置编辑',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configeditor',
|
||||
link: 'configeditor',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.configeditor',
|
||||
useRouter: false,
|
||||
id: 'configeditor',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '新建配置',
|
||||
title: '新建配置',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'newconfig',
|
||||
link: 'newconfig',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.newconfig',
|
||||
useRouter: false,
|
||||
id: 'newconfig',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '历史版本',
|
||||
title: '历史版本',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '配置回滚',
|
||||
title: '配置回滚',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'configRollback',
|
||||
link: 'configRollback',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.configRollback',
|
||||
useRouter: false,
|
||||
id: 'configRollback',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '历史详情',
|
||||
title: '历史详情',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'historyDetail',
|
||||
link: 'historyDetail',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.historyDetail',
|
||||
useRouter: false,
|
||||
id: 'historyDetail',
|
||||
},
|
||||
],
|
||||
serviceName: 'historyRollback',
|
||||
link: 'historyRollback',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.historyRollback',
|
||||
useRouter: false,
|
||||
id: 'historyRollback',
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '监听查询',
|
||||
title: '监听查询',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'listeningToQuery',
|
||||
link: 'listeningToQuery',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.listeningToQuery',
|
||||
useRouter: false,
|
||||
id: 'listeningToQuery',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enable: false,
|
||||
isExtend: true,
|
||||
name: '服务管理',
|
||||
title: '服务管理',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'serviceManagementVirtual',
|
||||
link: 'serviceManagementVirtual',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.serviceManagementVirtual',
|
||||
useRouter: false,
|
||||
id: 'com.alibaba.nacos.page.serviceManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '服务列表',
|
||||
title: '服务列表',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'serviceManagement',
|
||||
link: 'serviceManagement',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.serviceManagement',
|
||||
useRouter: false,
|
||||
id: 'serviceManagement',
|
||||
children: [
|
||||
{
|
||||
isExtend: true,
|
||||
name: '服务详情',
|
||||
title: '服务详情',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'serviceDetail',
|
||||
link: 'serviceDetail',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.ServiceDetail',
|
||||
useRouter: false,
|
||||
id: 'serviceDetail',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
isExtend: false,
|
||||
name: '订阅者列表',
|
||||
title: '订阅者列表',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'subscriberList',
|
||||
link: 'subscriberList',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.subscriberList',
|
||||
useRouter: false,
|
||||
id: 'subscriberList',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enable: true,
|
||||
isExtend: false,
|
||||
name: '命名空间',
|
||||
title: '命名空间',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'namespace',
|
||||
link: 'namespace',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: false,
|
||||
registerName: 'com.alibaba.nacos.page.namespace',
|
||||
useRouter: false,
|
||||
id: 'namespace',
|
||||
},
|
||||
{
|
||||
enable: true,
|
||||
isExtend: false,
|
||||
name: '修改密码',
|
||||
title: '修改密码',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'password',
|
||||
link: 'password',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
dontUseChild: true,
|
||||
registerName: 'com.alibaba.nacos.page.password',
|
||||
useRouter: false,
|
||||
id: 'password',
|
||||
},
|
||||
{
|
||||
enable: false,
|
||||
isExtend: true,
|
||||
name: '集群管理',
|
||||
title: '集群管理',
|
||||
isVirtual: true,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'clusterManagementVirtual',
|
||||
link: 'clusterManagementVirtual',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.clusterManagementVirtual',
|
||||
useRouter: false,
|
||||
id: 'com.alibaba.nacos.page.clusterManagementVirtual',
|
||||
children: [
|
||||
{
|
||||
isExtend: false,
|
||||
name: '节点状态',
|
||||
title: '节点状态',
|
||||
isVirtual: false,
|
||||
projectName: 'nacos',
|
||||
serviceName: 'clusterManagement',
|
||||
link: 'clusterManagement',
|
||||
hasFusion: true,
|
||||
template: '',
|
||||
registerName: 'com.alibaba.nacos.page.clusterManagement',
|
||||
useRouter: false,
|
||||
id: 'clusterManagement',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
defaultKey: 'configurationManagement',
|
||||
projectName: 'nacos',
|
||||
};
|
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Select, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getNamespaces } from '../../../reducers/namespace';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
const { Option } = Select;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@connect(state => ({ namespaces: state.namespace.namespaces }), { getNamespaces })
|
||||
@ConfigProvider.config
|
||||
class NewPermissions extends React.Component {
|
||||
static displayName = 'NewPermissions';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
getNamespaces: PropTypes.func,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
namespaces: PropTypes.array,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.props.getNamespaces();
|
||||
}
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
role: locale.roleError,
|
||||
resource: locale.resourceError,
|
||||
action: locale.actionError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length === 3) {
|
||||
return vals;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { getError } = this.field;
|
||||
const { visible, onOk, onCancel, locale, namespaces } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.addPermission}
|
||||
visible={visible}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.role} required help={getError('role')}>
|
||||
<Input name="role" trim placeholder={locale.rolePlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.resource} required help={getError('resource')}>
|
||||
<Select
|
||||
name="resource"
|
||||
placeholder={locale.resourcePlaceholder}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
{namespaces.map(({ namespace, namespaceShowName }) => (
|
||||
<Option value={`${namespace}:*:*`}>
|
||||
{namespaceShowName} {namespace ? `(${namespace})` : ''}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label={locale.action} required help={getError('action')}>
|
||||
<Select
|
||||
name="action"
|
||||
placeholder={locale.actionPlaceholder}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
<Option value="r">{locale.readOnly}(r)</Option>
|
||||
<Option value="w">{locale.writeOnly}(w)</Option>
|
||||
<Option value="rw">{locale.readWrite}(rw)</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewPermissions;
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getPermissions, createPermission, deletePermission } from '../../../reducers/authority';
|
||||
import { getNamespaces } from '../../../reducers/namespace';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import NewPermissions from './NewPermissions';
|
||||
|
||||
import './PermissionsManagement.scss';
|
||||
|
||||
@connect(
|
||||
state => ({
|
||||
permissions: state.authority.permissions,
|
||||
namespaces: state.namespace.namespaces,
|
||||
}),
|
||||
{ getPermissions, getNamespaces }
|
||||
)
|
||||
@ConfigProvider.config
|
||||
class PermissionsManagement extends React.Component {
|
||||
static displayName = 'PermissionsManagement';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
permissions: PropTypes.object,
|
||||
namespaces: PropTypes.object,
|
||||
getPermissions: PropTypes.func,
|
||||
getNamespaces: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
pageNo: 1,
|
||||
pageSize: 9,
|
||||
createPermission: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getPermissions();
|
||||
this.props.getNamespaces();
|
||||
}
|
||||
|
||||
getPermissions() {
|
||||
const { pageNo, pageSize } = this.state;
|
||||
this.props
|
||||
.getPermissions({ pageNo, pageSize })
|
||||
.then(() => {
|
||||
if (this.state.loading) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
})
|
||||
.catch(() => this.setState({ loading: false }));
|
||||
}
|
||||
|
||||
colseCreatePermission() {
|
||||
this.setState({ createPermissionVisible: false });
|
||||
}
|
||||
|
||||
getActionText(action) {
|
||||
const { locale } = this.props;
|
||||
return {
|
||||
r: `${locale.readOnly} (r)`,
|
||||
w: `${locale.writeOnly} (w)`,
|
||||
rw: `${locale.readWrite} (rw)`,
|
||||
}[action];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { permissions, namespaces = [], locale } = this.props;
|
||||
const { loading, pageSize, pageNo, createPermissionVisible } = this.state;
|
||||
return (
|
||||
<>
|
||||
<RegionGroup left={locale.privilegeManagement} />
|
||||
<div className="filter-panel">
|
||||
<Button type="primary" onClick={() => this.setState({ createPermissionVisible: true })}>
|
||||
{locale.addPermission}
|
||||
</Button>
|
||||
</div>
|
||||
<Table dataSource={permissions.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
|
||||
<Table.Column title={locale.role} dataIndex="role" />
|
||||
<Table.Column
|
||||
title={locale.resource}
|
||||
dataIndex="resource"
|
||||
cell={value => {
|
||||
const [item = {}] = namespaces.filter(({ namespace }) => {
|
||||
const [itemNamespace] = value.split(':');
|
||||
return itemNamespace === namespace;
|
||||
});
|
||||
const { namespaceShowName = '', namespace = '' } = item;
|
||||
return namespaceShowName + (namespace ? ` (${namespace})` : '');
|
||||
}}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.action}
|
||||
dataIndex="action"
|
||||
cell={action => this.getActionText(action)}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
cell={(value, index, record) => (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
warning
|
||||
onClick={() =>
|
||||
Dialog.confirm({
|
||||
title: locale.deletePermission,
|
||||
content: locale.deletePermissionTip,
|
||||
onOk: () =>
|
||||
deletePermission(record).then(() => {
|
||||
this.setState({ pageNo: 1 }, () => this.getPermissions());
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
{locale.deletePermission}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
{permissions.totalCount > pageSize && (
|
||||
<Pagination
|
||||
className="users-pagination"
|
||||
current={pageNo}
|
||||
total={permissions.totalCount}
|
||||
pageSize={pageSize}
|
||||
onChange={pageNo => this.setState({ pageNo }, () => this.getPermissions())}
|
||||
/>
|
||||
)}
|
||||
<NewPermissions
|
||||
visible={createPermissionVisible}
|
||||
onOk={permission =>
|
||||
createPermission(permission).then(res => {
|
||||
this.setState({ pageNo: 1 }, () => this.getPermissions());
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.colseCreatePermission()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PermissionsManagement;
|
|
@ -9,4 +9,4 @@
|
|||
* 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.
|
||||
*/
|
||||
*/
|
|
@ -11,14 +11,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
set(key, value) {
|
||||
window.localStorage.setItem(key, value);
|
||||
},
|
||||
get(key) {
|
||||
return window.localStorage.getItem(key);
|
||||
},
|
||||
remove(key) {
|
||||
window.localStorage.removeItem(key);
|
||||
},
|
||||
};
|
||||
import PermissionsManagement from './PermissionsManagement';
|
||||
|
||||
export default PermissionsManagement;
|
|
@ -0,0 +1,7 @@
|
|||
# 权限控制
|
||||
|
||||
> AuthorityControl
|
||||
|
||||
1. UserManagement => 用户管理
|
||||
2. RolesManagement => 角色管理
|
||||
3. PermissionsManagement => 权限管理
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@ConfigProvider.config
|
||||
class NewRole extends React.Component {
|
||||
static displayName = 'NewRole';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
};
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
role: locale.roleError,
|
||||
username: locale.usernameError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length === 2) {
|
||||
return vals;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale } = this.props;
|
||||
const { getError } = this.field;
|
||||
const { visible, onOk, onCancel } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.bindingRoles}
|
||||
visible={visible}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.role} required help={getError('role')}>
|
||||
<Input name="role" trim placeholder={locale.rolePlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.username} required help={getError('username')}>
|
||||
<Input name="username" placeholder={locale.usernamePlaceholder} />
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewRole;
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getRoles, createRole, deleteRole } from '../../../reducers/authority';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import NewRole from './NewRole';
|
||||
|
||||
import './RolesManagement.scss';
|
||||
|
||||
@connect(state => ({ roles: state.authority.roles }), { getRoles })
|
||||
@ConfigProvider.config
|
||||
class RolesManagement extends React.Component {
|
||||
static displayName = 'RolesManagement';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
roles: PropTypes.object,
|
||||
getRoles: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
pageNo: 1,
|
||||
pageSize: 9,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getRoles();
|
||||
}
|
||||
|
||||
getRoles() {
|
||||
const { pageNo, pageSize } = this.state;
|
||||
this.props
|
||||
.getRoles({ pageNo, pageSize })
|
||||
.then(() => {
|
||||
if (this.state.loading) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
})
|
||||
.catch(() => this.setState({ loading: false }));
|
||||
}
|
||||
|
||||
colseCreateRole() {
|
||||
this.setState({ createRoleVisible: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { roles, locale } = this.props;
|
||||
const { loading, pageSize, pageNo, createRoleVisible, passwordResetUser } = this.state;
|
||||
return (
|
||||
<>
|
||||
<RegionGroup left={locale.roleManagement} />
|
||||
<div className="filter-panel">
|
||||
<Button type="primary" onClick={() => this.setState({ createRoleVisible: true })}>
|
||||
{locale.bindingRoles}
|
||||
</Button>
|
||||
</div>
|
||||
<Table dataSource={roles.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
|
||||
<Table.Column title={locale.role} dataIndex="role" />
|
||||
<Table.Column title={locale.username} dataIndex="username" />
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
dataIndex="role"
|
||||
cell={(value, index, record) => {
|
||||
if (value === 'ROLE_ADMIN') {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
warning
|
||||
onClick={() =>
|
||||
Dialog.confirm({
|
||||
title: locale.deleteRole,
|
||||
content: locale.deleteRoleTip,
|
||||
onOk: () =>
|
||||
deleteRole(record).then(() => {
|
||||
this.setState({ pageNo: 1 }, () => this.getRoles());
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
{locale.deleteRole}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Table>
|
||||
{roles.totalCount > pageSize && (
|
||||
<Pagination
|
||||
className="users-pagination"
|
||||
current={pageNo}
|
||||
total={roles.totalCount}
|
||||
pageSize={pageSize}
|
||||
onChange={pageNo => this.setState({ pageNo }, () => this.getRoles())}
|
||||
/>
|
||||
)}
|
||||
<NewRole
|
||||
visible={createRoleVisible}
|
||||
onOk={role =>
|
||||
createRole(role).then(res => {
|
||||
this.getRoles();
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.colseCreateRole()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default RolesManagement;
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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.
|
||||
*/
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 RolesManagement from './RolesManagement';
|
||||
|
||||
export default RolesManagement;
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import './UserManagement.scss';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@ConfigProvider.config
|
||||
class NewUser extends React.Component {
|
||||
static displayName = 'NewUser';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
onOk: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
};
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
username: locale.usernameError,
|
||||
password: locale.passwordError,
|
||||
rePassword: locale.rePasswordError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length !== 3) {
|
||||
return null;
|
||||
}
|
||||
const [password, rePassword] = ['password', 'rePassword'].map(k => this.field.getValue(k));
|
||||
if (password !== rePassword) {
|
||||
this.field.setError('rePassword', locale.rePasswordError2);
|
||||
return null;
|
||||
}
|
||||
return vals;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale } = this.props;
|
||||
const { getError } = this.field;
|
||||
const { visible, onOk, onCancel } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.createUser}
|
||||
visible={visible}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.username} required help={getError('username')}>
|
||||
<Input name="username" trim placeholder={locale.usernamePlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.password} required help={getError('password')}>
|
||||
<Input name="password" htmlType="password" placeholder={locale.passwordPlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.rePassword} required help={getError('rePassword')}>
|
||||
<Input
|
||||
name="rePassword"
|
||||
htmlType="password"
|
||||
placeholder={locale.rePasswordPlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewUser;
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, Form, Input, Dialog, ConfigProvider } from '@alifd/next';
|
||||
import './UserManagement.scss';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 4 },
|
||||
wrapperCol: { span: 19 },
|
||||
};
|
||||
|
||||
@ConfigProvider.config
|
||||
class PasswordReset extends React.Component {
|
||||
static displayName = 'PasswordReset';
|
||||
|
||||
field = new Field(this);
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
visible: PropTypes.bool,
|
||||
username: PropTypes.string,
|
||||
onCancel: PropTypes.func,
|
||||
onOk: PropTypes.func,
|
||||
};
|
||||
|
||||
check() {
|
||||
const { locale } = this.props;
|
||||
const errors = {
|
||||
password: locale.passwordError,
|
||||
rePassword: locale.rePasswordError,
|
||||
};
|
||||
const vals = Object.keys(errors).map(key => {
|
||||
const val = this.field.getValue(key);
|
||||
if (!val) {
|
||||
this.field.setError(key, errors[key]);
|
||||
}
|
||||
return val;
|
||||
});
|
||||
if (vals.filter(v => v).length !== 2) {
|
||||
return null;
|
||||
}
|
||||
const [password, rePassword] = ['password', 'rePassword'].map(k => this.field.getValue(k));
|
||||
if (password !== rePassword) {
|
||||
this.field.setError('rePassword', locale.rePasswordError2);
|
||||
return null;
|
||||
}
|
||||
return [this.props.username, ...vals];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale } = this.props;
|
||||
const { getError } = this.field;
|
||||
const { username, onOk, onCancel } = this.props;
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
title={locale.resetPassword}
|
||||
visible={username}
|
||||
onOk={() => {
|
||||
const vals = this.check();
|
||||
if (vals) {
|
||||
onOk(vals).then(() => onCancel());
|
||||
}
|
||||
}}
|
||||
onClose={onCancel}
|
||||
onCancel={onCancel}
|
||||
afterClose={() => this.field.reset()}
|
||||
>
|
||||
<Form style={{ width: 400 }} {...formItemLayout} field={this.field}>
|
||||
<FormItem label={locale.username} required>
|
||||
<p>{username}</p>
|
||||
</FormItem>
|
||||
<FormItem label={locale.password} required help={getError('password')}>
|
||||
<Input name="password" htmlType="password" placeholder={locale.passwordPlaceholder} />
|
||||
</FormItem>
|
||||
<FormItem label={locale.rePassword} required help={getError('rePassword')}>
|
||||
<Input
|
||||
name="rePassword"
|
||||
htmlType="password"
|
||||
placeholder={locale.rePasswordPlaceholder}
|
||||
/>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PasswordReset;
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Dialog, Pagination, Table, ConfigProvider } from '@alifd/next';
|
||||
import { connect } from 'react-redux';
|
||||
import { getUsers, createUser, deleteUser, passwordReset } from '../../../reducers/authority';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import NewUser from './NewUser';
|
||||
import PasswordReset from './PasswordReset';
|
||||
|
||||
import './UserManagement.scss';
|
||||
|
||||
@connect(state => ({ users: state.authority.users }), { getUsers })
|
||||
@ConfigProvider.config
|
||||
class UserManagement extends React.Component {
|
||||
static displayName = 'UserManagement';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
users: PropTypes.object,
|
||||
getUsers: PropTypes.func,
|
||||
createUser: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
pageNo: 1,
|
||||
pageSize: 9,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getUsers();
|
||||
}
|
||||
|
||||
getUsers() {
|
||||
const { pageNo, pageSize } = this.state;
|
||||
this.props
|
||||
.getUsers({ pageNo, pageSize })
|
||||
.then(() => {
|
||||
if (this.state.loading) {
|
||||
this.setState({ loading: false });
|
||||
}
|
||||
})
|
||||
.catch(() => this.setState({ loading: false }));
|
||||
}
|
||||
|
||||
colseCreateUser() {
|
||||
this.setState({ createUserVisible: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { users, locale } = this.props;
|
||||
const { loading, pageSize, pageNo, createUserVisible, passwordResetUser } = this.state;
|
||||
return (
|
||||
<>
|
||||
<RegionGroup left={locale.userManagement} />
|
||||
<div className="filter-panel">
|
||||
<Button type="primary" onClick={() => this.setState({ createUserVisible: true })}>
|
||||
{locale.createUser}
|
||||
</Button>
|
||||
</div>
|
||||
<Table dataSource={users.pageItems} loading={loading} maxBodyHeight={476} fixedHeader>
|
||||
<Table.Column title={locale.username} dataIndex="username" />
|
||||
<Table.Column
|
||||
title={locale.password}
|
||||
dataIndex="password"
|
||||
cell={value => value.replace(/\S/g, '*')}
|
||||
/>
|
||||
<Table.Column
|
||||
title={locale.operation}
|
||||
dataIndex="username"
|
||||
cell={username => (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => this.setState({ passwordResetUser: username })}
|
||||
>
|
||||
{locale.resetPassword}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
warning
|
||||
onClick={() =>
|
||||
Dialog.confirm({
|
||||
title: locale.deleteUser,
|
||||
content: locale.deleteUserTip,
|
||||
onOk: () =>
|
||||
deleteUser(username).then(() => {
|
||||
this.setState({ pageNo: 1 }, () => this.getUsers());
|
||||
}),
|
||||
})
|
||||
}
|
||||
>
|
||||
{locale.deleteUser}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</Table>
|
||||
{users.totalCount > pageSize && (
|
||||
<Pagination
|
||||
className="users-pagination"
|
||||
current={pageNo}
|
||||
total={users.totalCount}
|
||||
pageSize={pageSize}
|
||||
onChange={pageNo => this.setState({ pageNo }, () => this.getUsers())}
|
||||
/>
|
||||
)}
|
||||
<NewUser
|
||||
visible={createUserVisible}
|
||||
onOk={user =>
|
||||
createUser(user).then(res => {
|
||||
this.setState({ pageNo: 1 }, () => this.getUsers());
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.colseCreateUser()}
|
||||
/>
|
||||
<PasswordReset
|
||||
username={passwordResetUser}
|
||||
onOk={user =>
|
||||
passwordReset(user).then(res => {
|
||||
this.getUsers();
|
||||
return res;
|
||||
})
|
||||
}
|
||||
onCancel={() => this.setState({ passwordResetUser: undefined })}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserManagement;
|
|
@ -10,15 +10,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
@import '../authority.scss';
|
||||
|
||||
import $ from 'jquery';
|
||||
|
||||
export default function ajaxrequest(options) {
|
||||
const promise = $.ajax({
|
||||
url: options.url,
|
||||
timeout: options.timeout, // 超时时间设置,单位毫秒设置为1小时
|
||||
dataType: options.dataType, // 返回的数据格式
|
||||
type: options.type,
|
||||
});
|
||||
return promise.done(data => ({ data }));
|
||||
.users-pagination {
|
||||
float: right;
|
||||
margin-top: 20px;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 UserManagement from './UserManagement';
|
||||
|
||||
export default UserManagement;
|
|
@ -11,6 +11,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import Password from './Password';
|
||||
|
||||
export default Password;
|
||||
.filter-panel {
|
||||
text-align: right;
|
||||
padding: 10px 0;
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
import React from 'react';
|
||||
import { Button, ConfigProvider, Dialog, Field, Form, Input, Loading, Tab } from '@alifd/next';
|
||||
import { getParams, request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
|
||||
import './index.scss';
|
||||
import PropTypes from 'prop-types';
|
||||
|
@ -140,9 +141,12 @@ class ConfigDetail extends React.Component {
|
|||
|
||||
goList() {
|
||||
this.props.history.push(
|
||||
`/configurationManagement?serverId=${this.serverId}&group=${this.searchGroup}&dataId=${
|
||||
this.searchDataId
|
||||
}&namespace=${this.tenant}`
|
||||
generateUrl('/configurationManagement', {
|
||||
serverId: this.serverId,
|
||||
group: this.searchGroup,
|
||||
dataId: this.searchDataId,
|
||||
namespace: this.tenant,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getParams } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import request from '../../../utils/request';
|
||||
import validateContent from 'utils/validateContent';
|
||||
import SuccessDialog from '../../../components/SuccessDialog';
|
||||
|
@ -96,7 +97,7 @@ class ConfigEditor extends React.Component {
|
|||
dataId: getParams('dataId').trim(),
|
||||
group,
|
||||
},
|
||||
() =>
|
||||
() => {
|
||||
this.getConfig(true).then(res => {
|
||||
if (!res) {
|
||||
this.getConfig();
|
||||
|
@ -107,7 +108,8 @@ class ConfigEditor extends React.Component {
|
|||
tabActiveKey: 'beta',
|
||||
betaPublishSuccess: true,
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
if (group) {
|
||||
|
@ -173,7 +175,6 @@ class ConfigEditor extends React.Component {
|
|||
}
|
||||
|
||||
clickTab(tabActiveKey) {
|
||||
console.log('tabActiveKey', tabActiveKey, tabActiveKey === 'beta');
|
||||
this.setState({ tabActiveKey }, () => this.getConfig(tabActiveKey === 'beta'));
|
||||
}
|
||||
|
||||
|
@ -215,26 +216,20 @@ class ConfigEditor extends React.Component {
|
|||
}
|
||||
|
||||
_publishConfig(beta = false) {
|
||||
const { locale } = this.props;
|
||||
const { betaIps, isNewConfig } = this.state;
|
||||
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
|
||||
if (beta) {
|
||||
headers.betaIps = betaIps;
|
||||
}
|
||||
const data = { ...this.state.form, content: this.getCodeVal() };
|
||||
const form = { ...this.state.form, content: this.getCodeVal() };
|
||||
const data = new FormData();
|
||||
Object.keys(form).forEach(key => {
|
||||
data.append(key, form[key]);
|
||||
});
|
||||
return request({
|
||||
url: 'v1/cs/configs',
|
||||
method: 'post',
|
||||
data,
|
||||
transformRequest: [
|
||||
function(data) {
|
||||
let ret = '';
|
||||
for (let it in data) {
|
||||
ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&';
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
],
|
||||
headers,
|
||||
}).then(res => {
|
||||
if (res) {
|
||||
|
@ -315,11 +310,11 @@ class ConfigEditor extends React.Component {
|
|||
|
||||
goBack() {
|
||||
const serverId = getParams('serverId') || '';
|
||||
const tenant = getParams('namespace');
|
||||
const searchGroup = getParams('searchGroup') || '';
|
||||
const searchDataId = getParams('searchDataId') || '';
|
||||
const namespace = getParams('namespace');
|
||||
const group = getParams('searchGroup') || '';
|
||||
const dataId = getParams('searchDataId') || '';
|
||||
this.props.history.push(
|
||||
`/configurationManagement?serverId=${serverId}&group=${searchGroup}&dataId=${searchDataId}&namespace=${tenant}`
|
||||
generateUrl('/configurationManagement', { serverId, group, dataId, namespace })
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getParams, request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import { Button, ConfigProvider, Dialog, Field, Form, Input } from '@alifd/next';
|
||||
|
||||
import './index.scss';
|
||||
|
@ -96,11 +97,10 @@ class ConfigRollback extends React.Component {
|
|||
}
|
||||
|
||||
goList() {
|
||||
const tenant = getParams('namespace');
|
||||
const namespace = getParams('namespace');
|
||||
const { serverId, dataId, group } = this;
|
||||
this.props.history.push(
|
||||
`/historyRollback?serverId=${this.serverId}&group=${this.group}&dataId=${
|
||||
this.dataId
|
||||
}&namespace=${tenant}`
|
||||
generateUrl('/historyRollback', { serverId, dataId, group, namespace })
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import PropTypes from 'prop-types';
|
|||
import { Button, Checkbox, ConfigProvider, Dialog, Field, Form, Input, Loading } from '@alifd/next';
|
||||
import SuccessDialog from '../../../components/SuccessDialog';
|
||||
import { getParams, request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
@ -91,13 +92,9 @@ class ConfigSync extends React.Component {
|
|||
const { locale = {} } = this.props;
|
||||
this.tenant = getParams('namespace') || '';
|
||||
this.serverId = getParams('serverId') || 'center';
|
||||
let url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${
|
||||
this.dataId
|
||||
}/group/${this.group}/tenant/${this.tenant}?id=`;
|
||||
let url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${this.dataId}/group/${this.group}/tenant/${this.tenant}?id=`;
|
||||
if (this.tenant === 'global' || !this.tenant) {
|
||||
url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${this.dataId}/group/${
|
||||
this.group
|
||||
}?id=`;
|
||||
url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${this.dataId}/group/${this.group}?id=`;
|
||||
}
|
||||
request({
|
||||
url,
|
||||
|
@ -168,9 +165,7 @@ class ConfigSync extends React.Component {
|
|||
request({
|
||||
type: 'put',
|
||||
contentType: 'application/json',
|
||||
url: `/diamond-ops/configList/serverId/${this.serverId}/dataId/${payload.dataId}/group/${
|
||||
payload.group
|
||||
}?id=`,
|
||||
url: `/diamond-ops/configList/serverId/${this.serverId}/dataId/${payload.dataId}/group/${payload.group}?id=`,
|
||||
data: JSON.stringify(payload),
|
||||
success(res) {
|
||||
const _payload = {};
|
||||
|
@ -193,7 +188,7 @@ class ConfigSync extends React.Component {
|
|||
const dataId = this.field.getValue('dataId');
|
||||
const gruop = this.field.getValue('group');
|
||||
this.props.history.push(
|
||||
`/diamond-ops/static/pages/config-sync/index.html?serverId=center&dataId=${dataId}&group=${gruop}`
|
||||
generateUrl('/diamond-ops/static/pages/config-sync/index.html', { dataId, gruop })
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -209,9 +204,8 @@ class ConfigSync extends React.Component {
|
|||
}
|
||||
|
||||
goResult() {
|
||||
this.props.history.push(
|
||||
`/consistencyEfficacy?serverId=${this.serverId}&dataId=${this.dataId}&group=${this.group}`
|
||||
);
|
||||
const { serverId, dataId, group } = this;
|
||||
this.props.history.push(generateUrl('/consistencyEfficacy', { serverId, dataId, group }));
|
||||
}
|
||||
|
||||
openLoading() {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -46,10 +46,11 @@ class DashboardCard extends React.Component {
|
|||
</strong>
|
||||
<strong>
|
||||
<span>
|
||||
{/* eslint-disable */}
|
||||
<a
|
||||
style={{ marginLeft: 10, color: '#33cde5' }}
|
||||
href={item.url}
|
||||
target={'_blank'}
|
||||
target="_blank"
|
||||
>
|
||||
{locale.viewDetails1}
|
||||
</a>
|
||||
|
|
|
@ -10,3 +10,10 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.next-pagination-size-selector {
|
||||
position: static !important;
|
||||
}
|
||||
.configuration-table {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
|
|
@ -81,9 +81,7 @@ class HistoryDetail extends React.Component {
|
|||
|
||||
goList() {
|
||||
this.props.history.push(
|
||||
`/historyRollback?serverId=${this.serverId}&group=${this.group}&dataId=${
|
||||
this.dataId
|
||||
}&namespace=${this.tenant}`
|
||||
`/historyRollback?serverId=${this.serverId}&group=${this.group}&dataId=${this.dataId}&namespace=${this.tenant}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import { ConfigProvider, Field, Form, Input, Loading, Pagination, Table } from '@alifd/next';
|
||||
import RegionGroup from 'components/RegionGroup';
|
||||
import { getParams, setParams, request, aliwareIntl } from '@/globalLib';
|
||||
import { getParams, setParams, request } from '@/globalLib';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
|
@ -83,31 +83,6 @@ class HistoryRollback extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 回车事件
|
||||
*/
|
||||
keyDownSearch(e) {
|
||||
const theEvent = e || window.event;
|
||||
const code = theEvent.keyCode || theEvent.which || theEvent.charCode;
|
||||
if (code === 13) {
|
||||
this.getData();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
window.addEventListener('keydown', this.keyDownSearch.bind(this), false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.keyDownSearch.bind(this));
|
||||
}
|
||||
|
||||
onSearch() {}
|
||||
|
||||
onChange() {}
|
||||
|
||||
cleanAndGetData(needclean = false) {
|
||||
if (needclean) {
|
||||
this.dataId = '';
|
||||
|
@ -133,9 +108,7 @@ class HistoryRollback extends React.Component {
|
|||
beforeSend() {
|
||||
self.openLoading();
|
||||
},
|
||||
url: `v1/cs/history?search=accurate&dataId=${this.dataId}&group=${
|
||||
this.group
|
||||
}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`,
|
||||
url: `v1/cs/history?search=accurate&dataId=${this.dataId}&group=${this.group}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`,
|
||||
success(data) {
|
||||
if (data != null) {
|
||||
self.setState({
|
||||
|
@ -151,8 +124,6 @@ class HistoryRollback extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
showMore() {}
|
||||
|
||||
renderCol(value, index, record) {
|
||||
const { locale = {} } = this.props;
|
||||
return (
|
||||
|
@ -175,60 +146,12 @@ class HistoryRollback extends React.Component {
|
|||
this.getData(value);
|
||||
}
|
||||
|
||||
onInputUpdate() {}
|
||||
|
||||
chooseFieldChange(fieldValue) {
|
||||
this.setState({
|
||||
fieldValue,
|
||||
});
|
||||
}
|
||||
|
||||
showSelect(value) {
|
||||
this.setState({
|
||||
selectValue: value,
|
||||
});
|
||||
if (value.indexOf('appName') !== -1) {
|
||||
this.setState({
|
||||
showAppName: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showAppName: false,
|
||||
});
|
||||
}
|
||||
if (value.indexOf('group') !== -1) {
|
||||
this.setState({
|
||||
showgroup: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
showgroup: false,
|
||||
});
|
||||
}
|
||||
this.chooseFieldChange(value);
|
||||
}
|
||||
|
||||
getAppName(value) {
|
||||
this.appName = value;
|
||||
this.setState({
|
||||
appName: value,
|
||||
});
|
||||
}
|
||||
|
||||
getDataId(value) {
|
||||
this.dataId = value;
|
||||
this.setState({
|
||||
dataId: value,
|
||||
});
|
||||
}
|
||||
|
||||
getGroup(value) {
|
||||
this.group = value;
|
||||
this.setState({
|
||||
group: value,
|
||||
});
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this.dataId = this.field.getValue('dataId');
|
||||
this.group = this.field.getValue('group');
|
||||
|
@ -265,10 +188,6 @@ class HistoryRollback extends React.Component {
|
|||
|
||||
chooseEnv(value) {}
|
||||
|
||||
renderLastTime(value, index, record) {
|
||||
return aliwareIntl.intlTimeFormat(record.lastModifiedTime);
|
||||
}
|
||||
|
||||
goDetail(record) {
|
||||
this.serverId = getParams('serverId') || 'center';
|
||||
this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数
|
||||
|
@ -373,8 +292,18 @@ class HistoryRollback extends React.Component {
|
|||
<Table.Column title="Group" dataIndex="group" />
|
||||
<Table.Column
|
||||
title={locale.lastUpdateTime}
|
||||
dataIndex="time"
|
||||
cell={this.renderLastTime.bind(this)}
|
||||
dataIndex="lastModifiedTime"
|
||||
cell={val => {
|
||||
if (!val) {
|
||||
return '';
|
||||
}
|
||||
try {
|
||||
const date = new Date(val);
|
||||
return date.toLocaleString();
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Table.Column title={locale.operation} cell={this.renderCol.bind(this)} />
|
||||
</Table>
|
||||
|
|
|
@ -183,7 +183,7 @@ class ListeningToQuery extends React.Component {
|
|||
},
|
||||
];
|
||||
return (
|
||||
<div style={{ padding: 10 }}>
|
||||
<>
|
||||
<Loading
|
||||
shape="flower"
|
||||
style={{ position: 'relative' }}
|
||||
|
@ -320,7 +320,7 @@ class ListeningToQuery extends React.Component {
|
|||
,
|
||||
</div>
|
||||
</Loading>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import React from 'react';
|
|||
import PropTypes from 'prop-types';
|
||||
import SuccessDialog from '../../../components/SuccessDialog';
|
||||
import { getParams, setParams, request, aliwareIntl } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import {
|
||||
Balloon,
|
||||
Button,
|
||||
|
@ -144,6 +145,13 @@ class NewConfig extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
tagSearch(value) {
|
||||
const { tagLst } = this.state;
|
||||
if (!tagLst.includes(value)) {
|
||||
this.setState({ tagLst: [value, ...tagLst] });
|
||||
}
|
||||
}
|
||||
|
||||
setConfigTags(value) {
|
||||
if (value.length > 5) {
|
||||
value.pop();
|
||||
|
@ -154,6 +162,7 @@ class NewConfig extends React.Component {
|
|||
}
|
||||
});
|
||||
this.setState({
|
||||
tagLst: value,
|
||||
config_tags: value,
|
||||
});
|
||||
}
|
||||
|
@ -192,9 +201,12 @@ class NewConfig extends React.Component {
|
|||
this.tenant = getParams('namespace') || '';
|
||||
this.serverId = getParams('serverId') || '';
|
||||
this.props.history.push(
|
||||
`/configurationManagement?serverId=${this.serverId}&group=${this.searchGroup}&dataId=${
|
||||
this.searchDataId
|
||||
}&namespace=${this.tenant}`
|
||||
generateUrl('/configurationManagement', {
|
||||
serverId: this.serverId,
|
||||
group: this.searchGroup,
|
||||
dataId: this.searchDataId,
|
||||
namespace: this.tenant,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -331,15 +343,15 @@ class NewConfig extends React.Component {
|
|||
}
|
||||
self.successDialog.current.getInstance().openDialog(_payload);
|
||||
},
|
||||
complete() {
|
||||
self.closeLoading();
|
||||
complete: () => {
|
||||
this.closeLoading();
|
||||
},
|
||||
error(res) {
|
||||
error: res => {
|
||||
this.closeLoading();
|
||||
Dialog.alert({
|
||||
language: aliwareIntl.currentLanguageCode || 'zh-cn',
|
||||
content: locale.publishFailed,
|
||||
});
|
||||
self.closeLoading();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -493,6 +505,7 @@ class NewConfig extends React.Component {
|
|||
>
|
||||
<Select
|
||||
size={'medium'}
|
||||
showSearch
|
||||
hasArrow
|
||||
style={{ width: '100%', height: '100%!important' }}
|
||||
autoWidth
|
||||
|
@ -503,6 +516,7 @@ class NewConfig extends React.Component {
|
|||
dataSource={this.state.tagLst}
|
||||
value={this.state.config_tags}
|
||||
onChange={this.setConfigTags.bind(this)}
|
||||
onSearch={val => this.tagSearch(val)}
|
||||
hasClear
|
||||
/>
|
||||
</FormItem>
|
||||
|
|
|
@ -4,8 +4,8 @@ import { withRouter } from 'react-router-dom';
|
|||
|
||||
import './index.scss';
|
||||
import Header from '../../layouts/Header';
|
||||
import { request } from '../../globalLib';
|
||||
import PropTypes from 'prop-types';
|
||||
import { login } from '../../reducers/base';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
|
@ -24,35 +24,29 @@ class Login extends React.Component {
|
|||
this.field = new Field(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (localStorage.getItem('token')) {
|
||||
const [baseUrl] = location.href.split('#');
|
||||
location.href = `${baseUrl}#/`;
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
const { locale = {} } = this.props;
|
||||
this.field.validate((errors, values) => {
|
||||
if (errors) {
|
||||
return;
|
||||
}
|
||||
request({
|
||||
type: 'post',
|
||||
url: 'v1/auth/login',
|
||||
data: values,
|
||||
success: ({ code, data }) => {
|
||||
if (code === 200) {
|
||||
// TODO: 封装一个方法存储、读取token
|
||||
localStorage.setItem('token', data);
|
||||
// TODO: 使用react router
|
||||
this.props.history.push('/');
|
||||
}
|
||||
if (code === 401) {
|
||||
Message.error({
|
||||
content: locale.invalidUsernameOrPassword,
|
||||
});
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
login(values)
|
||||
.then(res => {
|
||||
localStorage.setItem('token', JSON.stringify(res));
|
||||
this.props.history.push('/');
|
||||
})
|
||||
.catch(() => {
|
||||
Message.error({
|
||||
content: locale.invalidUsernameOrPassword,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -281,14 +281,6 @@ class NameSpace extends React.Component {
|
|||
return <div>{name}</div>;
|
||||
}
|
||||
|
||||
renderConfigCount(value, index, record) {
|
||||
return (
|
||||
<div>
|
||||
{value} / {record.quota}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale = {} } = this.props;
|
||||
const {
|
||||
|
@ -301,7 +293,7 @@ class NameSpace extends React.Component {
|
|||
namespaceOperation,
|
||||
} = locale;
|
||||
return (
|
||||
<div style={{ padding: 10 }} className="clearfix">
|
||||
<>
|
||||
<RegionGroup left={namespace} />
|
||||
<div className="fusion-demo">
|
||||
<Loading
|
||||
|
@ -329,12 +321,7 @@ class NameSpace extends React.Component {
|
|||
cell={this.renderName.bind(this)}
|
||||
/>
|
||||
<Table.Column title={namespaceNumber} dataIndex="namespace" />
|
||||
<Table.Column
|
||||
title={configuration}
|
||||
dataIndex="configCount"
|
||||
cell={this.renderConfigCount.bind(this)}
|
||||
/>
|
||||
|
||||
<Table.Column title={configuration} dataIndex="configCount" />
|
||||
<Table.Column
|
||||
title={namespaceOperation}
|
||||
dataIndex="time"
|
||||
|
@ -343,12 +330,11 @@ class NameSpace extends React.Component {
|
|||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NewNameSpace ref={this.newnamespace} getNameSpaces={this.getNameSpaces.bind(this)} />
|
||||
<EditorNameSpace ref={this.editgroup} getNameSpaces={this.getNameSpaces.bind(this)} />
|
||||
</Loading>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import RegionGroup from 'components/RegionGroup';
|
||||
import { ConfigProvider, Input, Field, Form, Message } from '@alifd/next';
|
||||
import { getParams, setParams, request } from '../../globalLib';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const FormItem = Form.Item;
|
||||
|
||||
@ConfigProvider.config
|
||||
class Password extends React.Component {
|
||||
static displayName = 'Password';
|
||||
|
||||
static propTypes = {
|
||||
locale: PropTypes.object,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.field = new Field(this);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {}
|
||||
|
||||
validatePassword(rule, value, callback) {
|
||||
const { locale = {} } = this.props;
|
||||
if (this.field.getValue('newPassword') !== this.field.getValue('confirmNewPassword')) {
|
||||
callback(locale.passwordNotConsistent);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
const { locale = {} } = this.props;
|
||||
this.field.validate((errors, values) => {
|
||||
if (errors) {
|
||||
return;
|
||||
}
|
||||
request({
|
||||
type: 'post',
|
||||
url: 'v1/auth/login',
|
||||
data: values,
|
||||
success: ({ code, data }) => {
|
||||
if (code === 200) {
|
||||
// TODO: 封装一个方法存储、读取token
|
||||
localStorage.setItem('token', data);
|
||||
// TODO: 使用react router
|
||||
this.props.history.push('/');
|
||||
}
|
||||
if (code === 401) {
|
||||
Message.error({
|
||||
content: locale.invalidUsernameOrPassword,
|
||||
});
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
Message.error({
|
||||
content: locale.invalidUsernameOrPassword,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
changePassword() {
|
||||
const { locale = {} } = this.props;
|
||||
this.field.validate((errors, values) => {
|
||||
if (errors) {
|
||||
return;
|
||||
}
|
||||
request({
|
||||
type: 'put',
|
||||
url: 'v1/auth/password',
|
||||
data: values,
|
||||
success: ({ code, data }) => {
|
||||
if (code === 200) {
|
||||
window.localStorage.clear();
|
||||
this.props.history.push('/login');
|
||||
}
|
||||
if (code === 401) {
|
||||
Message.error({
|
||||
content: locale.invalidPassword,
|
||||
});
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
Message.error({
|
||||
content: locale.invalidPassword,
|
||||
});
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { locale = {} } = this.props;
|
||||
const formItemLayout = {
|
||||
labelCol: { fixedSpan: 6 },
|
||||
wrapperCol: { span: 18 },
|
||||
};
|
||||
return (
|
||||
<div style={{ padding: 10 }}>
|
||||
<RegionGroup left={locale.changePassword} />
|
||||
<Form style={{ width: '300px' }} field={this.field}>
|
||||
<FormItem label={locale.oldPassword} required {...formItemLayout}>
|
||||
<Input
|
||||
htmlType="password"
|
||||
placeholder={locale.pleaseInputOldPassword}
|
||||
{...this.field.init('oldPassword', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: locale.passwordRequired,
|
||||
},
|
||||
],
|
||||
})}
|
||||
disabled={this.state.type === 0}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label={locale.newPassword} required {...formItemLayout}>
|
||||
<Input
|
||||
htmlType="password"
|
||||
placeholder={locale.pleaseInputNewPassword}
|
||||
{...this.field.init('newPassword', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: locale.passwordRequired,
|
||||
},
|
||||
],
|
||||
})}
|
||||
disabled={this.state.type === 0}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label={locale.checkPassword} required {...formItemLayout}>
|
||||
<Input
|
||||
htmlType="password"
|
||||
placeholder={locale.pleaseInputNewPasswordAgain}
|
||||
{...this.field.init('confirmNewPassword', {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: locale.passwordRequired,
|
||||
},
|
||||
{ validator: this.validatePassword.bind(this) },
|
||||
],
|
||||
})}
|
||||
disabled={this.state.type === 0}
|
||||
/>
|
||||
</FormItem>
|
||||
<FormItem label=" " {...formItemLayout}>
|
||||
<Form.Submit type="primary" onClick={this.changePassword.bind(this)}>
|
||||
{locale.changePassword}
|
||||
</Form.Submit>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Password;
|
|
@ -28,6 +28,7 @@ import {
|
|||
Switch,
|
||||
} from '@alifd/next';
|
||||
import { request } from '../../../globalLib';
|
||||
import { generateUrl } from '../../../utils/nacosutil';
|
||||
import RegionGroup from '../../../components/RegionGroup';
|
||||
import EditServiceDialog from '../ServiceDetail/EditServiceDialog';
|
||||
import ShowServiceCodeing from 'components/ShowCodeing/ShowServiceCodeing';
|
||||
|
@ -92,7 +93,6 @@ class ServiceList extends React.Component {
|
|||
];
|
||||
request({
|
||||
url: `v1/ns/catalog/services?${parameter.join('&')}`,
|
||||
beforeSend: () => this.openLoading(),
|
||||
success: ({ count = 0, serviceList = [] } = {}) => {
|
||||
this.setState({
|
||||
dataSource: serviceList,
|
||||
|
@ -105,7 +105,6 @@ class ServiceList extends React.Component {
|
|||
total: 0,
|
||||
currentPage: 0,
|
||||
}),
|
||||
complete: () => this.closeLoading(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -293,11 +292,12 @@ class ServiceList extends React.Component {
|
|||
*/
|
||||
<div>
|
||||
<a
|
||||
onClick={() =>
|
||||
onClick={() => {
|
||||
const { name, groupName } = record;
|
||||
this.props.history.push(
|
||||
`/serviceDetail?name=${record.name}&groupName=${record.groupName}`
|
||||
)
|
||||
}
|
||||
generateUrl('/serviceDetail', { name, groupName })
|
||||
);
|
||||
}}
|
||||
style={{ marginRight: 5 }}
|
||||
>
|
||||
{detail}
|
||||
|
|
|
@ -37,10 +37,7 @@ const FormItem = Form.Item;
|
|||
const { Row, Col } = Grid;
|
||||
const { Column } = Table;
|
||||
|
||||
@connect(
|
||||
state => ({ subscriberData: state.subscribers }),
|
||||
{ getSubscribers, removeSubscribers }
|
||||
)
|
||||
@connect(state => ({ subscriberData: state.subscribers }), { getSubscribers, removeSubscribers })
|
||||
@ConfigProvider.config
|
||||
class SubscriberList extends React.Component {
|
||||
static displayName = 'SubscriberList';
|
||||
|
|
|
@ -24,15 +24,8 @@ class Welcome extends React.Component {
|
|||
|
||||
render() {
|
||||
const { functionMode } = this.props;
|
||||
return (
|
||||
<div>
|
||||
{functionMode !== '' && (
|
||||
<Redirect
|
||||
to={`/${functionMode === 'naming' ? 'serviceManagement' : 'configurationManagement'}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const path = functionMode === 'naming' ? 'serviceManagement' : 'configurationManagement';
|
||||
return <>{functionMode !== '' && <Redirect to={`/${path}`} />}</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 { Message } from '@alifd/next';
|
||||
import request from '../utils/request';
|
||||
import { UPDATE_USER, SIGN_IN, USER_LIST, ROLE_LIST, PERMISSIONS_LIST } from '../constants';
|
||||
|
||||
const initialState = {
|
||||
users: {
|
||||
totalCount: 0,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: [],
|
||||
},
|
||||
roles: {
|
||||
totalCount: 0,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: [],
|
||||
},
|
||||
permissions: {
|
||||
totalCount: 0,
|
||||
pageNumber: 1,
|
||||
pagesAvailable: 1,
|
||||
pageItems: [],
|
||||
},
|
||||
};
|
||||
|
||||
const successMsg = res => {
|
||||
if (res.code === 200) {
|
||||
Message.success(res.message);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户列表
|
||||
* @param {*} params
|
||||
*/
|
||||
const getUsers = params => dispatch =>
|
||||
request.get('v1/auth/users', { params }).then(data => dispatch({ type: USER_LIST, data }));
|
||||
|
||||
/**
|
||||
* 创建用户
|
||||
* @param {*} param0
|
||||
*/
|
||||
const createUser = ([username, password]) =>
|
||||
request.post('v1/auth/users', { username, password }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 删除用户
|
||||
* @param {*} username
|
||||
*/
|
||||
const deleteUser = username =>
|
||||
request.delete('v1/auth/users', { params: { username } }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
* @param {*} param0
|
||||
*/
|
||||
const passwordReset = ([username, newPassword]) =>
|
||||
request.put('v1/auth/users', { username, newPassword }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
* @param {*} params
|
||||
*/
|
||||
|
||||
const getRoles = params => dispatch =>
|
||||
request.get('v1/auth/roles', { params }).then(data => dispatch({ type: ROLE_LIST, data }));
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
* @param {*} param0
|
||||
*/
|
||||
const createRole = ([role, username]) =>
|
||||
request.post('v1/auth/roles', { role, username }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
* @param {*} param0
|
||||
*/
|
||||
const deleteRole = role =>
|
||||
request.delete('v1/auth/roles', { params: role }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 权限列表
|
||||
* @param {*} params
|
||||
*/
|
||||
const getPermissions = params => dispatch =>
|
||||
request
|
||||
.get('v1/auth/permissions', { params })
|
||||
.then(data => dispatch({ type: PERMISSIONS_LIST, data }));
|
||||
|
||||
/**
|
||||
* 给角色添加权限
|
||||
* @param {*} param0
|
||||
*/
|
||||
const createPermission = ([role, resource, action]) =>
|
||||
request.post('v1/auth/permissions', { role, resource, action }).then(res => successMsg(res));
|
||||
|
||||
/**
|
||||
* 删除权限
|
||||
* @param {*} param0
|
||||
*/
|
||||
const deletePermission = permission =>
|
||||
request.delete('v1/auth/permissions', { params: permission }).then(res => successMsg(res));
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case USER_LIST:
|
||||
return { ...state, users: { ...action.data } };
|
||||
case ROLE_LIST:
|
||||
return { ...state, roles: { ...action.data } };
|
||||
case PERMISSIONS_LIST:
|
||||
return { ...state, permissions: { ...action.data } };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
getUsers,
|
||||
createUser,
|
||||
deleteUser,
|
||||
passwordReset,
|
||||
getRoles,
|
||||
createRole,
|
||||
deleteRole,
|
||||
getPermissions,
|
||||
createPermission,
|
||||
deletePermission,
|
||||
};
|
|
@ -20,6 +20,12 @@ const initialState = {
|
|||
functionMode: '',
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
* @param {*} param0
|
||||
*/
|
||||
const login = user => request.post('v1/auth/users/login', user);
|
||||
|
||||
const getState = () => dispatch =>
|
||||
request
|
||||
.get('v1/console/server/state')
|
||||
|
@ -52,4 +58,4 @@ export default (state = initialState, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
export { getState };
|
||||
export { getState, login };
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 request from '../utils/request';
|
||||
import { GET_CONFIGURATION } from '../constants';
|
||||
|
||||
const initialState = {
|
||||
configurations: [],
|
||||
};
|
||||
|
||||
const getConfigs = params => dispatch =>
|
||||
request
|
||||
.get('v1/cs/configs', { params })
|
||||
.then(data => dispatch({ type: GET_CONFIGURATION, data }));
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case GET_CONFIGURATION:
|
||||
return { ...state, configurations: action.data };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { getConfigs };
|
|
@ -14,5 +14,8 @@
|
|||
import locale from './locale';
|
||||
import base from './base';
|
||||
import subscribers from './subscribers';
|
||||
import authority from './authority';
|
||||
import namespace from './namespace';
|
||||
import configuration from './configuration';
|
||||
|
||||
export default { locale, base, subscribers };
|
||||
export default { locale, base, subscribers, authority, namespace, configuration };
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 1999-2018 Alibaba Group Holding Ltd.
|
||||
* 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 request from '../utils/request';
|
||||
import { GET_NAMESPACES } from '../constants';
|
||||
|
||||
const initialState = {
|
||||
namespaces: [],
|
||||
};
|
||||
|
||||
const getNamespaces = params => dispatch =>
|
||||
request.get('v1/console/namespaces', { params }).then(response => {
|
||||
const { code, data } = response;
|
||||
dispatch({
|
||||
type: GET_NAMESPACES,
|
||||
data: code === 200 ? data : [],
|
||||
});
|
||||
});
|
||||
|
||||
export default (state = initialState, action) => {
|
||||
switch (action.type) {
|
||||
case GET_NAMESPACES:
|
||||
return { ...state, namespaces: action.data };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export { getNamespaces };
|
|
@ -1,15 +0,0 @@
|
|||
function getValue(key) {
|
||||
if (!document.cookie) return null;
|
||||
const list = document.cookie.split(';') || [];
|
||||
for (const item of list) {
|
||||
const [k = '', v = ''] = item.split('=');
|
||||
if (k.trim() === key) return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function setValue(key, value) {
|
||||
document.cookie = `${key}=${value}`;
|
||||
}
|
||||
|
||||
export default { getValue, setValue };
|
|
@ -46,3 +46,26 @@ export const getParameter = (search, name) => {
|
|||
const [, value = ''] = hit.split('=');
|
||||
return value;
|
||||
};
|
||||
|
||||
export const isJsonString = str => {
|
||||
try {
|
||||
if (typeof JSON.parse(str) === 'object') {
|
||||
return true;
|
||||
}
|
||||
} catch (e) {}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const generateUrl = (url, params) => {
|
||||
return [
|
||||
url,
|
||||
'?',
|
||||
Object.keys(params)
|
||||
.map(key => [key, params[key]].join('='))
|
||||
.join('&'),
|
||||
].join('');
|
||||
};
|
||||
|
||||
export const isPlainObject = obj => {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
};
|
||||
|
|
|
@ -1,12 +1,52 @@
|
|||
import axios from 'axios';
|
||||
import qs from 'qs';
|
||||
import { Message } from '@alifd/next';
|
||||
import { browserHistory } from 'react-router';
|
||||
import { isPlainObject } from './nacosutil';
|
||||
// import { SUCCESS_RESULT_CODE } from '../constants';
|
||||
|
||||
const API_GENERAL_ERROR_MESSAGE = 'Request error, please try again later!';
|
||||
|
||||
function goLogin() {
|
||||
const url = window.location.href;
|
||||
localStorage.removeItem('token');
|
||||
const base_url = url.split('#')[0];
|
||||
window.location.href = `${base_url}#/login`;
|
||||
}
|
||||
|
||||
const request = () => {
|
||||
const instance = axios.create();
|
||||
|
||||
instance.interceptors.request.use(
|
||||
config => {
|
||||
const { url, params, data, method, headers } = config;
|
||||
if (!params) {
|
||||
config.params = {};
|
||||
}
|
||||
if (!url.includes('auth/users/login')) {
|
||||
let token = {};
|
||||
try {
|
||||
token = JSON.parse(localStorage.token);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
goLogin();
|
||||
}
|
||||
const { accessToken = '' } = token;
|
||||
config.params.accessToken = accessToken;
|
||||
config.headers = Object.assign({}, headers, { accessToken });
|
||||
}
|
||||
if (data && isPlainObject(data) && ['post', 'put'].includes(method)) {
|
||||
config.data = qs.stringify(data);
|
||||
if (!headers) {
|
||||
config.headers = {};
|
||||
}
|
||||
config.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
return config;
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
);
|
||||
|
||||
instance.interceptors.response.use(
|
||||
response => {
|
||||
const { success, resultCode, resultMessage = API_GENERAL_ERROR_MESSAGE } = response.data;
|
||||
|
@ -18,11 +58,24 @@ const request = () => {
|
|||
},
|
||||
error => {
|
||||
if (error.response) {
|
||||
const { data, status } = error.response;
|
||||
Message.error(data && typeof data === 'string' ? data : `HTTP ERROR: ${status}`);
|
||||
} else {
|
||||
Message.error(API_GENERAL_ERROR_MESSAGE);
|
||||
const { data = {}, status } = error.response;
|
||||
let message = `HTTP ERROR: ${status}`;
|
||||
if (typeof data === 'string') {
|
||||
message = data;
|
||||
} else if (typeof data === 'object') {
|
||||
message = data.message;
|
||||
}
|
||||
Message.error(message);
|
||||
|
||||
if (
|
||||
[401, 403].includes(status) &&
|
||||
['unknown user!', 'token invalid', 'token expired!'].includes(message)
|
||||
) {
|
||||
goLogin();
|
||||
}
|
||||
return Promise.reject(error.response);
|
||||
}
|
||||
Message.error(API_GENERAL_ERROR_MESSAGE);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-upms</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-upms-api</artifactId>
|
||||
|
@ -37,7 +37,7 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-core</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<parent>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-upms</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>pig-upms-biz</artifactId>
|
||||
|
@ -36,31 +36,31 @@
|
|||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-upms-api</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--安全模块-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-security</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--日志处理-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-log</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--接口文档-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-swagger</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--mybatis 模块-->
|
||||
<dependency>
|
||||
<groupId>com.pig4cloud</groupId>
|
||||
<artifactId>pig-common-mybatis</artifactId>
|
||||
<version>2.7.1</version>
|
||||
<version>2.7.2</version>
|
||||
</dependency>
|
||||
<!--注册中心客户端-->
|
||||
<dependency>
|
||||
|
|
|
@ -102,13 +102,14 @@ public class MenuController {
|
|||
* 新增菜单
|
||||
*
|
||||
* @param sysMenu 菜单信息
|
||||
* @return success/false
|
||||
* @return 含ID 菜单信息
|
||||
*/
|
||||
@SysLog("新增菜单")
|
||||
@PostMapping
|
||||
@PreAuthorize("@pms.hasPermission('sys_menu_add')")
|
||||
public R save(@Valid @RequestBody SysMenu sysMenu) {
|
||||
return R.ok(sysMenuService.save(sysMenu));
|
||||
sysMenuService.save(sysMenu);
|
||||
return R.ok(sysMenu);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue