mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-11-29 19:02:54 +00:00
Fixed the errors that prevented the project from launching and displaying the page. Fixed the modal window to correctly appear. Fixed the API to handle CRUD requests.
This commit is contained in:
parent
c0ec0221e2
commit
6c3dc5ff68
7 changed files with 14164 additions and 153 deletions
|
|
@ -121,10 +121,10 @@ type Client struct {
|
|||
}
|
||||
|
||||
type Server struct {
|
||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Name string `json:"name" gorm:"unique;not null"`
|
||||
Address string `json:"address" gorm:"not null"`
|
||||
Port int `json:"port" gorm:"not null"`
|
||||
APIKey string `json:"apiKey" gorm:"not null"`
|
||||
Enable bool `json:"enable" gorm:"default:true"`
|
||||
Id int `json:"id" gorm:"primaryKey;autoIncrement" form:"id"`
|
||||
Name string `json:"name" gorm:"unique;not null" form:"name"`
|
||||
Address string `json:"address" gorm:"not null" form:"address"`
|
||||
Port int `json:"port" gorm:"not null" form:"port"`
|
||||
APIKey string `json:"apiKey" gorm:"not null" form:"apiKey"`
|
||||
Enable bool `json:"enable" gorm:"default:true" form:"enable"`
|
||||
}
|
||||
|
|
|
|||
13894
web/assets/bootstrap/bootstrap.min.css
vendored
Normal file
13894
web/assets/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
7
web/assets/bootstrap/bootstrap.min.js
vendored
Normal file
7
web/assets/bootstrap/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -12,9 +12,10 @@ import (
|
|||
// APIController handles the main API routes for the 3x-ui panel, including inbounds and server management.
|
||||
type APIController struct {
|
||||
BaseController
|
||||
inboundController *InboundController
|
||||
serverController *ServerController
|
||||
Tgbot service.Tgbot
|
||||
inboundController *InboundController
|
||||
serverController *ServerController
|
||||
multiServerController *MultiServerController
|
||||
Tgbot service.Tgbot
|
||||
}
|
||||
|
||||
// NewAPIController creates a new APIController instance and initializes its routes.
|
||||
|
|
@ -48,6 +49,10 @@ func (a *APIController) initRouter(g *gin.RouterGroup) {
|
|||
server := api.Group("/server")
|
||||
a.serverController = NewServerController(server)
|
||||
|
||||
// Servers api
|
||||
servers := api.Group("/servers")
|
||||
a.multiServerController = NewMultiServerController(servers)
|
||||
|
||||
// Extra routes
|
||||
api.GET("/backuptotgbot", a.BackuptoTgbot)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ func NewMultiServerController(g *gin.RouterGroup) *MultiServerController {
|
|||
}
|
||||
|
||||
func (c *MultiServerController) initRouter(g *gin.RouterGroup) {
|
||||
g = g.Group("/server")
|
||||
|
||||
g.GET("/list", c.getServers)
|
||||
g.POST("/add", c.addServer)
|
||||
|
|
|
|||
|
|
@ -1,174 +1,277 @@
|
|||
{{template "header" .}}
|
||||
{{ template "page/head_start" .}}
|
||||
<!-- <link rel="stylesheet" href="{{ .base_path }}assets/jquery/jquery.modal.min.css" /> -->
|
||||
<link rel="stylesheet" href="{{ .base_path }}assets/bootstrap/bootstrap.min.css">
|
||||
{{ template "page/head_end" .}} {{ template
|
||||
"page/body_start" .}}
|
||||
|
||||
<div id="app" class="row" v-cloak>
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Server Management</h3>
|
||||
<div class="card-tools">
|
||||
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Address</th>
|
||||
<th>Port</th>
|
||||
<th>Enabled</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(server, index) in servers">
|
||||
<td>{{index + 1}}</td>
|
||||
<td>{{server.name}}</td>
|
||||
<td>{{server.address}}</td>
|
||||
<td>{{server.port}}</td>
|
||||
<td>
|
||||
<span v-if="server.enable" class="badge bg-success">Yes</span>
|
||||
<span v-else class="badge bg-danger">No</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-info btn-sm" @click="showEditModal(server)">Edit</button>
|
||||
<button class="btn btn-danger btn-sm" @click="deleteServer(server.id)">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' settings-page'">
|
||||
<a-sidebar></a-sidebar>
|
||||
<a-layout id="content-layout">
|
||||
<a-layout-content>
|
||||
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||
<transition name="list" appear>
|
||||
<a-alert type="error" v-if="confAlerts.length>0 && loadingStates.fetched"
|
||||
:style="{ marginBottom: '10px' }" message='{{ i18n "secAlertTitle" }}' color="red" show-icon
|
||||
slot="description">
|
||||
<b>{{ i18n "secAlertConf" }}</b>
|
||||
<ul>
|
||||
<li v-for="a in confAlerts">[[ a ]]</li>
|
||||
</ul>
|
||||
</template>
|
||||
</a-alert>
|
||||
</transition>
|
||||
<transition name="list" appear>
|
||||
<template>
|
||||
<div class="row" v-cloak>
|
||||
<div class="col-md-12">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Server Management</h3>
|
||||
<div class="card-tools">
|
||||
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Name</th>
|
||||
<th>Address</th>
|
||||
<th>Port</th>
|
||||
<th>Enabled</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-if="servers.length>0">
|
||||
<tr v-for="(server, index) in servers">
|
||||
<td>[[ index + 1 ]]</td>
|
||||
<td>[[ server.name ]]</td>
|
||||
<td>[[ server.address ]]</td>
|
||||
<td>[[ server.port ]]</td>
|
||||
<td><span v-if="server.enable"
|
||||
class="badge bg-success">Yes</span><span v-else
|
||||
class="badge bg-danger">No</span>
|
||||
</td>
|
||||
<td><button class="btn btn-info btn-sm"
|
||||
@click="showEditModal(server)">Edit</button><button
|
||||
class="btn btn-danger btn-sm"
|
||||
@click="deleteServer(server.id)">Delete</button></td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-else>
|
||||
<td colspan="6" class="text-center">No servers found</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add/Edit Modal -->
|
||||
<div class="modal fade" id="serverModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{modal.title}}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" class="form-control" v-model="modal.server.name">
|
||||
|
||||
</template>
|
||||
</transition>
|
||||
<transition>
|
||||
<template>
|
||||
<!-- Add/Edit Modal -->
|
||||
<div class="modal fade" data-backdrop="false" id="serverModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">[[ modal.title ]]</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" class="form-control" v-model="modal.server.name" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Address (IP or Domain)</label>
|
||||
<input type="text" class="form-control"
|
||||
v-model="modal.server.address" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Port</label>
|
||||
<input type="number" class="form-control"
|
||||
v-model.number="modal.server.port" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key</label>
|
||||
<input type="text" class="form-control" v-model="modal.server.apiKey" />
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input"
|
||||
v-model="modal.server.enable" />
|
||||
<label class="form-check-label">Enabled</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
Close
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="saveServer">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Address (IP or Domain)</label>
|
||||
<input type="text" class="form-control" v-model="modal.server.address">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Port</label>
|
||||
<input type="number" class="form-control" v-model.number="modal.server.port">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key</label>
|
||||
<input type="text" class="form-control" v-model="modal.server.apiKey">
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input" v-model="modal.server.enable">
|
||||
<label class="form-check-label">Enabled</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" @click="saveServer">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</transition>
|
||||
|
||||
</a-spin>
|
||||
|
||||
</a-layout-content>
|
||||
|
||||
</a-layout>
|
||||
</a-layout>
|
||||
|
||||
|
||||
{{template "page/body_scripts" .}} {{template "component/aSidebar" .}}
|
||||
{{template "component/aThemeSwitch" .}}
|
||||
<!-- <script src="{{ .base_path }}assets/jquery/jquery.min.js"></script> -->
|
||||
<!--<script src="{{ .base_path }}assets/jquery/jquery.modal.min.js"></script> -->
|
||||
|
||||
<script src="{{ .base_path }}assets/bootstrap/bootstrap.min.js"></script>
|
||||
|
||||
<script>
|
||||
const app = new Vue({
|
||||
el: '#app',
|
||||
delimiters: ["[[", "]]"],
|
||||
mixins: [MediaQueryMixin],
|
||||
el: "#app",
|
||||
data: {
|
||||
servers: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Server 1',
|
||||
address: '127.0.0.1',
|
||||
port: 8080,
|
||||
apiKey: '1234567890',
|
||||
enable: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Server 2',
|
||||
address: 'example.com',
|
||||
port: 8081,
|
||||
apiKey: '0987654321',
|
||||
enable: false
|
||||
}
|
||||
],
|
||||
themeSwitcher,
|
||||
loadingStates: {
|
||||
fetched: false,
|
||||
spinning: false,
|
||||
},
|
||||
servers: [],
|
||||
modal: {
|
||||
title: '',
|
||||
title: "",
|
||||
server: {
|
||||
name: '',
|
||||
address: '',
|
||||
name: "",
|
||||
address: "",
|
||||
port: 0,
|
||||
apiKey: '',
|
||||
enable: true
|
||||
}
|
||||
}
|
||||
apiKey: "",
|
||||
enable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showAddModal() {
|
||||
this.modal.title = 'Add Server';
|
||||
this.modal.server = {
|
||||
name: '',
|
||||
address: '',
|
||||
port: 0,
|
||||
apiKey: '',
|
||||
enable: true
|
||||
};
|
||||
$('#serverModal').modal('show');
|
||||
},
|
||||
showEditModal(server) {
|
||||
this.modal.title = 'Edit Server';
|
||||
this.modal.server = Object.assign({}, server);
|
||||
$('#serverModal').modal('show');
|
||||
},
|
||||
saveServer() {
|
||||
let url = '{{.base_path}}server/add';
|
||||
if (this.modal.server.id) {
|
||||
url = `{{.base_path}}server/update/${this.modal.server.id}`;
|
||||
}
|
||||
axios.post(url, this.modal.server)
|
||||
loadServers() {
|
||||
|
||||
axios.get('{{.base_path}}panel/api/servers/list')
|
||||
.then(response => {
|
||||
alert(response.data.msg);
|
||||
$('#serverModal').modal('hide');
|
||||
this.loadServers();
|
||||
this.servers = response.data.obj;
|
||||
if (this.servers.length == 0) {
|
||||
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
alert(error);
|
||||
});
|
||||
},
|
||||
showAddModal() {
|
||||
this.modal.title = "Add Server";
|
||||
this.modal.server = {
|
||||
name: "",
|
||||
address: "",
|
||||
port: 0,
|
||||
apiKey: "",
|
||||
enable: true,
|
||||
};
|
||||
|
||||
const modalEl = document.getElementById('serverModal');
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
modal.show();
|
||||
},
|
||||
showEditModal(server) {
|
||||
this.modal.title = "Edit Server";
|
||||
this.modal.server = Object.assign({}, server);
|
||||
|
||||
const modalEl = document.getElementById('serverModal');
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
modal.show();
|
||||
},
|
||||
saveServer() {
|
||||
let url = "{{.base_path}}panel/api/servers/add";
|
||||
if (this.modal.server.id) {
|
||||
url = `{{.base_path}}panel/api/servers/update/${this.modal.server.id}`;
|
||||
}
|
||||
|
||||
console.log(this.modal.server);
|
||||
axios
|
||||
.post(url, this.modal.server)
|
||||
.then((response) => {
|
||||
alert(response.data.msg);
|
||||
|
||||
const modalEl = document.getElementById('serverModal');
|
||||
const modal = bootstrap.Modal.getInstance(modalEl);
|
||||
modal.hide();
|
||||
|
||||
this.loadServers();
|
||||
})
|
||||
.catch((error) => {
|
||||
alert(error.response.data.msg);
|
||||
});
|
||||
},
|
||||
deleteServer(id) {
|
||||
if (!confirm('Are you sure you want to delete this server?')) {
|
||||
if (!confirm("Are you sure you want to delete this server?")) {
|
||||
return;
|
||||
}
|
||||
axios.post(`{{.base_path}}server/del/${id}`)
|
||||
.then(response => {
|
||||
axios
|
||||
.post(`{{.base_path}}panel/api/servers/del/${id}`)
|
||||
.then((response) => {
|
||||
alert(response.data.msg);
|
||||
this.loadServers();
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
alert(error.response.data.msg);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
confAlerts: {
|
||||
get: function () {
|
||||
if (!this.allSetting) return [];
|
||||
var alerts = [];
|
||||
if (window.location.protocol !== "https:")
|
||||
alerts.push('{{ i18n "secAlertSSL" }}');
|
||||
if (this.allSetting.webPort === 2053)
|
||||
alerts.push('{{ i18n "secAlertPanelPort" }}');
|
||||
panelPath = window.location.pathname.split("/").length < 4;
|
||||
if (panelPath && this.allSetting.webBasePath == "/")
|
||||
alerts.push('{{ i18n "secAlertPanelURI" }}');
|
||||
if (this.allSetting.subEnable) {
|
||||
subPath =
|
||||
this.allSetting.subURI.length > 0
|
||||
? new URL(this.allSetting.subURI).pathname
|
||||
: this.allSetting.subPath;
|
||||
if (subPath == "/sub/")
|
||||
alerts.push('{{ i18n "secAlertSubURI" }}');
|
||||
}
|
||||
if (this.allSetting.subJsonEnable) {
|
||||
subJsonPath =
|
||||
this.allSetting.subJsonURI.length > 0
|
||||
? new URL(this.allSetting.subJsonURI).pathname
|
||||
: this.allSetting.subJsonPath;
|
||||
if (subJsonPath == "/json/")
|
||||
alerts.push('{{ i18n "secAlertSubJsonURI" }}');
|
||||
}
|
||||
return alerts;
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadServers();
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
{{template "footer" .}}
|
||||
|
||||
{{ template "page/body_end" .}}
|
||||
|
|
@ -72,6 +72,9 @@
|
|||
"emptyReverseDesc" = "Нет добавленных реверс-прокси."
|
||||
"somethingWentWrong" = "Что-то пошло не так"
|
||||
|
||||
"Server" = "Сервер"
|
||||
"Servers" = "Серверы"
|
||||
|
||||
[subscription]
|
||||
"title" = "Информация о подписке"
|
||||
"subId" = "ID подписки"
|
||||
|
|
|
|||
Loading…
Reference in a new issue