yubioath-flutter/qml/Main.qml

382 lines
11 KiB
QML
Raw Normal View History

2017-02-02 16:16:52 +03:00
import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
2017-02-02 16:16:52 +03:00
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2
ApplicationWindow {
2017-02-06 17:00:11 +03:00
id: appWindow
2017-02-02 16:16:52 +03:00
width: 300
height: 400
2017-02-04 00:28:57 +03:00
minimumHeight: 400
minimumWidth: 300
visible: true
2017-01-27 16:28:06 +03:00
title: qsTr("Yubico Authenticator")
2017-02-07 12:12:22 +03:00
property var device: yk
2017-02-02 16:16:52 +03:00
property int expiration: 0
2017-02-07 12:12:22 +03:00
property var credentials: device.credentials
property var selectedCredential
2017-02-07 16:17:54 +03:00
property bool validated: device.validated
property bool hasDevice: device.hasDevice
PasswordPrompt {
id: passwordPrompt
}
2017-02-08 18:47:28 +03:00
onHasDeviceChanged: {
2017-02-07 16:17:54 +03:00
if (device.hasDevice) {
2017-02-09 12:57:28 +03:00
device.promptOrSkip(passwordPrompt)
2017-02-09 14:45:04 +03:00
} else {
passwordPrompt.close()
addCredential.close()
2017-02-07 16:17:54 +03:00
}
}
2017-02-02 16:16:52 +03:00
onCredentialsChanged: {
updateExpiration()
touchYourYubikey.close()
console.log('CREDENTIALS ', JSON.stringify(credentials))
}
2017-02-04 00:54:13 +03:00
SystemPalette {
id: palette
}
TextEdit {
id: clipboard
visible: false
function setClipboard(value) {
text = value
selectAll()
copy()
}
}
menuBar: MenuBar {
Menu {
title: qsTr("File")
2017-02-03 11:58:17 +03:00
MenuItem {
2017-02-10 14:44:10 +03:00
text: qsTr('Add credential...')
2017-02-06 11:41:00 +03:00
onTriggered: addCredential.open()
2017-02-03 11:58:17 +03:00
}
2017-02-10 14:44:10 +03:00
MenuItem {
text: qsTr('Set password...')
onTriggered: setPassword.open()
}
MenuItem {
text: qsTr("Exit")
onTriggered: Qt.quit()
}
}
Menu {
title: qsTr("Help")
MenuItem {
2017-01-27 16:28:06 +03:00
text: qsTr("About Yubico Authenticator")
onTriggered: aboutPage.show()
}
}
}
AboutPage {
id: aboutPage
}
2017-02-03 11:58:17 +03:00
AddCredential {
id: addCredential
}
2017-02-10 14:44:10 +03:00
SetPassword {
id: setPassword
2017-02-10 16:01:49 +03:00
onAccepted: passwordUpdated.open()
2017-02-10 14:44:10 +03:00
}
2017-02-07 16:17:54 +03:00
2017-02-10 16:01:49 +03:00
MessageDialog {
id: passwordUpdated
icon: StandardIcon.Information
title: qsTr("Password set")
text: qsTr("A new password has been set.")
standardButtons: StandardButton.Ok
}
2017-02-03 11:58:17 +03:00
MouseArea {
2017-02-07 12:12:22 +03:00
enabled: device.hasDevice
2017-02-03 18:23:28 +03:00
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: contextMenu.popup()
2017-02-03 11:58:17 +03:00
}
Menu {
id: contextMenu
2017-02-03 18:23:28 +03:00
MenuItem {
text: qsTr('Add...')
onTriggered: addCredential.open()
2017-02-03 11:58:17 +03:00
}
2017-02-03 18:23:28 +03:00
}
2017-02-03 11:58:17 +03:00
Menu {
id: credentialMenu
2017-02-03 18:23:28 +03:00
MenuItem {
text: qsTr('Copy')
2017-02-16 11:55:47 +03:00
shortcut: StandardKey.Copy
onTriggered: {
if (selectedCredential != null) {
clipboard.setClipboard(selectedCredential.code)
}
}
2017-02-03 18:23:28 +03:00
}
MenuItem {
visible: selectedCredential != null
2017-02-16 11:55:47 +03:00
&& (selectedCredential.oath_type === "hotp"
|| selectedCredential.touch === true)
2017-02-03 18:23:28 +03:00
text: qsTr('Generate code')
2017-02-16 11:55:47 +03:00
shortcut: "Space"
2017-02-03 18:23:28 +03:00
onTriggered: calculateCredential(selectedCredential)
}
MenuItem {
text: qsTr('Delete')
2017-02-16 11:55:47 +03:00
shortcut: StandardKey.Delete
2017-02-03 18:23:28 +03:00
onTriggered: confirmDeleteCredential.open()
}
2017-02-03 18:23:28 +03:00
}
2017-02-03 11:58:17 +03:00
2017-02-06 17:00:11 +03:00
ColumnLayout {
2017-02-06 15:42:17 +03:00
anchors.fill: parent
2017-02-06 17:00:11 +03:00
spacing: 0
ProgressBar {
id: progressBar
2017-02-09 14:45:04 +03:00
visible: hasDevice
2017-02-06 17:00:11 +03:00
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.maximumHeight: 10
Layout.minimumHeight: 10
Layout.minimumWidth: 300
Layout.fillWidth: true
maximumValue: 30
minimumValue: 0
style: ProgressBarStyle {
progress: Rectangle {
color: "#9aca3c"
}
2017-02-02 16:16:52 +03:00
2017-02-06 17:00:11 +03:00
background: Rectangle {
color: palette.mid
2017-02-02 16:16:52 +03:00
}
2017-02-06 15:42:17 +03:00
}
2017-02-06 17:00:11 +03:00
}
2017-02-02 16:16:52 +03:00
2017-02-06 17:00:11 +03:00
ScrollView {
id: scrollView
Layout.fillHeight: true
Layout.fillWidth: true
ColumnLayout {
width: scrollView.viewport.width
id: credentialsColumn
spacing: 0
2017-02-07 12:12:22 +03:00
visible: device.hasDevice
2017-02-06 17:07:43 +03:00
anchors.right: appWindow.right
anchors.left: appWindow.left
anchors.top: appWindow.top
2017-02-06 17:00:11 +03:00
Repeater {
2017-02-06 17:57:32 +03:00
id: repeater
model: filteredCredentials(credentials, search.text)
2017-02-06 17:00:11 +03:00
Rectangle {
id: credentialRectangle
color: {
if (selectedCredential != null) {
if (selectedCredential.name == modelData.name) {
return palette.dark
}
}
if (index % 2 == 0) {
return "#00000000"
}
return palette.alternateBase
}
2017-02-06 17:00:11 +03:00
Layout.fillWidth: true
Layout.minimumHeight: 70
Layout.alignment: Qt.AlignTop
MouseArea {
anchors.fill: parent
onClicked: {
if (mouse.button & Qt.LeftButton) {
if (selectedCredential != null
&& selectedCredential.name == modelData.name) {
selectedCredential = null
} else {
selectedCredential = modelData
}
}
if (mouse.button & Qt.RightButton) {
selectedCredential = modelData
credentialMenu.popup()
}
2017-02-06 17:00:11 +03:00
}
acceptedButtons: Qt.RightButton | Qt.LeftButton
2017-02-06 15:42:17 +03:00
}
2017-02-06 17:00:11 +03:00
ColumnLayout {
anchors.leftMargin: 10
spacing: -15
anchors.fill: parent
Text {
visible: hasIssuer(modelData.name)
text: qsTr('') + parseIssuer(modelData.name)
font.pointSize: 13
}
2017-02-15 17:14:34 +03:00
Text {
2017-02-06 17:00:11 +03:00
visible: modelData.code != null
text: qsTr('') + modelData.code
font.family: "Verdana"
font.pointSize: 22
}
Text {
text: hasIssuer(
modelData.name) ? qsTr(
'') + parseName(
modelData.name) : modelData.name
font.pointSize: 13
}
2017-02-06 15:42:17 +03:00
}
2017-02-02 16:16:52 +03:00
}
}
}
}
2017-02-06 17:00:11 +03:00
2017-02-06 17:07:43 +03:00
TextField {
id: search
2017-02-09 14:45:04 +03:00
visible: hasDevice
2017-02-06 17:07:43 +03:00
placeholderText: 'Search...'
Layout.fillWidth: true
2017-02-06 17:00:11 +03:00
}
2017-02-02 16:16:52 +03:00
}
2017-02-06 17:57:32 +03:00
2017-02-02 16:16:52 +03:00
MessageDialog {
id: touchYourYubikey
icon: StandardIcon.Information
title: qsTr("Touch your YubiKey")
text: qsTr("Touch your YubiKey to generate the code.")
standardButtons: StandardButton.NoButton
}
2017-02-03 18:23:28 +03:00
MessageDialog {
id: confirmDeleteCredential
icon: StandardIcon.Warning
title: qsTr("Delete credential?")
text: qsTr("Are you sure you want to delete the credential?")
standardButtons: StandardButton.Ok | StandardButton.Cancel
onAccepted: {
2017-02-07 12:12:22 +03:00
device.deleteCredential(selectedCredential)
device.refreshCredentials()
2017-02-03 18:23:28 +03:00
}
}
// @disable-check M301
YubiKey {
id: yk
onError: {
2017-01-30 16:59:58 +03:00
errorBox.text = traceback
errorBox.open()
}
2017-02-08 18:47:28 +03:00
onWrongPassword: {
2017-02-10 14:44:10 +03:00
passwordPrompt.open()
2017-02-08 18:47:28 +03:00
}
}
Timer {
2017-02-02 16:16:52 +03:00
id: ykTimer
triggeredOnStart: true
interval: 500
repeat: true
running: true
2017-02-07 12:12:22 +03:00
onTriggered: device.refresh()
}
2017-02-02 16:16:52 +03:00
Timer {
id: progressBarTimer
interval: 100
repeat: true
running: true
triggeredOnStart: true
onTriggered: {
var timeLeft = expiration - (Date.now() / 1000)
if (timeLeft <= 0 && progressBar.value > 0) {
2017-02-07 12:12:22 +03:00
device.refresh()
2017-02-02 16:16:52 +03:00
}
progressBar.value = timeLeft
}
}
2017-02-02 16:16:52 +03:00
Text {
2017-02-07 12:12:22 +03:00
visible: !device.hasDevice
2017-02-02 16:16:52 +03:00
id: noLoadedDeviceMessage
2017-02-07 12:12:22 +03:00
text: if (device.nDevices == 0) {
2017-02-02 16:16:52 +03:00
qsTr("No YubiKey detected")
2017-02-07 12:12:22 +03:00
} else if (device.nDevices == 1) {
2017-02-02 16:16:52 +03:00
qsTr("Connecting to YubiKey...")
} else {
qsTr("Multiple YubiKeys detected!")
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
2017-02-02 16:16:52 +03:00
MessageDialog {
id: errorBox
icon: StandardIcon.Critical
title: qsTr("Error!")
text: ""
standardButtons: StandardButton.Ok
}
2017-02-06 17:57:32 +03:00
function filteredCredentials(creds, search) {
var result = []
if (creds != null) {
for (var i = 0; i < creds.length; i++) {
var cred = creds[i]
if (cred.name.toLowerCase().indexOf(search.toLowerCase(
)) !== -1) {
result.push(creds[i])
}
}
}
return result
}
2017-02-02 17:44:49 +03:00
function hasIssuer(name) {
return name.indexOf(':') !== -1
}
function parseName(name) {
return name.split(":").slice(1).join(":")
}
function parseIssuer(name) {
return name.split(":", 1)
}
2017-02-02 16:16:52 +03:00
function calculateCredential(credential) {
2017-02-07 12:12:22 +03:00
device.calculate(credential)
2017-02-02 16:16:52 +03:00
if (credential.touch) {
touchYourYubikey.open()
}
}
function updateExpiration() {
var maxExpiration = 0
if (credentials !== null) {
for (var i = 0; i < credentials.length; i++) {
var exp = credentials[i].expiration
if (exp !== null && exp > maxExpiration) {
maxExpiration = exp
}
}
expiration = maxExpiration
}
}
}