mirror of
				https://github.com/TeaOSLab/EdgeAdmin.git
				synced 2025-11-04 05:00:25 +08:00 
			
		
		
		
	安全设置增加允许访问的国家地区、省份、是否局域网访问
This commit is contained in:
		@@ -23,6 +23,9 @@ var sharedSecurityConfig *SecurityConfig = nil
 | 
			
		||||
 | 
			
		||||
type SecurityConfig struct {
 | 
			
		||||
	Frame            string  `json:"frame"`
 | 
			
		||||
	AllowCountryIds  []int64 `json:"allowCountryIds"`
 | 
			
		||||
	AllowProvinceIds []int64 `json:"allowProvinceIds"`
 | 
			
		||||
	AllowLocal       bool    `json:"allowLocal"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadSecurityConfig() (*SecurityConfig, error) {
 | 
			
		||||
@@ -50,7 +53,7 @@ func UpdateSecurityConfig(securityConfig *SecurityConfig) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(1), &pb.UpdateSysSettingRequest{
 | 
			
		||||
	_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
 | 
			
		||||
		Code:      SecuritySettingName,
 | 
			
		||||
		ValueJSON: valueJSON,
 | 
			
		||||
	})
 | 
			
		||||
@@ -69,7 +72,7 @@ func loadSecurityConfig() (*SecurityConfig, error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(1), &pb.ReadSysSettingRequest{
 | 
			
		||||
	resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
 | 
			
		||||
		Code: SecuritySettingName,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -94,5 +97,6 @@ func loadSecurityConfig() (*SecurityConfig, error) {
 | 
			
		||||
func defaultSecurityConfig() *SecurityConfig {
 | 
			
		||||
	return &SecurityConfig{
 | 
			
		||||
		Frame:      FrameSameOrigin,
 | 
			
		||||
		AllowLocal: true,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -41,7 +41,7 @@ func (this *SyncClusterTask) loop() error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ctx := rpcClient.Context(1)
 | 
			
		||||
	ctx := rpcClient.Context(0)
 | 
			
		||||
	resp, err := rpcClient.NodeClusterRPC().FindAllChangedNodeClusters(ctx, &pb.FindAllChangedNodeClustersRequest{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import "strings"
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/iwind/TeaGo/types"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// format address
 | 
			
		||||
func FormatAddress(addr string) string {
 | 
			
		||||
@@ -13,3 +16,16 @@ func FormatAddress(addr string) string {
 | 
			
		||||
	addr = strings.TrimSpace(addr)
 | 
			
		||||
	return addr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 分割数字
 | 
			
		||||
func SplitNumbers(numbers string) (result []int64) {
 | 
			
		||||
	if len(numbers) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	pieces := strings.Split(numbers, ",")
 | 
			
		||||
	for _, piece := range pieces {
 | 
			
		||||
		number := types.Int64(strings.TrimSpace(piece))
 | 
			
		||||
		result = append(result, number)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,12 @@
 | 
			
		||||
package security
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/securitymanager"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/actions"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type IndexAction struct {
 | 
			
		||||
@@ -20,12 +23,52 @@ func (this *IndexAction) RunGet(params struct{}) {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 国家和地区
 | 
			
		||||
	countryMaps := []maps.Map{}
 | 
			
		||||
	for _, countryId := range config.AllowCountryIds {
 | 
			
		||||
		countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{CountryId: countryId})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		country := countryResp.Country
 | 
			
		||||
		if country != nil {
 | 
			
		||||
			countryMaps = append(countryMaps, maps.Map{
 | 
			
		||||
				"id":   country.Id,
 | 
			
		||||
				"name": country.Name,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["countries"] = countryMaps
 | 
			
		||||
 | 
			
		||||
	// 省份
 | 
			
		||||
	provinceMaps := []maps.Map{}
 | 
			
		||||
	for _, provinceId := range config.AllowProvinceIds {
 | 
			
		||||
		provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{ProvinceId: provinceId})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		province := provinceResp.Province
 | 
			
		||||
		if province != nil {
 | 
			
		||||
			provinceMaps = append(provinceMaps, maps.Map{
 | 
			
		||||
				"id":   province.Id,
 | 
			
		||||
				"name": province.Name,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["provinces"] = provinceMaps
 | 
			
		||||
 | 
			
		||||
	this.Data["config"] = config
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *IndexAction) RunPost(params struct {
 | 
			
		||||
	Frame           string
 | 
			
		||||
	CountryIdsJSON  []byte
 | 
			
		||||
	ProvinceIdsJSON []byte
 | 
			
		||||
	AllowLocal      bool
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
@@ -38,7 +81,34 @@ func (this *IndexAction) RunPost(params struct {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 框架
 | 
			
		||||
	config.Frame = params.Frame
 | 
			
		||||
 | 
			
		||||
	// 国家和地区
 | 
			
		||||
	countryIds := []int64{}
 | 
			
		||||
	if len(params.CountryIdsJSON) > 0 {
 | 
			
		||||
		err = json.Unmarshal(params.CountryIdsJSON, &countryIds)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	config.AllowCountryIds = countryIds
 | 
			
		||||
 | 
			
		||||
	// 省份
 | 
			
		||||
	provinceIds := []int64{}
 | 
			
		||||
	if len(params.ProvinceIdsJSON) > 0 {
 | 
			
		||||
		err = json.Unmarshal(params.ProvinceIdsJSON, &provinceIds)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	config.AllowProvinceIds = provinceIds
 | 
			
		||||
 | 
			
		||||
	// 允许本地
 | 
			
		||||
	config.AllowLocal = params.AllowLocal
 | 
			
		||||
 | 
			
		||||
	err = securitymanager.UpdateSecurityConfig(config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ func init() {
 | 
			
		||||
		server.
 | 
			
		||||
			Prefix("/ui").
 | 
			
		||||
			Get("/download", new(DownloadAction)).
 | 
			
		||||
			GetPost("/selectProvincesPopup", new(SelectProvincesPopupAction)).
 | 
			
		||||
			GetPost("/selectCountriesPopup", new(SelectCountriesPopupAction)).
 | 
			
		||||
 | 
			
		||||
			// 以下的需要压缩
 | 
			
		||||
			Helper(&actions.Gzip{Level: gzip.BestCompression}).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								internal/web/actions/default/ui/selectCountriesPopup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								internal/web/actions/default/ui/selectCountriesPopup.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
package ui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/utils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/actions"
 | 
			
		||||
	"github.com/iwind/TeaGo/lists"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SelectCountriesPopupAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SelectCountriesPopupAction) Init() {
 | 
			
		||||
	this.Nav("", "", "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SelectCountriesPopupAction) RunGet(params struct {
 | 
			
		||||
	CountryIds string
 | 
			
		||||
}) {
 | 
			
		||||
	selectedCountryIds := utils.SplitNumbers(params.CountryIds)
 | 
			
		||||
 | 
			
		||||
	countriesResp, err := this.RPC().RegionCountryRPC().FindAllEnabledRegionCountries(this.AdminContext(), &pb.FindAllEnabledRegionCountriesRequest{})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	countryMaps := []maps.Map{}
 | 
			
		||||
	for _, country := range countriesResp.Countries {
 | 
			
		||||
		countryMaps = append(countryMaps, maps.Map{
 | 
			
		||||
			"id":        country.Id,
 | 
			
		||||
			"name":      country.Name,
 | 
			
		||||
			"letter":    strings.ToUpper(string(country.Pinyin[0][0])),
 | 
			
		||||
			"isChecked": lists.ContainsInt64(selectedCountryIds, country.Id),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["countries"] = countryMaps
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SelectCountriesPopupAction) RunPost(params struct {
 | 
			
		||||
	CountryIds []int64
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
}) {
 | 
			
		||||
	countryMaps := []maps.Map{}
 | 
			
		||||
	for _, countryId := range params.CountryIds {
 | 
			
		||||
		countryResp, err := this.RPC().RegionCountryRPC().FindEnabledRegionCountry(this.AdminContext(), &pb.FindEnabledRegionCountryRequest{CountryId: countryId})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		country := countryResp.Country
 | 
			
		||||
		if country == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		countryMaps = append(countryMaps, maps.Map{
 | 
			
		||||
			"id":   country.Id,
 | 
			
		||||
			"name": country.Name,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["countries"] = countryMaps
 | 
			
		||||
 | 
			
		||||
	this.Success()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								internal/web/actions/default/ui/selectProvincesPopup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								internal/web/actions/default/ui/selectProvincesPopup.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
package ui
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/utils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/actions"
 | 
			
		||||
	"github.com/iwind/TeaGo/lists"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const ChinaCountryId = 1
 | 
			
		||||
 | 
			
		||||
type SelectProvincesPopupAction struct {
 | 
			
		||||
	actionutils.ParentAction
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SelectProvincesPopupAction) Init() {
 | 
			
		||||
	this.Nav("", "", "")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SelectProvincesPopupAction) RunGet(params struct {
 | 
			
		||||
	ProvinceIds string
 | 
			
		||||
}) {
 | 
			
		||||
	selectedProvinceIds := utils.SplitNumbers(params.ProvinceIds)
 | 
			
		||||
 | 
			
		||||
	provincesResp, err := this.RPC().RegionProvinceRPC().FindAllEnabledRegionProvincesWithCountryId(this.AdminContext(), &pb.FindAllEnabledRegionProvincesWithCountryIdRequest{CountryId: ChinaCountryId})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.ErrorPage(err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	provinceMaps := []maps.Map{}
 | 
			
		||||
	for _, province := range provincesResp.Provinces {
 | 
			
		||||
		provinceMaps = append(provinceMaps, maps.Map{
 | 
			
		||||
			"id":        province.Id,
 | 
			
		||||
			"name":      province.Name,
 | 
			
		||||
			"isChecked": lists.ContainsInt64(selectedProvinceIds, province.Id),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["provinces"] = provinceMaps
 | 
			
		||||
 | 
			
		||||
	this.Show()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *SelectProvincesPopupAction) RunPost(params struct {
 | 
			
		||||
	ProvinceIds []int64
 | 
			
		||||
 | 
			
		||||
	Must *actions.Must
 | 
			
		||||
	CSRF *actionutils.CSRF
 | 
			
		||||
}) {
 | 
			
		||||
	provinceMaps := []maps.Map{}
 | 
			
		||||
	for _, provinceId := range params.ProvinceIds {
 | 
			
		||||
		provinceResp, err := this.RPC().RegionProvinceRPC().FindEnabledRegionProvince(this.AdminContext(), &pb.FindEnabledRegionProvinceRequest{ProvinceId: provinceId})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.ErrorPage(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		province := provinceResp.Province
 | 
			
		||||
		if province == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		provinceMaps = append(provinceMaps, maps.Map{
 | 
			
		||||
			"id":   province.Id,
 | 
			
		||||
			"name": province.Name,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	this.Data["provinces"] = provinceMaps
 | 
			
		||||
 | 
			
		||||
	this.Success()
 | 
			
		||||
}
 | 
			
		||||
@@ -35,6 +35,12 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
 | 
			
		||||
	}
 | 
			
		||||
	action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
 | 
			
		||||
 | 
			
		||||
	// 检查IP
 | 
			
		||||
	if !checkIP(securityConfig, action.RequestRemoteIP()) {
 | 
			
		||||
		action.ResponseWriter.WriteHeader(http.StatusForbidden)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 检查系统是否已经配置过
 | 
			
		||||
	if !setup.IsConfigured() {
 | 
			
		||||
		action.RedirectURL("/setup")
 | 
			
		||||
@@ -82,8 +88,48 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
 | 
			
		||||
		return action.Data["teaTitle"].(string)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// 初始化变量
 | 
			
		||||
	modules := []maps.Map{
 | 
			
		||||
	action.Data["teaTitle"] = teaconst.ProductNameZH
 | 
			
		||||
	action.Data["teaName"] = teaconst.ProductNameZH
 | 
			
		||||
 | 
			
		||||
	resp, err := rpc.AdminRPC().FindAdminFullname(rpc.Context(0), &pb.FindAdminFullnameRequest{AdminId: int64(this.AdminId)})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.PrintError(err)
 | 
			
		||||
		action.Data["teaUsername"] = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		action.Data["teaUsername"] = resp.Fullname
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	action.Data["teaUserAvatar"] = ""
 | 
			
		||||
 | 
			
		||||
	action.Data["teaMenu"] = ""
 | 
			
		||||
	action.Data["teaModules"] = this.modules()
 | 
			
		||||
	action.Data["teaSubMenus"] = []map[string]interface{}{}
 | 
			
		||||
	action.Data["teaTabbar"] = []map[string]interface{}{}
 | 
			
		||||
	action.Data["teaVersion"] = teaconst.Version
 | 
			
		||||
	action.Data["teaIsSuper"] = false
 | 
			
		||||
	action.Data["teaDemoEnabled"] = teaconst.IsDemo
 | 
			
		||||
	if !action.Data.Has("teaSubMenu") {
 | 
			
		||||
		action.Data["teaSubMenu"] = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 菜单
 | 
			
		||||
	action.Data["firstMenuItem"] = ""
 | 
			
		||||
 | 
			
		||||
	// 未读消息数
 | 
			
		||||
	action.Data["teaBadge"] = 0
 | 
			
		||||
 | 
			
		||||
	// 调用Init
 | 
			
		||||
	initMethod := reflect.ValueOf(actionPtr).MethodByName("Init")
 | 
			
		||||
	if initMethod.IsValid() {
 | 
			
		||||
		initMethod.Call([]reflect.Value{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 菜单配置
 | 
			
		||||
func (this *UserMustAuth) modules() []maps.Map {
 | 
			
		||||
	return []maps.Map{
 | 
			
		||||
		{
 | 
			
		||||
			"code": "servers",
 | 
			
		||||
			"name": "网站服务",
 | 
			
		||||
@@ -136,46 +182,9 @@ func (this *UserMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramNam
 | 
			
		||||
			"icon": "history",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	action.Data["teaTitle"] = teaconst.ProductNameZH
 | 
			
		||||
	action.Data["teaName"] = teaconst.ProductNameZH
 | 
			
		||||
 | 
			
		||||
	resp, err := rpc.AdminRPC().FindAdminFullname(rpc.Context(0), &pb.FindAdminFullnameRequest{AdminId: int64(this.AdminId)})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.PrintError(err)
 | 
			
		||||
		action.Data["teaUsername"] = ""
 | 
			
		||||
	} else {
 | 
			
		||||
		action.Data["teaUsername"] = resp.Fullname
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	action.Data["teaUserAvatar"] = ""
 | 
			
		||||
 | 
			
		||||
	action.Data["teaMenu"] = ""
 | 
			
		||||
	action.Data["teaModules"] = modules
 | 
			
		||||
	action.Data["teaSubMenus"] = []map[string]interface{}{}
 | 
			
		||||
	action.Data["teaTabbar"] = []map[string]interface{}{}
 | 
			
		||||
	action.Data["teaVersion"] = teaconst.Version
 | 
			
		||||
	action.Data["teaIsSuper"] = false
 | 
			
		||||
	action.Data["teaDemoEnabled"] = teaconst.IsDemo
 | 
			
		||||
	if !action.Data.Has("teaSubMenu") {
 | 
			
		||||
		action.Data["teaSubMenu"] = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 菜单
 | 
			
		||||
	action.Data["firstMenuItem"] = ""
 | 
			
		||||
 | 
			
		||||
	// 未读消息数
 | 
			
		||||
	action.Data["teaBadge"] = 0
 | 
			
		||||
 | 
			
		||||
	// 调用Init
 | 
			
		||||
	initMethod := reflect.ValueOf(actionPtr).MethodByName("Init")
 | 
			
		||||
	if initMethod.IsValid() {
 | 
			
		||||
		initMethod.Call([]reflect.Value{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 跳转到登录页
 | 
			
		||||
func (this *UserMustAuth) login(action *actions.ActionObject) {
 | 
			
		||||
	action.RedirectURL("/")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,12 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
 | 
			
		||||
	}
 | 
			
		||||
	action.AddHeader("Content-Security-Policy", "default-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'")
 | 
			
		||||
 | 
			
		||||
	// 检查IP
 | 
			
		||||
	if !checkIP(securityConfig, action.RequestRemoteIP()) {
 | 
			
		||||
		action.ResponseWriter.WriteHeader(http.StatusForbidden)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								internal/web/helpers/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/web/helpers/utils.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
package helpers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	nodes "github.com/TeaOSLab/EdgeAdmin/internal/rpc"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAdmin/internal/securitymanager"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/iwind/TeaGo/lists"
 | 
			
		||||
	"github.com/iwind/TeaGo/logs"
 | 
			
		||||
	"net"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 检查用户IP
 | 
			
		||||
func checkIP(config *securitymanager.SecurityConfig, ipAddr string) bool {
 | 
			
		||||
	if config == nil {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 本地IP
 | 
			
		||||
	ip := net.ParseIP(ipAddr).To4()
 | 
			
		||||
	if ip == nil {
 | 
			
		||||
		logs.Println("[USER_MUST_AUTH]invalid client address: " + ipAddr)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if config.AllowLocal && isLocalIP(ip) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 检查位置
 | 
			
		||||
	if len(config.AllowCountryIds) > 0 || len(config.AllowProvinceIds) > 0 {
 | 
			
		||||
		rpc, err := nodes.SharedRPC()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error())
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		resp, err := rpc.IPLibraryRPC().LookupIPRegion(rpc.Context(0), &pb.LookupIPRegionRequest{Ip: ipAddr})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logs.Println("[USER_MUST_AUTH][ERROR]" + err.Error())
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		if resp.Region == nil {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		if len(config.AllowCountryIds) > 0 && !lists.ContainsInt64(config.AllowCountryIds, resp.Region.CountryId) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		if len(config.AllowProvinceIds) > 0 && !lists.ContainsInt64(config.AllowProvinceIds, resp.Region.ProvinceId) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 判断是否为本地IP
 | 
			
		||||
func isLocalIP(ip net.IP) bool {
 | 
			
		||||
	return ip[0] == 127 ||
 | 
			
		||||
		ip[0] == 10 ||
 | 
			
		||||
		(ip[0] == 172 && ip[1]&0xf0 == 16) ||
 | 
			
		||||
		(ip[0] == 192 && ip[1] == 168)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								web/public/js/components/common/countries-selector.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								web/public/js/components/common/countries-selector.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
Vue.component("countries-selector", {
 | 
			
		||||
	props: ["v-countries"],
 | 
			
		||||
	data: function () {
 | 
			
		||||
		let countries = this.vCountries
 | 
			
		||||
		if (countries == null) {
 | 
			
		||||
			countries = []
 | 
			
		||||
		}
 | 
			
		||||
		let countryIds = countries.$map(function (k, v) {
 | 
			
		||||
			return v.id
 | 
			
		||||
		})
 | 
			
		||||
		return {
 | 
			
		||||
			countries: countries,
 | 
			
		||||
			countryIds: countryIds
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		add: function () {
 | 
			
		||||
			let countryStringIds = this.countryIds.map(function (v) {
 | 
			
		||||
				return v.toString()
 | 
			
		||||
			})
 | 
			
		||||
			let that = this
 | 
			
		||||
			teaweb.popup("/ui/selectCountriesPopup?countryIds=" + countryStringIds.join(","), {
 | 
			
		||||
				width: "48em",
 | 
			
		||||
				height: "23em",
 | 
			
		||||
				callback: function (resp) {
 | 
			
		||||
					that.countries = resp.data.countries
 | 
			
		||||
					that.change()
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
		remove: function (index) {
 | 
			
		||||
			this.countries.$remove(index)
 | 
			
		||||
			this.change()
 | 
			
		||||
		},
 | 
			
		||||
		change: function () {
 | 
			
		||||
			this.countryIds = this.countries.$map(function (k, v) {
 | 
			
		||||
				return v.id
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	template: `<div>
 | 
			
		||||
	<input type="hidden" name="countryIdsJSON" :value="JSON.stringify(countryIds)"/>
 | 
			
		||||
	<div v-if="countries.length > 0" style="margin-bottom: 0.5em">
 | 
			
		||||
		<div v-for="(country, index) in countries" class="ui label tiny">{{country.name}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove"></i></a></div>
 | 
			
		||||
		<div class="ui divider"></div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div>
 | 
			
		||||
		<button class="ui button small" type="button" @click.prevent="add">+</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>`
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										51
									
								
								web/public/js/components/common/provinces-selector.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								web/public/js/components/common/provinces-selector.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
Vue.component("provinces-selector", {
 | 
			
		||||
	props: ["v-provinces"],
 | 
			
		||||
	data: function () {
 | 
			
		||||
		let provinces = this.vProvinces
 | 
			
		||||
		if (provinces == null) {
 | 
			
		||||
			provinces = []
 | 
			
		||||
		}
 | 
			
		||||
		let provinceIds = provinces.$map(function (k, v) {
 | 
			
		||||
			return v.id
 | 
			
		||||
		})
 | 
			
		||||
		return {
 | 
			
		||||
			provinces: provinces,
 | 
			
		||||
			provinceIds: provinceIds
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	methods: {
 | 
			
		||||
		add: function () {
 | 
			
		||||
			let provinceStringIds = this.provinceIds.map(function (v) {
 | 
			
		||||
				return v.toString()
 | 
			
		||||
			})
 | 
			
		||||
			let that = this
 | 
			
		||||
			teaweb.popup("/ui/selectProvincesPopup?provinceIds=" + provinceStringIds.join(","), {
 | 
			
		||||
				width: "48em",
 | 
			
		||||
				height: "23em",
 | 
			
		||||
				callback: function (resp) {
 | 
			
		||||
					that.provinces = resp.data.provinces
 | 
			
		||||
					that.change()
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		},
 | 
			
		||||
		remove: function (index) {
 | 
			
		||||
			this.provinces.$remove(index)
 | 
			
		||||
			this.change()
 | 
			
		||||
		},
 | 
			
		||||
		change: function () {
 | 
			
		||||
			this.provinceIds = this.provinces.$map(function (k, v) {
 | 
			
		||||
				return v.id
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	template: `<div>
 | 
			
		||||
	<input type="hidden" name="provinceIdsJSON" :value="JSON.stringify(provinceIds)"/>
 | 
			
		||||
	<div v-if="provinces.length > 0" style="margin-bottom: 0.5em">
 | 
			
		||||
		<div v-for="(province, index) in provinces" class="ui label tiny">{{province.name}} <a href="" title="删除" @click.prevent="remove(index)"><i class="icon remove"></i></a></div>
 | 
			
		||||
		<div class="ui divider"></div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div>
 | 
			
		||||
		<button class="ui button small" type="button" @click.prevent="add">+</button>
 | 
			
		||||
	</div>
 | 
			
		||||
</div>`
 | 
			
		||||
})
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
    {$TEA.VUE}
 | 
			
		||||
    {$echo "header"}
 | 
			
		||||
    <script type="text/javascript" src="/_/@default/@layout.js"></script>
 | 
			
		||||
	<script type="text/javascript" src="/ui/components.js?v=1.0.0"></script>
 | 
			
		||||
	<script type="text/javascript" src="/ui/components.js?v=v{$.teaVersion}"></script>
 | 
			
		||||
	<script type="text/javascript" src="/js/utils.js"></script>
 | 
			
		||||
	<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
 | 
			
		||||
	<script type="text/javascript" src="/js/date.tea.js"></script>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
	<link rel="stylesheet" type="text/css" href="/_/@default/@layout_override.css" media="all"/>
 | 
			
		||||
	{$echo "header"}
 | 
			
		||||
	<script type="text/javascript" src="/_/@default/@layout.js"></script>
 | 
			
		||||
	<script type="text/javascript" src="/ui/components.js"></script>
 | 
			
		||||
	<script type="text/javascript" src="/ui/components.js?v=v{$.teaVersion}"></script>
 | 
			
		||||
	<script type="text/javascript" src="/js/utils.js"></script>
 | 
			
		||||
	<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
 | 
			
		||||
	<script type="text/javascript" src="/js/date.tea.js"></script>
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,27 @@
 | 
			
		||||
				<p class="comment">当前服务被别的网页框架嵌套的条件限制。</p>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td>允许访问的国家和地区</td>
 | 
			
		||||
			<td>
 | 
			
		||||
				<countries-selector :v-countries="countries"></countries-selector>
 | 
			
		||||
				<p class="comment">设置后,只有这些国家和地区才能访问管理界面,如果不设置表示没有限制。</p>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td>允许访问的省份(中国)</td>
 | 
			
		||||
			<td>
 | 
			
		||||
				<provinces-selector :v-provinces="provinces"></provinces-selector>
 | 
			
		||||
				<p class="comment">设置后,只有这些省份才能访问管理界面,如果不设置表示没有限制。</p>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td>允许局域网访问</td>
 | 
			
		||||
			<td>
 | 
			
		||||
				<checkbox name="allowLocal" v-model="config.allowLocal"></checkbox>
 | 
			
		||||
				<p class="comment">选中表示允许在本机和局域网访问。</p>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
	</table>
 | 
			
		||||
	<submit-btn></submit-btn>
 | 
			
		||||
</form>
 | 
			
		||||
							
								
								
									
										16
									
								
								web/views/@default/ui/selectCountriesPopup.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								web/views/@default/ui/selectCountriesPopup.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
.region-letter-group .item {
 | 
			
		||||
  padding-left: 1em !important;
 | 
			
		||||
  padding-right: 1em !important;
 | 
			
		||||
}
 | 
			
		||||
.country-group {
 | 
			
		||||
  padding-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
.country-group .country-list .item {
 | 
			
		||||
  float: left;
 | 
			
		||||
  width: 12em;
 | 
			
		||||
  margin-bottom: 0.5em;
 | 
			
		||||
}
 | 
			
		||||
.country-group .country-list .item .checkbox label {
 | 
			
		||||
  font-size: 12px !important;
 | 
			
		||||
}
 | 
			
		||||
/*# sourceMappingURL=selectCountriesPopup.css.map */
 | 
			
		||||
							
								
								
									
										1
									
								
								web/views/@default/ui/selectCountriesPopup.css.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/views/@default/ui/selectCountriesPopup.css.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"version":3,"sources":["selectCountriesPopup.less"],"names":[],"mappings":"AAAA,oBACC;EACC,4BAAA;EACA,6BAAA;;AAIF;EAaC,mBAAA;;AAbD,cACC,cACC;EACC,WAAA;EACA,WAAA;EACA,oBAAA;;AALH,cACC,cACC,MAKC,UAAU;EACT,0BAAA","file":"selectCountriesPopup.css"}
 | 
			
		||||
							
								
								
									
										27
									
								
								web/views/@default/ui/selectCountriesPopup.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web/views/@default/ui/selectCountriesPopup.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
{$layout "layout_popup"}
 | 
			
		||||
 | 
			
		||||
<h3>选择国家和地区</h3>
 | 
			
		||||
<form method="post" data-tea-success="success" data-tea-action="$" class="ui form">
 | 
			
		||||
	<csrf-token></csrf-token>
 | 
			
		||||
	<table class="ui table selectable">
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td>
 | 
			
		||||
				<div class="ui menu tabular tiny region-letter-group">
 | 
			
		||||
					<a href="" v-for="group in letterGroups" class="item" :class="{active: group == selectedGroup}" @click.prevent="selectGroup(group)">{{group}}</a>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div v-for="group in letterGroups">
 | 
			
		||||
					<div v-for="letter in group" v-show="letterCountries[letter] != null && group == selectedGroup" class="country-group">
 | 
			
		||||
						<h4>{{letter}}</h4>
 | 
			
		||||
						<div class="country-list">
 | 
			
		||||
							<div class="item" v-for="country in letterCountries[letter]">
 | 
			
		||||
								<checkbox name="countryIds" :v-value="country.id" v-model="country.isChecked">{{country.name}}</checkbox>
 | 
			
		||||
							</div>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="clear"></div>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
	</table>
 | 
			
		||||
	<submit-btn></submit-btn>
 | 
			
		||||
</form>
 | 
			
		||||
							
								
								
									
										21
									
								
								web/views/@default/ui/selectCountriesPopup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								web/views/@default/ui/selectCountriesPopup.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
	this.letterGroups = [
 | 
			
		||||
		"ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VWX", "YZ"
 | 
			
		||||
	];
 | 
			
		||||
	this.selectedGroup = "ABC"
 | 
			
		||||
	this.letterCountries = {}
 | 
			
		||||
	let that = this
 | 
			
		||||
	this.countries.forEach(function (country) {
 | 
			
		||||
		if (typeof (that.letterCountries[country.letter]) == "undefined") {
 | 
			
		||||
			that.letterCountries[country.letter] = []
 | 
			
		||||
		}
 | 
			
		||||
		that.letterCountries[country.letter].push(country)
 | 
			
		||||
	})
 | 
			
		||||
	this.isCheckingAll = false
 | 
			
		||||
 | 
			
		||||
	this.selectGroup = function (group) {
 | 
			
		||||
		this.selectedGroup = group
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.success = NotifyPopup
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										22
									
								
								web/views/@default/ui/selectCountriesPopup.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								web/views/@default/ui/selectCountriesPopup.less
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
.region-letter-group {
 | 
			
		||||
	.item {
 | 
			
		||||
		padding-left: 1em !important;
 | 
			
		||||
		padding-right: 1em !important;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.country-group {
 | 
			
		||||
	.country-list {
 | 
			
		||||
		.item {
 | 
			
		||||
			float: left;
 | 
			
		||||
			width: 12em;
 | 
			
		||||
			margin-bottom: 0.5em;
 | 
			
		||||
 | 
			
		||||
			.checkbox label {
 | 
			
		||||
				font-size: 12px !important;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	padding-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								web/views/@default/ui/selectProvincesPopup.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/views/@default/ui/selectProvincesPopup.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
.province-list .item {
 | 
			
		||||
  float: left;
 | 
			
		||||
  width: 12em;
 | 
			
		||||
  margin-bottom: 0.5em;
 | 
			
		||||
}
 | 
			
		||||
.province-list .item .checkbox label {
 | 
			
		||||
  font-size: 12px !important;
 | 
			
		||||
}
 | 
			
		||||
/*# sourceMappingURL=selectProvincesPopup.css.map */
 | 
			
		||||
							
								
								
									
										1
									
								
								web/views/@default/ui/selectProvincesPopup.css.map
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								web/views/@default/ui/selectProvincesPopup.css.map
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
{"version":3,"sources":["selectProvincesPopup.less"],"names":[],"mappings":"AAAA,cACC;EACC,WAAA;EACA,WAAA;EACA,oBAAA;;AAJF,cACC,MAKC,UAAU;EACT,0BAAA","file":"selectProvincesPopup.css"}
 | 
			
		||||
							
								
								
									
										19
									
								
								web/views/@default/ui/selectProvincesPopup.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								web/views/@default/ui/selectProvincesPopup.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
{$layout "layout_popup"}
 | 
			
		||||
 | 
			
		||||
<h3>选择省份</h3>
 | 
			
		||||
 | 
			
		||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success">
 | 
			
		||||
	<csrf-token></csrf-token>
 | 
			
		||||
	<table class="ui table selectable">
 | 
			
		||||
		<tr>
 | 
			
		||||
			<td>
 | 
			
		||||
				<div class="province-list">
 | 
			
		||||
					<div class="item" v-for="province in provinces">
 | 
			
		||||
						<checkbox name="provinceIds" :v-value="province.id" v-model="province.isChecked">{{province.name}}</checkbox>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</td>
 | 
			
		||||
		</tr>
 | 
			
		||||
	</table>
 | 
			
		||||
	<submit-btn></submit-btn>
 | 
			
		||||
</form>
 | 
			
		||||
							
								
								
									
										3
									
								
								web/views/@default/ui/selectProvincesPopup.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/views/@default/ui/selectProvincesPopup.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
Tea.context(function () {
 | 
			
		||||
	this.success = NotifyPopup
 | 
			
		||||
})
 | 
			
		||||
							
								
								
									
										11
									
								
								web/views/@default/ui/selectProvincesPopup.less
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								web/views/@default/ui/selectProvincesPopup.less
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
.province-list {
 | 
			
		||||
	.item {
 | 
			
		||||
		float: left;
 | 
			
		||||
		width: 12em;
 | 
			
		||||
		margin-bottom: 0.5em;
 | 
			
		||||
 | 
			
		||||
		.checkbox label {
 | 
			
		||||
			font-size: 12px !important;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user