Browse Source

Dev (#285)

* fix bug

* updata UI

* 0.3.2

### Added

- [Files] Files can now be selected multiple files and downloaded, deleted, moved, etc.
- [Apps] Support to modify the application opening address.([#204](https://github.com/IceWhaleTech/CasaOS/issues/204))

### Changed

- [Apps] Hide the display of non-essential environment variables in the application.
- [System] Network, disk, cpu, memory, etc. information is modified to be pushed via socket.
- [System] Optimize opening speed.([#214](https://github.com/IceWhaleTech/CasaOS/issues/214))
### Fixed

- [System] Fixed the problem that sync data cannot submit the device ID ([#68](https://github.com/IceWhaleTech/CasaOS/issues/68))
- [Files] Fixed the code editor center alignment display problem.([#210](https://github.com/IceWhaleTech/CasaOS/issues/210))
- [Files] Fixed the problem of wrong name when downloading files.([#240](https://github.com/IceWhaleTech/CasaOS/issues/240))
- [System] Fixed the network display as a negative number problem.([#224](https://github.com/IceWhaleTech/CasaOS/issues/224))

* Modify log help class

* Fix some bugs in 0.3.2

* Solve the operation file queue problem

* Exclude web folders

* update UI

* add cancel file operate

* Update UI

* Merge sockets to transfer data

* Conflict Resolution

* Update send data interval

* Update UI

* fixed bug

- Fix the problem of application opening failure on non-80 ports
- Modify port failure problem
- Modify environment variables disappearing problem
link 3 năm trước cách đây
mục cha
commit
94d0efdb12
16 tập tin đã thay đổi với 131 bổ sung136 xóa
  1. 7 0
      CHANGELOG.md
  2. 1 1
      UI
  3. 0 10
      conf/conf.ini.sample
  4. 11 10
      route/route.go
  5. 15 5
      route/v1/docker.go
  6. 90 30
      route/v1/system.go
  7. 1 75
      route/v1/zima_info.go
  8. 2 1
      service/docker.go
  9. 3 3
      types/system.go
  10. 1 1
      web/index.html
  11. 0 0
      web/js/11.js
  12. 0 0
      web/js/12.js
  13. 0 0
      web/js/3.js
  14. 0 0
      web/js/4.js
  15. 0 0
      web/js/8.js
  16. 0 0
      web/js/app.js

+ 7 - 0
CHANGELOG.md

@@ -18,6 +18,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+## [0.3.2.1] - 2022-06-13
+
+### Fixed
+
+- Fix the problem of application opening failure on non-80 ports ([#283](https://github.com/IceWhaleTech/CasaOS/issues/283) [#280](https://github.com/IceWhaleTech/CasaOS/issues/280))
+- Modify port failure problem ([#282](https://github.com/IceWhaleTech/CasaOS/issues/282))
+- Modify environment variables disappearing problem([#284](https://github.com/IceWhaleTech/CasaOS/issues/284))
 ## [0.3.2] - 2022-06-10
 
 ### Added

+ 1 - 1
UI

@@ -1 +1 @@
-Subproject commit f14c6d8c591bc5219587f7e20491103e32799b4e
+Subproject commit 62a6bd44d5740e3cb0ba0694d37c943147bea362

+ 0 - 10
conf/conf.ini.sample

@@ -21,20 +21,10 @@ Handshake = socket.casaos.io
 Token = 
 USBAutoMount =
 
-[user]
-UserName = 
-PWD = 
-Email = user@gmail.com
-Description = description
-Initialized = false
-Avatar = 
-NickName = 
-PublicKey =
 
 [system]
 ConfigStr = 
 WidgetList =
-Analyse =
 
 
 [file]

+ 11 - 10
route/route.go

@@ -64,6 +64,8 @@ func InitRouter() *gin.Engine {
 			v1UserGroup.POST("/person/info", v1.PostUserPersonInfo)
 
 			v1UserGroup.GET("/shareid", v1.GetUserShareID)
+			// v1UserGroup.GET("/custom/:name")
+			// v1UserGroup.POST("/custom/:name")
 
 		}
 		v1AppGroup := v1Group.Group("/app")
@@ -112,7 +114,7 @@ func InitRouter() *gin.Engine {
 		v1SysGroup := v1Group.Group("/sys")
 		v1SysGroup.Use()
 		{
-			v1SysGroup.GET("/check", v1.CheckVersion)
+			v1SysGroup.GET("/version/check", v1.GetSystemCheckVersion)
 			v1SysGroup.GET("/hardware/info", v1.GetSystemHardwareInfo)
 			v1SysGroup.POST("/update", v1.SystemUpdate)
 			v1SysGroup.GET("/wsssh", v1.WsSsh)
@@ -123,15 +125,14 @@ func InitRouter() *gin.Engine {
 			v1SysGroup.POST("/widget/config", v1.PostSetWidgetConfig)
 			v1SysGroup.GET("/port", v1.GetCasaOSPort)
 			v1SysGroup.PUT("/port", v1.PutCasaOSPort)
-			v1SysGroup.POST("/kill", v1.PostKillCasaOS)
-			v1SysGroup.GET("/info", v1.Info)
-			v1SysGroup.PUT("/usb/off", v1.PutSystemOffUSBAutoMount)
-			v1SysGroup.PUT("/usb/on", v1.PutSystemOnUSBAutoMount)
-			v1SysGroup.GET("/usb", v1.GetSystemUSBAutoMount)
-			v1SysGroup.GET("/cpu", v1.CupInfo)
-			v1SysGroup.GET("/mem", v1.MemInfo)
-			v1SysGroup.GET("/disk", v1.DiskInfo)
-			v1SysGroup.GET("/network", v1.NetInfo)
+			v1SysGroup.POST("/stop", v1.PostKillCasaOS)
+			v1SysGroup.GET("/utilization", v1.GetSystemUtilization)
+			v1SysGroup.PUT("/usb/:status", v1.PutSystemUSBAutoMount)
+			v1SysGroup.GET("/usb/status", v1.GetSystemUSBAutoMount)
+			v1SysGroup.GET("/cpu", v1.GetSystemCupInfo)
+			v1SysGroup.GET("/mem", v1.GetSystemMemInfo)
+			v1SysGroup.GET("/disk", v1.GetSystemDiskInfo)
+			v1SysGroup.GET("/network", v1.GetSystemNetInfo)
 
 		}
 		v1FileGroup := v1Group.Group("/file")

+ 15 - 5
route/v1/docker.go

@@ -1184,14 +1184,24 @@ func ContainerUpdateInfo(c *gin.Context) {
 	var envs model.EnvArray
 	// json2.Unmarshal([]byte(appInfo.Envs), &envs)
 
-	for _, v := range info.Config.Env {
-		showENV := info.Config.Labels["show_env"]
-		showENVList := strings.Split(showENV, ",")
-		showENVMap := make(map[string]string)
+	showENV := info.Config.Labels["show_env"]
+	showENVList := strings.Split(showENV, ",")
+	showENVMap := make(map[string]string)
+	if len(showENVList) > 1 {
 		for _, name := range showENVList {
 			showENVMap[name] = "1"
 		}
-		if _, ok := showENVMap[v]; ok {
+	}
+	for _, v := range info.Config.Env {
+		if len(showENVList) > 1 {
+			if _, ok := showENVMap[strings.Split(v, "=")[0]]; ok {
+				temp := model.Env{
+					Name:  strings.Split(v, "=")[0],
+					Value: strings.Split(v, "=")[1],
+				}
+				envs = append(envs, temp)
+			}
+		} else {
 			temp := model.Env{
 				Name:  strings.Split(v, "=")[0],
 				Value: strings.Split(v, "=")[1],

+ 90 - 30
route/v1/system.go

@@ -29,8 +29,8 @@ import (
 // @Tags sys
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
-// @Router /sys/check [get]
-func CheckVersion(c *gin.Context) {
+// @Router /sys/version/check [get]
+func GetSystemCheckVersion(c *gin.Context) {
 	need, version := version.IsNeedUpdate()
 	if need {
 		installLog := model2.AppNotify{}
@@ -47,7 +47,6 @@ func CheckVersion(c *gin.Context) {
 	data["version"] = version
 	data["current_version"] = types.CURRENTVERSION
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: data})
-	return
 }
 
 // @Summary 系统信息
@@ -65,7 +64,7 @@ func SystemUpdate(c *gin.Context) {
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS)})
 }
 
-//系统配置
+//Get system config
 func GetSystemConfig(c *gin.Context) {
 	c.JSON(http.StatusOK, model.Result{Success: oasis_err.SUCCESS, Message: oasis_err.GetMsg(oasis_err.SUCCESS), Data: json.RawMessage(config.SystemConfigInfo.ConfigStr)})
 }
@@ -182,11 +181,14 @@ func GetCasaOSPort(c *gin.Context) {
 // @Accept application/json
 // @Tags sys
 // @Security ApiKeyAuth
-// @Param port formData string true "port"
+// @Param port json string true "port"
 // @Success 200 {string} string "ok"
 // @Router /sys/port [put]
 func PutCasaOSPort(c *gin.Context) {
-	port, err := strconv.Atoi(c.PostForm("port"))
+	json := make(map[string]string)
+	c.BindJSON(&json)
+	portStr := json["port"]
+	port, err := strconv.Atoi(portStr)
 	if err != nil {
 		c.JSON(http.StatusOK,
 			model.Result{
@@ -241,7 +243,7 @@ func GetGuideCheck(c *gin.Context) {
 // @Tags sys
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
-// @Router /sys/kill [post]
+// @Router /sys/restart [post]
 func PostKillCasaOS(c *gin.Context) {
 	os.Exit(0)
 }
@@ -253,9 +255,16 @@ func PostKillCasaOS(c *gin.Context) {
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
 // @Router /sys/usb/off [put]
-func PutSystemOffUSBAutoMount(c *gin.Context) {
-	service.MyService.System().UpdateUSBAutoMount("False")
-	service.MyService.System().ExecUSBAutoMountShell("False")
+func PutSystemUSBAutoMount(c *gin.Context) {
+	status := c.Param("status")
+	if status == "on" {
+		service.MyService.System().UpdateUSBAutoMount("True")
+		service.MyService.System().ExecUSBAutoMountShell("True")
+	} else {
+		service.MyService.System().UpdateUSBAutoMount("False")
+		service.MyService.System().ExecUSBAutoMountShell("False")
+	}
+
 	c.JSON(http.StatusOK,
 		model.Result{
 			Success: oasis_err.SUCCESS,
@@ -303,31 +312,14 @@ func GetSystemHardwareInfo(c *gin.Context) {
 		})
 }
 
-// @Summary Turn off usb auto-mount
-// @Produce  application/json
-// @Accept application/json
-// @Tags sys
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /sys/usb/on [put]
-func PutSystemOnUSBAutoMount(c *gin.Context) {
-	service.MyService.System().UpdateUSBAutoMount("True")
-	service.MyService.System().ExecUSBAutoMountShell("True")
-	c.JSON(http.StatusOK,
-		model.Result{
-			Success: oasis_err.SUCCESS,
-			Message: oasis_err.GetMsg(oasis_err.SUCCESS),
-		})
-}
-
-// @Summary system info
+// @Summary system utilization
 // @Produce  application/json
 // @Accept application/json
 // @Tags sys
 // @Security ApiKeyAuth
 // @Success 200 {string} string "ok"
-// @Router /sys/info [get]
-func Info(c *gin.Context) {
+// @Router /sys/utilization [get]
+func GetSystemUtilization(c *gin.Context) {
 	var data = make(map[string]interface{}, 6)
 
 	list := service.MyService.Disk().LSBLK(true)
@@ -471,3 +463,71 @@ func GetSystemSocketPort(c *gin.Context) {
 			Data:    config.ServerInfo.SocketPort,
 		})
 }
+
+// @Summary get cpu info
+// @Produce  application/json
+// @Accept application/json
+// @Tags sys
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /sys/cpu [get]
+func GetSystemCupInfo(c *gin.Context) {
+	cpu := service.MyService.System().GetCpuPercent()
+	num := service.MyService.System().GetCpuCoreNum()
+	data := make(map[string]interface{})
+	data["percent"] = cpu
+	data["num"] = num
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: data})
+
+}
+
+// @Summary get mem info
+// @Produce  application/json
+// @Accept application/json
+// @Tags sys
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /sys/mem [get]
+func GetSystemMemInfo(c *gin.Context) {
+	mem := service.MyService.System().GetMemInfo()
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: mem})
+
+}
+
+// @Summary get disk info
+// @Produce  application/json
+// @Accept application/json
+// @Tags sys
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /sys/disk [get]
+func GetSystemDiskInfo(c *gin.Context) {
+	disk := service.MyService.ZiMa().GetDiskInfo()
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: disk})
+}
+
+// @Summary get Net info
+// @Produce  application/json
+// @Accept application/json
+// @Tags sys
+// @Security ApiKeyAuth
+// @Success 200 {string} string "ok"
+// @Router /sys/net [get]
+func GetSystemNetInfo(c *gin.Context) {
+	netList := service.MyService.System().GetNetInfo()
+
+	newNet := []model.IOCountersStat{}
+	for _, n := range netList {
+		for _, netCardName := range service.MyService.System().GetNet(true) {
+			if n.Name == netCardName {
+				item := *(*model.IOCountersStat)(unsafe.Pointer(&n))
+				item.State = strings.TrimSpace(service.MyService.ZiMa().GetNetState(n.Name))
+				item.Time = time.Now().Unix()
+				newNet = append(newNet, item)
+				break
+			}
+		}
+	}
+
+	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: newNet})
+}

+ 1 - 75
route/v1/zima_info.go

@@ -2,7 +2,7 @@
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2021-09-30 18:18:14
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-05-27 18:07:13
+ * @LastEditTime: 2022-06-13 15:20:56
  * @FilePath: /CasaOS/route/v1/zima_info.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -12,9 +12,6 @@ package v1
 
 import (
 	"net/http"
-	"strings"
-	"time"
-	"unsafe"
 
 	"github.com/IceWhaleTech/CasaOS/model"
 	oasis_err2 "github.com/IceWhaleTech/CasaOS/pkg/utils/oasis_err"
@@ -22,77 +19,6 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-// @Summary 获取cpu信息
-// @Produce  application/json
-// @Accept application/json
-// @Tags zima
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zima/getcpuinfo [get]
-func CupInfo(c *gin.Context) {
-	//检查参数是否正确
-	cpu := service.MyService.System().GetCpuPercent()
-	num := service.MyService.System().GetCpuCoreNum()
-	data := make(map[string]interface{})
-	data["percent"] = cpu
-	data["num"] = num
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: data})
-
-}
-
-// @Summary 获取内存信息
-// @Produce  application/json
-// @Accept application/json
-// @Tags zima
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zima/getmeminfo [get]
-func MemInfo(c *gin.Context) {
-
-	//检查参数是否正确
-	mem := service.MyService.System().GetMemInfo()
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: mem})
-
-}
-
-// @Summary 获取硬盘信息
-// @Produce  application/json
-// @Accept application/json
-// @Tags zima
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zima/getdiskinfo [get]
-func DiskInfo(c *gin.Context) {
-	disk := service.MyService.ZiMa().GetDiskInfo()
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: disk})
-}
-
-// @Summary 获取网络信息
-// @Produce  application/json
-// @Accept application/json
-// @Tags zima
-// @Security ApiKeyAuth
-// @Success 200 {string} string "ok"
-// @Router /zima/getnetinfo [get]
-func NetInfo(c *gin.Context) {
-	netList := service.MyService.System().GetNetInfo()
-
-	newNet := []model.IOCountersStat{}
-	for _, n := range netList {
-		for _, netCardName := range service.MyService.System().GetNet(true) {
-			if n.Name == netCardName {
-				item := *(*model.IOCountersStat)(unsafe.Pointer(&n))
-				item.State = strings.TrimSpace(service.MyService.ZiMa().GetNetState(n.Name))
-				item.Time = time.Now().Unix()
-				newNet = append(newNet, item)
-				break
-			}
-		}
-	}
-
-	c.JSON(http.StatusOK, model.Result{Success: oasis_err2.SUCCESS, Message: oasis_err2.GetMsg(oasis_err2.SUCCESS), Data: newNet})
-}
-
 // @Summary 获取信息系统信息
 // @Produce  application/json
 // @Accept application/json

+ 2 - 1
service/docker.go

@@ -438,13 +438,14 @@ func (ds *dockerService) DockerContainerCreate(imageName string, m model.Customi
 
 	var envArr []string
 	var showENV []string
+	showENV = append(showENV, "casaos")
 	for _, e := range m.Envs {
+		showENV = append(showENV, e.Name)
 		if strings.HasPrefix(e.Value, "$") {
 			envArr = append(envArr, e.Name+"="+env_helper.ReplaceDefaultENV(e.Value, MyService.System().GetTimeZone()))
 			continue
 		}
 		if len(e.Value) > 0 {
-			showENV = append(showENV, e.Name)
 			if e.Value == "port_map" {
 				envArr = append(envArr, e.Name+"="+m.PortMap)
 				continue

+ 3 - 3
types/system.go

@@ -2,7 +2,7 @@
  * @Author: LinkLeong link@icewhale.com
  * @Date: 2022-02-17 18:53:22
  * @LastEditors: LinkLeong
- * @LastEditTime: 2022-06-02 20:40:36
+ * @LastEditTime: 2022-06-13 19:24:15
  * @FilePath: /CasaOS/types/system.go
  * @Description:
  * @Website: https://www.casaos.io
@@ -10,6 +10,6 @@
  */
 package types
 
-const CURRENTVERSION = "0.3.2"
+const CURRENTVERSION = "0.3.2.1"
 
-const BODY = "<li></li>"
+const BODY = ""

+ 1 - 1
web/index.html

@@ -20,7 +20,7 @@
   <title>
     CasaOS
   </title>
-<link href="/ui/css/10.32be8789.css" rel="prefetch"><link href="/ui/css/11.dc77452d.css" rel="prefetch"><link href="/ui/css/16.1f93b660.css" rel="prefetch"><link href="/ui/css/17.046fd3d8.css" rel="prefetch"><link href="/ui/css/8.eb842258.css" rel="prefetch"><link href="/ui/css/9.e1b97a16.css" rel="prefetch"><link href="/ui/js/0.js" rel="prefetch"><link href="/ui/js/1.js" rel="prefetch"><link href="/ui/js/10.js" rel="prefetch"><link href="/ui/js/11.js" rel="prefetch"><link href="/ui/js/12.js" rel="prefetch"><link href="/ui/js/13.js" rel="prefetch"><link href="/ui/js/14.js" rel="prefetch"><link href="/ui/js/15.js" rel="prefetch"><link href="/ui/js/16.js" rel="prefetch"><link href="/ui/js/17.js" rel="prefetch"><link href="/ui/js/18.js" rel="prefetch"><link href="/ui/js/2.js" rel="prefetch"><link href="/ui/js/3.js" rel="prefetch"><link href="/ui/js/4.js" rel="prefetch"><link href="/ui/js/5.js" rel="prefetch"><link href="/ui/js/6.js" rel="prefetch"><link href="/ui/js/7.js" rel="prefetch"><link href="/ui/js/8.js" rel="prefetch"><link href="/ui/js/9.js" rel="prefetch"><link href="/ui/css/app.08addc01.css" rel="preload" as="style"><link href="/ui/css/vendors~app.a048753d.css" rel="preload" as="style"><link href="/ui/js/app.js" rel="preload" as="script"><link href="/ui/js/vendors~app.js" rel="preload" as="script"><link href="/ui/css/vendors~app.a048753d.css" rel="stylesheet"><link href="/ui/css/app.08addc01.css" rel="stylesheet"></head>
+<link href="/ui/css/10.d72d6157.css" rel="prefetch"><link href="/ui/css/11.f8326b27.css" rel="prefetch"><link href="/ui/css/16.a16d5119.css" rel="prefetch"><link href="/ui/css/17.cf8c898a.css" rel="prefetch"><link href="/ui/css/8.11ecc7a4.css" rel="prefetch"><link href="/ui/css/9.e57f27f9.css" rel="prefetch"><link href="/ui/js/0.js" rel="prefetch"><link href="/ui/js/1.js" rel="prefetch"><link href="/ui/js/10.js" rel="prefetch"><link href="/ui/js/11.js" rel="prefetch"><link href="/ui/js/12.js" rel="prefetch"><link href="/ui/js/13.js" rel="prefetch"><link href="/ui/js/14.js" rel="prefetch"><link href="/ui/js/15.js" rel="prefetch"><link href="/ui/js/16.js" rel="prefetch"><link href="/ui/js/17.js" rel="prefetch"><link href="/ui/js/18.js" rel="prefetch"><link href="/ui/js/2.js" rel="prefetch"><link href="/ui/js/3.js" rel="prefetch"><link href="/ui/js/4.js" rel="prefetch"><link href="/ui/js/5.js" rel="prefetch"><link href="/ui/js/6.js" rel="prefetch"><link href="/ui/js/7.js" rel="prefetch"><link href="/ui/js/8.js" rel="prefetch"><link href="/ui/js/9.js" rel="prefetch"><link href="/ui/css/app.4293eecd.css" rel="preload" as="style"><link href="/ui/css/vendors~app.8a2c28d7.css" rel="preload" as="style"><link href="/ui/js/app.js" rel="preload" as="script"><link href="/ui/js/vendors~app.js" rel="preload" as="script"><link href="/ui/css/vendors~app.8a2c28d7.css" rel="stylesheet"><link href="/ui/css/app.4293eecd.css" rel="stylesheet"></head>
 
 <body>
   <noscript>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
web/js/11.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
web/js/12.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
web/js/3.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
web/js/4.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
web/js/8.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
web/js/app.js


Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác