diff --git a/conf/config-temp.toml b/conf/config-temp.toml
index cff8ebe..5604587 100644
--- a/conf/config-temp.toml
+++ b/conf/config-temp.toml
@@ -7,9 +7,6 @@ address = ":5000"
driver = "sqlite3"
connect = "./data/ignite.db"
-[ss]
-image = "goignite/ss-go:latest"
-
[host]
address = "localhost"
from = 5001
diff --git a/controllers/index.go b/controllers/index.go
index 256c345..d241a28 100644
--- a/controllers/index.go
+++ b/controllers/index.go
@@ -64,7 +64,7 @@ func (router *MainRouter) SignupHandler(c *gin.Context) {
pwd := c.PostForm("password")
confirmPwd := c.PostForm("confirm-password")
- matched, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username)
+ matched, _ := regexp.MatchString("^[a-zA-Z0-9][a-zA-Z0-9_.-]+$", username)
if !matched {
fmt.Println("Username is invalid!")
diff --git a/controllers/panel.go b/controllers/panel.go
index 4b7caef..414f68d 100644
--- a/controllers/panel.go
+++ b/controllers/panel.go
@@ -11,6 +11,27 @@ import (
"github.com/go-ignite/ignite/ss"
)
+var (
+ servers = []string{"SS", "SSR"}
+ ssMethods = []string{"aes-256-cfb", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305"}
+ ssrMethods = []string{"aes-256-cfb", "aes-256-ctr", "chacha20", "chacha20-ietf"}
+ serverMethodsMap = map[string]map[string]bool{}
+)
+
+func init() {
+ ssMethodMap := map[string]bool{}
+ for _, method := range ssMethods {
+ ssMethodMap[method] = true
+ }
+ ssrMethodMap := map[string]bool{}
+ for _, method := range ssrMethods {
+ ssrMethodMap[method] = true
+ }
+
+ serverMethodsMap["SS"] = ssMethodMap
+ serverMethodsMap["SSR"] = ssrMethodMap
+}
+
func (router *MainRouter) PanelIndexHandler(c *gin.Context) {
userID, exists := c.Get("userId")
@@ -32,16 +53,24 @@ func (router *MainRouter) PanelIndexHandler(c *gin.Context) {
}
uInfo := &models.UserInfo{
- Id: user.Id,
- Host: ss.Host,
- Username: user.Username,
- Status: user.Status,
- PackageUsed: fmt.Sprintf("%.2f", user.PackageUsed),
- PackageLimit: user.PackageLimit,
- PackageLeft: fmt.Sprintf("%.2f", float32(user.PackageLimit)-user.PackageUsed),
- ServicePort: user.ServicePort,
- ServicePwd: user.ServicePwd,
- Expired: user.Expired.Format("2006-01-02"),
+ Id: user.Id,
+ Host: ss.Host,
+ Username: user.Username,
+ Status: user.Status,
+ PackageUsed: fmt.Sprintf("%.2f", user.PackageUsed),
+ PackageLimit: user.PackageLimit,
+ PackageLeft: fmt.Sprintf("%.2f", float32(user.PackageLimit)-user.PackageUsed),
+ ServicePort: user.ServicePort,
+ ServicePwd: user.ServicePwd,
+ ServiceMethod: user.ServiceMethod,
+ ServiceType: user.ServiceType,
+ Expired: user.Expired.Format("2006-01-02"),
+ }
+ if uInfo.ServiceMethod == "" {
+ uInfo.ServiceMethod = "aes-256-cfb"
+ }
+ if uInfo.ServiceType == "" {
+ uInfo.ServiceType = "SS"
}
if user.PackageLimit == 0 {
@@ -51,7 +80,10 @@ func (router *MainRouter) PanelIndexHandler(c *gin.Context) {
}
c.HTML(http.StatusOK, "panel.html", gin.H{
- "uInfo": uInfo,
+ "uInfo": uInfo,
+ "ss_methods": ssMethods,
+ "ssr_methods": ssrMethods,
+ "servers": servers,
})
}
@@ -65,26 +97,43 @@ func (router *MainRouter) LogoutHandler(c *gin.Context) {
func (router *MainRouter) CreateServiceHandler(c *gin.Context) {
userID, _ := c.Get("userId")
+ method := c.PostForm("method")
+ serverType := c.PostForm("server-type")
- user := new(models.User)
- router.db.Id(userID).Get(user)
+ fmt.Println("UserID", userID)
+ fmt.Println("ServerType:", serverType)
+ fmt.Println("Method:", method)
- //Get all used ports.
- var usedPorts []int
- router.db.Table("user").Cols("service_port").Find(&usedPorts)
+ methodMap, ok := serverMethodsMap[serverType]
+ if !ok {
+ resp := models.Response{Success: false, Message: "服务类型配置错误!"}
+ c.JSON(http.StatusOK, resp)
+ return
+ }
+ if !methodMap[method] {
+ resp := models.Response{Success: false, Message: "加密方法配置错误!"}
+ c.JSON(http.StatusOK, resp)
+ return
+ }
+
+ user := new(models.User)
+ router.db.Id(userID).Get(user)
if user.ServiceId != "" {
- resp := models.Response{Success: false, Message: "Service already created!"}
+ resp := models.Response{Success: false, Message: "服务已创建!"}
c.JSON(http.StatusOK, resp)
return
}
- // 1. Create ss service
- result, err := ss.CreateAndStartContainer(user.Username, &usedPorts)
+ //Get all used ports.
+ var usedPorts []int
+ router.db.Table("user").Cols("service_port").Find(&usedPorts)
+ // 1. Create ss service
+ result, err := ss.CreateAndStartContainer(serverType, user.Username, method, &usedPorts)
if err != nil {
log.Println("Create ss service error:", err.Error())
- resp := models.Response{Success: false, Message: "Create service error!"}
+ resp := models.Response{Success: false, Message: "创建服务失败!"}
c.JSON(http.StatusOK, resp)
return
}
@@ -94,8 +143,9 @@ func (router *MainRouter) CreateServiceHandler(c *gin.Context) {
user.ServiceId = result.ID
user.ServicePort = result.Port
user.ServicePwd = result.Password
- affected, err := router.db.Id(userID).Cols("status", "service_port", "service_pwd", "service_id").Update(user)
-
+ user.ServiceMethod = method
+ user.ServiceType = serverType
+ affected, err := router.db.Id(userID).Cols("status", "service_port", "service_pwd", "service_id", "service_method", "service_type").Update(user)
if affected == 0 || err != nil {
if err != nil {
log.Println("Update user info error:", err.Error())
@@ -104,14 +154,14 @@ func (router *MainRouter) CreateServiceHandler(c *gin.Context) {
//Force remove created container
ss.RemoveContainer(result.ID)
- resp := models.Response{Success: false, Message: "Create service error!"}
+ resp := models.Response{Success: false, Message: "更新用户信息失败!"}
c.JSON(http.StatusOK, resp)
return
}
result.PackageLimit = user.PackageLimit
result.Host = ss.Host
- resp := models.Response{Success: true, Message: "OK!", Data: result}
+ resp := models.Response{Success: true, Message: "服务创建成功!", Data: result}
c.JSON(http.StatusOK, resp)
}
diff --git a/controllers/router.go b/controllers/router.go
index 1861c11..37eb020 100644
--- a/controllers/router.go
+++ b/controllers/router.go
@@ -17,7 +17,6 @@ type MainRouter struct {
func (self *MainRouter) Initialize(r *gin.Engine) {
ss.Host = utils.HOST_Address
- ss.ImageUrl = utils.SS_Image
ss.PortRange = []int{utils.HOST_From, utils.HOST_To}
//Init session store
@@ -39,8 +38,11 @@ func (self *MainRouter) Initialize(r *gin.Engine) {
}
go func() {
- if err := ss.PullImage(); err != nil {
- log.Printf("Pull image [%s] error: %s\n", ss.ImageUrl, err.Error())
+ if err := ss.PullImage(ss.SS_IMAGE); err != nil {
+ log.Printf("Pull image [%s] error: %s\n", ss.SS_IMAGE, err.Error())
+ }
+ if err := ss.PullImage(ss.SSR_IMAGE); err != nil {
+ log.Printf("Pull image [%s] error: %s\n", ss.SSR_IMAGE, err.Error())
}
}()
self.router.Run(utils.APP_Address)
diff --git a/models/user.go b/models/user.go
index 1cb8c42..5b48b67 100644
--- a/models/user.go
+++ b/models/user.go
@@ -11,8 +11,10 @@ type User struct {
PackageUsed float32 //Package bandwidth used, unit: GB
Status int `xorm:"default 0"` // 0=>not created 1=>running 2=>stopped
ServiceId string //SS container id
+ ServiceType string //SS container type
ServicePort int `xorm:"not null default 0"` //Docker service port for SS
ServicePwd string //Password for SS
+ ServiceMethod string //Encryption method for SS
LastStatsResult uint64 //Last time stats result,unit: byte
LastStatsTime *time.Time //Last time stats time
Created time.Time `xorm:"created"`
@@ -31,5 +33,7 @@ type UserInfo struct {
PackageLeftPercent string
ServicePort int
ServicePwd string
+ ServiceMethod string
+ ServiceType string
Expired string
}
diff --git a/ss/ss.go b/ss/ss.go
index f489df3..22f3ab9 100644
--- a/ss/ss.go
+++ b/ss/ss.go
@@ -13,8 +13,12 @@ import (
"github.com/go-ignite/ignite/utils"
)
+const (
+ SS_IMAGE = "goignite/ss-libev:latest"
+ SSR_IMAGE = "goignite/ssr:latest"
+)
+
var (
- ImageUrl string
client *docker.Client
PortRange []int
Host string
@@ -28,8 +32,17 @@ func init() {
}
}
-func CreateContainer(name string, usedPorts *[]int) (*models.ServiceResult, error) {
- PullImage()
+func CreateContainer(serverType, name, method string, usedPorts *[]int) (*models.ServiceResult, error) {
+ image := ""
+ switch serverType {
+ case "SS":
+ image = SS_IMAGE
+ case "SSR":
+ image = SSR_IMAGE
+ default:
+ return nil, errors.New("invalid server type")
+ }
+ PullImage(image)
password := utils.NewPasswd(16)
port, err := getAvailablePort(usedPorts)
if err != nil {
@@ -39,13 +52,14 @@ func CreateContainer(name string, usedPorts *[]int) (*models.ServiceResult, erro
container, err := client.CreateContainer(docker.CreateContainerOptions{
Name: name,
Config: &docker.Config{
- Image: ImageUrl,
- Cmd: []string{"-k", password, "-p", portStr},
- ExposedPorts: map[docker.Port]struct{}{docker.Port(portStr + "/tcp"): {}},
+ Image: image,
+ Cmd: []string{"-k", password, "-m", method},
},
HostConfig: &docker.HostConfig{
PortBindings: map[docker.Port][]docker.PortBinding{
- docker.Port(portStr + "/tcp"): {{HostPort: portStr}}},
+ docker.Port("3389/tcp"): {{HostPort: portStr}},
+ docker.Port("3389/udp"): {{HostPort: portStr}},
+ },
RestartPolicy: docker.AlwaysRestart(),
},
})
@@ -61,11 +75,11 @@ func CreateContainer(name string, usedPorts *[]int) (*models.ServiceResult, erro
}
func StartContainer(id string) error {
- return client.StartContainer(id, nil)
+ return client.StartContainer(id, &docker.HostConfig{})
}
-func PullImage() error {
- return client.PullImage(docker.PullImageOptions{Repository: ImageUrl, OutputStream: os.Stdout},
+func PullImage(image string) error {
+ return client.PullImage(docker.PullImageOptions{Repository: image, OutputStream: os.Stdout},
docker.AuthConfiguration{})
}
@@ -130,8 +144,8 @@ func GetContainerStatsOutNet(id string) (uint64, error) {
return stats.Networks["eth0"].TxBytes, nil
}
-func CreateAndStartContainer(name string, usedPorts *[]int) (*models.ServiceResult, error) {
- r, err := CreateContainer(name, usedPorts)
+func CreateAndStartContainer(serverType, name, method string, usedPorts *[]int) (*models.ServiceResult, error) {
+ r, err := CreateContainer(serverType, name, method, usedPorts)
if err != nil {
return nil, err
}
diff --git a/static/css/dropdown.css b/static/css/dropdown.css
new file mode 100644
index 0000000..ed7e8f3
--- /dev/null
+++ b/static/css/dropdown.css
@@ -0,0 +1,79 @@
+
+ .big {
+ font-size: 1.2em;
+ }
+
+ .small {
+ font-size: .7em;
+ }
+
+ .square {
+ width: .7em;
+ height: .7em;
+ margin: .5em;
+ display: inline-block;
+ }
+
+ /* Custom dropdown */
+ .custom-dropdown {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ margin: 10px; /* demo only */
+ }
+
+ .custom-dropdown select {
+ background-color: rgba(61, 201, 179, 1);
+ color: #fff;
+ font-size: inherit;
+ padding: .5em;
+ padding-right: 2.5em;
+ width: 245px;
+ border: 0;
+ margin: 0;
+ border-radius: 3px;
+ text-indent: 0.01px;
+ text-overflow: '';
+ -webkit-appearance: button; /* hide default arrow in chrome OSX */
+ }
+
+ .custom-dropdown::before,
+ .custom-dropdown::after {
+ content: "";
+ position: absolute;
+ pointer-events: none;
+ }
+
+ .custom-dropdown::after { /* Custom dropdown arrow */
+ content: "\25BC";
+ height: 1em;
+ font-size: .625em;
+ line-height: 1;
+ right: 1.2em;
+ top: 50%;
+ margin-top: -.5em;
+ }
+
+ .custom-dropdown::before { /* Custom dropdown arrow cover */
+ width: 2em;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ border-radius: 0 3px 3px 0;
+ }
+
+ .custom-dropdown select[disabled] {
+ color: rgba(0,0,0,.3);
+ }
+
+ .custom-dropdown select[disabled]::after {
+ color: rgba(0,0,0,.1);
+ }
+
+ .custom-dropdown::before {
+ background-color: rgba(0,0,0,.15);
+ }
+
+ .custom-dropdown::after {
+ color: rgba(0,0,0,.4);
+ }
\ No newline at end of file
diff --git a/static/css/styles.css b/static/css/styles.css
index 3e00754..a214f64 100644
--- a/static/css/styles.css
+++ b/static/css/styles.css
@@ -204,11 +204,6 @@ h2::after { content: " "; border: solid 2px #3dc9b3; display: block; width: 35px
transition: background-color .5s ease 0s;
}
-ul.dropdown-menu {
- background-color: black;
-}
-
-
@media (max-width: 992px) {
.opaque-navbar {
background-color: black;
@@ -217,6 +212,26 @@ ul.dropdown-menu {
}
}
+@media (max-width: 768px) {
+ .navbar-fixed-bottom {
+ display: none;
+ }
+ .hero,
+ .infobox {
+ width: 100% !important;
+ }
+ .basic-slide {
+ display: block !important;
+ justify-content: space-around;
+ max-height: 200px;
+ overflow: auto;
+ }
+ .progressbar {
+ display: none !important;
+ }
+}
+
+
/* ==========================================================================
Hero
========================================================================== */
@@ -235,25 +250,13 @@ Hero
.signup-btn, .login-btn{ background: rgba(61, 201, 179, 1); padding: 20px 40px; border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px; -o-border-radius: 5px; color: #fff; text-transform: uppercase; letter-spacing: 1px; display: inline-block; -webkit-box-shadow: 0px 3px 0px 0px #309383; -moz-box-shadow: 0px 3px 0px 0px #309383; box-shadow: 0px 3px 0px 0px #309383; margin: 20px 0 0; font-weight: bold; }
.signup-btn:hover, .login-btn:hover, .signup-btn:focus, .login-btn:focus { background: rgba(61, 201, 179, 0.6); color: #fff; }
-/* ========================================================================== */
-
-video {
- position: fixed;
- top: 50%;
- left: 50%;
- min-width: 100%;
- min-height: 100%;
- width: auto;
- height: auto;
- z-index: -100;
- transform: translateX(-50%) translateY(-50%);
- background: url('/static/images/helloworld.jpg') no-repeat;
- background-size: cover;
- transition: 1s opacity;
+.signup-loginhref {
+ color: #a1a9b0
}
-
+/* ========================================================================== */
#signup { padding: 40px 0 0px 0; height: auto; display: none;}
+
#login { padding: 40px 0 0px 0; height: auto; display: none;}
form {
@@ -264,9 +267,6 @@ form {
.form h1 { margin: 0px 0px 20px 0;}
form input {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
outline: 0;
border: 1px solid rgba(255, 255, 255, 0.4);
background-color: rgba(255, 255, 255, 0.2);
@@ -284,19 +284,19 @@ form input {
}
form input:hover {
- background-color: rgba(255, 255, 255, 0.4);
+ background-color: rgba(255, 255, 255, 0.5);
+}
+form input::placeholder {
+ color: rgba(255, 255, 255, .7);
}
form input:focus {
background-color: white;
width: 300px;
- color: #53e3a6;
+ color: #555;
}
.common-btn {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
outline: 0;
background: rgba(61, 201, 179, 1);
border: 0;
@@ -344,6 +344,10 @@ InfoBox
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
}
+.infobox.multiline {
+ width: 650px;
+}
+
.header-bar {
width: 100%;
height: 50px;
@@ -352,7 +356,7 @@ InfoBox
font-size: 20px;
/*background: rgba(236, 155, 59, 0.67);*/
padding: 13px 10px;
- margin: 0px 0px 5px 0px;
+ margin: 0px 0px 0px 0px;
border-top-left-radius: 15px;
border-top-right-radius: 15px;
}
@@ -365,7 +369,7 @@ InfoBox
font-size: 20px;
/*background: rgba(236, 155, 59, 0.67);*/
padding: 13px 10px;
- margin: 5px 0px 5px 0px;
+ margin: 0px 0px 5px 0px;
}
.infobox span {
@@ -374,9 +378,19 @@ InfoBox
margin: 5px 10px;
}
+.basic-slide_line {
+ margin: 10px auto;
+ width: 280px;
+ position: relative;
+}
+
.basic-slide {
+ padding: 10px 0;
+}
+
+.basic-slide_input {
+ width: 100%;
display: inline-block;
- width: 280px;
padding: 10px 0 10px 0px;
font-family: "Open Sans", sans;
font-weight: 400;
@@ -389,7 +403,18 @@ InfoBox
text-align: center;
}
-.basic-slide + label {
+.multiline .basic-slide {
+ display: flex;
+ justify-content: space-around;
+}
+
+.basic-slide_wrap {
+ display: block;
+ width: 280px;
+ margin: auto;
+}
+
+.basic-slide_label {
display: inline-block;
position: absolute;
top: 0;
@@ -444,6 +469,24 @@ InfoBox
margin-left: 2px;
}
+
+/* video */
+.bg-videosnap,
+.bg-video {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: -100;
+ min-width: 100%;
+ min-height: 100%;
+ background: url('/static/images/helloworld.jpg') no-repeat;
+ background-size: cover;
+ transition: 1s opacity;
+}
+
/*Bubbles*/
.bg-bubbles {
position: absolute;
@@ -556,8 +599,13 @@ InfoBox
}
}
+
/*Footer
========================================================================== */
+.footer {
+ display: flex;
+ justify-content: space-between;
+}
footer a { color: #a1a9b0; }
footer { background-color: rgba(0,0,0,0.5); padding: 5px 0 5px; }
footer p { color: #fff; }
@@ -580,3 +628,5 @@ Waypoints
.delay-05s { animation-delay: 0.5s; -webkit-animation-delay: 0.5s; }
.delay-1s { animation-delay: 1s; -webkit-animation-delay: 1s; }
.wp3 { background: url('/static/images/iphone-bg.png') no-repeat center center; background-position: 200px 10px; height: 100%; }
+
+
diff --git a/static/js/panel.js b/static/js/panel.js
index ada3e8d..34e8e3b 100644
--- a/static/js/panel.js
+++ b/static/js/panel.js
@@ -1,17 +1,45 @@
var Panel = function () {
var createHandler = function () {
+ // $('#extend').on('click', function(e) {
+ // if($('#extend').hasClass('fa-angle-double-up')) {
+ // $('#extend').removeClass('fa-angle-double-up');
+ // $('#extend').addClass('fa-angle-double-down');
+ // $('#account-detail').slideToggle('slow');
+ // } else {
+ // $('#extend').removeClass('fa-angle-double-down');
+ // $('#extend').addClass('fa-angle-double-up');
+ // $('#account-detail').slideToggle('slow');
+ // }
+ // });
+
+ $('#server-type').on('change', function (e) {
+ var methods = [];
+ if (this.value == 'SS') {
+ // for ss
+ methods = ssMethods;
+ } else if (this.value == "SSR") {
+ // for ssr
+ methods = ssrMethods;
+ }
+ $("#method").empty();
+ if (methods.length == 0) {
+ $("#method").append("");
+ } else {
+ for (i in methods) {
+ $("#method").append("");
+ }
+ }
+ });
+
$('#create-btn').on('click', function (e) {
e.preventDefault();
- //1. Hide create-btn.
- $('#form-title').css('display', 'none');
- $('#create-btn').css('display', 'none');
-
- //2. Show loading icon.
+ // Show loading
+ $('#create-form').css('display', 'none');
$('.boxLoading').fadeIn(500);
- //3. Send create SS service request & show account info panel.
+ // Send create SS service request & show account info panel.
var form = $('#create-form');
var url = form.attr("action");
$.post(url, form.serialize(), function (resp) {
@@ -19,18 +47,22 @@ var Panel = function () {
$('#host').val(resp.data.host);
$('#port').val(resp.data.servicePort);
$('#pwd').val(resp.data.servicePwd);
+ $('#encrypt').val($('#method').val());
+ $('#types').val($('#server-type').val());
- $('#package-limit').html(resp.data.packageLimit+'