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("/add", c.addServer)
|
||||||
g.POST("/del/:id", c.delServer)
|
g.POST("/del/:id", c.delServer)
|
||||||
g.POST("/update/:id", c.updateServer)
|
g.POST("/update/:id", c.updateServer)
|
||||||
|
g.GET("/onlines", c.getOnlineClients)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MultiServerController) getServers(ctx *gin.Context) {
|
func (c *MultiServerController) getServers(ctx *gin.Context) {
|
||||||
|
|
@ -36,6 +37,15 @@ func (c *MultiServerController) getServers(ctx *gin.Context) {
|
||||||
jsonObj(ctx, servers, nil)
|
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) {
|
func (c *MultiServerController) addServer(ctx *gin.Context) {
|
||||||
server := &model.Server{}
|
server := &model.Server{}
|
||||||
err := ctx.ShouldBind(server)
|
err := ctx.ShouldBind(server)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,37 @@
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
||||||
</div>
|
</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>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
|
|
@ -50,6 +81,31 @@
|
||||||
<td>[[ server.name ]]</td>
|
<td>[[ server.name ]]</td>
|
||||||
<td>[[ server.address ]]</td>
|
<td>[[ server.address ]]</td>
|
||||||
<td>[[ server.port ]]</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"
|
<td><span v-if="server.enable"
|
||||||
class="badge bg-success">Yes</span><span v-else
|
class="badge bg-success">Yes</span><span v-else
|
||||||
class="badge bg-danger">No</span>
|
class="badge bg-danger">No</span>
|
||||||
|
|
@ -154,6 +210,10 @@
|
||||||
el: "#app",
|
el: "#app",
|
||||||
data: {
|
data: {
|
||||||
themeSwitcher,
|
themeSwitcher,
|
||||||
|
onlineClients: [],
|
||||||
|
isRefreshEnabled: localStorage.getItem("isRefreshEnabled") === "true" ? true : false,
|
||||||
|
refreshing: false,
|
||||||
|
refreshInterval: Number(localStorage.getItem("refreshInterval")) || 5000,
|
||||||
loadingStates: {
|
loadingStates: {
|
||||||
fetched: false,
|
fetched: false,
|
||||||
spinning: false,
|
spinning: false,
|
||||||
|
|
@ -173,18 +233,42 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loadServers() {
|
loadServers() {
|
||||||
|
|
||||||
axios.get('/panel/api/servers/list')
|
axios.get('/panel/api/servers/list')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.servers = response.data.obj;
|
this.servers = response.data.obj;
|
||||||
|
this.servers = servers.map(s => {
|
||||||
|
s.status = 'Checking...';
|
||||||
|
return s;
|
||||||
|
});
|
||||||
if (this.servers.length == 0) {
|
if (this.servers.length == 0) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
this.checkStatuses();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
alert(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() {
|
showAddModal() {
|
||||||
this.modal.title = "Add Server";
|
this.modal.title = "Add Server";
|
||||||
this.modal.server = {
|
this.modal.server = {
|
||||||
|
|
@ -208,6 +292,16 @@
|
||||||
const modal = new bootstrap.Modal(modalEl);
|
const modal = new bootstrap.Modal(modalEl);
|
||||||
modal.show();
|
modal.show();
|
||||||
},
|
},
|
||||||
|
async startDataRefreshLoop() {
|
||||||
|
while (this.isRefreshEnabled) {
|
||||||
|
try {
|
||||||
|
await this.getOnlineUsers();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
await PromiseUtil.sleep(this.refreshInterval);
|
||||||
|
}
|
||||||
|
},
|
||||||
saveServer() {
|
saveServer() {
|
||||||
let url = "/panel/api/servers/add";
|
let url = "/panel/api/servers/add";
|
||||||
if (this.modal.server.id) {
|
if (this.modal.server.id) {
|
||||||
|
|
@ -286,6 +380,12 @@
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadServers();
|
this.loadServers();
|
||||||
|
if (this.isRefreshEnabled) {
|
||||||
|
this.startDataRefreshLoop();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.getDBInbounds();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v2/database"
|
"github.com/mhsanaei/3x-ui/v2/database"
|
||||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||||
)
|
)
|
||||||
|
|
@ -21,6 +25,39 @@ func (s *MultiServerService) GetServer(id int) (*model.Server, error) {
|
||||||
return &server, err
|
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 {
|
func (s *MultiServerService) AddServer(server *model.Server) error {
|
||||||
db := database.GetDB()
|
db := database.GetDB()
|
||||||
return db.Create(server).Error
|
return db.Create(server).Error
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue