Skip to content

Commit

Permalink
Finish config / analytics opt out support
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanleclaire committed Mar 9, 2022
1 parent 61d3841 commit 0dc6b28
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 69 deletions.
18 changes: 18 additions & 0 deletions assets/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM rust:slim-buster

RUN rustup toolchain install nightly && rustup default nightly && rustup component add rustfmt
RUN apt-get update && apt-get install -y git pkg-config libudev-dev make libclang-dev clang
ENV SOLANA_VERSION v1.8.14
RUN git clone -b $SOLANA_VERSION --depth 1 https://github.com/solana-labs/solana
WORKDIR solana
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/solana/target/release/build \
--mount=type=cache,target=/solana/target/release/deps \
--mount=type=cache,target=/solana/target/release/incremental \
cargo build --release

FROM debian:bullseye-slim

RUN apt-get update && apt-get install -y bzip2
VOLUME ["/var/lib/solana-ledger"]
COPY --from=0 /solana/target/release/* /usr/local/bin
5 changes: 5 additions & 0 deletions assets/docker/x86.Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM solanalabs/solana:v1.9.2

RUN apt-get update && apt-get install -y curl build-essential
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH $PATH:/root/.cargo/bin
2 changes: 1 addition & 1 deletion assets/migrations/0002_config.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
CREATE TABLE config (
id INTEGER PRIMARY KEY,
key TEXT NOT NULL,
val TEXT NOT NULL,
val TEXT NOT NULL
);
10 changes: 9 additions & 1 deletion src/common/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import amplitude from 'amplitude-js';
import { ConfigKey } from 'types/types';

// TODO(nathanleclaire): Not the largest fan of this spaghetti-ish import
// renderer is really supposed to import common not vice versa
import store from '../renderer/store';

const AMPLITUDE_KEY = 'f1cde3642f7e0f483afbb7ac15ae8277';
const AMPLITUDE_HEARTBEAT_INTERVAL = 3600000;

amplitude.getInstance().init(AMPLITUDE_KEY);

const analytics = (event: string, metadata: any) => {
if (process.env.NODE_ENV !== 'development' /* and user has not opted out */) {
if (
process.env.NODE_ENV !== 'development' &&
store.getState().config.values[ConfigKey.AnalyticsEnabled]
) {
amplitude.getInstance().logEvent(event, metadata);
}
};
Expand Down
5 changes: 2 additions & 3 deletions src/main/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { KEY_PATH } from './const';
const HEXDUMP_BYTES = 512;
const AIRDROP_AMOUNT = 100;

const addKeypair = async (net: Net, kpPath: string) => {
const addKeypair = async (kpPath: string) => {
const kp = sol.Keypair.generate();

// goofy looking but otherwise stringify encodes Uint8Array like:
Expand Down Expand Up @@ -80,10 +80,9 @@ async function accounts(msg: AccountsRequest): Promise<AccountsResponse> {
await fs.promises.access(KEY_PATH);
} catch {
logger.info('Creating root key', { KEY_PATH });
await addKeypair(msg.net, KEY_PATH);
await addKeypair(KEY_PATH);
}
const kp = await localKeypair(KEY_PATH);
logger.info('accounts', { net, pubKey: kp.publicKey });
const solConn = new sol.Connection(netToURL(net));
const existingAccounts = await db.all(
'SELECT * FROM account WHERE net = ? ORDER BY created_at DESC, humanName ASC',
Expand Down
27 changes: 21 additions & 6 deletions src/main/config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { WBConfigRequest, WBConfigResponse } from 'types/types';
import {
ConfigAction,
ConfigMap,
WBConfigRequest,
WBConfigResponse,
} from '../types/types';
import { db } from './db';

async function wbConfig(msg: WBConfigRequest): Promise<WBConfigResponse> {
const { action, key } = msg;
if (action === 'set') {
if (action === ConfigAction.Set) {
const { val } = msg;
db.run('UPDATE config SET val = ? WHERE name = ?', val, key);
return { val };
const existingRow = await db.get('SELECT * FROM config WHERE key = ?', key);
if (existingRow) {
await db.run('UPDATE config SET val = ? WHERE key = ?', val, key);
} else {
await db.run('INSERT INTO config (key, val) VALUES (?, ?)', key, val);
}
}
const val = await db.get('SELECT val FROM config WHERE name = ?', key);
return { val };
const cfgVals = await db.all('SELECT * FROM config');
const values: ConfigMap = {};
if (cfgVals) {
cfgVals.forEach((setting: any) => {
values[setting.key] = setting.val;
});
}
return { values };
}

export default wbConfig;
7 changes: 7 additions & 0 deletions src/renderer/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,10 @@ $top-nav-height: 47px; // hack -- def better way to compute somehow
.dropdown-menu {
z-index: 1020; // hack to fix other items being on top of dropdown
}

.btn-outline-dark {
&:hover {
color: $black;
background-color: $white;
}
}
197 changes: 142 additions & 55 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,16 @@ import {
faNetworkWired,
faCircle,
} from '@fortawesome/free-solid-svg-icons';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, validatorActions } from './slices/mainSlice';
import { Net } from '../types/types';
import { configActions, RootState, validatorActions } from './slices/mainSlice';
import { ConfigAction, ConfigKey, Net } from '../types/types';
import analytics from 'common/analytics';
import Toast from './components/Toast';
import Accounts from './nav/Accounts';
import Anchor from './nav/Anchor';
import Validator from './nav/Validator';
import { Button, ToggleButton, ToggleButtonGroup } from 'react-bootstrap';

declare global {
interface Window {
Expand Down Expand Up @@ -111,8 +112,11 @@ export default function App() {
const dispatch = useDispatch();
const { toasts } = useSelector((state: RootState) => state.toast);
const validator = useSelector((state: RootState) => state.validator);
const config = useSelector((state: RootState) => state.config);
const { net } = validator;

const [analyticsEnabled, setAnalyticsEnabled] = useState('yes');

useEffect(() => {
const listener = (resp: any) => {
const { method, res } = resp;
Expand All @@ -126,13 +130,24 @@ export default function App() {
dispatch(validatorActions.setWaitingForRun(false));
}
break;
case 'config':
dispatch(
configActions.set({
loading: false,
values: res.values,
})
);
break;
default:
}
};
window.electron.ipcRenderer.on('main', listener);
window.electron.ipcRenderer.validatorState({
net: Net.Localhost,
});
window.electron.ipcRenderer.config({
action: ConfigAction.Get,
});

return () => {
window.electron.ipcRenderer.removeListener('main', listener);
Expand Down Expand Up @@ -169,61 +184,133 @@ export default function App() {
);
}

return (
<Router>
<Switch>
<div className="row flex-nowrap g-0">
<div className="col-auto">
<Nav />
{toasts.map((t) => (
<Toast {...t} />
))}
</div>
<div className="col-10 bg-white ms-4">
<div className="row sticky-top sticky-nav bg-white-translucent">
<div>
<Header />
<span className="float-end">
{statusDisplay}
<DropdownButton
size="sm"
id="dropdown-basic-button"
title={netDropdownTitle}
onSelect={netDropdownSelect}
className="ms-2 float-end"
variant="light"
align="end"
>
<Dropdown.Item eventKey={Net.Localhost} href="#">
{Net.Localhost}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Dev} href="#">
{Net.Dev}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Test} href="#">
{Net.Test}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.MainnetBeta} href="#">
{Net.MainnetBeta}
</Dropdown.Item>
</DropdownButton>
</span>
</div>
</div>
<div className="row flex-nowrap">
<Route exact path="/">
<Accounts />
</Route>
<Route path="/validator">
<Validator />
</Route>
<Route path="/anchor">
<Anchor />
</Route>
let mainDisplay = <></>;

console.log(config);
if (!config.loading && !(`${ConfigKey.AnalyticsEnabled}` in config.values)) {
mainDisplay = (
<div className="container">
<div className="mt-2">
<h3>Will you help us out?</h3>
Workbench collects usage analytics. You can audit this code on{' '}
<a
href="https://github.com/workbenchapp/solana-workbench"
target="_blank"
rel="noreferrer"
>
Github
</a>
. You can opt out below.
</div>
<div className="mt-2 mb-2">
<h5>What We Collect</h5>
<ul>
<li>Which features are popular</li>
<li>System properties like OS version</li>
<li>How often people are using Workbench</li>
</ul>
We do not collect addresses or private keys.
</div>
<ToggleButtonGroup type="radio" name="options" defaultValue={1}>
<ToggleButton
checked={analyticsEnabled === 'yes'}
key="yes"
type="radio"
value="yes"
variant={analyticsEnabled === 'yes' ? 'dark' : 'outline-dark'}
onClick={() => {
setAnalyticsEnabled('yes');
}}
>
Sure, I'll Help
</ToggleButton>
<ToggleButton
checked={analyticsEnabled === 'no'}
key="no"
value="no"
type="radio"
variant={analyticsEnabled === 'no' ? 'dark' : 'outline-dark'}
onClick={() => {
setAnalyticsEnabled('no');
}}
>
No Thanks
</ToggleButton>
</ToggleButtonGroup>
<Button
className="ms-2"
variant="primary"
onClick={() => {
window.electron.ipcRenderer.config({
action: ConfigAction.Set,
key: ConfigKey.AnalyticsEnabled,
val: analyticsEnabled,
});
}}
>
<span className="ms-1 text-white">Start</span>
</Button>
</div>
);
} else {
mainDisplay = (
<div className="row flex-nowrap g-0">
<div className="col-auto">
<Nav />
{toasts.map((t) => (
<Toast {...t} />
))}
</div>
<div className="col-10 bg-white ms-4">
<div className="row sticky-top sticky-nav bg-white-translucent">
<div>
<Header />
<span className="float-end">
{statusDisplay}
<DropdownButton
size="sm"
id="dropdown-basic-button"
title={netDropdownTitle}
onSelect={netDropdownSelect}
className="ms-2 float-end"
variant="light"
align="end"
>
<Dropdown.Item eventKey={Net.Localhost} href="#">
{Net.Localhost}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Dev} href="#">
{Net.Dev}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.Test} href="#">
{Net.Test}
</Dropdown.Item>
<Dropdown.Item eventKey={Net.MainnetBeta} href="#">
{Net.MainnetBeta}
</Dropdown.Item>
</DropdownButton>
</span>
</div>
</div>
<div className="row flex-nowrap">
<Route exact path="/">
<Accounts />
</Route>
<Route path="/validator">
<Validator />
</Route>
<Route path="/anchor">
<Anchor />
</Route>
</div>
</div>
</Switch>
</div>
);
}

return (
<Router>
<Switch>{mainDisplay}</Switch>
</Router>
);
}
Loading

0 comments on commit 0dc6b28

Please sign in to comment.