|
|
@ -2,6 +2,27 @@ |
|
|
import { ref, reactive } from 'vue'; |
|
|
import { ref, reactive } from 'vue'; |
|
|
import sha256 from 'crypto-js/sha256'; |
|
|
import sha256 from 'crypto-js/sha256'; |
|
|
|
|
|
|
|
|
|
|
|
import { useDateTime } from './composables/useDateTime'; |
|
|
|
|
|
import { useTimeAgo } from './composables/useTimeAgo'; |
|
|
|
|
|
|
|
|
|
|
|
import UpdateNotification from './components/UpdateNotification.vue'; |
|
|
|
|
|
import ClientNewButton from './components/ClientNewButton.vue'; |
|
|
|
|
|
import ClientName from './components/ClientName.vue'; |
|
|
|
|
|
import ClientAvatar from './components/ClientAvatar.vue'; |
|
|
|
|
|
import ClientAddress from './components/ClientAddress.vue'; |
|
|
|
|
|
import IconUpArrow from './components/icons/IconUpArrow.vue'; |
|
|
|
|
|
import IconDownArrow from './components/icons/IconDownArrow.vue'; |
|
|
|
|
|
import IconLogout from './components/icons/IconLogout.vue'; |
|
|
|
|
|
import IconQRCode from './components/icons/IconQRCode.vue'; |
|
|
|
|
|
import IconDownload from './components/icons/IconDownload.vue'; |
|
|
|
|
|
import IconDelete from './components/icons/IconDelete.vue'; |
|
|
|
|
|
import IconNew from './components/icons/IconNew.vue'; |
|
|
|
|
|
import LoadingSpinner from './components/LoadingSpinner.vue'; |
|
|
|
|
|
import IconClose from './components/icons/IconClose.vue'; |
|
|
|
|
|
import IconWarning from './components/icons/IconWarning.vue'; |
|
|
|
|
|
import IconAvatarDefault from './components/icons/IconAvatarDefault.vue'; |
|
|
|
|
|
import ClientTransfer from './components/ClientTransfer.vue' |
|
|
|
|
|
|
|
|
class API { |
|
|
class API { |
|
|
async call({ method, path, body }) { |
|
|
async call({ method, path, body }) { |
|
|
const res = await fetch(`./api${path}`, { |
|
|
const res = await fetch(`./api${path}`, { |
|
|
@ -208,168 +229,8 @@ export default { |
|
|
|
|
|
|
|
|
const api = new API(); |
|
|
const api = new API(); |
|
|
|
|
|
|
|
|
const dateTime = (value) => { |
|
|
const { dateTime } = useDateTime(); |
|
|
return new Intl.DateTimeFormat(undefined, { |
|
|
const { timeago } = useTimeAgo(); |
|
|
year: 'numeric', |
|
|
|
|
|
month: 'short', |
|
|
|
|
|
day: 'numeric', |
|
|
|
|
|
hour: 'numeric', |
|
|
|
|
|
minute: 'numeric', |
|
|
|
|
|
}).format(value); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const refresh = async ({ updateCharts = false } = {}) => { |
|
|
|
|
|
if (!authenticated.value) return; |
|
|
|
|
|
|
|
|
|
|
|
const clientsData = await api.getClients(); |
|
|
|
|
|
clients.value = clientsData.map((client) => { |
|
|
|
|
|
if (client.name.includes('@') && client.name.includes('.')) { |
|
|
|
|
|
client.avatar = `https://www.gravatar.com/avatar/${sha256(client.name)}?d=blank`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!clientsPersist[client.id]) { |
|
|
|
|
|
clientsPersist[client.id] = {}; |
|
|
|
|
|
clientsPersist[client.id].transferRxHistory = Array(50).fill(0); |
|
|
|
|
|
clientsPersist[client.id].transferRxPrevious = client.transferRx; |
|
|
|
|
|
clientsPersist[client.id].transferTxHistory = Array(50).fill(0); |
|
|
|
|
|
clientsPersist[client.id].transferTxPrevious = client.transferTx; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Debug |
|
|
|
|
|
// client.transferRx = clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; |
|
|
|
|
|
// client.transferTx = clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; |
|
|
|
|
|
|
|
|
|
|
|
if (updateCharts) { |
|
|
|
|
|
clientsPersist[client.id].transferRxCurrent = |
|
|
|
|
|
client.transferRx - clientsPersist[client.id].transferRxPrevious; |
|
|
|
|
|
clientsPersist[client.id].transferRxPrevious = client.transferRx; |
|
|
|
|
|
clientsPersist[client.id].transferTxCurrent = |
|
|
|
|
|
client.transferTx - clientsPersist[client.id].transferTxPrevious; |
|
|
|
|
|
clientsPersist[client.id].transferTxPrevious = client.transferTx; |
|
|
|
|
|
|
|
|
|
|
|
clientsPersist[client.id].transferRxHistory.push( |
|
|
|
|
|
clientsPersist[client.id].transferRxCurrent |
|
|
|
|
|
); |
|
|
|
|
|
clientsPersist[client.id].transferRxHistory.shift(); |
|
|
|
|
|
|
|
|
|
|
|
clientsPersist[client.id].transferTxHistory.push( |
|
|
|
|
|
clientsPersist[client.id].transferTxCurrent |
|
|
|
|
|
); |
|
|
|
|
|
clientsPersist[client.id].transferTxHistory.shift(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
client.transferTxCurrent = clientsPersist[client.id].transferTxCurrent; |
|
|
|
|
|
client.transferRxCurrent = clientsPersist[client.id].transferRxCurrent; |
|
|
|
|
|
|
|
|
|
|
|
client.transferTxHistory = clientsPersist[client.id].transferTxHistory; |
|
|
|
|
|
client.transferRxHistory = clientsPersist[client.id].transferRxHistory; |
|
|
|
|
|
client.transferMax = Math.max( |
|
|
|
|
|
...client.transferTxHistory, |
|
|
|
|
|
...client.transferRxHistory |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
client.hoverTx = clientsPersist[client.id].hoverTx; |
|
|
|
|
|
client.hoverRx = clientsPersist[client.id].hoverRx; |
|
|
|
|
|
|
|
|
|
|
|
return client; |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const login = (e) => { |
|
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
|
|
|
|
if (!password.value) return; |
|
|
|
|
|
if (authenticating.value) return; |
|
|
|
|
|
|
|
|
|
|
|
authenticating.value = true; |
|
|
|
|
|
api |
|
|
|
|
|
.createSession({ |
|
|
|
|
|
password: password.value, |
|
|
|
|
|
}) |
|
|
|
|
|
.then(async () => { |
|
|
|
|
|
const session = await api.getSession(); |
|
|
|
|
|
authenticated.value = session.authenticated; |
|
|
|
|
|
requiresPassword.value = session.requiresPassword; |
|
|
|
|
|
return refresh(); |
|
|
|
|
|
}) |
|
|
|
|
|
.catch((err) => { |
|
|
|
|
|
alert(err.message || err.toString()); |
|
|
|
|
|
}) |
|
|
|
|
|
.finally(() => { |
|
|
|
|
|
authenticating.value = false; |
|
|
|
|
|
password.value = null; |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const logout = (e) => { |
|
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
|
|
|
|
api |
|
|
|
|
|
.deleteSession() |
|
|
|
|
|
.then(() => { |
|
|
|
|
|
authenticated.value = false; |
|
|
|
|
|
clients.value = null; |
|
|
|
|
|
}) |
|
|
|
|
|
.catch((err) => { |
|
|
|
|
|
alert(err.message || err.toString()); |
|
|
|
|
|
}); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const createClient = () => { |
|
|
|
|
|
const name = clientCreateName.value; |
|
|
|
|
|
if (!name) return; |
|
|
|
|
|
|
|
|
|
|
|
api |
|
|
|
|
|
.createClient({ name }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => refresh().catch(console.error)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const deleteClient = (client) => { |
|
|
|
|
|
api |
|
|
|
|
|
.deleteClient({ clientId: client.id }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => refresh().catch(console.error)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const enableClient = (client) => { |
|
|
|
|
|
api |
|
|
|
|
|
.enableClient({ clientId: client.id }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => refresh().catch(console.error)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const disableClient = (client) => { |
|
|
|
|
|
api |
|
|
|
|
|
.disableClient({ clientId: client.id }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => refresh().catch(console.error)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const updateClientName = (client, name) => { |
|
|
|
|
|
api |
|
|
|
|
|
.updateClientName({ clientId: client.id, name }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => refresh().catch(console.error)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const updateClientAddress = (client, address) => { |
|
|
|
|
|
api |
|
|
|
|
|
.updateClientAddress({ clientId: client.id, address }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => refresh().catch(console.error)); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const toggleTheme = () => { |
|
|
|
|
|
if (isDark.value) { |
|
|
|
|
|
localStorage.theme = 'light'; |
|
|
|
|
|
document.documentElement.classList.remove('dark'); |
|
|
|
|
|
} else { |
|
|
|
|
|
localStorage.theme = 'dark'; |
|
|
|
|
|
document.documentElement.classList.add('dark'); |
|
|
|
|
|
} |
|
|
|
|
|
isDark.value = !isDark.value; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
authenticated, |
|
|
authenticated, |
|
|
@ -396,16 +257,7 @@ export default { |
|
|
chartOptions, |
|
|
chartOptions, |
|
|
|
|
|
|
|
|
dateTime, |
|
|
dateTime, |
|
|
refresh, |
|
|
timeago, |
|
|
login, |
|
|
|
|
|
logout, |
|
|
|
|
|
createClient, |
|
|
|
|
|
deleteClient, |
|
|
|
|
|
enableClient, |
|
|
|
|
|
disableClient, |
|
|
|
|
|
updateClientName, |
|
|
|
|
|
updateClientAddress, |
|
|
|
|
|
toggleTheme, |
|
|
|
|
|
|
|
|
|
|
|
api, |
|
|
api, |
|
|
}; |
|
|
}; |
|
|
@ -469,6 +321,184 @@ export default { |
|
|
}) |
|
|
}) |
|
|
.catch(console.error); |
|
|
.catch(console.error); |
|
|
}, |
|
|
}, |
|
|
|
|
|
components: { |
|
|
|
|
|
UpdateNotification, |
|
|
|
|
|
ClientNewButton, |
|
|
|
|
|
ClientName, |
|
|
|
|
|
ClientAvatar, |
|
|
|
|
|
ClientAddress, |
|
|
|
|
|
IconUpArrow, |
|
|
|
|
|
IconDownArrow, |
|
|
|
|
|
IconLogout, |
|
|
|
|
|
IconQRCode, |
|
|
|
|
|
IconDownload, |
|
|
|
|
|
IconDelete, |
|
|
|
|
|
IconNew, |
|
|
|
|
|
LoadingSpinner, |
|
|
|
|
|
IconClose, |
|
|
|
|
|
IconWarning, |
|
|
|
|
|
IconAvatarDefault, |
|
|
|
|
|
ClientTransfer, |
|
|
|
|
|
}, |
|
|
|
|
|
methods: { |
|
|
|
|
|
handleNewClient() { |
|
|
|
|
|
this.clientCreate = true; |
|
|
|
|
|
this.clientCreateName = ''; |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
async refresh ({ updateCharts = false } = {}) { |
|
|
|
|
|
if (!this.authenticated) return; |
|
|
|
|
|
|
|
|
|
|
|
const clientsData = await this.api.getClients(); |
|
|
|
|
|
this.clients = clientsData.map((client) => { |
|
|
|
|
|
if (client.name.includes('@') && client.name.includes('.')) { |
|
|
|
|
|
client.avatar = `https://www.gravatar.com/avatar/${sha256(client.name)}?d=blank`; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!this.clientsPersist[client.id]) { |
|
|
|
|
|
this.clientsPersist[client.id] = {}; |
|
|
|
|
|
this.clientsPersist[client.id].transferRxHistory = Array(50).fill(0); |
|
|
|
|
|
this.clientsPersist[client.id].transferRxPrevious = client.transferRx; |
|
|
|
|
|
this.clientsPersist[client.id].transferTxHistory = Array(50).fill(0); |
|
|
|
|
|
this.clientsPersist[client.id].transferTxPrevious = client.transferTx; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Debug |
|
|
|
|
|
// client.transferRx = this.clientsPersist[client.id].transferRxPrevious + Math.random() * 1000; |
|
|
|
|
|
// client.transferTx = this.clientsPersist[client.id].transferTxPrevious + Math.random() * 1000; |
|
|
|
|
|
|
|
|
|
|
|
if (updateCharts) { |
|
|
|
|
|
this.clientsPersist[client.id].transferRxCurrent = |
|
|
|
|
|
client.transferRx - this.clientsPersist[client.id].transferRxPrevious; |
|
|
|
|
|
this.clientsPersist[client.id].transferRxPrevious = client.transferRx; |
|
|
|
|
|
this.clientsPersist[client.id].transferTxCurrent = |
|
|
|
|
|
client.transferTx - this.clientsPersist[client.id].transferTxPrevious; |
|
|
|
|
|
this.clientsPersist[client.id].transferTxPrevious = client.transferTx; |
|
|
|
|
|
|
|
|
|
|
|
this.clientsPersist[client.id].transferRxHistory.push( |
|
|
|
|
|
this.clientsPersist[client.id].transferRxCurrent |
|
|
|
|
|
); |
|
|
|
|
|
this.clientsPersist[client.id].transferRxHistory.shift(); |
|
|
|
|
|
|
|
|
|
|
|
this.clientsPersist[client.id].transferTxHistory.push( |
|
|
|
|
|
this.clientsPersist[client.id].transferTxCurrent |
|
|
|
|
|
); |
|
|
|
|
|
this.clientsPersist[client.id].transferTxHistory.shift(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
client.transferTxCurrent = this.clientsPersist[client.id].transferTxCurrent; |
|
|
|
|
|
client.transferRxCurrent = this.clientsPersist[client.id].transferRxCurrent; |
|
|
|
|
|
|
|
|
|
|
|
client.transferTxHistory = this.clientsPersist[client.id].transferTxHistory; |
|
|
|
|
|
client.transferRxHistory = this.clientsPersist[client.id].transferRxHistory; |
|
|
|
|
|
client.transferMax = Math.max( |
|
|
|
|
|
...client.transferTxHistory, |
|
|
|
|
|
...client.transferRxHistory |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
client.hoverTx = this.clientsPersist[client.id].hoverTx; |
|
|
|
|
|
client.hoverRx = this.clientsPersist[client.id].hoverRx; |
|
|
|
|
|
|
|
|
|
|
|
return client; |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
login (e) { |
|
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
|
|
|
|
if (!this.password) return; |
|
|
|
|
|
if (this.authenticating) return; |
|
|
|
|
|
|
|
|
|
|
|
this.authenticating = true; |
|
|
|
|
|
this.api |
|
|
|
|
|
.createSession({ |
|
|
|
|
|
password: this.password, |
|
|
|
|
|
}) |
|
|
|
|
|
.then(async () => { |
|
|
|
|
|
const session = await this.api.getSession(); |
|
|
|
|
|
this.authenticated = session.authenticated; |
|
|
|
|
|
this.requiresPassword = session.requiresPassword; |
|
|
|
|
|
return this.refresh(); |
|
|
|
|
|
}) |
|
|
|
|
|
.catch((err) => { |
|
|
|
|
|
alert(err.message || err.toString()); |
|
|
|
|
|
}) |
|
|
|
|
|
.finally(() => { |
|
|
|
|
|
this.authenticating = false; |
|
|
|
|
|
this.password = null; |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
logout (e) { |
|
|
|
|
|
e.preventDefault(); |
|
|
|
|
|
|
|
|
|
|
|
this.api |
|
|
|
|
|
.deleteSession() |
|
|
|
|
|
.then(() => { |
|
|
|
|
|
this.authenticated = false; |
|
|
|
|
|
this.clients = null; |
|
|
|
|
|
}) |
|
|
|
|
|
.catch((err) => { |
|
|
|
|
|
alert(err.message || err.toString()); |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
createClient () { |
|
|
|
|
|
const name = this.clientCreateName; |
|
|
|
|
|
if (!name) return; |
|
|
|
|
|
|
|
|
|
|
|
this.api |
|
|
|
|
|
.createClient({ name }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => this.refresh().catch(console.error)); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
deleteClient (client) { |
|
|
|
|
|
this.api |
|
|
|
|
|
.deleteClient({ clientId: client.id }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => this.refresh().catch(console.error)); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
enableClient (client) { |
|
|
|
|
|
this.api |
|
|
|
|
|
.enableClient({ clientId: client.id }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => this.refresh().catch(console.error)); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
disableClient (client) { |
|
|
|
|
|
this.api |
|
|
|
|
|
.disableClient({ clientId: client.id }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => this.refresh().catch(console.error)); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
updateClientName (client, name) { |
|
|
|
|
|
this.api |
|
|
|
|
|
.updateClientName({ clientId: client.id, name }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => this.refresh().catch(console.error)); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
updateClientAddress (client, address) { |
|
|
|
|
|
this.api |
|
|
|
|
|
.updateClientAddress({ clientId: client.id, address }) |
|
|
|
|
|
.catch((err) => alert(err.message || err.toString())) |
|
|
|
|
|
.finally(() => this.refresh().catch(console.error)); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
toggleTheme () { |
|
|
|
|
|
if (this.isDark) { |
|
|
|
|
|
localStorage.theme = 'light'; |
|
|
|
|
|
document.documentElement.classList.remove('dark'); |
|
|
|
|
|
} else { |
|
|
|
|
|
localStorage.theme = 'dark'; |
|
|
|
|
|
document.documentElement.classList.add('dark'); |
|
|
|
|
|
} |
|
|
|
|
|
this.isDark = !this.isDark; |
|
|
|
|
|
}, |
|
|
|
|
|
}, |
|
|
}; |
|
|
}; |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
@ -482,20 +512,7 @@ export default { |
|
|
@click="logout" |
|
|
@click="logout" |
|
|
> |
|
|
> |
|
|
Logout |
|
|
Logout |
|
|
<svg |
|
|
<IconLogout /> |
|
|
class="h-3 inline" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</span> |
|
|
</span> |
|
|
<h1 class="text-4xl dark:text-neutral-200 font-medium mt-2 mb-2"> |
|
|
<h1 class="text-4xl dark:text-neutral-200 font-medium mt-2 mb-2"> |
|
|
<img |
|
|
<img |
|
|
@ -507,63 +524,12 @@ export default { |
|
|
</h1> |
|
|
</h1> |
|
|
<h2 class="text-sm text-gray-400 dark:text-neutral-400 mb-10"></h2> |
|
|
<h2 class="text-sm text-gray-400 dark:text-neutral-400 mb-10"></h2> |
|
|
|
|
|
|
|
|
<div |
|
|
<UpdateNotification :latestRelease="latestRelease" :currentRelease="currentRelease" /> |
|
|
v-if="latestRelease" |
|
|
|
|
|
class="bg-red-800 dark:bg-red-100 p-4 text-white dark:text-red-600 text-sm font-small mb-10 rounded-md shadow-lg" |
|
|
|
|
|
:title="`v${currentRelease} → v${latestRelease.version}`" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="container mx-auto flex flex-row flex-auto items-center"> |
|
|
|
|
|
<div class="flex-grow"> |
|
|
|
|
|
<p class="font-bold">There is an update available!</p> |
|
|
|
|
|
<p>{{ latestRelease.changelog }}</p> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<a |
|
|
|
|
|
href="https://github.com/wg-easy/wg-easy#updating" |
|
|
|
|
|
target="_blank" |
|
|
|
|
|
class="p-3 rounded-md bg-white dark:bg-red-100 float-right font-sm font-semibold text-red-800 dark:text-red-600 flex-shrink-0 border-2 border-red-800 dark:border-red-600 hover:border-white dark:hover:border-red-600 hover:text-white dark:hover:text-red-100 hover:bg-red-800 dark:hover:bg-red-600 transition-all" |
|
|
|
|
|
> |
|
|
|
|
|
Update → |
|
|
|
|
|
</a> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div |
|
|
<div |
|
|
class="shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden" |
|
|
class="shadow-md rounded-lg bg-white dark:bg-neutral-700 overflow-hidden" |
|
|
> |
|
|
> |
|
|
<div |
|
|
<ClientNewButton @create-new-client="handleNewClient" /> |
|
|
class="flex flex-row flex-auto items-center p-3 px-5 border-b-2 border-gray-100 dark:border-neutral-600" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="flex-grow"> |
|
|
|
|
|
<p class="text-2xl font-medium dark:text-neutral-200">Clients</p> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="flex-shrink-0"> |
|
|
|
|
|
<button |
|
|
|
|
|
@click=" |
|
|
|
|
|
clientCreate = true; |
|
|
|
|
|
clientCreateName = ''; |
|
|
|
|
|
" |
|
|
|
|
|
class="hover:bg-red-800 hover:border-red-800 hover:text-white text-gray-700 dark:text-neutral-200 border-2 border-gray-100 dark:border-neutral-600 py-2 px-4 rounded inline-flex items-center transition" |
|
|
|
|
|
> |
|
|
|
|
|
<svg |
|
|
|
|
|
class="w-4 mr-2" |
|
|
|
|
|
inline |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
<span class="text-sm">New</span> |
|
|
|
|
|
</button> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div> |
|
|
<div> |
|
|
<!-- Client --> |
|
|
<!-- Client --> |
|
|
@ -599,208 +565,44 @@ export default { |
|
|
class="relative p-5 z-10 flex flex-col md:flex-row justify-between" |
|
|
class="relative p-5 z-10 flex flex-col md:flex-row justify-between" |
|
|
> |
|
|
> |
|
|
<div class="flex items-center pb-2 md:pb-0"> |
|
|
<div class="flex items-center pb-2 md:pb-0"> |
|
|
<div class="h-10 w-10 mr-5 rounded-full bg-gray-50 relative"> |
|
|
<ClientAvatar :client="client" /> |
|
|
<svg |
|
|
|
|
|
class="w-6 m-2 text-gray-300" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 20 20" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
fill-rule="evenodd" |
|
|
|
|
|
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" |
|
|
|
|
|
clip-rule="evenodd" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
<img |
|
|
|
|
|
v-if="client.avatar" |
|
|
|
|
|
:src="client.avatar" |
|
|
|
|
|
class="w-10 rounded-full absolute top-0 left-0" |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<div |
|
|
|
|
|
v-if=" |
|
|
|
|
|
client.latestHandshakeAt && |
|
|
|
|
|
new Date() - new Date(client.latestHandshakeAt) < |
|
|
|
|
|
1000 * 60 * 10 |
|
|
|
|
|
" |
|
|
|
|
|
> |
|
|
|
|
|
<div |
|
|
|
|
|
class="animate-ping w-4 h-4 p-1 bg-red-100 dark:bg-red-100 rounded-full absolute -bottom-1 -right-1" |
|
|
|
|
|
></div> |
|
|
|
|
|
<div |
|
|
|
|
|
class="w-2 h-2 bg-red-800 dark:bg-red-600 rounded-full absolute bottom-0 right-0" |
|
|
|
|
|
></div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="flex-grow"> |
|
|
<div class="flex-grow"> |
|
|
<!-- Name --> |
|
|
<!-- Name --> |
|
|
<div |
|
|
<ClientName |
|
|
class="text-gray-700 dark:text-neutral-200 group" |
|
|
:client="client" |
|
|
:title=" |
|
|
:clientEditName="clientEditName" |
|
|
'Created on ' + dateTime(new Date(client.createdAt)) |
|
|
:clientEditNameId="clientEditNameId" |
|
|
" |
|
|
@update-client-name="updateClientName" |
|
|
> |
|
|
/> |
|
|
<!-- Show --> |
|
|
|
|
|
<input |
|
|
|
|
|
v-show="clientEditNameId === client.id" |
|
|
|
|
|
v-model="clientEditName" |
|
|
|
|
|
v-on:keyup.enter=" |
|
|
|
|
|
updateClientName(client, clientEditName); |
|
|
|
|
|
clientEditName = null; |
|
|
|
|
|
clientEditNameId = null; |
|
|
|
|
|
" |
|
|
|
|
|
v-on:keyup.escape=" |
|
|
|
|
|
clientEditName = null; |
|
|
|
|
|
clientEditNameId = null; |
|
|
|
|
|
" |
|
|
|
|
|
:ref="'client-' + client.id + '-name'" |
|
|
|
|
|
class="rounded px-1 border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 dark:placeholder:text-neutral-500 outline-none w-30" |
|
|
|
|
|
/> |
|
|
|
|
|
<span |
|
|
|
|
|
v-show="clientEditNameId !== client.id" |
|
|
|
|
|
class="inline-block border-t-2 border-b-2 border-transparent" |
|
|
|
|
|
>{{ client.name }}</span |
|
|
|
|
|
> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Edit --> |
|
|
|
|
|
<span |
|
|
|
|
|
v-show="clientEditNameId !== client.id" |
|
|
|
|
|
@click=" |
|
|
|
|
|
clientEditName = client.name; |
|
|
|
|
|
clientEditNameId = client.id; |
|
|
|
|
|
setTimeout( |
|
|
|
|
|
() => |
|
|
|
|
|
$refs[ |
|
|
|
|
|
'client-' + client.id + '-name' |
|
|
|
|
|
][0].select(), |
|
|
|
|
|
1 |
|
|
|
|
|
); |
|
|
|
|
|
" |
|
|
|
|
|
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" |
|
|
|
|
|
> |
|
|
|
|
|
<svg |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Info --> |
|
|
<!-- Info --> |
|
|
<div class="text-gray-400 dark:text-neutral-400 text-xs"> |
|
|
<div class="text-gray-400 dark:text-neutral-400 text-xs"> |
|
|
<!-- Address --> |
|
|
<!-- Address --> |
|
|
<span class="group block md:inline-block pb-1 md:pb-0"> |
|
|
<ClientAddress |
|
|
<!-- Show --> |
|
|
:client="client" |
|
|
<input |
|
|
@update-address="updateClientAddress" |
|
|
v-show="clientEditAddressId === client.id" |
|
|
/> |
|
|
v-model="clientEditAddress" |
|
|
|
|
|
v-on:keyup.enter=" |
|
|
|
|
|
updateClientAddress(client, clientEditAddress); |
|
|
|
|
|
clientEditAddress = null; |
|
|
|
|
|
clientEditAddressId = null; |
|
|
|
|
|
" |
|
|
|
|
|
v-on:keyup.escape=" |
|
|
|
|
|
clientEditAddress = null; |
|
|
|
|
|
clientEditAddressId = null; |
|
|
|
|
|
" |
|
|
|
|
|
:ref="'client-' + client.id + '-address'" |
|
|
|
|
|
class="rounded border-2 dark:bg-neutral-700 border-gray-100 dark:border-neutral-600 focus:border-gray-200 dark:focus:border-neutral-500 outline-none w-20 text-black dark:text-neutral-300 dark:placeholder:text-neutral-500" |
|
|
|
|
|
/> |
|
|
|
|
|
<span |
|
|
|
|
|
v-show="clientEditAddressId !== client.id" |
|
|
|
|
|
class="inline-block border-t-2 border-b-2 border-transparent" |
|
|
|
|
|
>{{ client.address }}</span |
|
|
|
|
|
> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Edit --> |
|
|
|
|
|
<span |
|
|
|
|
|
v-show="clientEditAddressId !== client.id" |
|
|
|
|
|
@click=" |
|
|
|
|
|
clientEditAddress = client.address; |
|
|
|
|
|
clientEditAddressId = client.id; |
|
|
|
|
|
setTimeout( |
|
|
|
|
|
() => |
|
|
|
|
|
$refs[ |
|
|
|
|
|
'client-' + client.id + '-address' |
|
|
|
|
|
][0].select(), |
|
|
|
|
|
1 |
|
|
|
|
|
); |
|
|
|
|
|
" |
|
|
|
|
|
class="cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" |
|
|
|
|
|
> |
|
|
|
|
|
<svg |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
class="h-4 w-4 inline align-middle opacity-25 hover:opacity-100" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</span> |
|
|
|
|
|
</span> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Transfer TX --> |
|
|
<!-- Transfer TX --> |
|
|
<!-- FIXME: add "bytes" --> |
|
|
<!-- FIXME: add "bytes" --> |
|
|
<span |
|
|
<ClientTransfer |
|
|
v-if="client.transferTx" |
|
|
:transferData="client.transferTx" |
|
|
:title="'Total Download: ' + bytes(client.transferTx)" |
|
|
:transferDataCurrent="client.transferTxCurrent" |
|
|
|
|
|
title="'Total Download: ' + client.transferTx" |
|
|
> |
|
|
> |
|
|
· |
|
|
<IconDownArrow /> |
|
|
<svg |
|
|
</ClientTransfer> |
|
|
class="align-middle h-3 inline" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 20 20" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
fill-rule="evenodd" |
|
|
|
|
|
d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z" |
|
|
|
|
|
clip-rule="evenodd" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
<!-- FIXME: add "bytes" --> |
|
|
|
|
|
{{ client.transferTxCurrent | bytes }}/s |
|
|
|
|
|
</span> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Transfer RX --> |
|
|
<!-- Transfer RX --> |
|
|
<!-- FIXME: add "bytes" --> |
|
|
<!-- FIXME: add "bytes" --> |
|
|
<span |
|
|
<ClientTransfer |
|
|
v-if="client.transferRx" |
|
|
:transferData="client.transferRx" |
|
|
:title="'Total Upload: ' + bytes(client.transferRx)" |
|
|
:transferDataCurrent="client.transferRxCurrent" |
|
|
|
|
|
title="'Total Upload: ' + client.transferRx" |
|
|
> |
|
|
> |
|
|
· |
|
|
<IconUpArrow /> |
|
|
<svg |
|
|
</ClientTransfer> |
|
|
class="align-middle h-3 inline" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 20 20" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
fill-rule="evenodd" |
|
|
|
|
|
d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z" |
|
|
|
|
|
clip-rule="evenodd" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
<!-- FIXME: add "bytes" --> |
|
|
|
|
|
{{ client.transferRxCurrent | bytes }}/s |
|
|
|
|
|
</span> |
|
|
|
|
|
|
|
|
|
|
|
<!-- Last seen --> |
|
|
<!-- Last seen --> |
|
|
<span |
|
|
<span |
|
|
@ -812,7 +614,7 @@ export default { |
|
|
> |
|
|
> |
|
|
<!-- FIXME: add "timeago" --> |
|
|
<!-- FIXME: add "timeago" --> |
|
|
<!-- · {{ new Date(client.latestHandshakeAt) | timeago }} --> |
|
|
<!-- · {{ new Date(client.latestHandshakeAt) | timeago }} --> |
|
|
· {{ new Date(client.latestHandshakeAt) }} |
|
|
· {{ timeago(client.latestHandshakeAt) }} |
|
|
</span> |
|
|
</span> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@ -848,20 +650,7 @@ export default { |
|
|
qrcode = `./api/wireguard/client/${client.id}/qrcode.svg` |
|
|
qrcode = `./api/wireguard/client/${client.id}/qrcode.svg` |
|
|
" |
|
|
" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<IconQRCode /> |
|
|
class="w-5" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 20h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</button> |
|
|
</button> |
|
|
|
|
|
|
|
|
<!-- Download Config --> |
|
|
<!-- Download Config --> |
|
|
@ -873,20 +662,7 @@ export default { |
|
|
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition" |
|
|
class="align-middle inline-block bg-gray-100 dark:bg-neutral-600 dark:text-neutral-300 hover:bg-red-800 dark:hover:bg-red-800 hover:text-white dark:hover:text-white p-2 rounded transition" |
|
|
title="Download Configuration" |
|
|
title="Download Configuration" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<IconDownload /> |
|
|
class="w-5" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</a> |
|
|
</a> |
|
|
|
|
|
|
|
|
<!-- Delete --> |
|
|
<!-- Delete --> |
|
|
@ -895,18 +671,7 @@ export default { |
|
|
title="Delete Client" |
|
|
title="Delete Client" |
|
|
@click="clientDelete = client" |
|
|
@click="clientDelete = client" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<IconDelete /> |
|
|
class="w-5" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 20 20" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
fill-rule="evenodd" |
|
|
|
|
|
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" |
|
|
|
|
|
clip-rule="evenodd" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</button> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@ -924,21 +689,7 @@ export default { |
|
|
" |
|
|
" |
|
|
class="bg-red-800 hover:bg-red-700 text-white border-2 border-none py-2 px-4 rounded inline-flex items-center transition" |
|
|
class="bg-red-800 hover:bg-red-700 text-white border-2 border-none py-2 px-4 rounded inline-flex items-center transition" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<IconNew class="w-4 mr-2" /> |
|
|
class="w-4 mr-2" |
|
|
|
|
|
inline |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
<span class="text-sm">New Client</span> |
|
|
<span class="text-sm">New Client</span> |
|
|
</button> |
|
|
</button> |
|
|
</p> |
|
|
</p> |
|
|
@ -947,26 +698,7 @@ export default { |
|
|
v-if="clients === null" |
|
|
v-if="clients === null" |
|
|
class="text-gray-200 dark:text-red-300 p-5" |
|
|
class="text-gray-200 dark:text-red-300 p-5" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<LoadingSpinner /> |
|
|
class="w-5 animate-spin mx-auto" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<circle |
|
|
|
|
|
class="opacity-25" |
|
|
|
|
|
cx="12" |
|
|
|
|
|
cy="12" |
|
|
|
|
|
r="10" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
stroke-width="4" |
|
|
|
|
|
></circle> |
|
|
|
|
|
<path |
|
|
|
|
|
class="opacity-75" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" |
|
|
|
|
|
></path> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
@ -981,20 +713,7 @@ export default { |
|
|
@click="qrcode = null" |
|
|
@click="qrcode = null" |
|
|
class="absolute right-4 top-4 text-gray-600 dark:text-neutral-500 hover:text-gray-800 dark:hover:text-neutral-700" |
|
|
class="absolute right-4 top-4 text-gray-600 dark:text-neutral-500 hover:text-gray-800 dark:hover:text-neutral-700" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<IconClose /> |
|
|
class="w-8" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M6 18L18 6M6 6l12 12" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</button> |
|
|
</button> |
|
|
<img :src="qrcode" /> |
|
|
<img :src="qrcode" /> |
|
|
</div> |
|
|
</div> |
|
|
@ -1051,21 +770,7 @@ export default { |
|
|
<div |
|
|
<div |
|
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-800 sm:mx-0 sm:h-10 sm:w-10" |
|
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-800 sm:mx-0 sm:h-10 sm:w-10" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<IconNew class="h-6 w-6 text-white" /> |
|
|
class="h-6 w-6 text-white" |
|
|
|
|
|
inline |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<div |
|
|
<div |
|
|
class="flex-grow mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" |
|
|
class="flex-grow mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left" |
|
|
@ -1144,6 +849,7 @@ export default { |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- This element is to trick the browser into centering the modal contents. --> |
|
|
<!-- This element is to trick the browser into centering the modal contents. --> |
|
|
|
|
|
<!-- TODO: Check if still relevant? --> |
|
|
<span |
|
|
<span |
|
|
class="hidden sm:inline-block sm:align-middle sm:h-screen" |
|
|
class="hidden sm:inline-block sm:align-middle sm:h-screen" |
|
|
aria-hidden="true" |
|
|
aria-hidden="true" |
|
|
@ -1173,21 +879,7 @@ export default { |
|
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10" |
|
|
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10" |
|
|
> |
|
|
> |
|
|
<!-- Heroicon name: outline/exclamation --> |
|
|
<!-- Heroicon name: outline/exclamation --> |
|
|
<svg |
|
|
<IconWarning /> |
|
|
class="h-6 w-6 text-red-600" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
fill="none" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
aria-hidden="true" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
stroke-linecap="round" |
|
|
|
|
|
stroke-linejoin="round" |
|
|
|
|
|
stroke-width="2" |
|
|
|
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> |
|
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> |
|
|
<h3 |
|
|
<h3 |
|
|
@ -1247,18 +939,7 @@ export default { |
|
|
<div |
|
|
<div |
|
|
class="h-20 w-20 mb-10 mt-5 mx-auto rounded-full bg-red-800 dark:bg-red-800 relative overflow-hidden" |
|
|
class="h-20 w-20 mb-10 mt-5 mx-auto rounded-full bg-red-800 dark:bg-red-800 relative overflow-hidden" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<IconAvatarDefault class="w-10 h-10 m-5 text-white dark:text-white" /> |
|
|
class="w-10 h-10 m-5 text-white dark:text-white" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 20 20" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<path |
|
|
|
|
|
fill-rule="evenodd" |
|
|
|
|
|
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" |
|
|
|
|
|
clip-rule="evenodd" |
|
|
|
|
|
/> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<input |
|
|
<input |
|
|
@ -1273,26 +954,7 @@ export default { |
|
|
v-if="authenticating" |
|
|
v-if="authenticating" |
|
|
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed" |
|
|
class="bg-red-800 dark:bg-red-800 w-full rounded shadow py-2 text-sm text-white dark:text-white cursor-not-allowed" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<LoadingSpinner /> |
|
|
class="w-5 animate-spin mx-auto" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<circle |
|
|
|
|
|
class="opacity-25" |
|
|
|
|
|
cx="12" |
|
|
|
|
|
cy="12" |
|
|
|
|
|
r="10" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
stroke-width="4" |
|
|
|
|
|
></circle> |
|
|
|
|
|
<path |
|
|
|
|
|
class="opacity-75" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" |
|
|
|
|
|
></path> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</button> |
|
|
</button> |
|
|
<input |
|
|
<input |
|
|
v-if="!authenticating && password" |
|
|
v-if="!authenticating && password" |
|
|
@ -1313,26 +975,7 @@ export default { |
|
|
v-if="authenticated === null" |
|
|
v-if="authenticated === null" |
|
|
class="text-gray-300 dark:text-red-300 pt-24 pb-12" |
|
|
class="text-gray-300 dark:text-red-300 pt-24 pb-12" |
|
|
> |
|
|
> |
|
|
<svg |
|
|
<LoadingSpinner /> |
|
|
class="w-5 animate-spin mx-auto" |
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg" |
|
|
|
|
|
viewBox="0 0 24 24" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
> |
|
|
|
|
|
<circle |
|
|
|
|
|
class="opacity-25" |
|
|
|
|
|
cx="12" |
|
|
|
|
|
cy="12" |
|
|
|
|
|
r="10" |
|
|
|
|
|
stroke="currentColor" |
|
|
|
|
|
stroke-width="4" |
|
|
|
|
|
></circle> |
|
|
|
|
|
<path |
|
|
|
|
|
class="opacity-75" |
|
|
|
|
|
fill="currentColor" |
|
|
|
|
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" |
|
|
|
|
|
></path> |
|
|
|
|
|
</svg> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
|