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 {
|
type Server struct {
|
||||||
Id int `json:"id" gorm:"primaryKey;autoIncrement"`
|
Id int `json:"id" gorm:"primaryKey;autoIncrement" form:"id"`
|
||||||
Name string `json:"name" gorm:"unique;not null"`
|
Name string `json:"name" gorm:"unique;not null" form:"name"`
|
||||||
Address string `json:"address" gorm:"not null"`
|
Address string `json:"address" gorm:"not null" form:"address"`
|
||||||
Port int `json:"port" gorm:"not null"`
|
Port int `json:"port" gorm:"not null" form:"port"`
|
||||||
APIKey string `json:"apiKey" gorm:"not null"`
|
APIKey string `json:"apiKey" gorm:"not null" form:"apiKey"`
|
||||||
Enable bool `json:"enable" gorm:"default:true"`
|
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.
|
// APIController handles the main API routes for the 3x-ui panel, including inbounds and server management.
|
||||||
type APIController struct {
|
type APIController struct {
|
||||||
BaseController
|
BaseController
|
||||||
inboundController *InboundController
|
inboundController *InboundController
|
||||||
serverController *ServerController
|
serverController *ServerController
|
||||||
Tgbot service.Tgbot
|
multiServerController *MultiServerController
|
||||||
|
Tgbot service.Tgbot
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIController creates a new APIController instance and initializes its routes.
|
// 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")
|
server := api.Group("/server")
|
||||||
a.serverController = NewServerController(server)
|
a.serverController = NewServerController(server)
|
||||||
|
|
||||||
|
// Servers api
|
||||||
|
servers := api.Group("/servers")
|
||||||
|
a.multiServerController = NewMultiServerController(servers)
|
||||||
|
|
||||||
// Extra routes
|
// Extra routes
|
||||||
api.GET("/backuptotgbot", a.BackuptoTgbot)
|
api.GET("/backuptotgbot", a.BackuptoTgbot)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ func NewMultiServerController(g *gin.RouterGroup) *MultiServerController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *MultiServerController) initRouter(g *gin.RouterGroup) {
|
func (c *MultiServerController) initRouter(g *gin.RouterGroup) {
|
||||||
g = g.Group("/server")
|
|
||||||
|
|
||||||
g.GET("/list", c.getServers)
|
g.GET("/list", c.getServers)
|
||||||
g.POST("/add", c.addServer)
|
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>
|
<a-layout id="app" v-cloak :class="themeSwitcher.currentTheme + ' settings-page'">
|
||||||
<div class="col-md-12">
|
<a-sidebar></a-sidebar>
|
||||||
<div class="card">
|
<a-layout id="content-layout">
|
||||||
<div class="card-header">
|
<a-layout-content>
|
||||||
<h3 class="card-title">Server Management</h3>
|
<a-spin :spinning="loadingStates.spinning" :delay="500" tip='{{ i18n "loading"}}'>
|
||||||
<div class="card-tools">
|
<transition name="list" appear>
|
||||||
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
<a-alert type="error" v-if="confAlerts.length>0 && loadingStates.fetched"
|
||||||
</div>
|
:style="{ marginBottom: '10px' }" message='{{ i18n "secAlertTitle" }}' color="red" show-icon
|
||||||
</div>
|
slot="description">
|
||||||
<div class="card-body">
|
<b>{{ i18n "secAlertConf" }}</b>
|
||||||
<table class="table table-bordered">
|
<ul>
|
||||||
<thead>
|
<li v-for="a in confAlerts">[[ a ]]</li>
|
||||||
<tr>
|
</ul>
|
||||||
<th>#</th>
|
</template>
|
||||||
<th>Name</th>
|
</a-alert>
|
||||||
<th>Address</th>
|
</transition>
|
||||||
<th>Port</th>
|
<transition name="list" appear>
|
||||||
<th>Enabled</th>
|
<template>
|
||||||
<th>Actions</th>
|
<div class="row" v-cloak>
|
||||||
</tr>
|
<div class="col-md-12">
|
||||||
</thead>
|
<div class="card-header">
|
||||||
<tbody>
|
<h3 class="card-title">Server Management</h3>
|
||||||
<tr v-for="(server, index) in servers">
|
<div class="card-tools">
|
||||||
<td>{{index + 1}}</td>
|
<button class="btn btn-primary" @click="showAddModal">Add Server</button>
|
||||||
<td>{{server.name}}</td>
|
</div>
|
||||||
<td>{{server.address}}</td>
|
</div>
|
||||||
<td>{{server.port}}</td>
|
<div class="card-body">
|
||||||
<td>
|
<table class="table table-bordered">
|
||||||
<span v-if="server.enable" class="badge bg-success">Yes</span>
|
<thead>
|
||||||
<span v-else class="badge bg-danger">No</span>
|
<tr>
|
||||||
</td>
|
<th>#</th>
|
||||||
<td>
|
<th>Name</th>
|
||||||
<button class="btn btn-info btn-sm" @click="showEditModal(server)">Edit</button>
|
<th>Address</th>
|
||||||
<button class="btn btn-danger btn-sm" @click="deleteServer(server.id)">Delete</button>
|
<th>Port</th>
|
||||||
</td>
|
<th>Enabled</th>
|
||||||
</tr>
|
<th>Actions</th>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
</thead>
|
||||||
</div>
|
<tbody>
|
||||||
</div>
|
<template v-if="servers.length>0">
|
||||||
</div>
|
<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">
|
</template>
|
||||||
<div class="modal-dialog" role="document">
|
</transition>
|
||||||
<div class="modal-content">
|
<transition>
|
||||||
<div class="modal-header">
|
<template>
|
||||||
<h5 class="modal-title">{{modal.title}}</h5>
|
<!-- Add/Edit Modal -->
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<div class="modal fade" data-backdrop="false" id="serverModal" tabindex="-1" role="dialog">
|
||||||
<span aria-hidden="true">×</span>
|
<div class="modal-dialog" role="document">
|
||||||
</button>
|
<div class="modal-content">
|
||||||
</div>
|
<div class="modal-header">
|
||||||
<div class="modal-body">
|
<h5 class="modal-title">[[ modal.title ]]</h5>
|
||||||
<form>
|
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||||
<div class="form-group">
|
aria-label="Close">
|
||||||
<label>Name</label>
|
<span aria-hidden="true">×</span>
|
||||||
<input type="text" class="form-control" v-model="modal.server.name">
|
</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>
|
||||||
<div class="form-group">
|
</template>
|
||||||
<label>Address (IP or Domain)</label>
|
</transition>
|
||||||
<input type="text" class="form-control" v-model="modal.server.address">
|
|
||||||
</div>
|
</a-spin>
|
||||||
<div class="form-group">
|
|
||||||
<label>Port</label>
|
</a-layout-content>
|
||||||
<input type="number" class="form-control" v-model.number="modal.server.port">
|
|
||||||
</div>
|
</a-layout>
|
||||||
<div class="form-group">
|
</a-layout>
|
||||||
<label>API Key</label>
|
|
||||||
<input type="text" class="form-control" v-model="modal.server.apiKey">
|
|
||||||
</div>
|
{{template "page/body_scripts" .}} {{template "component/aSidebar" .}}
|
||||||
<div class="form-check">
|
{{template "component/aThemeSwitch" .}}
|
||||||
<input type="checkbox" class="form-check-input" v-model="modal.server.enable">
|
<!-- <script src="{{ .base_path }}assets/jquery/jquery.min.js"></script> -->
|
||||||
<label class="form-check-label">Enabled</label>
|
<!--<script src="{{ .base_path }}assets/jquery/jquery.modal.min.js"></script> -->
|
||||||
</div>
|
|
||||||
</form>
|
<script src="{{ .base_path }}assets/bootstrap/bootstrap.min.js"></script>
|
||||||
</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>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const app = new Vue({
|
const app = new Vue({
|
||||||
el: '#app',
|
delimiters: ["[[", "]]"],
|
||||||
|
mixins: [MediaQueryMixin],
|
||||||
|
el: "#app",
|
||||||
data: {
|
data: {
|
||||||
servers: [
|
themeSwitcher,
|
||||||
{
|
loadingStates: {
|
||||||
id: 1,
|
fetched: false,
|
||||||
name: 'Server 1',
|
spinning: false,
|
||||||
address: '127.0.0.1',
|
},
|
||||||
port: 8080,
|
servers: [],
|
||||||
apiKey: '1234567890',
|
|
||||||
enable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Server 2',
|
|
||||||
address: 'example.com',
|
|
||||||
port: 8081,
|
|
||||||
apiKey: '0987654321',
|
|
||||||
enable: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
modal: {
|
modal: {
|
||||||
title: '',
|
title: "",
|
||||||
server: {
|
server: {
|
||||||
name: '',
|
name: "",
|
||||||
address: '',
|
address: "",
|
||||||
port: 0,
|
port: 0,
|
||||||
apiKey: '',
|
apiKey: "",
|
||||||
enable: true
|
enable: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showAddModal() {
|
loadServers() {
|
||||||
this.modal.title = 'Add Server';
|
|
||||||
this.modal.server = {
|
axios.get('{{.base_path}}panel/api/servers/list')
|
||||||
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)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
alert(response.data.msg);
|
this.servers = response.data.obj;
|
||||||
$('#serverModal').modal('hide');
|
if (this.servers.length == 0) {
|
||||||
this.loadServers();
|
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.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);
|
alert(error.response.data.msg);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
deleteServer(id) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
axios.post(`{{.base_path}}server/del/${id}`)
|
axios
|
||||||
.then(response => {
|
.post(`{{.base_path}}panel/api/servers/del/${id}`)
|
||||||
|
.then((response) => {
|
||||||
alert(response.data.msg);
|
alert(response.data.msg);
|
||||||
this.loadServers();
|
this.loadServers();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
alert(error.response.data.msg);
|
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() {
|
mounted() {
|
||||||
this.loadServers();
|
this.loadServers();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
{{ template "page/body_end" .}}
|
||||||
{{template "footer" .}}
|
|
||||||
|
|
||||||
|
|
@ -72,6 +72,9 @@
|
||||||
"emptyReverseDesc" = "Нет добавленных реверс-прокси."
|
"emptyReverseDesc" = "Нет добавленных реверс-прокси."
|
||||||
"somethingWentWrong" = "Что-то пошло не так"
|
"somethingWentWrong" = "Что-то пошло не так"
|
||||||
|
|
||||||
|
"Server" = "Сервер"
|
||||||
|
"Servers" = "Серверы"
|
||||||
|
|
||||||
[subscription]
|
[subscription]
|
||||||
"title" = "Информация о подписке"
|
"title" = "Информация о подписке"
|
||||||
"subId" = "ID подписки"
|
"subId" = "ID подписки"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue