admins can now delete devices

pull/69/head
James Batt 4 years ago
parent a569920d28
commit ddd030864c

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [next]
### Added
- Admin users can now delete devices from the "all devices" page (issue [#57](https://github.com/Place1/wg-access-server/issues/57))
### Bug Fixes
- Fixes website routing to solve 404s (issue [#56](https://github.com/Place1/wg-access-server/issues/56))

@ -56,7 +56,17 @@ func (d *DeviceService) DeleteDevice(ctx context.Context, req *proto.DeleteDevic
return nil, status.Errorf(codes.PermissionDenied, "not authenticated")
}
if err := d.DeviceManager.DeleteDevice(user.Subject, req.GetName()); err != nil {
deviceOwner := user.Subject
if req.Owner != nil {
if user.Claims.Contains("admin") {
deviceOwner = req.Owner.Value
} else {
return nil, status.Errorf(codes.PermissionDenied, "must be an admin")
}
}
if err := d.DeviceManager.DeleteDevice(deviceOwner, req.GetName()); err != nil {
ctxlogrus.Extract(ctx).Error(err)
return nil, status.Errorf(codes.Internal, "failed to delete device")
}

@ -2,6 +2,7 @@ syntax = "proto3";
package proto;
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
@ -45,6 +46,11 @@ message ListDevicesRes {
message DeleteDeviceReq {
string name = 1;
// admin's may delete a device owned
// by someone other than the current user
// if empty, defaults to the current user
google.protobuf.StringValue owner = 2;
}
message ListAllDevicesReq {

@ -9,6 +9,7 @@ import (
proto "github.com/golang/protobuf/proto"
empty "github.com/golang/protobuf/ptypes/empty"
timestamp "github.com/golang/protobuf/ptypes/timestamp"
wrappers "github.com/golang/protobuf/ptypes/wrappers"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
@ -279,10 +280,14 @@ func (m *ListDevicesRes) GetItems() []*Device {
}
type DeleteDeviceReq struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// admin's may delete a device owned
// by someone other than the current user
// if empty, defaults to the current user
Owner *wrappers.StringValue `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *DeleteDeviceReq) Reset() { *m = DeleteDeviceReq{} }
@ -317,6 +322,13 @@ func (m *DeleteDeviceReq) GetName() string {
return ""
}
func (m *DeleteDeviceReq) GetOwner() *wrappers.StringValue {
if m != nil {
return m.Owner
}
return nil
}
type ListAllDevicesReq struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@ -400,40 +412,42 @@ func init() {
func init() { proto.RegisterFile("devices.proto", fileDescriptor_6d27ec3f2c0e2043) }
var fileDescriptor_6d27ec3f2c0e2043 = []byte{
// 513 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x53, 0xdb, 0x6e, 0xd3, 0x40,
0x10, 0x8d, 0x93, 0xe6, 0xe2, 0x49, 0x5c, 0xe8, 0x04, 0xaa, 0x95, 0x01, 0x35, 0x72, 0x55, 0x29,
0x4f, 0xa9, 0x08, 0x42, 0x82, 0x07, 0x24, 0x82, 0x5a, 0x84, 0x00, 0x21, 0x64, 0xf1, 0x6e, 0x39,
0xde, 0xa1, 0x5d, 0xd5, 0xb7, 0x7a, 0xb7, 0x41, 0xf9, 0x00, 0xbe, 0x93, 0x5f, 0x41, 0xbb, 0xeb,
0xa4, 0x49, 0x5a, 0x2e, 0x4f, 0xf6, 0x9c, 0x73, 0xe6, 0x76, 0xc6, 0x06, 0x8f, 0xd3, 0x42, 0x24,
0x24, 0x27, 0x65, 0x55, 0xa8, 0x02, 0xdb, 0xe6, 0xe1, 0x1f, 0x5d, 0x14, 0xc5, 0x45, 0x4a, 0xa7,
0x26, 0x9a, 0xdf, 0x7c, 0x3f, 0x55, 0x22, 0x23, 0xa9, 0xe2, 0xac, 0xb4, 0x3a, 0xff, 0xc9, 0xae,
0x80, 0xb2, 0x52, 0x2d, 0x2d, 0x19, 0xfc, 0x6a, 0x41, 0xe7, 0xcc, 0x94, 0x45, 0x84, 0xbd, 0x3c,
0xce, 0x88, 0x39, 0x23, 0x67, 0xec, 0x86, 0xe6, 0x1d, 0x1f, 0x41, 0xbb, 0xf8, 0x91, 0x53, 0xc5,
0x9a, 0x06, 0xb4, 0x01, 0x3e, 0x03, 0x28, 0x6f, 0xe6, 0xa9, 0x48, 0xa2, 0x2b, 0x5a, 0xb2, 0x96,
0xa1, 0x5c, 0x8b, 0x7c, 0xa2, 0x25, 0x32, 0xe8, 0xc6, 0x9c, 0x57, 0x24, 0x25, 0xdb, 0x33, 0xdc,
0x2a, 0xc4, 0xd7, 0x00, 0x49, 0x45, 0xb1, 0x22, 0x1e, 0xc5, 0x8a, 0xb5, 0x47, 0xce, 0xb8, 0x3f,
0xf5, 0x27, 0x76, 0xbe, 0xc9, 0x6a, 0xbe, 0xc9, 0xb7, 0xd5, 0x02, 0xa1, 0x5b, 0xab, 0x67, 0x0a,
0x9f, 0x82, 0x9b, 0x14, 0x79, 0x4e, 0x89, 0x22, 0xce, 0x3a, 0x23, 0x67, 0xdc, 0x0b, 0x6f, 0x01,
0xfc, 0x08, 0xc3, 0x34, 0x96, 0x2a, 0xba, 0x8c, 0x73, 0x2e, 0x2f, 0xe3, 0x2b, 0x8a, 0xb4, 0x0b,
0xac, 0xfb, 0xcf, 0x0e, 0x07, 0x3a, 0xed, 0xc3, 0x2a, 0x4b, 0xe3, 0x78, 0x0c, 0x5e, 0x45, 0x09,
0x89, 0x05, 0x45, 0xf3, 0xa5, 0x22, 0xc9, 0x7a, 0x23, 0x67, 0xdc, 0x0a, 0x07, 0x35, 0xf8, 0x4e,
0x63, 0x78, 0x02, 0xfb, 0xaa, 0x8a, 0x73, 0x99, 0x09, 0x55, 0xab, 0x5c, 0xa3, 0xf2, 0x56, 0xa8,
0x95, 0xf9, 0xd0, 0xa3, 0x9c, 0x97, 0x85, 0xc8, 0x15, 0x03, 0xe3, 0xc5, 0x3a, 0xd6, 0x2e, 0x1a,
0x3b, 0x23, 0xe3, 0x7a, 0xdf, 0xba, 0x68, 0x90, 0x2f, 0xda, 0xfa, 0x23, 0xe8, 0x5b, 0x9a, 0xb2,
0x58, 0xa4, 0x6c, 0x60, 0x78, 0x9b, 0x71, 0xae, 0x11, 0x3d, 0x82, 0x15, 0x94, 0x55, 0xb1, 0x10,
0x9c, 0x2a, 0xe6, 0x19, 0x8d, 0x67, 0xd0, 0xaf, 0x35, 0x18, 0xcc, 0x60, 0x30, 0xe3, 0xdc, 0xde,
0x38, 0xa4, 0xeb, 0x7b, 0xcf, 0xbc, 0x7d, 0xd0, 0xe6, 0xce, 0x41, 0x83, 0x87, 0xb0, 0xff, 0x59,
0x48, 0x65, 0x6b, 0xc8, 0x90, 0xae, 0x83, 0x97, 0x3b, 0x88, 0xc4, 0x63, 0x68, 0x0b, 0x45, 0x99,
0x64, 0xce, 0xa8, 0x35, 0xee, 0x4f, 0x3d, 0x6b, 0xf6, 0xa4, 0xee, 0x6b, 0xb9, 0xe0, 0x04, 0x1e,
0x9c, 0x51, 0x4a, 0x8a, 0xfe, 0x3a, 0x4e, 0x30, 0x84, 0x03, 0x5d, 0x7d, 0x96, 0xa6, 0x1b, 0x2d,
0x5f, 0xdd, 0x05, 0xff, 0xaf, 0xeb, 0xf4, 0x67, 0x13, 0xba, 0x75, 0x0e, 0x3e, 0x07, 0x77, 0xed,
0x06, 0x0e, 0x6b, 0xf9, 0xa6, 0x3f, 0xfe, 0x76, 0x8d, 0xa0, 0x81, 0x6f, 0xa0, 0xbf, 0xb1, 0x2b,
0x3e, 0xae, 0xf9, 0x6d, 0x47, 0xfc, 0x7b, 0x61, 0x19, 0x34, 0xf0, 0x2d, 0x0c, 0x36, 0x77, 0xc6,
0xc3, 0x75, 0xfd, 0x2d, 0x23, 0xfc, 0xc3, 0x3b, 0x5f, 0xe9, 0xb9, 0xfe, 0x4f, 0x83, 0x06, 0xbe,
0xb7, 0x66, 0xdf, 0x6e, 0x8e, 0x6c, 0xa3, 0xd9, 0x96, 0x4b, 0xfe, 0x9f, 0x18, 0x19, 0x34, 0xe6,
0x1d, 0x43, 0xbd, 0xf8, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x89, 0x1c, 0x7b, 0x9d, 0x48, 0x04, 0x00,
0x00,
// 548 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0xdb, 0x6e, 0xd3, 0x4e,
0x10, 0xc6, 0xe3, 0xa6, 0x39, 0x78, 0x1c, 0xf7, 0xff, 0xef, 0x06, 0xaa, 0x95, 0x29, 0x34, 0x72,
0x85, 0x94, 0xab, 0x54, 0x04, 0x21, 0xc1, 0x05, 0x12, 0x41, 0x2d, 0x42, 0x80, 0x10, 0x32, 0x08,
0x89, 0x2b, 0x6b, 0x63, 0x0f, 0xa9, 0x55, 0x9f, 0xba, 0xbb, 0x49, 0x95, 0x07, 0xe0, 0x39, 0x79,
0x15, 0xe4, 0xdd, 0x4d, 0xea, 0x24, 0xe5, 0x70, 0x95, 0xec, 0xf7, 0x7d, 0xb3, 0x33, 0xfe, 0x8d,
0x0d, 0x6e, 0x8c, 0x8b, 0x24, 0x42, 0x31, 0x2a, 0x79, 0x21, 0x0b, 0xd2, 0x52, 0x3f, 0xde, 0xa3,
0x59, 0x51, 0xcc, 0x52, 0x3c, 0x53, 0xa7, 0xe9, 0xfc, 0xfb, 0xd9, 0x0d, 0x67, 0x65, 0x89, 0xdc,
0xc4, 0xbc, 0x93, 0x6d, 0x5f, 0x26, 0x19, 0x0a, 0xc9, 0xb2, 0xd2, 0x04, 0x1e, 0x6c, 0x07, 0x30,
0x2b, 0xe5, 0x52, 0x9b, 0xfe, 0xcf, 0x26, 0xb4, 0xcf, 0x55, 0x5b, 0x42, 0x60, 0x3f, 0x67, 0x19,
0x52, 0x6b, 0x60, 0x0d, 0xed, 0x40, 0xfd, 0x27, 0xf7, 0xa0, 0x55, 0xdc, 0xe4, 0xc8, 0xe9, 0x9e,
0x12, 0xf5, 0x81, 0x3c, 0x04, 0x28, 0xe7, 0xd3, 0x34, 0x89, 0xc2, 0x2b, 0x5c, 0xd2, 0xa6, 0xb2,
0x6c, 0xad, 0xbc, 0xc7, 0x25, 0xa1, 0xd0, 0x61, 0x71, 0xcc, 0x51, 0x08, 0xba, 0xaf, 0xbc, 0xd5,
0x91, 0xbc, 0x00, 0x88, 0x38, 0x32, 0x89, 0x71, 0xc8, 0x24, 0x6d, 0x0d, 0xac, 0xa1, 0x33, 0xf6,
0x46, 0x7a, 0xbe, 0xd1, 0x6a, 0xbe, 0xd1, 0x97, 0xd5, 0x03, 0x04, 0xb6, 0x49, 0x4f, 0x24, 0x39,
0x06, 0x3b, 0x2a, 0xf2, 0x1c, 0x23, 0x89, 0x31, 0x6d, 0x0f, 0xac, 0x61, 0x37, 0xb8, 0x15, 0xc8,
0x3b, 0xe8, 0xa7, 0x4c, 0xc8, 0xf0, 0x92, 0xe5, 0xb1, 0xb8, 0x64, 0x57, 0x18, 0x56, 0x14, 0x68,
0xe7, 0xaf, 0x1d, 0x0e, 0xab, 0xb2, 0xb7, 0xab, 0xaa, 0x4a, 0x27, 0xa7, 0xe0, 0x72, 0x8c, 0x30,
0x59, 0x60, 0x38, 0x5d, 0x4a, 0x14, 0xb4, 0x3b, 0xb0, 0x86, 0xcd, 0xa0, 0x67, 0xc4, 0xd7, 0x95,
0x46, 0x1e, 0xc3, 0x81, 0xe4, 0x2c, 0x17, 0x59, 0x22, 0x4d, 0xca, 0x56, 0x29, 0x77, 0xa5, 0xea,
0x98, 0x07, 0x5d, 0xcc, 0xe3, 0xb2, 0x48, 0x72, 0x49, 0x41, 0xb1, 0x58, 0x9f, 0x2b, 0x8a, 0x0a,
0x67, 0xa8, 0xa8, 0x3b, 0x9a, 0xa2, 0x52, 0x3e, 0x56, 0xe8, 0x4f, 0xc0, 0xd1, 0x36, 0x66, 0x2c,
0x49, 0x69, 0x4f, 0xf9, 0xba, 0xe2, 0xa2, 0x52, 0xaa, 0x11, 0x74, 0xa0, 0xe4, 0xc5, 0x22, 0x89,
0x91, 0x53, 0x57, 0x65, 0x5c, 0xa5, 0x7e, 0x32, 0xa2, 0x3f, 0x81, 0xde, 0x24, 0x8e, 0xf5, 0x8e,
0x03, 0xbc, 0xbe, 0x73, 0xcd, 0x9b, 0x0b, 0xdd, 0xdb, 0x5a, 0xa8, 0xff, 0x3f, 0x1c, 0x7c, 0x48,
0x84, 0xd4, 0x77, 0x88, 0x00, 0xaf, 0xfd, 0x67, 0x5b, 0x8a, 0x20, 0xa7, 0xd0, 0x4a, 0x24, 0x66,
0x82, 0x5a, 0x83, 0xe6, 0xd0, 0x19, 0xbb, 0x1a, 0xf6, 0xc8, 0xf4, 0xd5, 0x9e, 0xff, 0x0d, 0xfe,
0x3b, 0xc7, 0x14, 0x25, 0xfe, 0x79, 0x9c, 0x71, 0xfd, 0xad, 0x73, 0xc6, 0xc7, 0x3b, 0xfb, 0xfb,
0x2c, 0x79, 0x92, 0xcf, 0xbe, 0xb2, 0x74, 0x8e, 0xe6, 0x9d, 0xf4, 0xfb, 0x70, 0x58, 0x4d, 0x34,
0x49, 0xd3, 0xda, 0x98, 0xcf, 0x77, 0xc5, 0x7f, 0x9b, 0x74, 0xfc, 0x63, 0x0f, 0x3a, 0xa6, 0x86,
0x3c, 0x01, 0x7b, 0x4d, 0x90, 0xf4, 0x4d, 0xbc, 0xce, 0xd4, 0xdb, 0xbc, 0xc3, 0x6f, 0x90, 0x97,
0xe0, 0xd4, 0xf8, 0x90, 0xfb, 0xc6, 0xdf, 0xa4, 0xe8, 0xdd, 0x29, 0x0b, 0xbf, 0x41, 0x5e, 0x41,
0xaf, 0xce, 0x89, 0x1c, 0xad, 0xef, 0xdf, 0x80, 0xe7, 0x1d, 0xed, 0x90, 0xb9, 0xa8, 0xbe, 0x6d,
0xbf, 0x41, 0xde, 0xe8, 0x05, 0xdd, 0x3e, 0x39, 0xa1, 0xb5, 0x66, 0x1b, 0x94, 0xbc, 0xdf, 0x39,
0xc2, 0x6f, 0x4c, 0xdb, 0xca, 0x7a, 0xfa, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x66, 0x8c, 0x07, 0x7b,
0x9c, 0x04, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.

@ -7,7 +7,7 @@ const backend = window.location.origin + '/api';
export const grpc = {
server: new Server(backend),
devices: new Devices(backend),
}
};
// https://github.com/SafetyCulture/grpc-web-devtools
const devtools = (window as any).__GRPCWEB_DEVTOOLS__;

@ -2,11 +2,7 @@ import React from 'react';
import CssBaseline from '@material-ui/core/CssBaseline';
import Box from '@material-ui/core/Box';
import Navigation from './components/Navigation';
import {
BrowserRouter as Router,
Switch,
Route,
} from 'react-router-dom';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { observer } from 'mobx-react';
import { grpc } from './Api';
import { AppState } from './AppState';
@ -15,14 +11,13 @@ import { AllDevices } from './pages/admin/AllDevices';
@observer
export class App extends React.Component {
async componentDidMount() {
AppState.info = await grpc.server.info({});
}
render() {
if (!AppState.info) {
return <p>loading...</p>
return <p>loading...</p>;
}
return (
<Router>
@ -31,11 +26,11 @@ export class App extends React.Component {
<Box component="div" m={2}>
<Switch>
<Route exact path="/" component={YourDevices} />
{AppState.info.isAdmin &&
{AppState.info.isAdmin && (
<>
<Route exact path="/admin/all-devices" component={AllDevices} />
</>
}
)}
</Switch>
</Box>
</Router>

@ -2,10 +2,8 @@ import { observable } from 'mobx';
import { InfoRes } from './sdk/server_pb';
class GlobalAppState {
@observable
info?: InfoRes.AsObject;
}
export const AppState = new GlobalAppState();
@ -15,5 +13,5 @@ console.info('see global app state by typing "window.AppState"');
Object.assign(window as any, {
get AppState() {
return JSON.parse(JSON.stringify(AppState));
}
},
});

@ -1,8 +1,8 @@
import formatDistance from "date-fns/formatDistance";
import formatDistance from 'date-fns/formatDistance';
import timestamp_pb from 'google-protobuf/google/protobuf/timestamp_pb';
import { toDate } from "./Api";
import { fromResource, lazyObservable } from "mobx-utils";
import { toast } from "./components/Toast";
import { toDate } from './Api';
import { fromResource, lazyObservable } from 'mobx-utils';
import { toast } from './components/Toast';
export function sleep(seconds: number) {
return new Promise((resolve) => {
@ -22,7 +22,7 @@ export function lastSeen(timestamp: timestamp_pb.Timestamp.AsObject | undefined)
}
export function lazy<T>(cb: () => Promise<T>) {
const resource = lazyObservable<T>(async sink => {
const resource = lazyObservable<T>(async (sink) => {
sink(await cb());
});
@ -41,7 +41,7 @@ export function autorefresh<T>(seconds: number, cb: () => Promise<T>) {
let sink: ((next: T) => void) | undefined;
const resource = fromResource<T>(
async s => {
async (s) => {
sink = s;
running = true;
while (running) {
@ -51,7 +51,7 @@ export function autorefresh<T>(seconds: number, cb: () => Promise<T>) {
},
() => {
running = false;
}
},
);
return {

@ -28,7 +28,6 @@ interface Props {
@observer
export class AddDevice extends React.Component<Props> {
@observable
dialogOpen = false;
@ -81,25 +80,22 @@ export class AddDevice extends React.Component<Props> {
this.configFileUri = URL.createObjectURL(new Blob([configFile]));
this.dialogOpen = true;
this.reset();
} catch (error) {
console.log(error);
// TODO: unwrap grpc error message
this.error = 'failed';
}
}
};
reset = () => {
this.formState.name = '';
}
};
render() {
return (
<>
<Card>
<CardHeader
title="Add A Device"
/>
<CardHeader title="Add A Device" />
<CardContent>
<form onSubmit={this.submit}>
<FormControl error={!!this.error} fullWidth>
@ -107,47 +103,29 @@ export class AddDevice extends React.Component<Props> {
<Input
id="device-name"
value={this.formState.name}
onChange={(event) => this.formState.name = event.currentTarget.value}
onChange={(event) => (this.formState.name = event.currentTarget.value)}
aria-describedby="device-name-text"
/>
<FormHelperText id="device-name-text">{this.error}</FormHelperText>
</FormControl>
<Typography component="div" align="right">
<Button
color="secondary"
type="button"
onClick={this.reset}
>
<Button color="secondary" type="button" onClick={this.reset}>
Cancel
</Button>
<Button
color="primary"
variant="contained"
endIcon={<AddIcon />}
type="submit"
>
<Button color="primary" variant="contained" endIcon={<AddIcon />} type="submit">
Add
</Button>
</Typography>
</form>
</CardContent>
</Card>
<Dialog
disableBackdropClick
disableEscapeKeyDown
maxWidth="xl"
open={this.dialogOpen}
>
<Dialog disableBackdropClick disableEscapeKeyDown maxWidth="xl" open={this.dialogOpen}>
<DialogTitle>Get Connected</DialogTitle>
<DialogContent>
<GetConnected
qrCodeUri={this.qrCode!}
configFile={this.configFile!}
configFileUri={this.configFileUri!}
/>
<GetConnected qrCodeUri={this.qrCode!} configFile={this.configFile!} configFileUri={this.configFileUri!} />
</DialogContent>
<DialogActions>
<Button color="secondary" variant="outlined" onClick={() => this.dialogOpen = false}>
<Button color="secondary" variant="outlined" onClick={() => (this.dialogOpen = false)}>
Done
</Button>
</DialogActions>

@ -11,7 +11,7 @@ import { lastSeen } from '../Util';
import { AppState } from '../AppState';
import { IconMenu } from './IconMenu';
import { PopoverDisplay } from './PopoverDisplay';
import { Device } from '../sdk/devices_pb'
import { Device } from '../sdk/devices_pb';
import { grpc } from '../Api';
import { observer } from 'mobx-react';
@ -42,10 +42,7 @@ export class DeviceListItem extends React.Component<Props> {
avatar={
<Avatar style={{ backgroundColor: device.connected ? '#76de8a' : '#bdbdbd' }}>
{/* <DonutSmallIcon /> */}
{device.connected
? <WifiIcon />
: <WifiOffIcon />
}
{device.connected ? <WifiIcon /> : <WifiOffIcon />}
</Avatar>
}
action={
@ -59,7 +56,7 @@ export class DeviceListItem extends React.Component<Props> {
<CardContent>
<table cellPadding="5">
<tbody>
{AppState.info?.metadataEnabled && device.connected &&
{AppState.info?.metadataEnabled && device.connected && (
<>
<tr>
<td>Endpoint</td>
@ -74,8 +71,8 @@ export class DeviceListItem extends React.Component<Props> {
<td>{numeral(device.receiveBytes).format('0b')}</td>
</tr>
</>
}
{AppState.info?.metadataEnabled && !device.connected &&
)}
{AppState.info?.metadataEnabled && !device.connected && (
<>
<tr>
<td>Disconnected</td>
@ -85,10 +82,12 @@ export class DeviceListItem extends React.Component<Props> {
<td>{lastSeen(device.lastHandshakeTime)}</td>
</tr>
</>
}
)}
<tr>
<td>Public key</td>
<td><PopoverDisplay label="show">{device.publicKey}</PopoverDisplay></td>
<td>
<PopoverDisplay label="show">{device.publicKey}</PopoverDisplay>
</td>
</tr>
</tbody>
</table>

@ -9,7 +9,6 @@ import { AddDevice } from './AddDevice';
@observer
export class Devices extends React.Component {
@observable
devices = autorefresh(5, async () => {
return (await grpc.devices.listDevices({})).items;
@ -21,7 +20,7 @@ export class Devices extends React.Component {
render() {
if (!this.devices.current) {
return <p>loading...</p>
return <p>loading...</p>;
}
return (
<Grid container spacing={3} justify="center">

@ -7,7 +7,7 @@ import List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import ListItemText from '@material-ui/core/ListItemText';
import Typography from '@material-ui/core/Typography';
import DescriptionOutlined from '@material-ui/icons/DescriptionOutlined'
import DescriptionOutlined from '@material-ui/icons/DescriptionOutlined';
import { MacOSIcon, IosIcon, WindowsIcon, LinuxIcon, AndroidIcon } from './Icons';
import { TabPanel } from './TabPanel';
import { Platform, getPlatform } from '../Platform';
@ -179,15 +179,12 @@ export class GetConnected extends React.Component<Props> {
</TabPanel>
<TabPanel for={Platform.Unknown} value={this.state.platform}>
<Grid container direction="row" justify="space-around" alignItems="center">
<Typography variant="body1" component="pre" style={{maxWidth: '100%', overflow: 'auto'}}>
{this.props.configFile}
</Typography>
<Button
color="primary"
onClick={() => setClipboard(this.props.configFile)}
>
Copy To Clipboard
</Button>
<Typography variant="body1" component="pre" style={{ maxWidth: '100%', overflow: 'auto' }}>
{this.props.configFile}
</Typography>
<Button color="primary" onClick={() => setClipboard(this.props.configFile)}>
Copy To Clipboard
</Button>
</Grid>
</TabPanel>
<Grid container justify="center">

@ -10,7 +10,7 @@ import Link from '@material-ui/core/Link';
import Button from '@material-ui/core/Button';
import Chip from '@material-ui/core/Chip';
const useStyles = makeStyles(theme => ({
const useStyles = makeStyles((theme) => ({
title: {
flexGrow: 1,
},
@ -23,27 +23,31 @@ export default function Navigation() {
<AppBar position="static">
<Toolbar>
<Typography variant="h6" className={classes.title}>
<span>Welcome</span>
{AppState.info?.isAdmin &&
<Chip label="admin" color="secondary" variant="outlined" size="small" style={{ marginLeft: 20, background: 'white' }} />
}
<Link to="/" color="inherit" component={NavLink}>
Welcome
</Link>
{AppState.info?.isAdmin && (
<Chip
label="admin"
color="secondary"
variant="outlined"
size="small"
style={{ marginLeft: 20, background: 'white' }}
/>
)}
</Typography>
{AppState.info?.isAdmin &&
{AppState.info?.isAdmin && (
<Link to="/admin/all-devices" color="inherit" component={NavLink}>
<Button color="inherit">
All Devices
</Button>
<Button color="inherit">All Devices</Button>
</Link>
}
)}
{hasAuthCookie &&
{hasAuthCookie && (
<Link href="/signout" color="inherit">
<Button color="inherit">
Logout
</Button>
<Button color="inherit">Logout</Button>
</Link>
}
)}
</Toolbar>
</AppBar>
);

@ -15,7 +15,13 @@ export class PopoverDisplay extends React.Component<Props> {
render() {
return (
<React.Fragment>
<Button size="small" variant="outlined" color="secondary" style={{padding: 0}} onClick={event => this.setState({ anchorEl: event.currentTarget })}>
<Button
size="small"
variant="outlined"
color="secondary"
style={{ padding: 0 }}
onClick={(event) => this.setState({ anchorEl: event.currentTarget })}
>
{this.props.label}
</Button>
<Popover

@ -0,0 +1,40 @@
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import Dialog from '@material-ui/core/Dialog';
import Typography from '@material-ui/core/Typography';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import DialogContentText from '@material-ui/core/DialogContentText';
export function present<T>(content: (close: (result: T) => void) => React.ReactNode) {
const root = document.createElement('div');
document.body.appendChild(root);
return new Promise<T>((resolve) => {
const close = (result: T) => {
unmountComponentAtNode(root);
resolve(result);
};
render(<>{content(close)}</>, root);
});
}
export function confirm(msg: string): Promise<boolean> {
return present<boolean>((close) => (
<Dialog open={true} onClose={() => close(false)}>
<DialogTitle>Confirm</DialogTitle>
<DialogContent>
<DialogContentText>{msg}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={() => close(false)} variant="contained" color="primary" autoFocus>
Cancel
</Button>
<Button onClick={() => close(true)} variant="outlined" color="secondary">
Ok
</Button>
</DialogActions>
</Dialog>
));
}

@ -3,8 +3,6 @@ import { Devices } from '../components/Devices';
export class YourDevices extends React.Component {
render() {
return (
<Devices />
);
return <Devices />;
}
}

@ -5,21 +5,33 @@ import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Button from '@material-ui/core/Button';
import { observer } from 'mobx-react';
import { grpc } from '../../Api';
import { lastSeen, lazy } from '../../Util';
import { Device } from '../../sdk/devices_pb';
import { confirm } from '../../components/Present';
@observer
export class AllDevices extends React.Component {
devices = lazy(async () => {
const res = await grpc.devices.listAllDevices({});
return res.items;
});
deleteDevice = async (device: Device.AsObject) => {
if (await confirm('Are you sure?')) {
await grpc.devices.deleteDevice({
name: device.name,
owner: { value: device.owner },
});
await this.devices.refresh();
}
};
render() {
if (!this.devices.current) {
return <p>loading...</p>
return <p>loading...</p>;
}
const rows = this.devices.current;
@ -27,7 +39,7 @@ export class AllDevices extends React.Component {
// show the provider column
// when there is more than 1 provider in use
// i.e. not all devices are from the same auth provider.
const showProviderCol = rows.length >= 2 && rows.some(r => r.ownerProvider !== rows[0].ownerProvider);
const showProviderCol = rows.length >= 2 && rows.some((r) => r.ownerProvider !== rows[0].ownerProvider);
return (
<TableContainer>
@ -35,12 +47,11 @@ export class AllDevices extends React.Component {
<TableHead>
<TableRow>
<TableCell>Owner</TableCell>
{showProviderCol &&
<TableCell>Auth Provider</TableCell>
}
{showProviderCol && <TableCell>Auth Provider</TableCell>}
<TableCell>Device</TableCell>
<TableCell>Connected</TableCell>
<TableCell>Last Seen</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
@ -49,12 +60,15 @@ export class AllDevices extends React.Component {
<TableCell component="th" scope="row">
{row.ownerName || row.ownerEmail || row.owner}
</TableCell>
{showProviderCol &&
<TableCell>{row.ownerProvider}</TableCell>
}
{showProviderCol && <TableCell>{row.ownerProvider}</TableCell>}
<TableCell>{row.name}</TableCell>
<TableCell>{row.connected ? 'yes' : 'no'}</TableCell>
<TableCell>{lastSeen(row.lastHandshakeTime)}</TableCell>
<TableCell>
<Button variant="outlined" color="secondary" onClick={() => this.deleteDevice(row)}>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>

File diff suppressed because it is too large Load Diff

@ -8,350 +8,329 @@ import * as grpcWeb from 'grpc-web';
import * as googleProtobufWrappers from 'google-protobuf/google/protobuf/wrappers_pb';
export class Server {
private client_ = new grpcWeb.GrpcWebClientBase({
format: 'text',
});
private methodInfoInfo = new grpcWeb.AbstractClientBase.MethodInfo(
InfoRes,
(req: InfoReq) => req.serializeBinary(),
InfoRes.deserializeBinary
);
constructor(
private hostname: string,
private defaultMetadata?: () => grpcWeb.Metadata,
) { }
info(req: InfoReq.AsObject, metadata?: grpcWeb.Metadata): Promise<InfoRes.AsObject> {
return new Promise((resolve, reject) => {
const message = InfoReqFromObject(req);
this.client_.rpcCall(
this.hostname + '/proto.Server/Info',
message,
Object.assign({}, this.defaultMetadata ? this.defaultMetadata() : {}, metadata),
this.methodInfoInfo,
(err: grpcWeb.Error, res: InfoRes) => {
if (err) {
reject(err);
} else {
resolve(res.toObject());
}
},
);
});
}
private client_ = new grpcWeb.GrpcWebClientBase({
format: 'text',
});
private methodInfoInfo = new grpcWeb.AbstractClientBase.MethodInfo(
InfoRes,
(req: InfoReq) => req.serializeBinary(),
InfoRes.deserializeBinary,
);
constructor(private hostname: string, private defaultMetadata?: () => grpcWeb.Metadata) {}
info(req: InfoReq.AsObject, metadata?: grpcWeb.Metadata): Promise<InfoRes.AsObject> {
return new Promise((resolve, reject) => {
const message = InfoReqFromObject(req);
this.client_.rpcCall(
this.hostname + '/proto.Server/Info',
message,
Object.assign({}, this.defaultMetadata ? this.defaultMetadata() : {}, metadata),
this.methodInfoInfo,
(err: grpcWeb.Error, res: InfoRes) => {
if (err) {
reject(err);
} else {
resolve(res.toObject());
}
},
);
});
}
}
export declare namespace InfoReq {
export type AsObject = {
}
export type AsObject = {};
}
export class InfoReq extends jspb.Message {
private static repeatedFields_ = [
];
constructor(data?: jspb.Message.MessageArray) {
super();
jspb.Message.initialize(this, data || [], 0, -1, InfoReq.repeatedFields_, null);
}
serializeBinary(): Uint8Array {
const writer = new jspb.BinaryWriter();
InfoReq.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
}
toObject(): InfoReq.AsObject {
let f: any;
return {
};
}
static serializeBinaryToWriter(message: InfoReq, writer: jspb.BinaryWriter): void {
}
static deserializeBinary(bytes: Uint8Array): InfoReq {
var reader = new jspb.BinaryReader(bytes);
var message = new InfoReq();
return InfoReq.deserializeBinaryFromReader(message, reader);
}
static deserializeBinaryFromReader(message: InfoReq, reader: jspb.BinaryReader): InfoReq {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
const field = reader.getFieldNumber();
switch (field) {
default:
reader.skipField();
break;
}
}
return message;
}
private static repeatedFields_ = [];
constructor(data?: jspb.Message.MessageArray) {
super();
jspb.Message.initialize(this, data || [], 0, -1, InfoReq.repeatedFields_, null);
}
serializeBinary(): Uint8Array {
const writer = new jspb.BinaryWriter();
InfoReq.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
}
toObject(): InfoReq.AsObject {
let f: any;
return {};
}
static serializeBinaryToWriter(message: InfoReq, writer: jspb.BinaryWriter): void {}
static deserializeBinary(bytes: Uint8Array): InfoReq {
var reader = new jspb.BinaryReader(bytes);
var message = new InfoReq();
return InfoReq.deserializeBinaryFromReader(message, reader);
}
static deserializeBinaryFromReader(message: InfoReq, reader: jspb.BinaryReader): InfoReq {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
const field = reader.getFieldNumber();
switch (field) {
default:
reader.skipField();
break;
}
}
return message;
}
}
export declare namespace InfoRes {
export type AsObject = {
publicKey: string,
host?: googleProtobufWrappers.StringValue.AsObject,
port: number,
hostVpnIp: string,
metadataEnabled: boolean,
isAdmin: boolean,
allowedIps: string,
dnsEnabled: boolean,
dnsAddress: string,
}
export type AsObject = {
publicKey: string;
host?: googleProtobufWrappers.StringValue.AsObject;
port: number;
hostVpnIp: string;
metadataEnabled: boolean;
isAdmin: boolean;
allowedIps: string;
dnsEnabled: boolean;
dnsAddress: string;
};
}
export class InfoRes extends jspb.Message {
private static repeatedFields_ = [
];
constructor(data?: jspb.Message.MessageArray) {
super();
jspb.Message.initialize(this, data || [], 0, -1, InfoRes.repeatedFields_, null);
}
getPublicKey(): string {
return jspb.Message.getFieldWithDefault(this, 1, "");
}
setPublicKey(value: string): void {
(jspb.Message as any).setProto3StringField(this, 1, value);
}
getHost(): googleProtobufWrappers.StringValue {
return jspb.Message.getWrapperField(this, googleProtobufWrappers.StringValue, 2);
}
setHost(value?: googleProtobufWrappers.StringValue): void {
(jspb.Message as any).setWrapperField(this, 2, value);
}
getPort(): number {
return jspb.Message.getFieldWithDefault(this, 3, 0);
}
setPort(value: number): void {
(jspb.Message as any).setProto3IntField(this, 3, value);
}
getHostVpnIp(): string {
return jspb.Message.getFieldWithDefault(this, 4, "");
}
setHostVpnIp(value: string): void {
(jspb.Message as any).setProto3StringField(this, 4, value);
}
getMetadataEnabled(): boolean {
return jspb.Message.getFieldWithDefault(this, 5, false);
}
setMetadataEnabled(value: boolean): void {
(jspb.Message as any).setProto3BooleanField(this, 5, value);
}
getIsAdmin(): boolean {
return jspb.Message.getFieldWithDefault(this, 6, false);
}
setIsAdmin(value: boolean): void {
(jspb.Message as any).setProto3BooleanField(this, 6, value);
}
getAllowedIps(): string {
return jspb.Message.getFieldWithDefault(this, 7, "");
}
setAllowedIps(value: string): void {
(jspb.Message as any).setProto3StringField(this, 7, value);
}
getDnsEnabled(): boolean {
return jspb.Message.getFieldWithDefault(this, 8, false);
}
setDnsEnabled(value: boolean): void {
(jspb.Message as any).setProto3BooleanField(this, 8, value);
}
getDnsAddress(): string {
return jspb.Message.getFieldWithDefault(this, 9, "");
}
setDnsAddress(value: string): void {
(jspb.Message as any).setProto3StringField(this, 9, value);
}
serializeBinary(): Uint8Array {
const writer = new jspb.BinaryWriter();
InfoRes.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
}
toObject(): InfoRes.AsObject {
let f: any;
return {publicKey: this.getPublicKey(),
host: (f = this.getHost()) && f.toObject(),
port: this.getPort(),
hostVpnIp: this.getHostVpnIp(),
metadataEnabled: this.getMetadataEnabled(),
isAdmin: this.getIsAdmin(),
allowedIps: this.getAllowedIps(),
dnsEnabled: this.getDnsEnabled(),
dnsAddress: this.getDnsAddress(),
};
}
static serializeBinaryToWriter(message: InfoRes, writer: jspb.BinaryWriter): void {
const field1 = message.getPublicKey();
if (field1.length > 0) {
writer.writeString(1, field1);
}
const field2 = message.getHost();
if (field2 != null) {
writer.writeMessage(2, field2, googleProtobufWrappers.StringValue.serializeBinaryToWriter);
}
const field3 = message.getPort();
if (field3 != 0) {
writer.writeInt32(3, field3);
}
const field4 = message.getHostVpnIp();
if (field4.length > 0) {
writer.writeString(4, field4);
}
const field5 = message.getMetadataEnabled();
if (field5 != false) {
writer.writeBool(5, field5);
}
const field6 = message.getIsAdmin();
if (field6 != false) {
writer.writeBool(6, field6);
}
const field7 = message.getAllowedIps();
if (field7.length > 0) {
writer.writeString(7, field7);
}
const field8 = message.getDnsEnabled();
if (field8 != false) {
writer.writeBool(8, field8);
}
const field9 = message.getDnsAddress();
if (field9.length > 0) {
writer.writeString(9, field9);
}
}
static deserializeBinary(bytes: Uint8Array): InfoRes {
var reader = new jspb.BinaryReader(bytes);
var message = new InfoRes();
return InfoRes.deserializeBinaryFromReader(message, reader);
}
static deserializeBinaryFromReader(message: InfoRes, reader: jspb.BinaryReader): InfoRes {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
const field = reader.getFieldNumber();
switch (field) {
case 1:
const field1 = reader.readString()
message.setPublicKey(field1);
break;
case 2:
const field2 = new googleProtobufWrappers.StringValue();
reader.readMessage(field2, googleProtobufWrappers.StringValue.deserializeBinaryFromReader);
message.setHost(field2);
break;
case 3:
const field3 = reader.readInt32()
message.setPort(field3);
break;
case 4:
const field4 = reader.readString()
message.setHostVpnIp(field4);
break;
case 5:
const field5 = reader.readBool()
message.setMetadataEnabled(field5);
break;
case 6:
const field6 = reader.readBool()
message.setIsAdmin(field6);
break;
case 7:
const field7 = reader.readString()
message.setAllowedIps(field7);
break;
case 8:
const field8 = reader.readBool()
message.setDnsEnabled(field8);
break;
case 9:
const field9 = reader.readString()
message.setDnsAddress(field9);
break;
default:
reader.skipField();
break;
}
}
return message;
}
private static repeatedFields_ = [];
constructor(data?: jspb.Message.MessageArray) {
super();
jspb.Message.initialize(this, data || [], 0, -1, InfoRes.repeatedFields_, null);
}
getPublicKey(): string {
return jspb.Message.getFieldWithDefault(this, 1, '');
}
setPublicKey(value: string): void {
(jspb.Message as any).setProto3StringField(this, 1, value);
}
getHost(): googleProtobufWrappers.StringValue {
return jspb.Message.getWrapperField(this, googleProtobufWrappers.StringValue, 2);
}
setHost(value?: googleProtobufWrappers.StringValue): void {
(jspb.Message as any).setWrapperField(this, 2, value);
}
getPort(): number {
return jspb.Message.getFieldWithDefault(this, 3, 0);
}
setPort(value: number): void {
(jspb.Message as any).setProto3IntField(this, 3, value);
}
getHostVpnIp(): string {
return jspb.Message.getFieldWithDefault(this, 4, '');
}
setHostVpnIp(value: string): void {
(jspb.Message as any).setProto3StringField(this, 4, value);
}
getMetadataEnabled(): boolean {
return jspb.Message.getFieldWithDefault(this, 5, false);
}
setMetadataEnabled(value: boolean): void {
(jspb.Message as any).setProto3BooleanField(this, 5, value);
}
getIsAdmin(): boolean {
return jspb.Message.getFieldWithDefault(this, 6, false);
}
setIsAdmin(value: boolean): void {
(jspb.Message as any).setProto3BooleanField(this, 6, value);
}
getAllowedIps(): string {
return jspb.Message.getFieldWithDefault(this, 7, '');
}
setAllowedIps(value: string): void {
(jspb.Message as any).setProto3StringField(this, 7, value);
}
getDnsEnabled(): boolean {
return jspb.Message.getFieldWithDefault(this, 8, false);
}
setDnsEnabled(value: boolean): void {
(jspb.Message as any).setProto3BooleanField(this, 8, value);
}
getDnsAddress(): string {
return jspb.Message.getFieldWithDefault(this, 9, '');
}
setDnsAddress(value: string): void {
(jspb.Message as any).setProto3StringField(this, 9, value);
}
serializeBinary(): Uint8Array {
const writer = new jspb.BinaryWriter();
InfoRes.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
}
toObject(): InfoRes.AsObject {
let f: any;
return {
publicKey: this.getPublicKey(),
host: (f = this.getHost()) && f.toObject(),
port: this.getPort(),
hostVpnIp: this.getHostVpnIp(),
metadataEnabled: this.getMetadataEnabled(),
isAdmin: this.getIsAdmin(),
allowedIps: this.getAllowedIps(),
dnsEnabled: this.getDnsEnabled(),
dnsAddress: this.getDnsAddress(),
};
}
static serializeBinaryToWriter(message: InfoRes, writer: jspb.BinaryWriter): void {
const field1 = message.getPublicKey();
if (field1.length > 0) {
writer.writeString(1, field1);
}
const field2 = message.getHost();
if (field2 != null) {
writer.writeMessage(2, field2, googleProtobufWrappers.StringValue.serializeBinaryToWriter);
}
const field3 = message.getPort();
if (field3 != 0) {
writer.writeInt32(3, field3);
}
const field4 = message.getHostVpnIp();
if (field4.length > 0) {
writer.writeString(4, field4);
}
const field5 = message.getMetadataEnabled();
if (field5 != false) {
writer.writeBool(5, field5);
}
const field6 = message.getIsAdmin();
if (field6 != false) {
writer.writeBool(6, field6);
}
const field7 = message.getAllowedIps();
if (field7.length > 0) {
writer.writeString(7, field7);
}
const field8 = message.getDnsEnabled();
if (field8 != false) {
writer.writeBool(8, field8);
}
const field9 = message.getDnsAddress();
if (field9.length > 0) {
writer.writeString(9, field9);
}
}
static deserializeBinary(bytes: Uint8Array): InfoRes {
var reader = new jspb.BinaryReader(bytes);
var message = new InfoRes();
return InfoRes.deserializeBinaryFromReader(message, reader);
}
static deserializeBinaryFromReader(message: InfoRes, reader: jspb.BinaryReader): InfoRes {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
const field = reader.getFieldNumber();
switch (field) {
case 1:
const field1 = reader.readString();
message.setPublicKey(field1);
break;
case 2:
const field2 = new googleProtobufWrappers.StringValue();
reader.readMessage(field2, googleProtobufWrappers.StringValue.deserializeBinaryFromReader);
message.setHost(field2);
break;
case 3:
const field3 = reader.readInt32();
message.setPort(field3);
break;
case 4:
const field4 = reader.readString();
message.setHostVpnIp(field4);
break;
case 5:
const field5 = reader.readBool();
message.setMetadataEnabled(field5);
break;
case 6:
const field6 = reader.readBool();
message.setIsAdmin(field6);
break;
case 7:
const field7 = reader.readString();
message.setAllowedIps(field7);
break;
case 8:
const field8 = reader.readBool();
message.setDnsEnabled(field8);
break;
case 9:
const field9 = reader.readString();
message.setDnsAddress(field9);
break;
default:
reader.skipField();
break;
}
}
return message;
}
}
function InfoReqFromObject(obj: InfoReq.AsObject | undefined): InfoReq | undefined {
if (obj === undefined) {
return undefined;
}
const message = new InfoReq();
return message;
if (obj === undefined) {
return undefined;
}
const message = new InfoReq();
return message;
}
function InfoResFromObject(obj: InfoRes.AsObject | undefined): InfoRes | undefined {
if (obj === undefined) {
return undefined;
}
const message = new InfoRes();
message.setPublicKey(obj.publicKey);
message.setHost(StringValueFromObject(obj.host));
message.setPort(obj.port);
message.setHostVpnIp(obj.hostVpnIp);
message.setMetadataEnabled(obj.metadataEnabled);
message.setIsAdmin(obj.isAdmin);
message.setAllowedIps(obj.allowedIps);
message.setDnsEnabled(obj.dnsEnabled);
message.setDnsAddress(obj.dnsAddress);
return message;
if (obj === undefined) {
return undefined;
}
const message = new InfoRes();
message.setPublicKey(obj.publicKey);
message.setHost(StringValueFromObject(obj.host));
message.setPort(obj.port);
message.setHostVpnIp(obj.hostVpnIp);
message.setMetadataEnabled(obj.metadataEnabled);
message.setIsAdmin(obj.isAdmin);
message.setAllowedIps(obj.allowedIps);
message.setDnsEnabled(obj.dnsEnabled);
message.setDnsAddress(obj.dnsAddress);
return message;
}
function StringValueFromObject(obj: googleProtobufWrappers.StringValue.AsObject | undefined): googleProtobufWrappers.StringValue | undefined {
if (obj === undefined) {
return undefined;
}
const message = new googleProtobufWrappers.StringValue();
message.setValue(obj.value);
return message;
function StringValueFromObject(
obj: googleProtobufWrappers.StringValue.AsObject | undefined,
): googleProtobufWrappers.StringValue | undefined {
if (obj === undefined) {
return undefined;
}
const message = new googleProtobufWrappers.StringValue();
message.setValue(obj.value);
return message;
}

Loading…
Cancel
Save