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:
Дмитрий Саенко 2025-10-09 12:18:49 +03:00
parent c0ec0221e2
commit 6c3dc5ff68
7 changed files with 14164 additions and 153 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

7
web/assets/bootstrap/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -14,6 +14,7 @@ type APIController struct {
BaseController
inboundController *InboundController
serverController *ServerController
multiServerController *MultiServerController
Tgbot service.Tgbot
}
@ -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)
}

View file

@ -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)

View file

@ -1,8 +1,29 @@
{{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'">
<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">
<div class="card-header">
<h3 class="card-title">Server Management</h3>
<div class="card-tools">
@ -22,19 +43,24 @@
</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>[[ 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>
@ -42,13 +68,19 @@
</div>
</div>
</template>
</transition>
<transition>
<template>
<!-- Add/Edit Modal -->
<div class="modal fade" id="serverModal" tabindex="-1" role="dialog">
<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="close" data-dismiss="modal" aria-label="Close">
<h5 class="modal-title">[[ modal.title ]]</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
@ -56,119 +88,190 @@
<form>
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control" v-model="modal.server.name">
<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">
<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">
<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">
<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">
<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>
<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>
</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
themeSwitcher,
loadingStates: {
fetched: false,
spinning: false,
},
{
id: 2,
name: 'Server 2',
address: 'example.com',
port: 8081,
apiKey: '0987654321',
enable: 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" .}}

View file

@ -72,6 +72,9 @@
"emptyReverseDesc" = "Нет добавленных реверс-прокси."
"somethingWentWrong" = "Что-то пошло не так"
"Server" = "Сервер"
"Servers" = "Серверы"
[subscription]
"title" = "Информация о подписке"
"subId" = "ID подписки"