mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2024-11-06 16:00:28 +01:00
The UI and backend of API keys is done!
This commit is contained in:
parent
935129f0a5
commit
881d62d69d
@ -76,7 +76,7 @@ class CustomJsonEncoder(DefaultJSONProvider):
|
|||||||
super().__init__(app)
|
super().__init__(app)
|
||||||
|
|
||||||
def default(self, o):
|
def default(self, o):
|
||||||
if isinstance(o, WireguardConfiguration) or isinstance(o, Peer) or isinstance(o, PeerJob) or isinstance(o, Log):
|
if isinstance(o, WireguardConfiguration) or isinstance(o, Peer) or isinstance(o, PeerJob) or isinstance(o, Log) or isinstance(o, DashboardAPIKey):
|
||||||
return o.toJson()
|
return o.toJson()
|
||||||
return super().default(self, o)
|
return super().default(self, o)
|
||||||
|
|
||||||
@ -117,7 +117,7 @@ class Logger:
|
|||||||
existingTable = [t['name'] for t in existingTable]
|
existingTable = [t['name'] for t in existingTable]
|
||||||
|
|
||||||
if "JobLog" not in existingTable:
|
if "JobLog" not in existingTable:
|
||||||
self.loggerdbCursor.execute("CREATE TABLE JobLog (LogID VARCHAR NOT NULL, JobID NOT NULL, LogDate DATETIME DEFAULT (strftime('%Y-%m-%d %H:%M:%S','now')), Status VARCHAR NOT NULL, Message VARCHAR, PRIMARY KEY (LogID))")
|
self.loggerdbCursor.execute("CREATE TABLE JobLog (LogID VARCHAR NOT NULL, JobID NOT NULL, LogDate DATETIME DEFAULT (strftime('%Y-%m-%d %H:%M:%S','now', 'localtime')), Status VARCHAR NOT NULL, Message VARCHAR, PRIMARY KEY (LogID))")
|
||||||
self.loggerdb.commit()
|
self.loggerdb.commit()
|
||||||
def log(self, JobID: str, Status: bool = True, Message: str = "") -> bool:
|
def log(self, JobID: str, Status: bool = True, Message: str = "") -> bool:
|
||||||
try:
|
try:
|
||||||
@ -311,7 +311,6 @@ class PeerJobs:
|
|||||||
if operator == "lst":
|
if operator == "lst":
|
||||||
return x < y
|
return x < y
|
||||||
|
|
||||||
|
|
||||||
class WireguardConfiguration:
|
class WireguardConfiguration:
|
||||||
class InvalidConfigurationFileException(Exception):
|
class InvalidConfigurationFileException(Exception):
|
||||||
def __init__(self, m):
|
def __init__(self, m):
|
||||||
@ -763,7 +762,6 @@ class WireguardConfiguration:
|
|||||||
"SaveConfig": self.SaveConfig
|
"SaveConfig": self.SaveConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Peer:
|
class Peer:
|
||||||
def __init__(self, tableData, configuration: WireguardConfiguration):
|
def __init__(self, tableData, configuration: WireguardConfiguration):
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
@ -891,18 +889,24 @@ PersistentKeepalive = {str(self.keepalive)}
|
|||||||
self.jobs = AllPeerJobs.searchJob(self.configuration.Name, self.id)
|
self.jobs = AllPeerJobs.searchJob(self.configuration.Name, self.id)
|
||||||
# print(AllPeerJobs.searchJob(self.configuration.Name, self.id))
|
# print(AllPeerJobs.searchJob(self.configuration.Name, self.id))
|
||||||
|
|
||||||
|
|
||||||
# Regex Match
|
# Regex Match
|
||||||
def regex_match(regex, text):
|
def regex_match(regex, text):
|
||||||
pattern = re.compile(regex)
|
pattern = re.compile(regex)
|
||||||
return pattern.search(text) is not None
|
return pattern.search(text) is not None
|
||||||
|
|
||||||
|
|
||||||
def iPv46RegexCheck(ip):
|
def iPv46RegexCheck(ip):
|
||||||
return re.match(
|
return re.match(
|
||||||
'((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))',
|
'((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))',
|
||||||
ip)
|
ip)
|
||||||
|
|
||||||
|
class DashboardAPIKey:
|
||||||
|
def __init__(self, Key: str, CreatedAt: str, ExpiredAt: str):
|
||||||
|
self.Key = Key
|
||||||
|
self.CreatedAt = CreatedAt
|
||||||
|
self.ExpiredAt = ExpiredAt
|
||||||
|
|
||||||
|
def toJson(self):
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
class DashboardConfig:
|
class DashboardConfig:
|
||||||
|
|
||||||
@ -927,7 +931,8 @@ class DashboardConfig:
|
|||||||
"version": DASHBOARD_VERSION,
|
"version": DASHBOARD_VERSION,
|
||||||
"dashboard_refresh_interval": "60000",
|
"dashboard_refresh_interval": "60000",
|
||||||
"dashboard_sort": "status",
|
"dashboard_sort": "status",
|
||||||
"dashboard_theme": "dark"
|
"dashboard_theme": "dark",
|
||||||
|
"dashboard_api_key": "false"
|
||||||
},
|
},
|
||||||
"Peers": {
|
"Peers": {
|
||||||
"peer_global_DNS": "1.1.1.1",
|
"peer_global_DNS": "1.1.1.1",
|
||||||
@ -947,6 +952,34 @@ class DashboardConfig:
|
|||||||
exist, currentData = self.GetConfig(section, key)
|
exist, currentData = self.GetConfig(section, key)
|
||||||
if not exist:
|
if not exist:
|
||||||
self.SetConfig(section, key, value, True)
|
self.SetConfig(section, key, value, True)
|
||||||
|
self.__createAPIKeyTable()
|
||||||
|
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||||
|
|
||||||
|
def __createAPIKeyTable(self):
|
||||||
|
existingTable = cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name = 'DashboardAPIKeys'").fetchall()
|
||||||
|
if len(existingTable) == 0:
|
||||||
|
cursor.execute("CREATE TABLE DashboardAPIKeys (Key VARCHAR NOT NULL PRIMARY KEY, CreatedAt DATETIME NOT NULL DEFAULT (datetime('now', 'localtime')), ExpiredAt VARCHAR)")
|
||||||
|
sqldb.commit()
|
||||||
|
|
||||||
|
def __getAPIKeys(self) -> list[DashboardAPIKey]:
|
||||||
|
keys = cursor.execute("SELECT * FROM DashboardAPIKeys WHERE ExpiredAt IS NULL OR ExpiredAt > datetime('now', 'localtime') ORDER BY CreatedAt DESC").fetchall()
|
||||||
|
fKeys = []
|
||||||
|
for k in keys:
|
||||||
|
fKeys.append(DashboardAPIKey(*k))
|
||||||
|
return fKeys
|
||||||
|
|
||||||
|
def createAPIKeys(self, ExpiredAt = None):
|
||||||
|
newKey = secrets.token_urlsafe(32)
|
||||||
|
cursor.execute('INSERT INTO DashboardAPIKeys (Key, ExpiredAt) VALUES (?, ?)', (newKey, ExpiredAt,))
|
||||||
|
sqldb.commit()
|
||||||
|
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||||
|
|
||||||
|
def deleteAPIKey(self, key):
|
||||||
|
cursor.execute("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
|
||||||
|
sqldb.commit()
|
||||||
|
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def __configValidation(self, key, value: Any) -> [bool, str]:
|
def __configValidation(self, key, value: Any) -> [bool, str]:
|
||||||
if type(value) is str and len(value) == 0:
|
if type(value) is str and len(value) == 0:
|
||||||
@ -1051,11 +1084,6 @@ class DashboardConfig:
|
|||||||
return the_dict
|
return the_dict
|
||||||
|
|
||||||
|
|
||||||
DashboardConfig = DashboardConfig()
|
|
||||||
WireguardConfigurations: dict[str, WireguardConfiguration] = {}
|
|
||||||
AllPeerJobs: PeerJobs = PeerJobs()
|
|
||||||
JobLogger: Logger = Logger()
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Private Functions
|
Private Functions
|
||||||
'''
|
'''
|
||||||
@ -1333,6 +1361,36 @@ def API_updateDashboardConfigurationItem():
|
|||||||
|
|
||||||
return ResponseObject()
|
return ResponseObject()
|
||||||
|
|
||||||
|
@app.route('/api/getDashboardAPIKeys', methods=['GET'])
|
||||||
|
def API_getDashboardAPIKeys():
|
||||||
|
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
|
||||||
|
return ResponseObject(data=DashboardConfig.DashboardAPIKeys)
|
||||||
|
return ResponseObject(False, "Dashboard API Keys function is disbaled")
|
||||||
|
|
||||||
|
@app.route('/api/newDashboardAPIKey', methods=['POST'])
|
||||||
|
def API_newDashboardAPIKey():
|
||||||
|
data = request.get_json()
|
||||||
|
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
|
||||||
|
try:
|
||||||
|
if data['neverExpire']:
|
||||||
|
expiredAt = None
|
||||||
|
else:
|
||||||
|
expiredAt = datetime.strptime(data['ExpiredAt'], '%Y-%m-%dT%H:%M:%S')
|
||||||
|
DashboardConfig.createAPIKeys(expiredAt)
|
||||||
|
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
|
||||||
|
except Exception as e:
|
||||||
|
return ResponseObject(False, str(e))
|
||||||
|
return ResponseObject(False, "Dashboard API Keys function is disbaled")
|
||||||
|
|
||||||
|
@app.route('/api/deleteDashboardAPIKey', methods=['POST'])
|
||||||
|
def API_deleteDashboardAPIKey():
|
||||||
|
data = request.get_json()
|
||||||
|
if DashboardConfig.GetConfig('Server', 'dashboard_api_key'):
|
||||||
|
if len(data['Key']) > 0 and len(list(filter(lambda x : x.Key == data['Key'], DashboardConfig.DashboardAPIKeys))) > 0:
|
||||||
|
DashboardConfig.deleteAPIKey(data['Key'])
|
||||||
|
return ResponseObject(True, data=DashboardConfig.DashboardAPIKeys)
|
||||||
|
return ResponseObject(False, "Dashboard API Keys function is disbaled")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/updatePeerSettings/<configName>', methods=['POST'])
|
@app.route('/api/updatePeerSettings/<configName>', methods=['POST'])
|
||||||
def API_updatePeerSettings(configName):
|
def API_updatePeerSettings(configName):
|
||||||
@ -1771,6 +1829,10 @@ def gunicornConfig():
|
|||||||
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
||||||
sqldb.row_factory = sqlite3.Row
|
sqldb.row_factory = sqlite3.Row
|
||||||
cursor = sqldb.cursor()
|
cursor = sqldb.cursor()
|
||||||
|
DashboardConfig = DashboardConfig()
|
||||||
|
WireguardConfigurations: dict[str, WireguardConfiguration] = {}
|
||||||
|
AllPeerJobs: PeerJobs = PeerJobs()
|
||||||
|
JobLogger: Logger = Logger()
|
||||||
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
_, app_ip = DashboardConfig.GetConfig("Server", "app_ip")
|
||||||
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
_, app_port = DashboardConfig.GetConfig("Server", "app_port")
|
||||||
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
|
_, WG_CONF_PATH = DashboardConfig.GetConfig("Server", "wg_conf_path")
|
||||||
|
@ -71,7 +71,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="d-flex flex-column">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm">
|
<div class="col-sm">
|
||||||
<div class="form-group mb-2">
|
<div class="form-group mb-2">
|
||||||
@ -109,8 +109,8 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-success btn-sm fw-bold rounded-3" @click="this.useValidation()">
|
<button class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm" @click="this.useValidation()">
|
||||||
<i class="bi bi-key-fill me-2"></i>Update Password
|
<i class="bi bi-save2-fill me-2"></i>Update Password
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
<script>
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
import {v4} from "uuid";
|
||||||
|
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
||||||
|
import NewDashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/newDashboardAPIKey.vue";
|
||||||
|
import DashboardAPIKey from "@/components/settingsComponent/dashboardAPIKeysComponents/dashboardAPIKey.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "dashboardAPIKeys",
|
||||||
|
components: {DashboardAPIKey, NewDashboardAPIKey},
|
||||||
|
setup(){
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
return {store};
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
value: this.store.Configuration.Server.dashboard_api_key,
|
||||||
|
apiKeys: [],
|
||||||
|
newDashboardAPIKey: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async toggleDashboardAPIKeys(){
|
||||||
|
await fetchPost("/api/updateDashboardConfigurationItem", {
|
||||||
|
section: "Server",
|
||||||
|
key: "dashboard_api_key",
|
||||||
|
value: this.value
|
||||||
|
}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.store.Configuration.Peers[this.targetData] = this.value;
|
||||||
|
this.store.newMessage("Server",
|
||||||
|
`API Keys function is successfully ${this.value ? 'enabled':'disabled'}`, "success")
|
||||||
|
}else{
|
||||||
|
this.value = this.store.Configuration.Peers[this.targetData];
|
||||||
|
this.store.newMessage("Server",
|
||||||
|
`API Keys function is failed ${this.value ? 'enabled':'disabled'}`, "danger")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value:{
|
||||||
|
immediate: true,
|
||||||
|
handler(newValue){
|
||||||
|
if (newValue){
|
||||||
|
fetchGet("/api/getDashboardAPIKeys", {}, (res) => {
|
||||||
|
console.log(res)
|
||||||
|
if(res.status){
|
||||||
|
this.apiKeys = res.data
|
||||||
|
}else{
|
||||||
|
this.apiKeys = []
|
||||||
|
this.store.newMessage("Server", res.message, "danger")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
this.apiKeys = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card mb-4 shadow rounded-3">
|
||||||
|
<div class="card-header d-flex">
|
||||||
|
API Keys
|
||||||
|
<div class="form-check form-switch ms-auto">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
v-model="this.value"
|
||||||
|
@change="this.toggleDashboardAPIKeys()"
|
||||||
|
role="switch" id="allowAPIKeysSwitch">
|
||||||
|
<label class="form-check-label" for="allowAPIKeysSwitch">
|
||||||
|
{{this.value ? 'Enabled':'Disabled'}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body position-relative d-flex flex-column gap-2" v-if="this.value">
|
||||||
|
<button class="ms-auto btn bg-primary-subtle text-primary-emphasis border-1 border-primary-subtle rounded-3 shadow-sm"
|
||||||
|
@click="this.newDashboardAPIKey = true"
|
||||||
|
>
|
||||||
|
<i class="bi bi-key me-2"></i> Create
|
||||||
|
</button>
|
||||||
|
<div class="card" style="height: 300px" v-if="this.apiKeys.length === 0">
|
||||||
|
<div class="card-body d-flex text-muted">
|
||||||
|
<span class="m-auto">
|
||||||
|
No Dashboard API Key
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex flex-column gap-2 position-relative" v-else style="min-height: 300px">
|
||||||
|
<TransitionGroup name="apiKey">
|
||||||
|
<DashboardAPIKey v-for="key in this.apiKeys" :apiKey="key"
|
||||||
|
:key="key.Key"
|
||||||
|
@deleted="(nkeys) => this.apiKeys = nkeys"></DashboardAPIKey>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
<Transition name="zoomReversed">
|
||||||
|
<NewDashboardAPIKey v-if="this.newDashboardAPIKey"
|
||||||
|
@created="(data) => this.apiKeys = data"
|
||||||
|
@close="this.newDashboardAPIKey = false"
|
||||||
|
></NewDashboardAPIKey>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.apiKey-move, /* apply transition to moving elements */
|
||||||
|
.apiKey-enter-active,
|
||||||
|
.apiKey-leave-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apiKey-enter-from,
|
||||||
|
.apiKey-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
transform: translateY(30px) scale(0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ensure leaving items are taken out of layout flow so that moving
|
||||||
|
animations can be calculated correctly. */
|
||||||
|
.apiKey-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,66 @@
|
|||||||
|
<script>
|
||||||
|
import {fetchPost} from "@/utilities/fetch.js";
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "dashboardAPIKey",
|
||||||
|
props: {
|
||||||
|
apiKey: Object
|
||||||
|
},
|
||||||
|
setup(){
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
return {store};
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
confirmDelete: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteAPIKey(){
|
||||||
|
fetchPost("/api/deleteDashboardAPIKey", {
|
||||||
|
Key: this.apiKey.Key
|
||||||
|
}, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.$emit('deleted', res.data);
|
||||||
|
this.store.newMessage("Server", "API Key deleted", "success");
|
||||||
|
}else{
|
||||||
|
this.store.newMessage("Server", res.message, "danger")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card rounded-3 shadow-sm">
|
||||||
|
<div class="card-body d-flex gap-3 align-items-center" v-if="!this.confirmDelete">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<small class="text-muted">Key</small>{{this.apiKey.Key}}
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center gap-2 ms-auto">
|
||||||
|
<small class="text-muted">Expire At</small>
|
||||||
|
{{this.apiKey.ExpiredAt ? this.apiKey.ExpiredAt : 'Never'}}
|
||||||
|
</div>
|
||||||
|
<a role="button" class="btn btn-sm bg-danger-subtle text-danger-emphasis rounded-3" @click="this.confirmDelete = true">
|
||||||
|
<i class="bi bi-trash-fill"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else class="card-body d-flex gap-3 align-items-center justify-content-end">
|
||||||
|
Are you sure to delete this API key?
|
||||||
|
<a role="button" class="btn btn-sm bg-success-subtle text-success-emphasis rounded-3"
|
||||||
|
@click="this.deleteAPIKey()"
|
||||||
|
>
|
||||||
|
<i class="bi bi-check-lg"></i>
|
||||||
|
</a>
|
||||||
|
<a role="button" class="btn btn-sm bg-secondary-subtle text-secondary-emphasis rounded-3" @click="this.confirmDelete = false">
|
||||||
|
<i class="bi bi-x-lg"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -0,0 +1,84 @@
|
|||||||
|
<script>
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {fetchPost} from "@/utilities/fetch.js";
|
||||||
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "newDashboardAPIKey",
|
||||||
|
data(){
|
||||||
|
return{
|
||||||
|
newKeyData:{
|
||||||
|
ExpiredAt: dayjs().add(1, 'd').format("YYYY-MM-DDTHH:mm:ss"),
|
||||||
|
neverExpire: false
|
||||||
|
},
|
||||||
|
submitting: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(){
|
||||||
|
const store = DashboardConfigurationStore();
|
||||||
|
return {store};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log(this.newKeyData.ExpiredAt)
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
submitNewAPIKey(){
|
||||||
|
this.submitting = true;
|
||||||
|
fetchPost('/api/newDashboardAPIKey', this.newKeyData, (res) => {
|
||||||
|
if (res.status){
|
||||||
|
this.$emit('created', res.data);
|
||||||
|
this.store.newMessage("Server", "New API Key created", "success");
|
||||||
|
this.$emit('close')
|
||||||
|
}else{
|
||||||
|
this.store.newMessage("Server", res.message, "danger")
|
||||||
|
}
|
||||||
|
this.submitting = false;
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fixDate(date){
|
||||||
|
console.log(dayjs(date).format("YYYY-MM-DDTHH:mm:ss"))
|
||||||
|
return dayjs(date).format("YYYY-MM-DDTHH:mm:ss")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="position-absolute w-100 h-100 top-0 start-0 rounded-bottom-3 p-3 d-flex"
|
||||||
|
style="background-color: #00000060; backdrop-filter: blur(3px)">
|
||||||
|
<div class="card m-auto rounded-3 mt-5">
|
||||||
|
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4 pb-0">
|
||||||
|
Create API Key
|
||||||
|
<button type="button" class="btn-close ms-auto" @click="this.$emit('close')"></button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex gap-2 p-4 flex-column">
|
||||||
|
<small class="text-muted">When should this API Key expire?</small>
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<input class="form-control" type="datetime-local"
|
||||||
|
@change="this.newKeyData.ExpiredAt = this.fixDate(this.newKeyData.ExpiredAt)"
|
||||||
|
:disabled="this.newKeyData.neverExpire || this.submitting"
|
||||||
|
v-model="this.newKeyData.ExpiredAt">
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox"
|
||||||
|
v-model="this.newKeyData.neverExpire" id="neverExpire" :disabled="this.submitting">
|
||||||
|
<label class="form-check-label" for="neverExpire">
|
||||||
|
Never Expire (<i class="bi bi-emoji-grimace-fill"></i> Don't think that's a good idea)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<button class="ms-auto btn bg-success-subtle text-success-emphasis border-1 border-success-subtle rounded-3 shadow-sm"
|
||||||
|
:class="{disabled: this.submitting}"
|
||||||
|
@click="this.submitNewAPIKey()"
|
||||||
|
>
|
||||||
|
<i class="bi bi-check-lg me-2" v-if="!this.submitting"></i>
|
||||||
|
{{this.submitting ? 'Creating...':'Done'}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -28,13 +28,13 @@ export default {
|
|||||||
<div class="card mb-4 shadow rounded-3">
|
<div class="card mb-4 shadow rounded-3">
|
||||||
<p class="card-header">Dashboard Theme</p>
|
<p class="card-header">Dashboard Theme</p>
|
||||||
<div class="card-body d-flex gap-2">
|
<div class="card-body d-flex gap-2">
|
||||||
<button class="btn btn-outline-primary flex-grow-1"
|
<button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1"
|
||||||
@click="this.switchTheme('light')"
|
@click="this.switchTheme('light')"
|
||||||
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'light'}">
|
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'light'}">
|
||||||
<i class="bi bi-sun-fill"></i>
|
<i class="bi bi-sun-fill"></i>
|
||||||
Light
|
Light
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-outline-primary flex-grow-1"
|
<button class="btn bg-primary-subtle text-primary-emphasis flex-grow-1"
|
||||||
@click="this.switchTheme('dark')"
|
@click="this.switchTheme('dark')"
|
||||||
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'dark'}">
|
:class="{active: this.dashboardConfigurationStore.Configuration.Server.dashboard_theme === 'dark'}">
|
||||||
<i class="bi bi-moon-fill"></i>
|
<i class="bi bi-moon-fill"></i>
|
||||||
|
@ -9,7 +9,6 @@ export default {
|
|||||||
title: String,
|
title: String,
|
||||||
warning: false,
|
warning: false,
|
||||||
warningText: "",
|
warningText: "",
|
||||||
|
|
||||||
},
|
},
|
||||||
setup(){
|
setup(){
|
||||||
const store = DashboardConfigurationStore();
|
const store = DashboardConfigurationStore();
|
||||||
|
@ -10,11 +10,13 @@ import DashboardSettingsInputWireguardConfigurationPath
|
|||||||
import DashboardTheme from "@/components/settingsComponent/dashboardTheme.vue";
|
import DashboardTheme from "@/components/settingsComponent/dashboardTheme.vue";
|
||||||
import DashboardSettingsInputIPAddressAndPort
|
import DashboardSettingsInputIPAddressAndPort
|
||||||
from "@/components/settingsComponent/dashboardSettingsInputIPAddressAndPort.vue";
|
from "@/components/settingsComponent/dashboardSettingsInputIPAddressAndPort.vue";
|
||||||
|
import DashboardAPIKeys from "@/components/settingsComponent/dashboardAPIKeys.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "settings",
|
name: "settings",
|
||||||
methods: {ipV46RegexCheck},
|
methods: {ipV46RegexCheck},
|
||||||
components: {
|
components: {
|
||||||
|
DashboardAPIKeys,
|
||||||
DashboardSettingsInputIPAddressAndPort,
|
DashboardSettingsInputIPAddressAndPort,
|
||||||
DashboardTheme,
|
DashboardTheme,
|
||||||
DashboardSettingsInputWireguardConfigurationPath,
|
DashboardSettingsInputWireguardConfigurationPath,
|
||||||
@ -60,7 +62,6 @@ export default {
|
|||||||
:warning="true"
|
:warning="true"
|
||||||
warning-text="Remember to remove <code>/</code> at the end of your path. e.g <code>/etc/wireguard</code>"
|
warning-text="Remember to remove <code>/</code> at the end of your path. e.g <code>/etc/wireguard</code>"
|
||||||
>
|
>
|
||||||
|
|
||||||
</DashboardSettingsInputWireguardConfigurationPath>
|
</DashboardSettingsInputWireguardConfigurationPath>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -76,6 +77,7 @@ export default {
|
|||||||
</AccountSettingsInputPassword>
|
</AccountSettingsInputPassword>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<DashboardAPIKeys></DashboardAPIKeys>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1132,7 +1132,8 @@ pre.index-alert {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.zoom-enter-active,
|
.zoom-enter-active,
|
||||||
.zoom-leave-active {
|
.zoom-leave-active, .zoomReversed-enter-active,
|
||||||
|
.zoomReversed-leave-active {
|
||||||
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 0.9);
|
||||||
/*position: absolute;*/
|
/*position: absolute;*/
|
||||||
/*padding-top: 50px*/
|
/*padding-top: 50px*/
|
||||||
@ -1144,3 +1145,12 @@ pre.index-alert {
|
|||||||
filter: blur(3px);
|
filter: blur(3px);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zoomReversed-enter-from,
|
||||||
|
.zoomReversed-leave-to {
|
||||||
|
transform: scale(0.9);
|
||||||
|
filter: blur(3px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user