mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-11-29 02:42:51 +00:00
feature: получение онлайна с каждого сервера
This commit is contained in:
parent
971513464e
commit
9d0f2c953f
3 changed files with 148 additions and 1 deletions
|
|
@ -25,6 +25,7 @@ func (c *MultiServerController) initRouter(g *gin.RouterGroup) {
|
|||
g.POST("/add", c.addServer)
|
||||
g.POST("/del/:id", c.delServer)
|
||||
g.POST("/update/:id", c.updateServer)
|
||||
g.GET("/onlines", c.getOnlineClients)
|
||||
}
|
||||
|
||||
func (c *MultiServerController) getServers(ctx *gin.Context) {
|
||||
|
|
@ -36,6 +37,15 @@ func (c *MultiServerController) getServers(ctx *gin.Context) {
|
|||
jsonObj(ctx, servers, nil)
|
||||
}
|
||||
|
||||
func (c *MultiServerController) getOnlineClients(ctx *gin.Context) {
|
||||
clients, err := c.multiServerService.GetOnlineClients()
|
||||
if err != nil {
|
||||
jsonMsg(ctx, "Failed to get online clients", err)
|
||||
return
|
||||
}
|
||||
jsonObj(ctx, clients, nil)
|
||||
}
|
||||
|
||||
func (c *MultiServerController) addServer(ctx *gin.Context) {
|
||||
server := &model.Server{}
|
||||
err := ctx.ShouldBind(server)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,37 @@
|
|||
<div class="card-tools">
|
||||
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
||||
</div>
|
||||
<div class="card-tools">
|
||||
<template #extra>
|
||||
<a-button-group>
|
||||
<a-button icon="sync" @click="manualRefresh"
|
||||
:loading="refreshing"></a-button>
|
||||
<a-popover placement="bottomRight" trigger="click"
|
||||
:overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template #title>
|
||||
<div class="ant-custom-popover-title">
|
||||
<a-switch v-model="isRefreshEnabled" @change="toggleRefresh"
|
||||
size="small"></a-switch>
|
||||
<span>{{ i18n "pages.inbounds.autoRefresh" }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #content>
|
||||
<a-space direction="vertical">
|
||||
<span>{{ i18n "pages.inbounds.autoRefreshInterval" }}</span>
|
||||
<a-select v-model="refreshInterval"
|
||||
:disabled="!isRefreshEnabled" :style="{ width: '100%' }"
|
||||
@change="changeRefreshInterval"
|
||||
:dropdown-class-name="themeSwitcher.currentTheme">
|
||||
<a-select-option v-for="key in [5,10,30,60]"
|
||||
:value="key*1000">[[ key ]]s</a-select-option>
|
||||
</a-select>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-button icon="down"></a-button>
|
||||
</a-popover>
|
||||
</a-button-group>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered">
|
||||
|
|
@ -50,6 +81,31 @@
|
|||
<td>[[ server.name ]]</td>
|
||||
<td>[[ server.address ]]</td>
|
||||
<td>[[ server.port ]]</td>
|
||||
<td>
|
||||
<a-col :sm="12" :md="5">
|
||||
<a-custom-statistic
|
||||
title='{{ i18n "pages.inbounds.onlineClients" }}'
|
||||
:value="onlineClients.length"
|
||||
:style="{ marginTop: isMobile ? '10px' : 0 }">
|
||||
<template #prefix>
|
||||
<a-icon type="team"></a-icon>
|
||||
</template>
|
||||
<template #suffix>
|
||||
<a-popover title='{{ i18n "online" }}'
|
||||
:overlay-class-name="themeSwitcher.currentTheme">
|
||||
<template slot="content">
|
||||
<div v-for="clientEmail in onlineClients">
|
||||
<span>[[ clientEmail ]]</span>
|
||||
</div>
|
||||
</template>
|
||||
<a-tag color="blue"
|
||||
v-if="onlineClients.length">[[
|
||||
onlineClients.length ]]</a-tag>
|
||||
</a-popover>
|
||||
</template>
|
||||
</a-custom-statistic>
|
||||
</a-col>
|
||||
</td>
|
||||
<td><span v-if="server.enable"
|
||||
class="badge bg-success">Yes</span><span v-else
|
||||
class="badge bg-danger">No</span>
|
||||
|
|
@ -154,6 +210,10 @@
|
|||
el: "#app",
|
||||
data: {
|
||||
themeSwitcher,
|
||||
onlineClients: [],
|
||||
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||
refreshing: false,
|
||||
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||
loadingStates: {
|
||||
fetched: false,
|
||||
spinning: false,
|
||||
|
|
@ -173,18 +233,42 @@
|
|||
},
|
||||
methods: {
|
||||
loadServers() {
|
||||
|
||||
axios.get('/panel/api/servers/list')
|
||||
.then(response => {
|
||||
this.servers = response.data.obj;
|
||||
this.servers = servers.map(s => {
|
||||
s.status = 'Checking...';
|
||||
return s;
|
||||
});
|
||||
if (this.servers.length == 0) {
|
||||
|
||||
}
|
||||
this.checkStatuses();
|
||||
})
|
||||
.catch(error => {
|
||||
alert(error);
|
||||
});
|
||||
},
|
||||
async manualRefresh() {
|
||||
await this.getOnlineUsers();
|
||||
},
|
||||
|
||||
async getOnlineUsers() {
|
||||
const msg = await HttpUtil.post('/panel/api/servers/onlines');
|
||||
if (!msg.success) {
|
||||
return;
|
||||
}
|
||||
this.onlineClients = msg.obj != null ? msg.obj : [];
|
||||
},
|
||||
toggleRefresh() {
|
||||
localStorage.setItem("isRefreshEnabled", this.isRefreshEnabled);
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
},
|
||||
changeRefreshInterval() {
|
||||
localStorage.setItem("refreshInterval", this.refreshInterval);
|
||||
},
|
||||
showAddModal() {
|
||||
this.modal.title = "Add Server";
|
||||
this.modal.server = {
|
||||
|
|
@ -208,6 +292,16 @@
|
|||
const modal = new bootstrap.Modal(modalEl);
|
||||
modal.show();
|
||||
},
|
||||
async startDataRefreshLoop() {
|
||||
while (this.isRefreshEnabled) {
|
||||
try {
|
||||
await this.getOnlineUsers();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
await PromiseUtil.sleep(this.refreshInterval);
|
||||
}
|
||||
},
|
||||
saveServer() {
|
||||
let url = "/panel/api/servers/add";
|
||||
if (this.modal.server.id) {
|
||||
|
|
@ -286,6 +380,12 @@
|
|||
|
||||
mounted() {
|
||||
this.loadServers();
|
||||
if (this.isRefreshEnabled) {
|
||||
this.startDataRefreshLoop();
|
||||
}
|
||||
else {
|
||||
this.getDBInbounds();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v2/database"
|
||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||
)
|
||||
|
|
@ -21,6 +25,39 @@ func (s *MultiServerService) GetServer(id int) (*model.Server, error) {
|
|||
return &server, err
|
||||
}
|
||||
|
||||
// GetOnlineClients
|
||||
func (s *MultiServerService) GetOnlineClients() (map[int][]string, error) {
|
||||
db := database.GetDB()
|
||||
var servers []*model.Server
|
||||
err := db.Find(&servers).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var clients map[int][]string
|
||||
for _, server := range servers {
|
||||
var onlineResp struct {
|
||||
Success bool `json:"success"`
|
||||
Msg string `json:"msg"`
|
||||
Obj []string `json:"obj"`
|
||||
}
|
||||
url := fmt.Sprintf("http://%s:%d%spanel/api/inbounds/onlines", server.Address, server.Port, server.SecretWebPath)
|
||||
resp, err := http.Post(url, "application/json", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if err := json.NewDecoder(resp.Body).Decode(&onlineResp); err != nil {
|
||||
return nil, fmt.Errorf("decode online: %w", err)
|
||||
}
|
||||
if !onlineResp.Success {
|
||||
return nil, fmt.Errorf("failed to get online list at %s", server.Address)
|
||||
}
|
||||
clients[server.Id] = onlineResp.Obj
|
||||
}
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (s *MultiServerService) AddServer(server *model.Server) error {
|
||||
db := database.GetDB()
|
||||
return db.Create(server).Error
|
||||
|
|
|
|||
Loading…
Reference in a new issue