yubioath-flutter/qml/Main.qml

412 lines
12 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
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-16 12:04:35 +03:00
shortcut: StandardKey.New
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: {
2017-02-16 13:07:41 +03:00
if (repeater.selected != null) {
clipboard.setClipboard(repeater.selected.code)
2017-02-16 11:55:47 +03:00
}
}
2017-02-03 18:23:28 +03:00
}
MenuItem {
2017-02-16 13:07:41 +03:00
visible: repeater.selected != null
&& (repeater.selected.oath_type === "hotp"
|| repeater.selected.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-16 13:07:41 +03:00
onTriggered: calculateCredential(repeater.selected)
2017-02-03 18:23:28 +03:00
}
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-16 13:07:41 +03:00
Item {
id: arrowKeys
focus: true
Keys.onUpPressed: {
2017-02-16 13:25:15 +03:00
if (repeater.selectedIndex == null) {
repeater.selected = repeater.model[repeater.model.length - 1]
repeater.selectedIndex = repeater.model.length - 1
} else if (repeater.selectedIndex > 0) {
2017-02-16 13:07:41 +03:00
repeater.selected = repeater.model[repeater.selectedIndex - 1]
repeater.selectedIndex = repeater.selectedIndex - 1
}
}
Keys.onDownPressed: {
2017-02-16 13:25:15 +03:00
if (repeater.selectedIndex == null) {
repeater.selected = repeater.model[0]
repeater.selectedIndex = 0
} else if (repeater.selectedIndex < repeater.model.length - 1) {
2017-02-16 13:07:41 +03:00
repeater.selected = repeater.model[repeater.selectedIndex + 1]
repeater.selectedIndex = repeater.selectedIndex + 1
}
}
}
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-16 13:07:41 +03:00
property var selected
property var selectedIndex
2017-02-06 17:00:11 +03:00
Rectangle {
id: credentialRectangle
2017-02-16 13:07:41 +03:00
focus: true
color: {
2017-02-16 13:07:41 +03:00
if (repeater.selected != null) {
if (repeater.selected.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: {
2017-02-16 13:07:41 +03:00
arrowKeys.forceActiveFocus()
if (mouse.button & Qt.LeftButton) {
2017-02-16 13:07:41 +03:00
if (repeater.selected != null
&& repeater.selected.name == modelData.name) {
repeater.selected = null
repeater.selectedIndex = null
} else {
2017-02-16 13:07:41 +03:00
repeater.selected = modelData
repeater.selectedIndex = index
}
}
if (mouse.button & Qt.RightButton) {
2017-02-16 13:07:41 +03:00
repeater.selected = modelData
repeater.selectedIndex = index
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-16 13:07:41 +03:00
device.deleteCredential(repeater.selected)
2017-02-07 12:12:22 +03:00
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
}
}
}