mirror of
https://github.com/wikihost-opensource/als.git
synced 2025-12-24 12:57:59 +08:00
add: 添加 Speedtest.net 功能
This commit is contained in:
@@ -3,6 +3,7 @@ package als
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/samlm0/als/v2/als/client"
|
||||
"github.com/samlm0/als/v2/als/timer"
|
||||
"github.com/samlm0/als/v2/config"
|
||||
alsHttp "github.com/samlm0/als/v2/http"
|
||||
@@ -20,5 +21,6 @@ func Init() {
|
||||
go timer.SetupInterfaceBroadcast()
|
||||
}
|
||||
go timer.UpdateSystemResource()
|
||||
go client.HandleQueue()
|
||||
aHttp.Start()
|
||||
}
|
||||
|
||||
@@ -7,38 +7,57 @@ import (
|
||||
|
||||
var queueLine = make(map[context.Context]context.CancelFunc, 0)
|
||||
var queueLock = sync.Mutex{}
|
||||
var hasQueueWorker = false
|
||||
var queueStop = make(chan struct{})
|
||||
|
||||
func WaitQueue(ctx context.Context) {
|
||||
var queueNotify = make(map[context.Context]func(), 0)
|
||||
|
||||
func WaitQueue(ctx context.Context, cb func()) {
|
||||
queueCtx, cancel := context.WithCancel(ctx)
|
||||
queueLine[ctx] = cancel
|
||||
|
||||
LISTEN:
|
||||
queueLock.Lock()
|
||||
if !hasQueueWorker {
|
||||
hasQueueWorker = true
|
||||
go handleQueue()
|
||||
if cb != nil {
|
||||
queueNotify[ctx] = cb
|
||||
}
|
||||
queueLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-queueCtx.Done():
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-queueStop:
|
||||
go handleQueue()
|
||||
goto LISTEN
|
||||
}
|
||||
}
|
||||
|
||||
func handleQueue() {
|
||||
for ctx, notify := range queueLine {
|
||||
notify()
|
||||
<-ctx.Done()
|
||||
delete(queueLine, ctx)
|
||||
func GetQueuePostitionByCtx(ctx context.Context) (int, int) {
|
||||
total := len(queueLine)
|
||||
|
||||
found := false
|
||||
count := 0
|
||||
queueLock.Lock()
|
||||
for v, _ := range queueLine {
|
||||
count++
|
||||
if v == ctx {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
queueLock.Unlock()
|
||||
|
||||
if !found {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
return count, total
|
||||
}
|
||||
|
||||
func HandleQueue() {
|
||||
for {
|
||||
for ctx, notify := range queueLine {
|
||||
notify()
|
||||
<-ctx.Done()
|
||||
delete(queueLine, ctx)
|
||||
delete(queueNotify, ctx)
|
||||
|
||||
for _, callNotify := range queueNotify {
|
||||
callNotify()
|
||||
}
|
||||
}
|
||||
}
|
||||
hasQueueWorker = false
|
||||
<-queueStop
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ func Handle(c *gin.Context) {
|
||||
|
||||
writer := func(pipe io.ReadCloser, err error) {
|
||||
if err != nil {
|
||||
fmt.Println("Pipe closed", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
@@ -68,11 +67,6 @@ func Handle(c *gin.Context) {
|
||||
}
|
||||
|
||||
cmd.Wait()
|
||||
// err = cmd.Wait()
|
||||
// if err != nil {
|
||||
// 处理错误
|
||||
// fmt.Println("Error waiting for command:", err)
|
||||
// }
|
||||
|
||||
c.JSON(200, &gin.H{
|
||||
"success": true,
|
||||
|
||||
@@ -59,7 +59,7 @@ func HandleFakeFile(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
client.WaitQueue(c.Request.Context())
|
||||
client.WaitQueue(c.Request.Context(), nil)
|
||||
|
||||
filename = filename[0 : len(filename)-5]
|
||||
if !contains(config.Config.SpeedtestFileList, filename) {
|
||||
|
||||
102
backend/als/controller/speedtest/speedtest_cli.go
Normal file
102
backend/als/controller/speedtest/speedtest_cli.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package speedtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/samlm0/als/v2/als/client"
|
||||
)
|
||||
|
||||
var count = 1
|
||||
var lock = sync.Mutex{}
|
||||
|
||||
func fakeQueue() {
|
||||
go func() {
|
||||
lock.Lock()
|
||||
count++
|
||||
lock.Unlock()
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
client.WaitQueue(ctx, nil)
|
||||
fmt.Println(count)
|
||||
time.Sleep(time.Duration(count) * time.Second)
|
||||
cancel()
|
||||
}()
|
||||
}
|
||||
|
||||
func HandleSpeedtestDotNet(c *gin.Context) {
|
||||
nodeId, ok := c.GetQuery("node_id")
|
||||
v, _ := c.Get("clientSession")
|
||||
clientSession := v.(*client.ClientSession)
|
||||
if !ok {
|
||||
nodeId = ""
|
||||
}
|
||||
closed := false
|
||||
timeout := time.Second * 60
|
||||
count = 1
|
||||
ctx, cancel := context.WithTimeout(clientSession.GetContext(c.Request.Context()), timeout)
|
||||
defer func() {
|
||||
cancel()
|
||||
closed = true
|
||||
}()
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
closed = true
|
||||
}()
|
||||
client.WaitQueue(ctx, func() {
|
||||
pos, totalPos := client.GetQueuePostitionByCtx(ctx)
|
||||
msg, _ := json.Marshal(gin.H{"type": "queue", "pos": pos, "totalPos": totalPos})
|
||||
if !closed {
|
||||
clientSession.Channel <- &client.Message{
|
||||
Name: "SpeedtestStream",
|
||||
Content: string(msg),
|
||||
}
|
||||
}
|
||||
})
|
||||
args := []string{"--accept-license", "-f", "jsonl"}
|
||||
if nodeId != "" {
|
||||
args = append(args, "-s", nodeId)
|
||||
}
|
||||
cmd := exec.Command("speedtest", args...)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
writer := func(pipe io.ReadCloser, err error) {
|
||||
if err != nil {
|
||||
fmt.Println("Pipe closed", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
buf := make([]byte, 1024)
|
||||
n, err := pipe.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !closed {
|
||||
clientSession.Channel <- &client.Message{
|
||||
Name: "SpeedtestStream",
|
||||
Content: string(buf[:n]),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
go writer(cmd.StdoutPipe())
|
||||
go writer(cmd.StderrPipe())
|
||||
|
||||
cmd.Run()
|
||||
fmt.Println("speedtest-cli quit")
|
||||
c.JSON(200, &gin.H{
|
||||
"success": true,
|
||||
})
|
||||
}
|
||||
@@ -28,6 +28,10 @@ func SetupHttpRoute(e *gin.Engine) {
|
||||
v1.GET("/ping", ping.Handle)
|
||||
}
|
||||
|
||||
if config.Config.FeatureSpeedtestDotNet {
|
||||
v1.GET("/speedtest_dot_net", speedtest.HandleSpeedtestDotNet)
|
||||
}
|
||||
|
||||
if config.Config.FeatureIfaceTraffic {
|
||||
v1.GET("/cache/interfaces", cache.UpdateInterfaceCache)
|
||||
}
|
||||
|
||||
@@ -10,11 +10,11 @@ import (
|
||||
|
||||
func UpdateSystemResource() {
|
||||
var m runtime.MemStats
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
for {
|
||||
<-ticker.C
|
||||
runtime.ReadMemStats(&m)
|
||||
client.BroadCastMessage("MemoryUsage", strconv.Itoa(int(m.Alloc)))
|
||||
client.BroadCastMessage("MemoryUsage", strconv.Itoa(int(m.Sys)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,14 +27,15 @@ type ALSConfig struct {
|
||||
|
||||
SponsorMessage string `json:"sponsor_message"`
|
||||
|
||||
FeaturePing bool `json:"feature_ping"`
|
||||
FeatureShell bool `json:"feature_shell"`
|
||||
FeatureLibrespeed bool `json:"feature_librespeed"`
|
||||
FeatureFileSpeedtest bool `json:"feature_filespeedtest"`
|
||||
FeatureIperf3 bool `json:"feature_iperf3"`
|
||||
FeatureMTR bool `json:"feature_mtr"`
|
||||
FeatureTraceroute bool `json:"feature_traceroute"`
|
||||
FeatureIfaceTraffic bool `json:"feature_iface_traffic"`
|
||||
FeaturePing bool `json:"feature_ping"`
|
||||
FeatureShell bool `json:"feature_shell"`
|
||||
FeatureLibrespeed bool `json:"feature_librespeed"`
|
||||
FeatureFileSpeedtest bool `json:"feature_filespeedtest"`
|
||||
FeatureSpeedtestDotNet bool `json:"feature_speedtest_dot_net"`
|
||||
FeatureIperf3 bool `json:"feature_iperf3"`
|
||||
FeatureMTR bool `json:"feature_mtr"`
|
||||
FeatureTraceroute bool `json:"feature_traceroute"`
|
||||
FeatureIfaceTraffic bool `json:"feature_iface_traffic"`
|
||||
}
|
||||
|
||||
func GetDefaultConfig() *ALSConfig {
|
||||
@@ -49,14 +50,15 @@ func GetDefaultConfig() *ALSConfig {
|
||||
PublicIPv4: "",
|
||||
PublicIPv6: "",
|
||||
|
||||
FeaturePing: true,
|
||||
FeatureShell: true,
|
||||
FeatureLibrespeed: true,
|
||||
FeatureFileSpeedtest: true,
|
||||
FeatureIperf3: true,
|
||||
FeatureMTR: true,
|
||||
FeatureTraceroute: true,
|
||||
FeatureIfaceTraffic: true,
|
||||
FeaturePing: true,
|
||||
FeatureShell: true,
|
||||
FeatureLibrespeed: true,
|
||||
FeatureFileSpeedtest: true,
|
||||
FeatureSpeedtestDotNet: true,
|
||||
FeatureIperf3: true,
|
||||
FeatureMTR: true,
|
||||
FeatureTraceroute: true,
|
||||
FeatureIfaceTraffic: true,
|
||||
}
|
||||
|
||||
return defaultConfig
|
||||
@@ -70,6 +72,7 @@ func Load() {
|
||||
|
||||
func LoadWebConfig() {
|
||||
Load()
|
||||
LoadSponsorMessage()
|
||||
log.Default().Println("Loading config for web services...")
|
||||
|
||||
_, err := exec.LookPath("iperf3")
|
||||
@@ -102,10 +105,11 @@ func LoadSponsorMessage() {
|
||||
if err == nil {
|
||||
content, err := io.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
log.Default().Println("Loaded sponser message from url.")
|
||||
Config.SponsorMessage = string(content)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Default().Panicln("Failed to load sponsor message.")
|
||||
log.Default().Println("ERROR: Failed to load sponsor message.")
|
||||
}
|
||||
|
||||
@@ -23,12 +23,13 @@ func LoadFromEnv() {
|
||||
}
|
||||
|
||||
envVarsBool := map[string]*bool{
|
||||
"DISPLAY_TRAFFIC": &Config.FeatureIfaceTraffic,
|
||||
"ENABLE_SPEEDTEST": &Config.FeatureLibrespeed,
|
||||
"UTILITIES_PING": &Config.FeaturePing,
|
||||
"UTILITIES_FAKESHELL": &Config.FeatureShell,
|
||||
"UTILITIES_IPERF3": &Config.FeatureIperf3,
|
||||
"UTILITIES_MTR": &Config.FeatureMTR,
|
||||
"DISPLAY_TRAFFIC": &Config.FeatureIfaceTraffic,
|
||||
"ENABLE_SPEEDTEST": &Config.FeatureLibrespeed,
|
||||
"UTILITIES_SPEEDTESTDOTNET": &Config.FeatureSpeedtestDotNet,
|
||||
"UTILITIES_PING": &Config.FeaturePing,
|
||||
"UTILITIES_FAKESHELL": &Config.FeatureShell,
|
||||
"UTILITIES_IPERF3": &Config.FeatureIperf3,
|
||||
"UTILITIES_MTR": &Config.FeatureMTR,
|
||||
}
|
||||
|
||||
for envVar, configField := range envVarsString {
|
||||
|
||||
@@ -23,6 +23,7 @@ func defineMenuCommands(a *console.Console) console.Commands {
|
||||
"ping": config.Config.FeaturePing,
|
||||
"traceroute": config.Config.FeatureTraceroute,
|
||||
"nexttrace": config.Config.FeatureTraceroute,
|
||||
"speedtest": config.Config.FeatureSpeedtestDotNet,
|
||||
"mtr": config.Config.FeatureMTR,
|
||||
}
|
||||
|
||||
|
||||
@@ -29,4 +29,6 @@ install_from_github(){
|
||||
}
|
||||
|
||||
install_from_github "nxtrace" "Ntrace-V1" "/usr/local/bin/nexttrace" `fix_arch`
|
||||
chmod +x "/usr/local/bin/nexttrace"
|
||||
chmod +x "/usr/local/bin/nexttrace"
|
||||
|
||||
sh install-speedtest.sh
|
||||
5
scripts/install-speedtest.sh
Normal file
5
scripts/install-speedtest.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
wget -O /tmp/speedtest.tgz https://install.speedtest.net/app/cli/ookla-speedtest-1.2.0-linux-`uname -m`.tgz
|
||||
tar zxf /tmp/speedtest.tgz -C /tmp
|
||||
mv /tmp/speedtest /usr/local/bin/speedtest
|
||||
rm -rf /tmp/*
|
||||
@@ -12,7 +12,16 @@ const props = defineProps({
|
||||
const isClicked = ref(false)
|
||||
const message = useMessage()
|
||||
const copy = async (value) => {
|
||||
await navigator.clipboard.writeText(value)
|
||||
try {
|
||||
await navigator.clipboard.writeText(value)
|
||||
} catch (error) {
|
||||
const textarea = document.createElement('textarea')
|
||||
document.body.appendChild(textarea)
|
||||
textarea.textContent = value
|
||||
textarea.select()
|
||||
document?.execCommand('copy')
|
||||
textarea.remove()
|
||||
}
|
||||
isClicked.value = true
|
||||
if (!props['hideMessage']) {
|
||||
message.info('已复制到剪贴板')
|
||||
|
||||
@@ -35,3 +35,10 @@ const configKeyMap = {
|
||||
</div>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.sponsor a {
|
||||
color: #70c0e8;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
@@ -24,6 +24,12 @@ const tools = ref([
|
||||
enable: false,
|
||||
componentNode: _v(() => import('./Utilities/IPerf3.vue'))
|
||||
},
|
||||
{
|
||||
label: 'Speedtest.net',
|
||||
show: false,
|
||||
enable: false,
|
||||
componentNode: _v(() => import('./Utilities/SpeedtestNet.vue'))
|
||||
},
|
||||
{
|
||||
label: 'Shell',
|
||||
show: false,
|
||||
@@ -34,7 +40,7 @@ const tools = ref([
|
||||
|
||||
onMounted(() => {
|
||||
for (var tool of tools.value) {
|
||||
const configKey = 'feature_' + tool.label.toLowerCase()
|
||||
const configKey = 'feature_' + tool.label.toLowerCase().replace('.', '_dot_')
|
||||
console.log(configKey, config.value[configKey])
|
||||
tool.enable = config.value[configKey] ?? false
|
||||
}
|
||||
|
||||
234
ui/src/components/Utilities/SpeedtestNet.vue
Normal file
234
ui/src/components/Utilities/SpeedtestNet.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<script setup>
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { formatBytes } from '@/helper/unit'
|
||||
|
||||
let abortController = markRaw(new AbortController())
|
||||
|
||||
const appStore = useAppStore()
|
||||
const working = ref(false)
|
||||
const serverId = ref()
|
||||
const isCrash = ref(false)
|
||||
const isQueue = ref(false)
|
||||
const isSpeedtest = ref(false)
|
||||
const action = ref('')
|
||||
const queueStat = ref({
|
||||
pos: 0,
|
||||
total: 0
|
||||
})
|
||||
const progress = ref({
|
||||
sub: 0,
|
||||
full: 0
|
||||
})
|
||||
|
||||
const speedtestData = ref({
|
||||
ping: '0',
|
||||
download: '',
|
||||
upload: '',
|
||||
result: '',
|
||||
serverInfo: {
|
||||
id: '',
|
||||
name: '',
|
||||
pos: ''
|
||||
}
|
||||
})
|
||||
|
||||
const steps = ref({
|
||||
start: false,
|
||||
ping: false,
|
||||
download: false,
|
||||
upload: false,
|
||||
end: false
|
||||
})
|
||||
|
||||
const handleMessage = (e) => {
|
||||
const data = JSON.parse(e.data)
|
||||
console.log(data)
|
||||
switch (data.type) {
|
||||
case 'queue':
|
||||
isQueue.value = true
|
||||
queueStat.value.pos = data.pos
|
||||
queueStat.value.total = data.totalPos
|
||||
console.log(queueStat)
|
||||
break
|
||||
case 'testStart':
|
||||
isQueue.value = false
|
||||
isSpeedtest.value = true
|
||||
steps.value.start = true
|
||||
speedtestData.value.serverInfo.id = data.server.id
|
||||
speedtestData.value.serverInfo.name = data.server.name
|
||||
speedtestData.value.serverInfo.pos = data.server.country + ' - ' + data.server.location
|
||||
break
|
||||
case 'ping':
|
||||
action.value = '测试延迟'
|
||||
speedtestData.value.ping = data.ping.latency
|
||||
break
|
||||
case 'download':
|
||||
action.value = '下载'
|
||||
speedtestData.value.download = formatBytes(data.download.bandwidth, 2, true)
|
||||
progress.value.sub = Math.round(data.download.progress * 100)
|
||||
progress.value.full = Math.round(progress.value.sub / 2)
|
||||
break
|
||||
case 'upload':
|
||||
action.value = '上传'
|
||||
speedtestData.value.upload = formatBytes(data.upload.bandwidth, 2, true)
|
||||
progress.value.sub = Math.round(data.upload.progress * 100)
|
||||
progress.value.full = 50 + Math.round(progress.value.sub / 2)
|
||||
break
|
||||
case 'result':
|
||||
speedtestData.value.result = data.result.url
|
||||
speedtestData.value.download = formatBytes(data.download.bandwidth, 2, true)
|
||||
speedtestData.value.upload = formatBytes(data.upload.bandwidth, 2, true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const stopTest = () => {
|
||||
abortController.abort('')
|
||||
appStore.source.removeEventListener('SpeedtestStream', handleMessage)
|
||||
isSpeedtest.value = false
|
||||
}
|
||||
|
||||
const speedtest = async () => {
|
||||
if (working.value) return false
|
||||
abortController = new AbortController()
|
||||
working.value = true
|
||||
isSpeedtest.value = true
|
||||
action.value = ''
|
||||
isCrash.value = false
|
||||
progress.value = {
|
||||
sub: 0,
|
||||
full: 0
|
||||
}
|
||||
speedtestData.value = {
|
||||
ping: '0',
|
||||
download: '',
|
||||
upload: '',
|
||||
result: '',
|
||||
serverInfo: {
|
||||
id: '',
|
||||
name: '',
|
||||
pos: ''
|
||||
}
|
||||
}
|
||||
appStore.source.addEventListener('SpeedtestStream', handleMessage)
|
||||
try {
|
||||
await appStore.requestMethod(
|
||||
'speedtest_dot_net',
|
||||
{ node_id: serverId.value },
|
||||
abortController.signal
|
||||
)
|
||||
} catch (e) {}
|
||||
appStore.source.removeEventListener('SpeedtestStream', handleMessage)
|
||||
working.value = false
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
stopTest()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-space vertical>
|
||||
<n-input-group>
|
||||
<n-input
|
||||
:disabled="working"
|
||||
v-model:value="serverId"
|
||||
:style="{ width: '90%' }"
|
||||
placeholder="speedtest.net 服务器 ID (可空)"
|
||||
@keyup.enter="speedtest"
|
||||
/>
|
||||
<n-button :loading="working" type="primary" ghost @click="speedtest()"> Run </n-button>
|
||||
</n-input-group>
|
||||
<n-collapse-transition :show="isQueue">
|
||||
<n-spin>
|
||||
<n-alert :show-icon="false" :bordered="false">
|
||||
<br />
|
||||
<br />
|
||||
</n-alert>
|
||||
<template #description>
|
||||
测速请求正在排队中, 目前您在第 {{ queueStat.pos }} 位 (共 {{ queueStat.total }} 位)
|
||||
</template>
|
||||
</n-spin>
|
||||
</n-collapse-transition>
|
||||
|
||||
<n-collapse-transition :show="!isQueue && isSpeedtest && action == '' && !isCrash">
|
||||
<n-alert :show-icon="false" :bordered="false"> 测试很快开始... </n-alert>
|
||||
</n-collapse-transition>
|
||||
<n-collapse-transition :show="speedtestData.result != ''">
|
||||
<n-alert :show-icon="false" :bordered="false">
|
||||
<a :href="speedtestData.result" target="_blank">
|
||||
<img
|
||||
:src="speedtestData.result + '.png'"
|
||||
style="max-width: 300px; height: 100%; display: flex; margin: auto"
|
||||
/>
|
||||
</a>
|
||||
</n-alert>
|
||||
</n-collapse-transition>
|
||||
<n-collapse-transition :show="isSpeedtest && action != ''">
|
||||
<n-collapse-transition :show="working">
|
||||
<p>
|
||||
{{ action }} - 进度
|
||||
<span style="float: right">{{ progress.sub }}%</span>
|
||||
</p>
|
||||
<n-progress
|
||||
type="line"
|
||||
:percentage="progress.sub"
|
||||
:show-indicator="false"
|
||||
:processing="working"
|
||||
/>
|
||||
<p>
|
||||
总进度 <span style="float: right">{{ progress.full }}%</span>
|
||||
</p>
|
||||
<n-progress
|
||||
type="line"
|
||||
:percentage="progress.full"
|
||||
:show-indicator="false"
|
||||
:processing="working"
|
||||
/>
|
||||
</n-collapse-transition>
|
||||
<n-collapse-transition :show="isSpeedtest && speedtestData.serverInfo.id != ''">
|
||||
<n-divider v-if="working" />
|
||||
<n-table :bordered="true" :single-line="false">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>服务器 ID</td>
|
||||
<td>{{ speedtestData.serverInfo.id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>服务器位置</td>
|
||||
<td>{{ speedtestData.serverInfo.pos }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>服务器名称</td>
|
||||
<td>{{ speedtestData.serverInfo.name }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-collapse-transition>
|
||||
|
||||
<n-collapse-transition :show="isSpeedtest && speedtestData.ping != '0'">
|
||||
<n-divider />
|
||||
<n-table :bordered="true" :single-line="false">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>延迟</td>
|
||||
<td v-if="speedtestData.ping == '0'">等待开始</td>
|
||||
<td v-else>{{ speedtestData.ping }} ms</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>下载速度</td>
|
||||
<td v-if="speedtestData.download == ''">等待开始</td>
|
||||
<td v-else>{{ speedtestData.download }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>上传速度</td>
|
||||
<td v-if="speedtestData.upload == ''">等待开始</td>
|
||||
<td v-else>{{ speedtestData.upload }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</n-table>
|
||||
</n-collapse-transition>
|
||||
</n-collapse-transition>
|
||||
</n-space>
|
||||
</template>
|
||||
@@ -1,7 +1,7 @@
|
||||
export const formatBytes = (bytes, decimals = 2, bandwidth = false) => {
|
||||
if (bytes === 0) return '0 Bytes'
|
||||
|
||||
const k = 1024
|
||||
let k = 1024
|
||||
const dm = decimals < 0 ? 0 : decimals
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const bandwidthSizes = ['Bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbs', 'Ebps', 'Zbps', 'Ybps']
|
||||
@@ -9,7 +9,8 @@ export const formatBytes = (bytes, decimals = 2, bandwidth = false) => {
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
|
||||
if (bandwidth) {
|
||||
bytes = bytes * 10
|
||||
let k = 1000
|
||||
bytes = bytes * 8
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + bandwidthSizes[i]
|
||||
}
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
||||
|
||||
@@ -13,8 +13,8 @@ export const useAppStore = defineStore('app', () => {
|
||||
|
||||
const handleResize = () => {
|
||||
let width = window.innerWidth
|
||||
if (width > 650) {
|
||||
drawerWidth.value = 650
|
||||
if (width > 800) {
|
||||
drawerWidth.value = 800
|
||||
} else {
|
||||
drawerWidth.value = width
|
||||
}
|
||||
@@ -34,6 +34,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
const eventSource = new EventSource('./session')
|
||||
eventSource.addEventListener('SessionId', (e) => {
|
||||
sessionId.value = e.data
|
||||
console.log('session', e.data)
|
||||
})
|
||||
|
||||
eventSource.addEventListener('Config', (e) => {
|
||||
@@ -58,7 +59,7 @@ export const useAppStore = defineStore('app', () => {
|
||||
|
||||
const requestMethod = (method, data = {}, signal = null) => {
|
||||
let axiosConfig = {
|
||||
timeout: 1000 * 30, // 请求超时时间
|
||||
timeout: 1000 * 120, // 请求超时时间
|
||||
headers: {
|
||||
session: sessionId.value
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user