diff --git a/sub/html/sub.html b/sub/html/sub.html
new file mode 100644
index 00000000..a1b61d82
--- /dev/null
+++ b/sub/html/sub.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="fa" dir="rtl">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="renderer" content="webkit">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>{{ .sId }} - Sub Info</title>
+    <script src="https://unpkg.com/tailwindcss-cdn@3.4.10/tailwindcss-with-all-plugins.js"></script>
+    <link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@33.003/Vazirmatn-Variable-font-face.css"
+          rel="stylesheet" type="text/css" />
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"
+          integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A=="
+          crossorigin="anonymous" />
+    <script src="https://cdn.jsdelivr.net/npm/jalaali-js/dist/jalaali.min.js"></script>
+    <script src="https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js"
+            integrity="sha256-25ncr0CpJhgbzkUiR3wu/Fkk9sSykRG2qX+upHfJUos=" crossorigin="anonymous"></script>
+</head>
+
+<body class="flex items-center justify-center min-h-screen bg-gray-900 text-white font-[Vazirmatn] mr-4 ml-4">
+<div class="container text-center bg-gray-800 p-8 rounded-lg shadow-lg">
+    <h1 class="text-2xl font-bold mb-4">اطلاعات سابسکریپشن</h1>
+    <canvas id="qrcode" class="rounded-md inline mt-2 mb-3"></canvas>
+    <div class="text-lg mb-2"><i class="fa-regular fa-id-badge"></i> شناسه اشتراک : {{ .sId }}</div>
+    <div class="mb-2"><i class="fa-solid fa-circle-info"></i> وضعیت اشتراک : <span id="status"></span></div>
+    <div class="mb-2"><i class=" fa-solid fa-download"></i> دانلود : {{ .download }}</div>
+    <div class="mb-2"><i class=" fa-solid fa-upload"></i> آپلود : {{ .upload }}</div>
+    <div class="mb-2"><i class="fa-regular fa-calendar"></i> تاریخ پایان : <span id="timestamp"></span></div>
+    <div class="mb-4"><i class="fa-regular fa-star"></i> حجم کلی : <span id="total"></span></div>
+    <div class="bg-gray-700 rounded-lg shadow-lg p-4 font-mono flow-text break-words">
+        {{ range .result }}
+        <div class="text-gray-400 text-sm">
+            <button onclick="copyToClipboard('{{ . }}')"><i class="fa-regular fa-copy"></i></button>
+            {{ . }}
+        </div>
+        {{ end }}
+    </div>
+    <script>
+        // Get the HTML element to display the timestamp.
+        const humanDateElement = document.getElementById('timestamp');
+        // Convert timestamp to a human-readable date
+        const timestamp = parseInt("{{ .expire }}", 10) * 1000; // Parse and convert to milliseconds
+        const date = new Date(timestamp);
+        // Extract the Gregorian year, month, and day
+        const gregorianYear = date.getFullYear();
+        const gregorianMonth = date.getMonth() + 1; // Months are 0-indexed
+        const gregorianDay = date.getDate();
+        // Convert Gregorian to Jalali
+        const jalaliDate = jalaali.toJalaali(gregorianYear, gregorianMonth, gregorianDay);
+        // Format the Jalali date
+        const formattedJalaliDate = `${jalaliDate.jy}/${jalaliDate.jm}/${jalaliDate.jd}`;
+        // Display the Jalali date in the HTML
+        if ('{{ .expire }}' === '0') {
+            humanDateElement.textContent = 'بدون انقضا';
+        } else {
+            humanDateElement.textContent = formattedJalaliDate;
+        }
+        // Get the HTML element to display the status.
+        const statusElement = document.getElementById('status');
+        if (timestamp >= Date.now() && '{{ .downloadByte}}' + '{{ .uploadByte }}' <= '{{ .totalByte }}') {
+            statusElement.textContent = 'فعال';
+        } else {
+            if ('{{ .totalByte }}' === '0') {
+                statusElement.textContent = 'نامحدود';
+            } else {
+                statusElement.textContent = 'غیرفعال';
+            }
+        }
+        if ('{{ .totalByte }}' === '0') {
+            document.getElementById('total').textContent = '∞';
+        } else {
+            document.getElementById('total').textContent = '{{ .total }}';
+        }
+
+        function copyToClipboard(text) {
+            navigator.clipboard.writeText(text);
+        }
+
+        (function () {
+            var qr = new QRious({
+                element: document.getElementById('qrcode'),
+                value: '{{ .subUrl }}',
+                size: 250
+            });
+        })();
+    </script>
+    <div class="mt-4 flex justify-center gap-4">
+        <button class="flex items-center gap-2 px-3 py-3 bg-zinc-900 text-white rounded-lg hover:bg-zinc-600"
+                onclick="window.location.href='v2box://install-sub?url={{ .subUrl }}&name={{ .sId }}';">
+            <span>افزودن به V2Box</span>
+        </button>
+        <button class="flex items-center gap-2 px-3 py-3 bg-violet-900 text-white rounded-lg hover:bg-violet-600"
+                onclick="window.location.href='streisand://import/{{ .subUrl }}';">
+            <span>افزودن به Streisand</span>
+        </button>
+        <button class="flex items-center gap-2 px-3 py-3 bg-gray-100 text-black rounded-lg hover:bg-gray-500"
+                onclick="window.location.href='v2rayng://install-config?url={{ .subUrl }}';">
+            <span>افزودن به V2RayNG</span>
+        </button>
+    </div>
+</div>
+</div>
+</body>
+
+</html>
diff --git a/sub/sub.go b/sub/sub.go
index db582e8d..4ff7b8da 100644
--- a/sub/sub.go
+++ b/sub/sub.go
@@ -3,11 +3,14 @@ package sub
 import (
 	"context"
 	"crypto/tls"
+	"embed"
+	"html/template"
 	"io"
+	"io/fs"
 	"net"
 	"net/http"
+	"os"
 	"strconv"
-
 	"x-ui/config"
 	"x-ui/logger"
 	"x-ui/util/common"
@@ -15,9 +18,13 @@ import (
 	"x-ui/web/network"
 	"x-ui/web/service"
 
+	"github.com/gin-contrib/gzip"
 	"github.com/gin-gonic/gin"
 )
 
+//go:embed html/*
+var htmlFS embed.FS
+
 type Server struct {
 	httpServer *http.Server
 	listener   net.Listener
@@ -37,6 +44,48 @@ func NewServer() *Server {
 	}
 }
 
+func (s *Server) getHtmlFiles() ([]string, error) {
+	files := make([]string, 0)
+	dir, _ := os.Getwd()
+	err := fs.WalkDir(os.DirFS(dir), "sub/html", func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+		if d.IsDir() {
+			return nil
+		}
+		files = append(files, path)
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	return files, nil
+}
+
+func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) {
+	t := template.New("").Funcs(funcMap)
+	err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if d.IsDir() {
+			newT, err := t.ParseFS(htmlFS, path+"/*.html")
+			if err != nil {
+				// ignore
+				return nil
+			}
+			t = newT
+		}
+		return nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	return t, nil
+}
+
 func (s *Server) initRouter() (*gin.Engine, error) {
 	if config.IsDebug() {
 		gin.SetMode(gin.DebugMode)
@@ -48,6 +97,33 @@ func (s *Server) initRouter() (*gin.Engine, error) {
 
 	engine := gin.Default()
 
+	basePath, err := s.settingService.GetBasePath()
+	if err != nil {
+		return nil, err
+	}
+	engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/API/"})))
+
+	engine.Use(func(c *gin.Context) {
+		c.Set("base_path", basePath)
+	})
+
+	// set static files and template
+	if config.IsDebug() {
+		// for development
+		files, err := s.getHtmlFiles()
+		if err != nil {
+			return nil, err
+		}
+		engine.LoadHTMLFiles(files...)
+	} else {
+		// for production
+		template, err := s.getHtmlTemplate(engine.FuncMap)
+		if err != nil {
+			return nil, err
+		}
+		engine.SetHTMLTemplate(template)
+	}
+
 	subDomain, err := s.settingService.GetSubDomain()
 	if err != nil {
 		return nil, err
diff --git a/sub/subController.go b/sub/subController.go
index 9afbc8da..22c43f18 100644
--- a/sub/subController.go
+++ b/sub/subController.go
@@ -2,7 +2,10 @@ package sub
 
 import (
 	"encoding/base64"
+	"fmt"
+	"math"
 	"net"
+	"strconv"
 	"strings"
 
 	"github.com/gin-gonic/gin"
@@ -78,16 +81,45 @@ func (a *SUBController) subs(c *gin.Context) {
 		for _, sub := range subs {
 			result += sub + "\n"
 		}
+		resultSlice := strings.Split(strings.TrimSpace(result), "\n")
 
 		// Add headers
 		c.Writer.Header().Set("Subscription-Userinfo", header)
 		c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval)
 		c.Writer.Header().Set("Profile-Title", subId)
 
-		if a.subEncrypt {
-			c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
+		acceptHeader := c.GetHeader("Accept")
+		headerMap := parseHeaderString(header)
+		expireValue := headerMap["expire"]
+		upValue := formatBytes(headerMap["upload"], 2)
+		downValue := formatBytes(headerMap["download"], 2)
+		totalValue := formatBytes(headerMap["total"], 2)
+
+		currentURL := "https://" + c.Request.Host + c.Request.RequestURI
+
+		if strings.Contains(acceptHeader, "text/html") {
+			if a.subEncrypt {
+				c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
+			} else {
+				c.HTML(200, "sub.html", gin.H{
+					"result":       resultSlice,
+					"total":        totalValue,
+					"expire":       expireValue,
+					"upload":       upValue,
+					"download":     downValue,
+					"totalByte":    headerMap["total"],
+					"uploadByte":   headerMap["upload"],
+					"downloadByte": headerMap["download"],
+					"sId":          subId,
+					"subUrl":       currentURL,
+				})
+			}
 		} else {
-			c.String(200, result)
+			if a.subEncrypt {
+				c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
+			} else {
+				c.String(200, result)
+			}
 		}
 	}
 }
@@ -132,3 +164,31 @@ func getHostFromXFH(s string) (string, error) {
 	}
 	return s, nil
 }
+
+func parseHeaderString(header string) map[string]string {
+	headerMap := make(map[string]string)
+	pairs := strings.Split(header, ";")
+	for _, pair := range pairs {
+		kv := strings.Split(strings.TrimSpace(pair), "=")
+		if len(kv) == 2 {
+			headerMap[kv[0]] = kv[1]
+		}
+	}
+	return headerMap
+}
+
+func formatBytes(sizeStr string, precision int) string {
+	// Convert the string input to a float64
+	size, _ := strconv.ParseFloat(sizeStr, 64)
+
+	if size == 0 {
+		return "0 B"
+	}
+
+	// Calculate base and suffix
+	base := math.Log(size) / math.Log(1024)
+	suffixes := []string{"B", "K", "M", "G", "T"}
+
+	value := math.Pow(1024, base-math.Floor(base))
+	return fmt.Sprintf("%.*f %s", precision, value, suffixes[int(math.Floor(base))])
+}