feat: Handle privilege escalation gracefully even without PolicyKit (#599)

* feat: Handle privilege escalation gracefully even without PolicyKit

* build: upgrade tslint => 5.9.1

* build: add uhk-agent/package-lock.json

* feat: add error animation

* fix: display agent icon when user use ALT + TAB
This commit is contained in:
Róbert Kiss
2018-04-07 23:09:47 +02:00
committed by László Monda
parent 6e1f0ded9e
commit 6ccf005750
22 changed files with 1680 additions and 85 deletions

81
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "uhk-agent",
"version": "1.1.2",
"version": "1.1.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1414,9 +1414,9 @@
"dev": true
},
"commander": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==",
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
"dev": true
},
"compare-func": {
@@ -9428,9 +9428,9 @@
"dev": true
},
"resolve": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
"integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.6.0.tgz",
"integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==",
"dev": true,
"requires": {
"path-parse": "1.0.5"
@@ -10843,29 +10843,51 @@
}
},
"tslib": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz",
"integrity": "sha1-vIAEFkaRkjp5/oN4u+s9ogF1OOw=",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz",
"integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==",
"dev": true
},
"tslint": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.5.0.tgz",
"integrity": "sha1-EOjas+MGH6YelELozuOYKs8gpqo=",
"version": "5.9.1",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz",
"integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=",
"dev": true,
"requires": {
"babel-code-frame": "6.26.0",
"colors": "1.1.2",
"commander": "2.11.0",
"builtin-modules": "1.1.1",
"chalk": "2.3.2",
"commander": "2.15.1",
"diff": "3.4.0",
"glob": "7.1.2",
"js-yaml": "3.10.0",
"minimatch": "3.0.4",
"resolve": "1.4.0",
"resolve": "1.6.0",
"semver": "5.4.1",
"tslib": "1.7.1",
"tsutils": "2.12.0"
"tslib": "1.9.0",
"tsutils": "2.26.1"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"chalk": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.3.0"
}
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
@@ -10879,16 +10901,31 @@
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"supports-color": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
}
}
}
},
"tsutils": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.12.0.tgz",
"integrity": "sha1-yJKoTI8vjeE/jvMsLFw4tFfM6sY=",
"version": "2.26.1",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.26.1.tgz",
"integrity": "sha512-bnm9bcjOqOr1UljleL94wVCDlpa6KjfGaTkefeLch4GRafgDkROxPizbB/FxTEdI++5JqhxczRy/Qub0syNqZA==",
"dev": true,
"requires": {
"tslib": "1.7.1"
"tslib": "1.9.0"
}
},
"tty-browserify": {

View File

@@ -60,7 +60,7 @@
"svg-sprite": "1.3.7",
"ts-loader": "2.3.1",
"ts-node": "3.0.4",
"tslint": "5.5.0",
"tslint": "5.9.1",
"typescript": "2.5.2",
"webpack": "2.4.1"
},
@@ -76,11 +76,11 @@
"test:uhk-web": "lerna exec --scope uhk-web npm test",
"lint": "run-s -scn lint:ts lint:style",
"lint:ts": "run-p -sn lint:ts:electron-main lint:ts:electron-renderer lint:ts:web lint:ts:test-serializer lint:ts:uhk-usb",
"lint:ts:electron-main": "tslint --type-check --project ./packages/uhk-agent/tsconfig.json",
"lint:ts:electron-renderer": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.renderer.json",
"lint:ts:web": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.app.json",
"lint:ts:test-serializer": "tslint --type-check --project ./packages/test-serializer/tsconfig.json",
"lint:ts:uhk-usb": "tslint --type-check --project ./packages/uhk-usb/tsconfig.json",
"lint:ts:electron-main": "tslint --project ./packages/uhk-agent/tsconfig.json",
"lint:ts:electron-renderer": "tslint --project ./packages/uhk-web/src/tsconfig.renderer.json",
"lint:ts:web": "tslint --project ./packages/uhk-web/src/tsconfig.app.json",
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
"lint:ts:uhk-usb": "tslint --project ./packages/uhk-usb/tsconfig.json",
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
"build": "run-s build:common build:usb build:web build:electron",
"build:web": "lerna exec --scope uhk-web npm run build",
@@ -92,6 +92,7 @@
"server:web": "lerna exec --scope uhk-web npm start",
"server:electron": "lerna exec --scope uhk-web npm run server:renderer",
"electron": "lerna exec --scope uhk-agent npm start",
"electron:spe": "lerna exec --scope uhk-agent npm run electron:spe",
"standard-version": "standard-version",
"pack": "node ./scripts/release.js",
"sprites": "node ./scripts/generate-svg-sprites",

1309
packages/uhk-agent/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,7 @@
},
"scripts": {
"start": "electron ./dist/electron-main.js",
"electron:spe": "electron ./dist/electron-main.js --spe",
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
"install:build-deps": "cd ./dist && npm i",

View File

@@ -2,7 +2,7 @@
/// <reference path="./custom_types/command-line-args.d.ts"/>
import './polyfills';
import { app, BrowserWindow, ipcMain } from 'electron';
import { app, BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater';
import * as path from 'path';
@@ -10,7 +10,7 @@ import * as url from 'url';
import * as commandLineArgs from 'command-line-args';
import { UhkHidDevice, UhkOperations } from 'uhk-usb';
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
import { LogRegExps } from 'uhk-common';
import { CommandLineArgs, LogRegExps } from 'uhk-common';
import { DeviceService } from './services/device.service';
import { logger } from './services/logger.service';
import { AppUpdateService } from './services/app-update.service';
@@ -18,13 +18,13 @@ import { AppService } from './services/app.service';
import { SudoService } from './services/sudo.service';
import { UhkBlhost } from '../../uhk-usb/src';
import * as isDev from 'electron-is-dev';
import { CommandLineInputs } from './models/command-line-inputs';
const optionDefinitions = [
{name: 'addons', type: Boolean}
{name: 'addons', type: Boolean},
{name: 'spe', type: Boolean} // simulate privilege escalation error
];
const options: CommandLineInputs = commandLineArgs(optionDefinitions);
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
// import './dev-extension';
// require('electron-debug')({ showDevTools: true, enabled: true });
@@ -83,13 +83,13 @@ function createWindow() {
});
win.setMenuBarVisibility(false);
win.maximize();
uhkHidDeviceService = new UhkHidDevice(logger);
uhkHidDeviceService = new UhkHidDevice(logger, options);
uhkBlhost = new UhkBlhost(logger, packagesDir);
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
appUpdateService = new AppUpdateService(logger, win, app);
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
sudoService = new SudoService(logger);
sudoService = new SudoService(logger, options);
// and load the index.html of the app.
win.loadURL(url.format({

View File

@@ -1,3 +1,10 @@
export interface CommandLineInputs {
/**
* addons menu visible or not
*/
addons?: boolean;
/**
* simulate privilege escalation error
*/
spe?: boolean;
}

View File

@@ -5,12 +5,13 @@ import * as sudo from 'sudo-prompt';
import { dirSync } from 'tmp';
import { emptyDir, copy } from 'fs-extra';
import { IpcEvents, LogService, IpcResponse } from 'uhk-common';
import { CommandLineArgs, IpcEvents, LogService, IpcResponse } from 'uhk-common';
export class SudoService {
private rootDir: string;
constructor(private logService: LogService) {
constructor(private logService: LogService,
private options: CommandLineArgs) {
if (isDev) {
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '../../../../');
} else {
@@ -21,6 +22,19 @@ export class SudoService {
}
private async setPrivilege(event: Electron.Event) {
if (this.options.spe) {
const error = new Error('No polkit authentication agent found.');
this.logService.error('[SudoService] Simulate privilege escalation error ', error);
const response = new IpcResponse();
response.success = false;
response.error = {message: error.message};
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
return;
}
switch (process.platform) {
case 'linux':
await this.setPrivilegeOnLinux(event);
@@ -55,7 +69,7 @@ export class SudoService {
if (error) {
this.logService.error('[SudoService] Error when set privilege: ', error);
response.success = false;
response.error = error;
response.error = {message: error.message};
} else {
response.success = true;
}

View File

@@ -1,3 +1,10 @@
export interface CommandLineArgs {
addons: boolean;
/**
* addons menu visible or not
*/
addons?: boolean;
/**
* simulate privilege escalation error
*/
spe?: boolean;
}

View File

@@ -1,5 +1,5 @@
import { Device, devices, HID } from 'node-hid';
import { LogService } from 'uhk-common';
import { CommandLineArgs, LogService } from 'uhk-common';
import {
ConfigBufferId,
@@ -27,7 +27,8 @@ export class UhkHidDevice {
private _device: HID;
private _hasPermission = false;
constructor(private logService: LogService) {
constructor(private logService: LogService,
private options: CommandLineArgs) {
}
/**
@@ -38,6 +39,10 @@ export class UhkHidDevice {
* @returns {boolean}
*/
public hasPermission(): boolean {
if (this.options.spe) {
return false;
}
try {
if (this._hasPermission) {
return true;

View File

@@ -6167,6 +6167,19 @@
"deep-freeze-strict": "1.1.1"
}
},
"ngx-clipboard": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-8.0.0.tgz",
"integrity": "sha1-EJjC3G/oyAmJo1OUvlFvvwvJR3c=",
"requires": {
"ngx-window-token": "0.0.2"
}
},
"ngx-window-token": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-0.0.2.tgz",
"integrity": "sha1-aA7phrvm+V0H2q3xVMDs815YmSA="
},
"no-case": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",

View File

@@ -72,6 +72,7 @@
"ng2-dragula": "1.5.0",
"ng2-nouislider": "^1.7.6",
"ng2-select2": "1.0.0-beta.10",
"ngx-clipboard": "8.0.0",
"ngrx-store-freeze": "0.1.9",
"node-hid": "0.5.4",
"nouislider": "^10.1.0",

View File

@@ -1,4 +1,39 @@
<span class="privilege-checker-wrapper">
<uhk-message header="Cannot talk to your UHK" subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
<button class="btn btn-default btn-lg btn-primary" (click)="setUpPermissions()"> Set up permissions </button>
</span>
<div class="privilege-checker-wrapper">
<uhk-message header="Cannot talk to your UHK"
subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
<button class="btn btn-default btn-lg btn-primary"
(click)="setUpPermissions()"> Set up permissions
</button>
<div class="mt-10">
<a class="link-inline"
*ngIf="state.showWhatWillThisDo"
(click)="whatWillThisDo()">What will this do?
</a>
<div>
<p class="privilege-error"
#privilegeError
*ngIf="state.permissionSetupFailed">
Agent wasn't able to set up permissions via PolicyKit. This is most likely because the
<code>polkit</code> package is not installed on your system.
</p>
<div *ngIf="state.showWhatWillThisDoContent">
Agent uses the following script to set up permissions. You can run it manually as root, then
<a class="link-inline"
(click)="retry()">retry</a>.
<div class="copy-container">
<span class="fa fa-2x fa-copy"
ngxClipboard
[cbContent]="command"
title="Copy to clipboard"
data-toggle="tooltip"
data-placement="top"></span>
<pre><code>{{ command }}</code></pre>
</div>
</div>
</div>
</div>
</div>

View File

@@ -9,3 +9,19 @@
uhk-message {
max-width: 50%;
}
.privilege-error {
animation: error-fade-in 2s;
}
@keyframes error-fade-in {
0% {
color: white;
background-color: red;
}
100% {
color: inherit;
background-color: inherit;
}
}

View File

@@ -1,26 +1,61 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/ignoreElements';
import 'rxjs/add/operator/takeWhile';
import { Subscription } from 'rxjs/Subscription';
import { AppState } from '../../store/index';
import { AppState, getPrivilegePageState } from '../../store';
import { SetPrivilegeOnLinuxAction } from '../../store/actions/device';
import { LoadAppStartInfoAction, PrivilegeWhatWillThisDoAction } from '../../store/actions/app';
import { PrivilagePageSate } from '../../models/privilage-page-sate';
@Component({
selector: 'privilege-checker',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './privilege-checker.component.html',
styleUrls: ['./privilege-checker.component.scss']
})
export class PrivilegeCheckerComponent {
constructor(protected store: Store<AppState>) {
export class PrivilegeCheckerComponent implements OnInit, OnDestroy {
state: PrivilagePageSate;
command = `cat <<EOF >/etc/udev/rules.d/50-uhk60.rules
# Ultimate Hacking Keyboard rules
# These are the udev rules for accessing the USB interfaces of the UHK as non-root users.
# Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards.
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666"
EOF
udevadm trigger
udevadm settle`;
private stateSubscription: Subscription;
constructor(private store: Store<AppState>,
private cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.stateSubscription = this.store.select(getPrivilegePageState)
.subscribe(state => {
this.state = state;
this.cdRef.markForCheck();
});
}
ngOnDestroy(): void {
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
}
setUpPermissions(): void {
this.store.dispatch(new SetPrivilegeOnLinuxAction());
}
whatWillThisDo(): void {
this.store.dispatch(new PrivilegeWhatWillThisDoAction());
}
retry(): void {
this.store.dispatch(new LoadAppStartInfoAction());
}
}

View File

@@ -0,0 +1,5 @@
export interface PrivilagePageSate {
showWhatWillThisDo: boolean;
showWhatWillThisDoContent: boolean;
permissionSetupFailed: boolean;
}

View File

@@ -8,6 +8,7 @@ import { ConfirmationPopoverModule } from 'angular-confirmation-popover';
import { DragulaModule } from 'ng2-dragula/ng2-dragula';
import { Select2Module } from 'ng2-select2/ng2-select2';
import { NouisliderModule } from 'ng2-nouislider';
import { ClipboardModule } from 'ngx-clipboard';
import { AddOnComponent } from './components/add-on';
import { KeyboardSliderComponent } from './components/keyboard/slider';
@@ -186,7 +187,8 @@ import { Autofocus } from './directives/autofocus/autofocus.directive';
NotifierModule.withConfig(angularNotifierConfig),
ConfirmationPopoverModule.forRoot({
confirmButtonType: 'danger' // set defaults here
})
}),
ClipboardModule
],
providers: [
SvgModuleProviderService,

View File

@@ -17,7 +17,10 @@ export const ActionTypes = {
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'),
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received'),
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window')
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window'),
PRIVILEGE_WHAT_WILL_THIS_DO: type(PREFIX + 'What will this do clicked'),
SETUP_PERMISSION_ERROR: type(PREFIX + 'Setup permission error'),
LOAD_APP_START_INFO: type(PREFIX + 'Load app start info')
};
export class AppBootsrappedAction implements Action {
@@ -31,25 +34,29 @@ export class AppStartedAction implements Action {
export class ShowNotificationAction implements Action {
type = ActionTypes.APP_SHOW_NOTIFICATION;
constructor(public payload: Notification) { }
constructor(public payload: Notification) {
}
}
export class ApplyCommandLineArgsAction implements Action {
type = ActionTypes.APPLY_COMMAND_LINE_ARGS;
constructor(public payload: CommandLineArgs) { }
constructor(public payload: CommandLineArgs) {
}
}
export class ProcessAppStartInfoAction implements Action {
type = ActionTypes.APP_PROCESS_START_INFO;
constructor(public payload: AppStartInfo) { }
constructor(public payload: AppStartInfo) {
}
}
export class UndoLastAction implements Action {
type = ActionTypes.UNDO_LAST;
constructor(public payload: any) {}
constructor(public payload: any) {
}
}
export class UndoLastSuccessAction implements Action {
@@ -63,19 +70,37 @@ export class DismissUndoNotificationAction implements Action {
export class LoadHardwareConfigurationSuccessAction implements Action {
type = ActionTypes.LOAD_HARDWARE_CONFIGURATION_SUCCESS;
constructor(public payload: HardwareConfiguration) {}
constructor(public payload: HardwareConfiguration) {
}
}
export class ElectronMainLogReceivedAction implements Action {
type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED;
constructor(public payload: ElectronLogEntry) {}
constructor(public payload: ElectronLogEntry) {
}
}
export class OpenUrlInNewWindowAction implements Action {
type = ActionTypes.OPEN_URL_IN_NEW_WINDOW;
constructor(public payload: string) {}
constructor(public payload: string) {
}
}
export class PrivilegeWhatWillThisDoAction implements Action {
type = ActionTypes.PRIVILEGE_WHAT_WILL_THIS_DO;
}
export class SetupPermissionErrorAction implements Action {
type = ActionTypes.SETUP_PERMISSION_ERROR;
constructor(public payload: string) {
}
}
export class LoadAppStartInfoAction implements Action {
type = ActionTypes.LOAD_APP_START_INFO;
}
export type Actions
@@ -90,4 +115,7 @@ export type Actions
| LoadHardwareConfigurationSuccessAction
| ElectronMainLogReceivedAction
| OpenUrlInNewWindowAction
| PrivilegeWhatWillThisDoAction
| SetupPermissionErrorAction
| LoadAppStartInfoAction
;

View File

@@ -40,6 +40,13 @@ export class ApplicationEffects {
this.logService.info('Renderer appStart effect end');
});
@Effect({dispatch: false})
appStartInfo$: Observable<Action> = this.actions$
.ofType(ActionTypes.LOAD_APP_START_INFO)
.do(() => {
this.appRendererService.getAppStartInfo();
});
@Effect({dispatch: false})
showNotification$: Observable<Action> = this.actions$
.ofType<ShowNotificationAction>(ActionTypes.APP_SHOW_NOTIFICATION)

View File

@@ -31,7 +31,7 @@ import {
UpdateFirmwareWithAction
} from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service';
import { ShowNotificationAction } from '../actions/app';
import { SetupPermissionErrorAction, ShowNotificationAction } from '../actions/app';
import { AppState } from '../index';
import {
ActionTypes as UserConfigActions,
@@ -78,21 +78,15 @@ export class DeviceEffects {
setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$
.ofType<SetPrivilegeOnLinuxReplyAction>(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY)
.map(action => action.payload)
.mergeMap((response: any) => {
.map((response: any): any => {
if (response.success) {
return [
new ConnectionStateChangedAction({
return new ConnectionStateChangedAction({
connected: true,
hasPermission: true
})
];
});
}
return [
<any>new ShowNotificationAction({
type: NotificationType.Error,
message: response.error.message || response.error
})
];
return new SetupPermissionErrorAction(response.error);
});
@Effect({dispatch: false})

View File

@@ -51,6 +51,7 @@ export const getHardwareConfiguration = createSelector(appState, fromApp.getHard
export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLayout);
export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded);
export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo);
export const getPrivilegePageState = createSelector(appState, fromApp.getPrivilagePageState);
export const appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);

View File

@@ -1,13 +1,20 @@
import { ROUTER_NAVIGATION } from '@ngrx/router-store';
import { Action } from '@ngrx/store';
import { VersionInformation } from 'uhk-common';
import {
HardwareConfiguration,
Notification,
NotificationType,
runInElectron,
UserConfiguration,
VersionInformation
} from 'uhk-common';
import { HardwareConfiguration, Notification, NotificationType, runInElectron, UserConfiguration } from 'uhk-common';
import { ActionTypes, ShowNotificationAction } from '../actions/app';
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
import { ActionTypes as DeviceActionTypes } from '../actions/device';
import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum';
import { getVersions } from '../../util';
import { PrivilagePageSate } from '../../models/privilage-page-sate';
export interface State {
started: boolean;
@@ -19,6 +26,8 @@ export interface State {
configLoading: boolean;
hardwareConfig?: HardwareConfiguration;
agentVersionInfo?: VersionInformation;
privilegeWhatWillThisDoClicked: boolean;
permissionError?: any;
}
export const initialState: State = {
@@ -27,7 +36,8 @@ export const initialState: State = {
navigationCountAfterNotification: 0,
runningInElectron: runInElectron(),
configLoading: true,
agentVersionInfo: getVersions()
agentVersionInfo: getVersions(),
privilegeWhatWillThisDoClicked: false
};
export function reducer(state = initialState, action: Action & { payload: any }) {
@@ -115,6 +125,24 @@ export function reducer(state = initialState, action: Action & { payload: any })
};
}
case ActionTypes.PRIVILEGE_WHAT_WILL_THIS_DO:
return {
...state,
privilegeWhatWillThisDoClicked: true
};
case ActionTypes.SETUP_PERMISSION_ERROR:
return {
...state,
permissionError: action.payload
};
case DeviceActionTypes.SET_PRIVILEGE_ON_LINUX:
return {
...state,
permissionError: null
};
default:
return state;
}
@@ -134,3 +162,12 @@ export const getKeyboardLayout = (state: State): KeyboardLayout => {
};
export const deviceConfigurationLoaded = (state: State) => !state.runningInElectron ? true : !!state.hardwareConfig;
export const getAgentVersionInfo = (state: State) => state.agentVersionInfo || {} as VersionInformation;
export const getPrivilagePageState = (state: State): PrivilagePageSate => {
const permissionSetupFailed = !!state.permissionError;
return {
permissionSetupFailed,
showWhatWillThisDo: !state.privilegeWhatWillThisDoClicked && !permissionSetupFailed,
showWhatWillThisDoContent: state.privilegeWhatWillThisDoClicked || permissionSetupFailed
};
};

View File

@@ -115,3 +115,43 @@ a.disabled {
display: block;
}
}
a.link-inline {
cursor: pointer;
}
@mixin code-style() {
color: #6a737d;
background-color: #f6f8fa;
text-align: left;
}
code {
@include code-style();
}
pre {
code {
@include code-style();
}
}
.mt-10 {
margin-top: 10px;
}
.copy-container {
position: relative;
.fa-copy {
cursor: pointer;
color: #6a737d;
position: absolute;
right: 4px;
top: 4px;
&:hover {
color: darken(#6a737d, 15);
}
}
}