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

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 BaseController
inboundController *InboundController inboundController *InboundController
serverController *ServerController serverController *ServerController
multiServerController *MultiServerController
Tgbot service.Tgbot Tgbot service.Tgbot
} }
@ -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)
} }

View file

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

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="col-md-12">
<div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title">Server Management</h3> <h3 class="card-title">Server Management</h3>
<div class="card-tools"> <div class="card-tools">
@ -22,19 +43,24 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<template v-if="servers.length>0">
<tr v-for="(server, index) in servers"> <tr v-for="(server, index) in servers">
<td>{{index + 1}}</td> <td>[[ index + 1 ]]</td>
<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> <td><span v-if="server.enable"
<span v-if="server.enable" class="badge bg-success">Yes</span> class="badge bg-success">Yes</span><span v-else
<span v-else class="badge bg-danger">No</span> 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> </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> </tr>
</tbody> </tbody>
</table> </table>
@ -42,13 +68,19 @@
</div> </div>
</div> </div>
</template>
</transition>
<transition>
<template>
<!-- Add/Edit Modal --> <!-- 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-dialog" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">{{modal.title}}</h5> <h5 class="modal-title">[[ modal.title ]]</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"> <button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close">
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
@ -56,119 +88,190 @@
<form> <form>
<div class="form-group"> <div class="form-group">
<label>Name</label> <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>
<div class="form-group"> <div class="form-group">
<label>Address (IP or Domain)</label> <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>
<div class="form-group"> <div class="form-group">
<label>Port</label> <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>
<div class="form-group"> <div class="form-group">
<label>API Key</label> <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>
<div class="form-check"> <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> <label class="form-check-label">Enabled</label>
</div> </div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<button type="button" class="btn btn-primary" @click="saveServer">Save</button> Close
</button>
<button type="button" class="btn btn-primary" @click="saveServer">
Save
</button>
</div> </div>
</div> </div>
</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> <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,
apiKey: '1234567890',
enable: true
}, },
{ servers: [],
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" .}}

View file

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