Skip to content

Commit

Permalink
Merge pull request #347 from hamid-gh98/main
Browse files Browse the repository at this point in the history
[Feature] import/export database in the panel
  • Loading branch information
MHSanaei authored May 6, 2023
2 parents 78638a9 + 83c853f commit ac31d6d
Show file tree
Hide file tree
Showing 15 changed files with 281 additions and 26 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea
.vscode
tmp
backup/
bin/
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ apt-get install certbot -y
certbot certonly --standalone --agree-tos --register-unsafely-without-email -d yourdomain.com
certbot renew --dry-run
```
or you can use x-ui menu then number '16' (Apply for an SSL Certificate)

or you can use x-ui menu then number '16' (Apply for an SSL Certificate)

# Default settings

Expand Down Expand Up @@ -116,6 +116,7 @@ If you want to use routing to WARP follow steps as below:
- For more advanced configuration items, please refer to the panel
- Fix api routes (user setting will create with api)
- Support to change configs by different items provided in panel
- Support export/import database from panel

# Tg robot use

Expand Down Expand Up @@ -194,7 +195,6 @@ Reference syntax:
- Tron USDT (TRC20): `TXncxkvhkDWGts487Pjqq1qT9JmwRUz8CC`
# Pictures
![1](./media/1.png)
Expand Down
12 changes: 12 additions & 0 deletions database/db.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package database

import (
"bytes"
"io"
"io/fs"
"os"
"path"
Expand Down Expand Up @@ -104,3 +106,13 @@ func GetDB() *gorm.DB {
func IsNotFound(err error) bool {
return err == gorm.ErrRecordNotFound
}

func IsSQLiteDB(file io.Reader) (bool, error) {
signature := []byte("SQLite format 3\x00")
buf := make([]byte, len(signature))
_, err := file.Read(buf)
if err != nil {
return false, err
}
return bytes.Equal(buf, signature), nil
}
3 changes: 1 addition & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,7 @@ func migrateDb() {
log.Fatal(err)
}
fmt.Println("Start migrating database...")
inboundService.MigrationRequirements()
inboundService.RemoveOrphanedTraffics()
inboundService.MigrateDB()
fmt.Println("Migration done!")
}

Expand Down
2 changes: 1 addition & 1 deletion web/assets/css/custom.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#app {
height: 100%;
height: 100vh;
}

.ant-space {
Expand Down
12 changes: 8 additions & 4 deletions web/assets/js/axios-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

axios.interceptors.request.use(
config => {
config.data = Qs.stringify(config.data, {
arrayFormat: 'repeat'
});
if (config.data instanceof FormData) {
config.headers['Content-Type'] = 'multipart/form-data';
} else {
config.data = Qs.stringify(config.data, {
arrayFormat: 'repeat',
});
}
return config;
},
error => Promise.reject(error)
);
);
26 changes: 24 additions & 2 deletions web/controller/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (a *ServerController) initRouter(g *gin.RouterGroup) {
g.POST("/logs/:count", a.getLogs)
g.POST("/getConfigJson", a.getConfigJson)
g.GET("/getDb", a.getDb)
g.POST("/importDB", a.importDB)
g.POST("/getNewX25519Cert", a.getNewX25519Cert)
}

Expand Down Expand Up @@ -99,16 +100,15 @@ func (a *ServerController) stopXrayService(c *gin.Context) {
return
}
jsonMsg(c, "Xray stoped", err)

}

func (a *ServerController) restartXrayService(c *gin.Context) {
err := a.serverService.RestartXrayService()
if err != nil {
jsonMsg(c, "", err)
return
}
jsonMsg(c, "Xray restarted", err)

}

func (a *ServerController) getLogs(c *gin.Context) {
Expand Down Expand Up @@ -144,6 +144,28 @@ func (a *ServerController) getDb(c *gin.Context) {
c.Writer.Write(db)
}

func (a *ServerController) importDB(c *gin.Context) {
// Get the file from the request body
file, _, err := c.Request.FormFile("db")
if err != nil {
jsonMsg(c, "Error reading db file", err)
return
}
defer file.Close()
// Always restart Xray before return
defer a.serverService.RestartXrayService()
defer func() {
a.lastGetStatusTime = time.Now()
}()
// Import it
err = a.serverService.ImportDB(file)
if err != nil {
jsonMsg(c, "", err)
return
}
jsonObj(c, "Import DB", nil)
}

func (a *ServerController) getNewX25519Cert(c *gin.Context) {
cert, err := a.serverService.GetNewX25519Cert()
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion web/html/common/text_modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
:class="siderDrawer.isDarkTheme ? darkClass : ''"
:ok-button-props="{attrs:{id:'txt-modal-ok-btn'}}">
<a-button v-if="!ObjectUtil.isEmpty(txtModal.fileName)" type="primary" style="margin-bottom: 10px;"
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)" :download="txtModal.fileName">
:href="'data:application/text;charset=utf-8,' + encodeURIComponent(txtModal.content)"
:download="txtModal.fileName">
{{ i18n "download" }} [[ txtModal.fileName ]]
</a-button>
<a-input type="textarea" v-model="txtModal.content"
Expand Down
104 changes: 93 additions & 11 deletions web/html/xui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@
<a-col :sm="24" :md="12">
<a-card hoverable :class="siderDrawer.isDarkTheme ? darkClass : ''">
{{ i18n "menu.link" }}:
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">Log Reports</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">Config</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="getBackup">Backup</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openLogs(20)">{{ i18n "pages.index.logs" }}</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openConfig">{{ i18n "pages.index.config" }}</a-tag>
<a-tag color="blue" style="cursor: pointer;" @click="openBackup">{{ i18n "pages.index.backup" }}</a-tag>
</a-card>
</a-col>
<a-col :sm="24" :md="12">
Expand Down Expand Up @@ -188,6 +188,7 @@
</transition>
</a-layout-content>
</a-layout>

<a-modal id="version-modal" v-model="versionModal.visible" title='{{ i18n "pages.index.xraySwitch" }}'
:closable="true" @ok="() => versionModal.visible = false"
:class="siderDrawer.isDarkTheme ? darkClass : ''"
Expand All @@ -201,6 +202,7 @@ <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
</a-tag>
</template>
</a-modal>

<a-modal id="log-modal" v-model="logModal.visible" title="X-UI logs"
:closable="true" @ok="() => logModal.visible = false" @cancel="() => logModal.visible = false"
:class="siderDrawer.isDarkTheme ? darkClass : ''"
Expand All @@ -227,10 +229,28 @@ <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
{{ i18n "download" }} x-ui.log
</a-button>
</a-form-item>
</a-form>
</a-form>
<a-input type="textarea" v-model="logModal.logs" disabled="true"
:autosize="{ minRows: 10, maxRows: 22}"></a-input>
</a-modal>

<a-modal id="backup-modal" v-model="backupModal.visible" :title="backupModal.title"
:closable="true" :class="siderDrawer.isDarkTheme ? darkClass : ''"
@ok="() => backupModal.hide()" @cancel="() => backupModal.hide()">
<p style="color: inherit; font-size: 16px; padding: 4px 2px;">
<a-icon type="warning" style="color: inherit; font-size: 20px;"></a-icon>
[[ backupModal.description ]]
</p>
<a-space direction="horizontal" align="center" style="margin-bottom: 10px;">
<a-button type="primary" @click="exportDatabase()">
[[ backupModal.exportText ]]
</a-button>
<a-button type="primary" @click="importDatabase()">
[[ backupModal.importText ]]
</a-button>
</a-space>
</a-modal>

</a-layout>
{{template "js" .}}
{{template "textModal"}}
Expand Down Expand Up @@ -339,6 +359,29 @@ <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
},
};

const backupModal = {
visible: false,
title: '',
description: '',
exportText: '',
importText: '',
show({
title = '{{ i18n "pages.index.backupTitle" }}',
description = '{{ i18n "pages.index.backupDescription" }}',
exportText = '{{ i18n "pages.index.exportDatabase" }}',
importText = '{{ i18n "pages.index.importDatabase" }}',
}) {
this.title = title;
this.description = description;
this.exportText = exportText;
this.importText = importText;
this.visible = true;
},
hide() {
this.visible = false;
},
};

const app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
Expand All @@ -347,6 +390,7 @@ <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
status: new Status(),
versionModal,
logModal,
backupModal,
spinning: false,
loadingTip: '{{ i18n "loading"}}',
},
Expand Down Expand Up @@ -388,7 +432,6 @@ <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
},
});
},
//here add stop xray function
async stopXrayService() {
this.loading(true);
const msg = await HttpUtil.post('server/stopXrayService');
Expand All @@ -397,7 +440,6 @@ <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
return;
}
},
//here add restart xray function
async restartXrayService() {
this.loading(true);
const msg = await HttpUtil.post('server/restartXrayService');
Expand All @@ -413,20 +455,60 @@ <h2>{{ i18n "pages.index.xraySwitchClickDesk"}}</h2>
if (!msg.success) {
return;
}
logModal.show(msg.obj,rows);
logModal.show(msg.obj, rows);
},
async openConfig(){
async openConfig() {
this.loading(true);
const msg = await HttpUtil.post('server/getConfigJson');
this.loading(false);
if (!msg.success) {
return;
}
txtModal.show('config.json',JSON.stringify(msg.obj, null, 2),'config.json');
txtModal.show('config.json', JSON.stringify(msg.obj, null, 2), 'config.json');
},
getBackup(){
openBackup() {
backupModal.show({
title: '{{ i18n "pages.index.backupTitle" }}',
description: '{{ i18n "pages.index.backupDescription" }}',
exportText: '{{ i18n "pages.index.exportDatabase" }}',
importText: '{{ i18n "pages.index.importDatabase" }}',
});
},
exportDatabase() {
window.location = basePath + 'server/getDb';
}
},
importDatabase() {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.db';
fileInput.addEventListener('change', async (event) => {
const dbFile = event.target.files[0];
if (dbFile) {
const formData = new FormData();
formData.append('db', dbFile);
backupModal.hide();
this.loading(true);
const uploadMsg = await HttpUtil.post('server/importDB', formData, {
headers: {
'Content-Type': 'multipart/form-data',
}
});
this.loading(false);
if (!uploadMsg.success) {
return;
}
this.loading(true);
const restartMsg = await HttpUtil.post("/xui/setting/restartPanel");
this.loading(false);
if (restartMsg.success) {
this.loading(true);
await PromiseUtil.sleep(5000);
location.reload();
}
}
});
fileInput.click();
},
},
async mounted() {
while (true) {
Expand Down
2 changes: 1 addition & 1 deletion web/html/xui/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
</a-list>
</a-tab-pane>

<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding-top: 5px;">
<a-tab-pane key="2" tab='{{ i18n "pages.settings.securitySettings"}}' style="padding: 20px;">
<a-tabs default-active-key="sec-1" :class="siderDrawer.isDarkTheme ? darkClass : ''">
<a-tab-pane key="sec-1" tab='{{ i18n "pages.settings.security.admin"}}'>
<a-form :style="siderDrawer.isDarkTheme ? 'color: hsla(0,0%,100%,.65); padding: 20px;': 'background: white; padding: 20px;'">
Expand Down
11 changes: 10 additions & 1 deletion web/service/inbound.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ func (s *InboundService) DisableInvalidInbounds() (int64, error) {
count := result.RowsAffected
return count, err
}

func (s *InboundService) DisableInvalidClients() (int64, error) {
db := database.GetDB()
now := time.Now().Unix() * 1000
Expand All @@ -605,7 +606,8 @@ func (s *InboundService) DisableInvalidClients() (int64, error) {
count := result.RowsAffected
return count, err
}
func (s *InboundService) RemoveOrphanedTraffics() {

func (s *InboundService) MigrationRemoveOrphanedTraffics() {
db := database.GetDB()
db.Exec(`
DELETE FROM client_traffics
Expand All @@ -616,6 +618,7 @@ func (s *InboundService) RemoveOrphanedTraffics() {
)
`)
}

func (s *InboundService) AddClientStat(inboundId int, client *model.Client) error {
db := database.GetDB()

Expand All @@ -634,6 +637,7 @@ func (s *InboundService) AddClientStat(inboundId int, client *model.Client) erro
}
return nil
}

func (s *InboundService) UpdateClientStat(email string, client *model.Client) error {
db := database.GetDB()

Expand Down Expand Up @@ -1166,3 +1170,8 @@ func (s *InboundService) MigrationRequirements() {
// Remove orphaned traffics
db.Where("inbound_id = 0").Delete(xray.ClientTraffic{})
}

func (s *InboundService) MigrateDB() {
s.MigrationRequirements()
s.MigrationRemoveOrphanedTraffics()
}
Loading

0 comments on commit ac31d6d

Please sign in to comment.