🔖 Releasing / Version tags. 2.7.2

This commit is contained in:
冷冷 2020-04-12 12:26:22 +08:00
parent 2057ce35ee
commit b5dcbc68e7
103 changed files with 13336 additions and 3515 deletions

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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";
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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!");
}
}

View File

@ -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!");
}
}

View File

@ -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 ?

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -30,6 +30,7 @@
"generator-star-spacing": "off",
"wrap-iife": "off",
"arrow-parens": "off",
"indent": "off"
"indent": "off",
"comma-dangle": "off"
}
}

View File

@ -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"
}

View File

@ -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', {

View File

@ -125,9 +125,7 @@ class RegionGroup extends React.Component {
? false
: window.location.search.indexOf('hideTopbar=') === -1,
},
() => {
this.setRegionWidth();
}
() => this.setRegionWidth()
);
}

View File

@ -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';

View File

@ -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,
};

View File

@ -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,

View File

@ -1217,10 +1217,6 @@ form.vertical-margin-lg .form-group {
border-color: #e0e0e0 !important;
}
.main-container {
padding: 10px;
}
.row-bg-green {
background-color: #e4fdda;
}

View File

@ -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 })}
/>
</>
);
}
}

View File

@ -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>
</>
);
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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',
};

View File

@ -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;

View File

@ -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;

View File

@ -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.
*/
*/

View File

@ -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;

View File

@ -0,0 +1,7 @@
# 权限控制
> AuthorityControl
1. UserManagement => 用户管理
2. RolesManagement => 角色管理
3. PermissionsManagement => 权限管理

View File

@ -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;

View File

@ -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;

View File

@ -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.
*/

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>
&nbsp;&nbsp;&nbsp;
<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;

View File

@ -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;
}

View File

@ -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;

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import Password from './Password';
export default Password;
.filter-panel {
text-align: right;
padding: 10px 0;
}

View File

@ -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,
})
);
}

View File

@ -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 })
);
}

View File

@ -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 })
);
}

View File

@ -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() {

View File

@ -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>

View File

@ -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;
}

View File

@ -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}`
);
}

View File

@ -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>

View File

@ -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>
</>
);
}
}

View File

@ -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>

View File

@ -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,
});
},
});
});
});
};

View File

@ -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>
</>
);
}
}

View File

@ -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;

View File

@ -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}

View File

@ -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';

View File

@ -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}`} />}</>;
}
}

View File

@ -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,
};

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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 };

View File

@ -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]';
};

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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