Compare commits

..
10 Commits
Author SHA1 Message Date
nitowa a74af30b06 push 2024-09-08 15:12:39 +02:00
peter 0035b7463a working plugin import 2019-09-17 18:26:27 +02:00
peter e1340d6350 wat 2019-09-17 15:22:24 +02:00
peter 3e804fd0e6 many frontend changes that need generic change 2019-09-16 15:35:08 +02:00
Daniel Huebleitner 28d0c5a134 removed unused .gitmodules 2019-09-02 21:29:27 +02:00
Daniel Huebleitner e3330b634a fix 2019-09-02 21:25:35 +02:00
Daniel Huebleitner 26a8400ab1 install() -> installAdmin() 2019-09-02 18:36:06 +02:00
Daniel Huebleitner ec732d29cb replaced apiclient with paymentmanager 2019-09-02 16:56:43 +02:00
Daniel Huebleitner 28d441fd93 removed bak folder 2019-09-02 16:56:17 +02:00
peter 41b1a98e99 fix docker base image to support flatMap 2019-08-28 02:01:42 +02:00
75 changed files with 1020 additions and 14121 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
FROM alpine:3.9
RUN apk add npm git nodejs
FROM node:12.9.0-alpine
RUN apk add git
RUN git clone https://gitea.frontblock.me/fb-dist/admin.git dist
-1188
View File
File diff suppressed because it is too large Load Diff
-182
View File
@@ -1,182 +0,0 @@
## How to contribute to Knex.js
- Make changes in the `/lib` directory.
- Before sending a pull request for a feature or bug fix, be sure to have
[tests](https://github.com/tgriesser/knex/tree/master/test). Every pull request that changes the queries should have
also **integration tests which are ran against real database** (in addition to unit tests which checks which kind of queries
are being created).
- Use the same coding style as the rest of the
[codebase](https://github.com/tgriesser/knex/blob/master/knex.js).
- All pull requests should be made to the `master` branch.
- Pull request description should have link to corresponding PR of documentation branch.
- All pull requests that modify the public API should be updated in [types/index.d.ts](https://github.com/tgriesser/knex/blob/master/types/index.d.ts)
## Documentation
Documentation is no longer maintained in knex master repository. All the documentation pull requests should be sent to https://github.com/knex/documentation
Documentation pull requests should not be merged before knex version which has the new documented feature is released.
## I would like to add support for new dialect to knex, is it possible?
Currently there are already way too many dialects supported in `knex` and instead of adding new dialect to central codebase, all the dialects should be moved to separate npm packages out from `knex` core library with their respective maintainers and test suites.
So if you like to write your own dialect, you can just inherit own dialect from knex base classes and use it by passing dilaect to knex in knex configuration (https://runkit.com/embed/90b3cpyr4jh2):
```js
// simple dialect overriding sqlite3 dialect to use sqlite3-offline driver
require('sqlite3-offline');
const Knex = require('knex');
const Dialect = require(`knex/lib/dialects/sqlite3/index.js`);
Dialect.prototype._driver = () => require('sqlite3-offline');
const knex = Knex({
client: Dialect,
connection: ':memory:',
});
console.log(knex.select(knex.raw(1)).toSQL());
await knex.schema.createTable('fooobar', (t) => {
t.bigincrements('id');
t.string('data');
});
await knex('fooobar').insert({ data: 'nomnom' });
console.log('Gimme all the data:', await knex('fooobar'));
```
## What is minimal code to reproduce bug and why I have to provide that when I can just tell whats the problem is
Writing minimal reproduction code for the problem is time-consuming and sometimes it is also really hard, for
example when the original code where the bug happens is written using express or mocha. So why is it necessary
for me to commit so much time to it when the problem is in `knex`? Contributors should be grateful that I reported
the bug I found.
The point of runnable code to reproduce the problem is to easily verify that there really is a problem and that the one
who did the report did nothing wrong (surprisingly often problem is in the user code). So instead of just description
what to do the complete code encourages devs to actually test out that problem exists and start solving it and it
saves lots of time.
tl;dr list:
1. Actually in most of the cases developer already figures out what was the problem when writing the minimal test case
or if there was problem how stuff was initialized or how async code was written it is easy to point out the problem.
2. It motivates developer to actually try out if the bug really exist by not having to figure out from incomplete example
environment in which and how bug actually manifests.
3. There are currently very few people fixing knex issues and if one has to put easily 15-30 minutes time to issue just
to see that I cannot reproduce this issue it just wastes development hours that were available for improving knex.
Test case should initialize needed tables, insert needed data and fail...
```
const knex = require('knex')({
client: 'pg',
connection: 'postgres:///knex_test'
});
async function main() {
await knex.schema.createTable(...);
await knex('table').insert({foo: 'bar}');
await knex.destroy();
}
main();
```
Usually issues without reproduction code available are just closed and if the same issue is reported multiple
times maybe someone looks into it.
One easy way to setup database for your reproduction is to use database from knex's docker-compose setup (npm run db:start) and by checking the connection settings from tests' `test/knexfile.js`.
## Integration Tests
### The Easy Way
By default, Knex runs tests against sqlite3, postgresql, mysql, mysql2, mssql and oracledb drivers. All databases can be initialized and ran with docker.
Docker databases can be started and initialized and started with:
```bash
npm run db:start
```
and stopped with:
```bash
npm run db:stop
```
### Installing support for oracledb
Oracle has started providing precompiled driver libs for all the platforms, which makes it viable to run oracle tests also locally against oracledb running in docker.
Check message when running
```bash
npm install oracledb
```
and download driver library binary packages and unzip it to ~/lib directory.
### Specifying Databases
You can optionally specify which dialects to test using the `DB` environment variable. Values should be space separated and can include:
- mysql
- mysql2
- postgres
- sqlite3
- oracledb
- mssql
```bash
$ DB='postgres mysql' npm test
```
### Custom Configuration
If you'd like to override the database configuration (to use a different host, for example), you can override the path to the [default test configuration](https://github.com/tgriesser/knex/blob/master/test/knexfile.js) using the `KNEX_TEST` environment variable.
```bash
$ KNEX_TEST='./path/to/my/config.js' npm test
```
### Creating Postgres User
If you are running tests agains own local database one might need to setup test user and databse for knex to connect.
To create a new user, login to Postgres and use the following queries to add the user. This assumes you've already created the `knex_test` database.
```
CREATE ROLE postgres WITH LOGIN PASSWORD '';
GRANT ALL PRIVILEGES ON DATABASE "knex_test" TO postgres;
```
Once this is done, check it works by attempting to login:
```
psql -h localhost -U postgres -d knex_test
```
## Want to be Collaborator?
There is always room for more collaborators. Be active on resolving github issues / sending pull requests / reviewing code and we will ask you to join.
### Etiquette (/ˈɛtᵻkɛt/ or /ˈɛtᵻkɪt/, French: [e.ti.kɛt])
Make pull requests for your changes, do not commit directly to master (release stuff like fixing changelog are ok though).
All the pull requests must be peer reviewed by other collaborator, so don't merge your request before that. If there is no response ping others.
If you are going to add new feature to knex (not just a bugfix) it should be discussed first with others to agree on details.
Join Gitter chat if you feel to chat outside of github issues.
-22
View File
@@ -1,22 +0,0 @@
Copyright (c) 2013-present Tim Griesser
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-93
View File
@@ -1,93 +0,0 @@
# [knex.js](http://knexjs.org)
[![npm version](http://img.shields.io/npm/v/knex.svg)](https://npmjs.org/package/knex)
[![npm downloads](https://img.shields.io/npm/dm/knex.svg)](https://npmjs.org/package/knex)
[![Build Status](https://travis-ci.org/tgriesser/knex.svg?branch=master)](https://travis-ci.org/tgriesser/knex)
[![Coverage Status](https://coveralls.io/repos/tgriesser/knex/badge.svg?branch=master)](https://coveralls.io/r/tgriesser/knex?branch=master)
[![Dependencies Status](https://david-dm.org/tgriesser/knex.svg)](https://david-dm.org/tgriesser/knex)
[![Gitter chat](https://badges.gitter.im/tgriesser/knex.svg)](https://gitter.im/tgriesser/knex)
[![Language Grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/tgriesser/knex.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/tgriesser/knex/context:javascript)
> **A SQL query builder that is _flexible_, _portable_, and _fun_ to use!**
A batteries-included, multi-dialect (MSSQL, MySQL, PostgreSQL, SQLite3, Oracle (including Oracle Wallet Authentication)) query builder for
Node.js, featuring:
- [transactions](http://knexjs.org/#Transactions)
- [connection pooling](http://knexjs.org/#Installation-pooling)
- [streaming queries](http://knexjs.org/#Interfaces-Streams)
- both a [promise](http://knexjs.org/#Interfaces-Promises) and [callback](http://knexjs.org/#Interfaces-Callbacks) API
- a [thorough test suite](https://travis-ci.org/tgriesser/knex)
- the ability to [run in the Browser](http://knexjs.org/#Installation-browser)
Node.js versions 8+ are supported.
[Read the full documentation to get started!](http://knexjs.org)
[Or check out our Recipes wiki to search for solutions to some specific problems](https://github.com/tgriesser/knex/wiki/Recipes)
If upgrading from older version, see [Upgrading instructions](https://github.com/tgriesser/knex/blob/master/UPGRADING.md)
For support and questions, join the `#bookshelf` channel on freenode IRC
For an Object Relational Mapper, see:
- http://bookshelfjs.org
- https://github.com/Vincit/objection.js
To see the SQL that Knex will generate for a given query, see: [Knex Query Lab](http://michaelavila.com/knex-querylab/)
## Examples
We have several examples [on the website](http://knexjs.org). Here is the first one to get you started:
```js
const knex = require('knex')({
dialect: 'sqlite3',
connection: {
filename: './data.db',
},
});
// Create a table
knex.schema
.createTable('users', function(table) {
table.increments('id');
table.string('user_name');
})
// ...and another
.createTable('accounts', function(table) {
table.increments('id');
table.string('account_name');
table
.integer('user_id')
.unsigned()
.references('users.id');
})
// Then query the table...
.then(function() {
return knex('users').insert({ user_name: 'Tim' });
})
// ...and using the insert id, insert into the other table.
.then(function(rows) {
return knex('accounts').insert({ account_name: 'knex', user_id: rows[0] });
})
// Query both of the rows.
.then(function() {
return knex('users')
.join('accounts', 'users.id', 'accounts.user_id')
.select('users.user_name as user', 'accounts.account_name as account');
})
// .map over the results
.map(function(row) {
console.log(row);
})
// Finally, add a .catch handler for the promise chain
.catch(function(e) {
console.error(e);
});
```
-203
View File
@@ -1,203 +0,0 @@
{
"_from": "knex@0.19.2",
"_id": "knex@0.19.2",
"_inBundle": false,
"_integrity": "sha512-TVYvlp2esS4LjjJSz8XuE48bPJq4N3lWnETQVgJ3hXPEqjiDjxcTa3bCn6F5ipQuBaMAAaFHNrqsZm7BttogdA==",
"_location": "/knex",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "knex@0.19.2",
"name": "knex",
"escapedName": "knex",
"rawSpec": "0.19.2",
"saveSpec": null,
"fetchSpec": "0.19.2"
},
"_requiredBy": [
"#USER",
"/",
"/@types/knex",
"/frontblock-generic"
],
"_resolved": "https://registry.npmjs.org/knex/-/knex-0.19.2.tgz",
"_shasum": "056efdb33fb8c77d3d76266b5d1d12dc483c21b5",
"_spec": "knex@0.19.2",
"_where": "/home/cake/FB/development/repos/vendor/admin",
"author": {
"name": "Tim Griesser",
"url": "https://github.com/tgriesser"
},
"bin": {
"knex": "./bin/cli.js"
},
"browser": {
"./lib/migrate/Migrator.js": "./lib/util/noop.js",
"./lib/bin/cli.js": "./lib/util/noop.js",
"./lib/seed/Seeder.js": "./lib/util/noop.js",
"mssql": false,
"mssql/lib/base": false,
"tedious": false,
"mysql": false,
"mysql2": false,
"pg": false,
"pg-query-stream": false,
"oracle": false,
"sqlite3": false,
"oracledb": false
},
"bugs": {
"url": "https://github.com/tgriesser/knex/issues"
},
"buildDependencies": [
"rimraf"
],
"bundleDependencies": false,
"contributors": [
{
"name": "Simon Liden"
},
{
"name": "Mikael Lepisto"
},
{
"name": "Paul Gaurab",
"url": "https://lorefnon.tech"
},
{
"name": "Igor Savin",
"url": "https://www.codeflashbacks.com"
}
],
"dependencies": {
"bluebird": "^3.5.5",
"colorette": "1.0.8",
"commander": "^2.20.0",
"debug": "4.1.1",
"getopts": "2.2.4",
"inherits": "~2.0.4",
"interpret": "^1.2.0",
"liftoff": "3.1.0",
"lodash": "^4.17.15",
"mkdirp": "^0.5.1",
"pg-connection-string": "2.0.0",
"tarn": "^2.0.0",
"tildify": "2.0.0",
"uuid": "^3.3.2",
"v8flags": "^3.1.3"
},
"deprecated": false,
"description": "A batteries-included SQL query & schema builder for Postgres, MySQL and SQLite3 and the Browser",
"devDependencies": {
"@types/node": "^10.14.13",
"JSONStream": "^1.3.5",
"chai": "^4.2.0",
"chai-subset-in-order": "^2.1.3",
"cli-testlab": "^1.7.0",
"coveralls": "^3.0.5",
"cross-env": "^5.2.0",
"dtslint": "^0.9.0",
"eslint": "^6.1.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"husky": "^3.0.1",
"jake": "^8.1.1",
"lint-staged": "^9.2.0",
"mocha": "^6.2.0",
"mock-fs": "^4.10.1",
"mssql": "^5.1.0",
"mysql": "^2.17.1",
"mysql2": "^1.6.5",
"nyc": "^14.1.1",
"pg": "^7.11.0",
"pg-query-stream": "^2.0.0",
"prettier": "^1.18.2",
"rimraf": "^2.6.3",
"sinon": "^7.3.2",
"sinon-chai": "^3.3.0",
"source-map-support": "^0.5.12",
"sqlite3": "^4.0.9",
"tap-spec": "^5.0.0",
"tape": "^4.11.0",
"toxiproxy-node-client": "^2.0.6",
"typescript": "^3.5.3",
"webpack-cli": "^3.3.6"
},
"engines": {
"node": ">=8"
},
"files": [
"CONTRIBUTING.md",
"README.md",
"bin/*",
"lib/*",
"lib/*",
"knex.js",
"LICENSE",
"CHANGELOG.md",
"scripts/*",
"types/index.d.ts",
"types/result.d.ts"
],
"homepage": "https://knexjs.org",
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"keywords": [
"sql",
"query",
"postgresql",
"mysql",
"sqlite3",
"oracle",
"mssql"
],
"license": "MIT",
"lint-staged": {
"*.{js,json}": [
"prettier --write",
"git add"
]
},
"main": "knex.js",
"name": "knex",
"nyc": {
"check-coverage": true,
"lines": 84,
"statements": 82,
"functions": 83,
"branches": 69
},
"react-native": {
"./lib/migrate": "./lib/util/noop.js",
"./lib/seed": "./lib/util/noop.js"
},
"repository": {
"type": "git",
"url": "git://github.com/tgriesser/knex.git"
},
"scripts": {
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"db:start": "docker-compose -f scripts/docker-compose.yml up --build -d mysql oracledbxe postgres mssql; docker-compose -f scripts/docker-compose.yml up initmssqlknexdb waitmysql waitpostgres waitoracledbxe",
"db:stop": "docker-compose -f scripts/docker-compose.yml down",
"debug:tape": "node --inspect-brk test/tape/index.js",
"debug:test": "mocha --inspect-brk --exit -t 0 test/index.js",
"format": "prettier --write \"{lib,bin,scripts,test}/**/*.js\"",
"lint": "eslint \"lib/**/*.js\" \"test/**/*.js\"",
"lint:types": "dtslint types",
"stress:destroy": "docker-compose -f scripts/stress-test/docker-compose.yml stop",
"stress:init": "docker-compose -f scripts/stress-test/docker-compose.yml up --no-start && docker-compose -f scripts/stress-test/docker-compose.yml start",
"stress:test": "node scripts/stress-test/knex-stress-test.js | grep -A 5 -B 60 -- '- STATS '",
"test": "mocha --exit -t 10000 test/index.js && npm run test:tape && npm run test:cli",
"test:cli": "cross-env KNEX_PATH=../knex.js KNEX=bin/cli.js jake -f test/jake/Jakefile",
"test:nyc": "nyc mocha --exit --check-leaks --globals __core-js_shared__ -t 10000 test/index.js && npm run test:tape && npm run test:cli",
"test:sqlite": "cross-env DB=sqlite3 npm test",
"test:tape": "node test/tape/index.js | tap-spec"
},
"tonicExampleFilename": "scripts/runkit-example.js",
"types": "types/index.d.ts",
"version": "0.19.2"
}
-97
View File
@@ -1,97 +0,0 @@
version: '3'
services:
mssql:
image: microsoft/mssql-server-linux:2017-latest
ports:
- '21433:1433'
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=S0meVeryHardPassword
healthcheck:
test: /opt/mssql-tools/bin/sqlcmd -S mssql -U sa -P 'S0meVeryHardPassword' -Q 'select 1'
initmssqlknexdb:
image: microsoft/mssql-server-linux:2017-latest
links:
- mssql
depends_on:
- mssql
entrypoint:
- bash
- -c
- 'until /opt/mssql-tools/bin/sqlcmd -S mssql -U sa -P S0meVeryHardPassword -d master -Q "CREATE DATABASE knex_test"; do sleep 5; done'
mysql:
image: mysql
command: --default-authentication-plugin=mysql_native_password
ports:
- '23306:3306'
environment:
- MYSQL_ROOT_PASSWORD=testrootpassword
- MYSQL_DATABASE=knex_test
- MYSQL_USER=testuser
- MYSQL_PASSWORD=testpassword
healthcheck:
test:
[
'CMD',
'/usr/bin/mysql',
'-hlocalhost',
'-utestuser',
'-ptestpassword',
'-e',
'SELECT 1',
]
interval: 30s
timeout: 5s
retries: 3
restart: always
waitmysql:
image: mysql
links:
- mysql
depends_on:
- mysql
entrypoint:
- bash
- -c
- 'until /usr/bin/mysql -hmysql -utestuser -ptestpassword -e "SELECT 1"; do sleep 5; done'
postgres:
image: postgres:alpine
ports:
- '25432:5432'
environment:
- POSTGRES_USER=testuser
- POSTGRES_PASSWORD=knextest
- POSTGRES_DB=knex_test
waitpostgres:
image: postgres:alpine
links:
- postgres
depends_on:
- postgres
entrypoint:
- bash
- -c
- 'until /usr/local/bin/psql postgres://testuser:knextest@postgres/knex_test -c "SELECT 1"; do sleep 5; done'
oracledbxe:
image: quillbuilduser/oracle-18-xe
container_name: oracledbxe_container
ports:
- '21521:1521'
environment:
- ORACLE_ALLOW_REMOTE=true
waitoracledbxe:
image: quillbuilduser/oracle-18-xe
links:
- oracledbxe
depends_on:
- oracledbxe
environment:
- ORACLE_HOME=/opt/oracle/product/18c/dbhomeXE
entrypoint:
- bash
- -c
- 'until /opt/oracle/product/18c/dbhomeXE/bin/sqlplus -s sys/Oracle18@oracledbxe/XE as sysdba <<< "SELECT 13376411 FROM DUAL; exit;" | grep "13376411"; do echo "Could not connect to oracle... sleep for a while"; sleep 5; done'
-24
View File
@@ -1,24 +0,0 @@
# Checklist for crating knex @next releases
1. Go through all commits since the last release and add them to CHANGELOG.md under unreleased changes section.
2. Commit changes to CHANGELOG
3. Check that master compiles and tests are running fine (check also that CI tests are passing)
```
npm run build
# run bunch of tests, but skipping coverage which doesn't really work locally at least
npm plaintest
npm bin_test
npm oracledb:test
npm mssql:init
npm mssql:test
npm mssql:destroy
```
4. Update package.json version to be e.g. 0.16.0-next1 or 0.16.0-next2 and commit yo master
5. Publish it under @next tag
```
npm publish --tag next
```
-34
View File
@@ -1,34 +0,0 @@
#!/bin/bash -e
changelog=node_modules/.bin/changelog
update_version() {
echo "$(node -p "p=require('./${1}');p.version='${2}';JSON.stringify(p,null,2)")" > $1
echo "Updated ${1} version to ${2}"
}
current_version=$(node -p "require('./package').version")
printf "Next version (current is $current_version)? "
read next_version
if ! [[ $next_version =~ ^[0-9]\.[0-9]+\.[0-9](-.+)? ]]; then
echo "Version must be a valid semver string, e.g. 1.0.2 or 2.3.0-beta.1"
exit 1
fi
next_ref="v$next_version"
git add -u
npm run build
npm test
update_version 'package.json' $next_version
git commit -am "release $next_version"
git tag $next_version
git push --tags
npm publish
-18
View File
@@ -1,18 +0,0 @@
# Test scripts to evaluate stability of drivers / pool etc.
# To run this test you need to be in this directory + have node >= 8
# and startup docker containers with proxy and sql servers
docker-compose up --no-start
docker-compose start
# Select different test script to run:
node mysql2-random-hanging-every-now-and-then.js 2> /dev/null | grep -B500 -A2 -- "- STATS"
node mysql2-sudden-exit-without-error
node knex-stress-test.js | grep -A 3 -- "- STATS "
node reconnect-test-mysql-based-drivers.js 2> /dev/null | grep -A 3 -- "- STATS "
# Shut down docker instances when done:
docker-compose down
@@ -1,47 +0,0 @@
version: '3'
services:
toxiproxy:
image: shopify/toxiproxy
ports:
- "8474:8474"
- "23306:23306"
- "25432:25432"
- "21521:21521"
- "21433:21433"
links:
- "mysql"
- "postgresql"
- "oracledbxe"
- "mssql"
mysql:
image: mysql:5.7
ports:
- "33306:3306"
environment:
- TZ=UTC
- MYSQL_ROOT_PASSWORD=mysqlrootpassword
postgresql:
image: mdillon/postgis
ports:
- "35432:5432"
environment:
- POSTGRES_PASSWORD=postgresrootpassword
- POSTGRES_USER=postgres
oracledbxe:
image: wnameless/oracle-xe-11g
ports:
- "31521:1521"
environment:
- ORACLE_ALLOW_REMOTE=true
mssql:
image: microsoft/mssql-server-linux:2017-latest
ports:
- "31433:1433"
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=S0meVeryHardPassword
+304 -378
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -9,8 +9,8 @@
"build-backend": "tsc; npm run webpack",
"build-frontend": "npm run build-dashboard; cp ./dist/FrontblockLib.js ./dist/static",
"build-dashboard": "git submodule init && git submodule update --merge; cd src/frontend; npm i && npm run build; mkdir ../../dist/static; cp -r dist/* ../../dist/static",
"clean": "rm -rf lib static plugins conf dist widget .rpt2_cache *.js *.ts src/frontend/dist",
"update-frontblock": "rm -rf node_modules/frontblock*; npm install",
"clean": "rm -rf lib static plugins conf dist widget .rpt2_cache *.js *.ts src/frontend/dist data",
"update-frontblock": "npm remove frontblock frontblock-generic; npm install frontblock-generic@latest frontblock@latest",
"webpack": "webpack --config src/backend/webpack.prod.js --progress --colors"
},
"repository": {
@@ -25,13 +25,13 @@
"child-process-promise": "^2.2.1",
"debug": "^4.1.1",
"express": "^4.16.4",
"frontblock": "^0.14.0",
"frontblock-generic": "^0.30.6",
"frontblock": "^0.15.1",
"frontblock-generic": "^0.34.8",
"git-cherrypicker": "0.0.3",
"git-describe": "^4.0.4",
"http": "0.0.0",
"knex": "^0.19.2",
"log4js": "^4.4.0",
"log4js": "^4.5.1",
"lowdb": "^1.0.0",
"node-fetch": "^2.6.0",
"path": "^0.12.7",
+171 -151
View File
@@ -1,41 +1,32 @@
'use strict'
import * as Logger from 'log4js'
import * as Knex from 'knex'
import * as Path from 'path'
import { FrontblockApiClient, FrontblockApiConf } from 'frontblock';
import { AdminBase } from 'frontblock-generic/Admin';
import { FrontblockApi } from 'frontblock-generic/Api';
import { Plugin } from 'frontblock-generic/Plugin';
import { socketioRPC } from 'frontblock-generic/RPC';
import { GitUpdater, RepoFolderStatus } from './GitUpdater';
import { getLogger, SubscriptionResponse, Coin, AccountMap, TransactionMap } from 'frontblock-generic/Types';
import { AdminBase, NotificationSeverity, TableDefiniton } from 'frontblock-generic/Admin';
import { IFrontblockApiClient, FrontblockApi } from 'frontblock-generic/Api';
import { Plugin, RPCExporter } from 'frontblock-generic/Plugin';
Logger.configure({
appenders:
{
"admin": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories:
{
default: { appenders: [ 'admin' ], level: 'debug' }
}
})
const logger = Logger.getLogger("admin")
const logger:Logger.Logger = getLogger("admin", 'debug')
export type AdminConf = { httpPort: number}
export type AdminConf = {
httpPort: number,
apiConf: FrontblockApiConf
dbConf:Knex.Config,
eventBusConf: { [topic in string]: NotificationSeverity}
}
export class FrontblockAdmin extends AdminBase<AdminConf>{
private pluginUpdaters:{[name in string]:GitUpdater} = {}
constructor(runningPlugins: Plugin[] = []){
super(runningPlugins)
}
getDefaultConfig(): { apiConf: FrontblockApiConf; } & AdminConf & { dbConf:Knex.Config; } {
public apiClient: IFrontblockApiClient = this.makeApiClient(this.getConfigKey("apiConf"))
getDefaultConfig(): AdminConf {
return {
httpPort: 8080,
eventBusConf: {
},
apiConf: {
apiHost: "api.testnet.frontblock.me",
apiKey: "",
@@ -45,144 +36,173 @@ export class FrontblockAdmin extends AdminBase<AdminConf>{
dbConf: {
client: 'sqlite3',
connection: {
filename: "./data/ApiClient.sqlite"
filename: Path.join(__dirname, "data/Admin.sqlite")
},
useNullAsDefault: true
}
}
}
constructor(runningPlugins: Plugin[] = []){
super(runningPlugins)
this.initialize().then(() => this.makeKnex(this.getConfigKey('dbConf'))).then(() => logger.debug("Admin initialization finished"))
}
protected makeApiClient(conf: FrontblockApiConf): FrontblockApi {
// @ts-ignore
initApis(socket){
const quit = async(uid:string) => {return await this.quit(this.getConfigKey('apiConf').apiKey, uid) }
const exporter:RPCExporter = {
name: 'ApiClient',
exportRPCs: () => {
return [{
name: "subscribe",
func: async <C extends Coin>(coin: C, account: AccountMap[C]) => { return await this.subscribe(this.getConfigKey('apiConf').apiKey, coin, account)},
type: 'call',
visibility: 'private'
},{
name: "subsume",
func: async <C extends Coin>(coin: C, account: AccountMap[C], callback: (tx: TransactionMap[C]) => void) => { return await this.subsume(this.getConfigKey('apiConf').apiKey, coin, account, callback)},
type: 'hook',
unhook: quit,
visibility: 'private'
},{
name: 'unsubscribe',
func: async(uid:string) => { return await this.unsubscribe(this.getConfigKey('apiConf').apiKey, uid) },
type: 'call',
visibility: 'private'
},{
name: 'consume',
func: async<C extends Coin>(uid: string, callback: (tx: TransactionMap[C]) => void) => {return await this.consume(this.getConfigKey('apiConf').apiKey, uid, callback)},
type: 'hook',
unhook: quit,
visibility: 'private'
},{
name: 'quit',
func: quit,
type: 'call',
visibility: 'private'
},{
name: "getSubscriptions",
func: async() => { return await this.getSubscriptions() },
type: "call",
visibility: "private"
},{
name: "getConsumers",
func: async() => { return await this.getConsumers() },
type: "call",
visibility: "private"
}]
}
}
super.initApis(socket, [exporter])
}
async getConsumers(): Promise<SubscriptionResponse[]> {
const records = await this.knex.select('*').from('consumers')
return records.map(r => JSON.parse(r.JSON))
}
async getSubscriptions(): Promise<SubscriptionResponse[]> {
const records = await this.knex.select('*').from('subscriptions')
return records.map(r => JSON.parse(r.JSON))
}
private subscribe:FrontblockApi['subscribe'] = async (apiKey, coin, account) => {
const res = await this.apiClient.subscribe(apiKey, coin, account)
if(res instanceof SubscriptionResponse){
await this.insert('subscriptions', [{uuid: res.uid, coin:coin, JSON: JSON.stringify(res)}], "apiclient", "Info")
}
return res
}
private consume:FrontblockApi['consume'] = async (apikey, uuid, callback) => {
const res = await this.apiClient.consume(apikey, uuid, callback)
if(res instanceof SubscriptionResponse){
await this.insert('consumers', [{uuid: res.uid, coin:undefined, JSON: JSON.stringify(res)}], "apiclient", "Info")
}
return res
}
private unsubscribe:FrontblockApi['unsubscribe'] = async (apikey, uuid) => {
const res = await this.apiClient.unsubscribe(apikey, uuid)
await this.knex('subscriptions')
.where('uuid', uuid)
.del()
return res
}
private quit:FrontblockApi['quit'] = async (apikey, uuid) => {
const res = await this.apiClient.quit(apikey, uuid)
await this.knex('consumers')
.where('uuid', uuid)
.del()
return res
}
private subsume:FrontblockApi['subsume'] = async (apikey, coin, account, callback) => {
const res = await this.apiClient.subsume(apikey, coin, account, callback)
if(res instanceof SubscriptionResponse){
await Promise.all([
this.insert('consumers', [{
uuid: res.uid,
coin:coin,
JSON: JSON.stringify(res)
}], "apiclient", "Info"),
this.insert('subscriptions', [{
uuid: res.uid,
coin:coin,
JSON: JSON.stringify(new SubscriptionResponse(res.message!))
}], "apiclient", "Info")
])
}
return res
}
setConfig(conf:AdminConf & { dbConf: Knex.Config; eventBusConf: {[x in string]:NotificationSeverity} }):AdminConf & { dbConf: Knex.Config; eventBusConf: {[x in string]:NotificationSeverity} }{
const currConf = this.getConfig()
if(JSON.stringify(currConf.apiConf) !== JSON.stringify(conf.apiConf)){
this.makeApiClient(conf.apiConf)
}
return super.setConfig(conf)
}
setConfigKey(key: string, value: any):AdminConf & { dbConf: Knex.Config; eventBusConf: {[x in string]:NotificationSeverity} }{
if(key === 'apiConf'){
this.makeApiClient(value)
}
return super.setConfigKey(key, value)
}
getTableDefinitions(): TableDefiniton[] {
return [
...super.getTableDefinitions(),
{
name: 'subscriptions',
tableBuilder: function (table) {
table.string('uuid').primary();
table.string('coin');
table.string('JSON');
}
},{
name: 'consumers',
tableBuilder: function (table) {
table.string('uuid').primary();
table.string('coin');
table.string('JSON');
}
}]
}
protected makeApiClient(conf: FrontblockApiConf): IFrontblockApiClient {
if(this.apiClient) this.apiClient.disconnect()
this.apiClient = new FrontblockApiClient(conf)
// @ts-ignore
this.apiClient.connect()
return this.apiClient
}
exportRPCs():socketioRPC[]{
return [
...super.exportRPCs(),
{
name: 'installPlugin',
func: async (name:string, force = false) => {return await this.installPlugin(name, force)},
type: 'call',
visibility: 'private'
},{
name: 'startPlugin',
func: async (name:string) => {return await this.startPlugin(name)},
type: 'call',
visibility: 'private'
},{
name: 'updatePlugin',
func: async (name) => {return await this.updatePlugin(name)},
type: 'call',
visibility: 'private'
},{
name: 'setPluginVersion',
func: async (name, tag) => {return await this.setPluginVersion(name, tag)},
type: 'call',
visibility: 'private'
},{
name: 'getLoadedPluginNames',
func: async () => {return await this.getPlugins().map(p => p.name)},
type: 'call',
visibility: 'private'
},{
name: 'selfUpdate',
type:'call',
func: async (force: boolean) => {return await this.selfUpdate(force)},
visibility: 'private'
},
]
}
private async selfUpdate(force:boolean = false){
const updater = new GitUpdater("./dist")
let status = await updater.getStatus()
if(force || !status.remote || !status.remote.includes("fb-dist/admin") || !status.exists || status.empty || !status.currentTag){
logger.warn("Cloning fb-dist/admin into ./dist ..."+(force?" USING FORCE!":""))
status = await updater.cloneRepo("https://gitea.frontblock.me/fb-dist/admin.git", force)
this.destroy()
const installer = require("./Installer").install
installer(this.getPlugins())
}
return status
}
async installPlugin(name: string, force:boolean = false):Promise<RepoFolderStatus>{
logger.warn("Cloning fb-dist/"+name+".git into ./plugins/"+name+" ..."+(force?" USING FORCE!":""))
this.pluginUpdaters[name] = new GitUpdater("./plugins/"+name)
const status = await this.pluginUpdaters[name].cloneRepo("https://gitea.frontblock.me/fb-dist/"+name.toLowerCase()+".git", force)
return status
}
async startPlugin(name:string):Promise<boolean>{
if(!this.pluginUpdaters[name]) return false
const status = await this.pluginUpdaters[name].getStatus()
if(!status.exists || status.empty || !status.tags || status.tags.length === 0){
if(status.currentTag && !status.latestTag){
//git glitches sometimes if you check immediately after clone
logger.warn("re-fetching tag for "+name+"...")
return await this.startPlugin(name)
}
logger.error("Bad repo status", name, status)
return false
}
/* if(this.loadedPlugins[name]){
logger.error("Plugin", name, "is already started")
return false
}
*/
let str = "../plugins/"+name+"/Plugin"
const pluginClass = await eval('require')(str)
const pluginObj = new pluginClass.default()
await pluginObj.start()
this.addPlugin(pluginObj)
return true
}
async updatePlugin(name:string):Promise<boolean>{
if(!this.pluginUpdaters[name]) return false
const status = await this.pluginUpdaters[name].getStatus()
if(!status.exists || status.empty || !status.tags || status.tags.length === 0){
logger.error("Bad repo status", name, status)
return false
}
if(status.currentTag == status.latestTag){
logger.warn(name, "already at latest tag")
return false
}
/*
if(this.loadedPlugins[name]){
logger.error("Plugin", name, "is running. Stop it first")
return false
}
*/
this.pluginUpdaters[name].checkoutTag(status.latestTag!)
return true
}
async setPluginVersion(pluginName:string, tag:string):Promise<RepoFolderStatus>{
const status = await this.pluginUpdaters[pluginName].getStatus()
if(!status.exists || !status.tags || status.tags.length === 0 || !status.tags.includes(tag)){
logger.error("Bad repo status", pluginName, status)
return status
}
return await this.pluginUpdaters[pluginName].checkoutTag(tag)
}
}
process.on( 'SIGINT', function() {
logger.info( "Gracefully shutting down from SIGINT (Ctrl-C)" );
// some other closing procedures go here
process.exit( );
process.exit(0);
})
+5 -18
View File
@@ -1,35 +1,22 @@
import * as Logger from 'log4js'
import { Plugin } from "frontblock-generic/Plugin"
import { getLogger } from "frontblock-generic/Types"
var exec = require('child-process-promise').exec;
Logger.configure({
appenders:
{
"installer": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories:
{
default: { appenders: [ 'installer' ], level: 'debug' }
}
})
const logger = Logger.getLogger("installer")
const logger = getLogger("installer", 'info')
export type NPMPkgName = string
export type NPMVersion = string
export const install = (plugins: Plugin[] = []) => {
export const installAdmin = (plugins: Plugin[] = []) => {
const npmPkgs:[NPMPkgName, NPMVersion][] = [['sqlite3', '4.1.0'], ['knex', '0.19.2']]
const deps = npmPkgs.map(tuple => tuple.join('@') ).join(" ")
logger.info("Installing plaform dependencies: "+deps)
exec("npm i --prefix ./plugins " + deps).then(process => {
exec("npm i " + deps).then(async process => {
logger.debug(process.stdout)
const Admin = require("./Admin").FrontblockAdmin
new Admin(plugins)
const fbAdmin = new Admin(plugins)
})
}
+2 -2
View File
@@ -1,2 +1,2 @@
import { install } from "./Installer";
install([])
import { installAdmin } from "./Installer";
installAdmin([])
+6 -7
View File
@@ -5,9 +5,13 @@ module.exports = [{
target: "web",
entry: path.resolve(__dirname, '../../lib/FrontblockLib.js'),
externals: ['log4js'],
output: {
path: path.resolve(__dirname, '../../dist'),
filename: 'FrontblockLib.js',
},
optimization: {
minimize: false, // <---- disables uglify.
}
},{
mode: 'production',
@@ -19,7 +23,6 @@ module.exports = [{
__dirname: false,
Buffer: true,
},
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
@@ -32,9 +35,7 @@ module.exports = [{
]
},
externals:{
knex: "../plugins/node_modules/knex"
},
externals: ['knex'],
optimization: {
minimize: false
},
@@ -67,9 +68,7 @@ module.exports = [{
]
},
externals:{
"./Installer": "./Installer"
},
externals:["./Installer"],
optimization: {
minimize: false
},
+3 -3
View File
@@ -1,6 +1,6 @@
[submodule "src/app/apiclient"]
path = src/app/apiclient
url = ssh://git@gitea.frontblock.me:2222/fb-plugin/apiclient.git
[submodule "src/app/paymentmanager"]
path = src/app/paymentmanager
url = ssh://git@gitea.frontblock.me:2222/fb-plugin/paymentmanager.git
[submodule "src/app/wallet"]
path = src/app/wallet
url = ssh://git@gitea.frontblock.me:2222/fb-plugin/wallet.git
+7 -5
View File
@@ -15,8 +15,10 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {"path": "./custom-webpack.config.js"},
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
@@ -78,7 +80,7 @@
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"builder": "@angular-builders/custom-webpack:dev-server",
"tsConfig": "tsconfig.app.json",
"options": {
"browserTarget": "dashboard:build"
@@ -99,7 +101,7 @@
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"builder": "@angular-builders/custom-webpack:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
@@ -116,7 +118,7 @@
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"builder": "@angular-builders/custom-webpack:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
@@ -130,7 +132,7 @@
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"builder": "@angular-builders/custom-webpack:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "dashboard:serve"
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
externals: ['log4js']
}
+180 -488
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -7,13 +7,13 @@
"start-prodlike": "npm run build-assets && ng serve --aot=false --optimization=false --proxy-config proxy.conf.json --configuration=prodlike",
"build": "rm -f src/assets/*.js; ng build --prod --aot=false --optimization=false --build-optimizer=false",
"build-assets": "npm run copy-frontends; npm run deep-clean-plugins",
"update-frontblock": "rm -rf node_modules/frontblock*; npm install",
"get-submodules": "git submodule update --init && git submodule foreach git checkout master",
"copy-frontends": "for module in $(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'); do npm i --prefix $module && npm run --prefix $module build && cp $module/dist/FrontendPlugin.js ./src/assets/$(basename $module).js; done",
"deep-clean-plugins": "for module in $(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'); do rm -rf $module/node_modules; done",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
"e2e": "ng e2e",
"update-frontblock": "npm remove frontblock frontblock-generic; npm install frontblock-generic@latest frontblock@latest"
},
"private": true,
"dependencies": {
@@ -32,8 +32,8 @@
"@webcomponents/custom-elements": "^1.0.0",
"btc-hdkey": "0.0.17",
"coinselect": "^3.1.11",
"frontblock": "latest",
"frontblock-generic": "^0.23.0",
"frontblock": "^0.15.1",
"frontblock-generic": "^0.34.1",
"key-file-storage": "^2.2.4",
"node-fetch": "^2.6.0",
"rxjs": "~6.5.2",
-69
View File
@@ -1,69 +0,0 @@
kind: pipeline
name: default
steps:
- name: restore cache
image: drillster/drone-volume-cache
settings:
restore: true
mount:
- ./node_modules
volumes:
- name: cache
path: /cache
- name: npm install
image: node:12
commands:
- npm install
- name: npm run build
image: node:12
commands:
- npm run build
- name: rebuild cache
image: drillster/drone-volume-cache
settings:
rebuild: true
mount:
- ./node_modules
volumes:
- name: cache
path: /cache
- name: npm publish
image: plugins/npm
settings:
username: frontblock
password:
from_secret: npm_password
email: frontblock.me@gmail.com
event:
- tag
- name: deploy plugin
image: node:12
commands:
- git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
- git config --global user.name "${DRONE_COMMIT_AUTHOR}"
- git clone https://gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git
- cp -r ./dist/* ./${DRONE_REPO_NAME}
- cd ./${DRONE_REPO_NAME}
- git add -A
- git commit --allow-empty -m "drone tagged as version ${DRONE_TAG}"
- git tag ${DRONE_TAG}
- git push https://$GIT_USER:$GIT_PASSWORD@gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git master ${DRONE_TAG}
environment:
GIT_USER:
from_secret: git_user
GIT_PASSWORD:
from_secret: git_password
when:
event:
- tag
volumes:
- name: cache
host:
path: /tmp
-10
View File
@@ -1,10 +0,0 @@
dist
.rpt2_cache
node_modules
lib
*.d.ts
*.js
*.ts
!src/**/*
@@ -1,2 +0,0 @@
**/*
!FrontblockApiClient.*
@@ -1,16 +1,15 @@
import { Component, OnInit } from '@angular/core';
import { FrontblockApiConf } from '../backend/FrontblockApiClient';
import { isDevMode } from '@angular/core';
declare const fb
@Component({
selector: 'settings',
selector: '[apiclientconfig]',
template: `
<div class="clr-row">
<div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-auto clr-col-xl-auto">
<div class="card-header">
Settings
Frontblock API Client
</div>
<div class="card-block">
<div class="card-text">
@@ -61,15 +60,13 @@ declare const fb
</div>
</div>
</div>
<knex-config fbPlugin="ApiClient"></knex-config>
`
})
export class ApiclientFormComponent implements OnInit {
testnet: boolean = true
saving: boolean = false
advanced: boolean = false
data: FrontblockApiConf = {
data: any = {
apiHost: "api.testnet.frontblock.me",
apiPort: 10001,
tls: false,
@@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
declare const fb
@Component({
selector: '[apiclientconfig]',
template: `
consumers {{consumers}} subscribers {{subscribers}}
`
})
export class ApiclientWidgetComponent implements OnInit {
consumers = 0
subscribers = 0
constructor() { }
async ngOnInit() {
await new Promise((resolve, reject)=>{
let awaitAdmin: { (): void; (...args: any[]): void; }
(awaitAdmin = () => {
if(fb.ApiClient != null){
resolve()
}
setTimeout(awaitAdmin,25)
})()
})
fb.ApiClient.getConsumers().then(cons => this.consumers = cons.length)
fb.ApiClient.getSubscriptions().then(subs => this.subscribers = subs.length)
}
}
@@ -1,17 +0,0 @@
{
"apiConf": {
"apiHost": "api.testnet.frontblock.me",
"apiKey": "",
"apiPort": 10001,
"tls": false
},
"dbConf": {
"type": "sqlite",
"database": "./data/ApiClient.sqlite",
"synchronize": true,
"entities": [
null,
null
]
}
}
@@ -6,47 +6,56 @@ import { ApiclientFormComponent } from './apiclient-settings-form.component';
import { ApiclientConsumptionComponent } from './apiclient-consumptions.component';
import { ApiclientSubscriptionComponent } from './apiclient-subscriptions.component';
import { FrontendPlugin, SidebarEntries } from 'frontblock-generic/Plugin';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
// @ts-ignore
import { KnexConfigModule } from "../../../knex-config/knex-config.module";
import {FrontendPlugin, SidebarEntries, SidebarEntry} from 'frontblock-generic/Plugin'
import { ApiclientWidgetComponent } from './apiclient-widget.component';
@NgModule({
imports: [
FormsModule,
ClarityModule,
KnexConfigModule,
CommonModule,
RouterModule.forChild([
{path: "settings", component: ApiclientFormComponent},
{path: "subscriptions", component: ApiclientSubscriptionComponent},
{path: "consumers", component: ApiclientConsumptionComponent},
]),
],
exports: [RouterModule],
exports: [RouterModule, ApiclientFormComponent],
declarations: [
ApiclientFormComponent,
ApiclientConsumptionComponent,
ApiclientSubscriptionComponent
ApiclientSubscriptionComponent,
ApiclientFormComponent,
ApiclientWidgetComponent
],
entryComponents: [
ApiclientFormComponent,
ApiclientWidgetComponent
]
})
export class PluginModule implements FrontendPlugin{
getSidebarEntry(): SidebarEntries {
export class ApiclientModule implements FrontendPlugin<typeof ApiclientFormComponent, typeof ApiclientWidgetComponent>{
getSidebarEntry(): SidebarEntry | SidebarEntries {
return {
icon: "terminal",
parentRoute: "apiclient",
text: "Api client",
links: [{
route: "settings",
text: "Settings"
},{
route: "consumers",
route: "apiclient/consumers",
text: "Consumers"
},{
route: "subscriptions",
route: "apiclient/subscriptions",
text: "Subscriptions"
},]
}]
}
}
getSettingsComponent(): typeof ApiclientFormComponent{
return ApiclientFormComponent
}
getWidget(): typeof ApiclientWidgetComponent{
return ApiclientWidgetComponent
}
}
File diff suppressed because it is too large Load Diff
@@ -1,45 +0,0 @@
{
"name": "frontblock",
"version": "0.14.0",
"description": "frontblock shop-side library ",
"main": "FrontblockApiClient.js",
"scripts": {
"tsc": "tsc",
"build-backend": "webpack --config src/backend/webpack.prod.js --progress --colors",
"build-frontend": "webpack --config src/frontend/webpack.prod.js --progress --colors",
"build": "npm run clean; npm run tsc; npm run build-backend; npm run build-frontend",
"clean": "rm -rf *.js *.ts backend frontend .rpt2_cache dist lib",
"update-frontblock": "rm -rf node_modules/frontblock*; npm install"
},
"keywords": [
"api",
"client",
"frontblock"
],
"author": "",
"license": "ISC",
"dependencies": {
"@angular/common": "^8.2.1",
"@angular/core": "^8.2.1",
"@angular/forms": "^8.2.1",
"@angular/platform-browser": "^8.2.1",
"@angular/router": "^8.2.1",
"@clr/angular": "^2.1.1",
"@types/node": "^11.13.10",
"frontblock-generic": "^0.28.4",
"fs": "0.0.1-security",
"knex": "^0.19.2",
"log4js": "^4.3.1",
"node-fetch": "^2.5.0",
"path": "^0.12.7",
"rxjs": "^6.5.2",
"rxjs-compat": "^6.5.2"
},
"devDependencies": {
"ts-loader": "^6.0.4",
"typescript": "^3.4.5",
"webpack": "^4.39.1",
"webpack-cli": "^3.3.6",
"webpack-node-externals": "^1.7.2"
}
}
@@ -1,104 +0,0 @@
import { Coin, AccountMap, TransactionMap, SubscriptionResponse, ErrorResponse, SuccessResponse, parseResponse, parseSubResponse } from "frontblock-generic/Types";
import { FrontblockApi as FrontblockApi } from "frontblock-generic/Api";
import * as Logger from 'log4js'
Logger.configure({
appenders: {
"frontblock-api-client": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories: {
default: { appenders: ['frontblock-api-client'], level: 'debug' }
}
})
const logger = Logger.getLogger("frontblock-api-client")
const bsock = require('bsock')
export type FrontblockApiConf = {
apiHost: string
apiPort: number
tls?: boolean
apiKey?: string
}
/**
* Frontblock api connection lib
*/
export class FrontblockApiClient implements FrontblockApi {
protected started: boolean = false
protected apikey: string = ""
protected socket
constructor(private conf: FrontblockApiConf){
}
async consume<C extends Coin>(apikey: string, uid: string, callback: (tx: TransactionMap[C]) => void): Promise<SubscriptionResponse | ErrorResponse> {
const r = await this.socket.call("consume", apikey, uid)
const res = parseSubResponse(r)
if (res instanceof SubscriptionResponse) {
this.socket.hook(res.uid, (tx) => {
const deserialized = <TransactionMap[C]>JSON.parse(tx)
callback(deserialized)
})
}
return res
}
async quit(apikey: string, consumerUid: string): Promise<ErrorResponse | SuccessResponse> {
const r = await this.socket.call("quit", apikey, consumerUid)
const res = parseResponse(r)
return res
}
async subscribe<C extends Coin>(apikey: string, coin: C, account: AccountMap[C]): Promise<SubscriptionResponse | ErrorResponse> {
const r = await this.socket.call("subscribe", apikey, coin, account)
const res = parseSubResponse(r)
return res
}
async subsume<C extends Coin>(apikey: string, coin: C, account: AccountMap[C], callback: (tx: TransactionMap[C]) => void): Promise<SubscriptionResponse | ErrorResponse> {
const r = await this.socket.call("subsume", apikey, coin, account)
const res = parseSubResponse(r)
if (res instanceof SubscriptionResponse) {
this.socket.hook(res.uid, (tx) => {
const deserialized = <TransactionMap[C]>JSON.parse(tx)
callback(deserialized)
})
}
return res
}
async unsubscribe(apikey: string, uid: string): Promise<ErrorResponse | SuccessResponse> {
const r = await this.socket.call('unsubscribe', apikey, uid)
const res = parseResponse(r)
if (res instanceof SuccessResponse)
this.socket.unhook(uid)
return res
}
async getPluginList(): Promise<string[]> {
return await this.socket.call('getPluginList')
}
connect(): void {
if (this.started) {
logger.warn("FrontblockApiClient has already been started. Ignoring")
return
}
logger.info("Starting apiClient with ", this.conf)
this.socket = bsock.connect(this.conf.apiPort, this.conf.apiHost, this.conf.tls != null ? this.conf.tls : false)
this.started = true
}
disconnect(): void {
if (!this.started) {
logger.warn("FrontblockApiClient has not been started. Ignoring")
return
}
this.socket.close()
}
}
@@ -1,201 +0,0 @@
import { FrontblockApiClient, FrontblockApiConf } from "./FrontblockApiClient";
import { socketioRPC } from "frontblock-generic/RPC"
import { DatabasePlugin, TableDefiniton } from "frontblock-generic/DatabasePlugin"
import { Coin, AccountMap, TransactionMap, SubscriptionResponse } from "frontblock-generic/Types";
import * as knex from 'knex';
import * as Logger from 'log4js'
Logger.configure({
appenders: {
"frontblock-api-client": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories: {
default: { appenders: [ 'frontblock-api-client' ], level: 'debug' }
}
})
const logger = Logger.getLogger("frontblock-api-client")
/**
* Frontblock admin Plugin for FrontblockApiClient
*/
type ApiClientPluginConf = {
apiConf: FrontblockApiConf,
}
export default class FrontblockApiClientPlugin extends DatabasePlugin<ApiClientPluginConf>{
private apiClient : FrontblockApiClient
constructor(){
super("ApiClient")
}
getDefaultConfig(): ApiClientPluginConf & { dbConf: knex.Config; } {
return {
apiConf: {
apiHost: "api.testnet.frontblock.me",
apiKey: "",
apiPort: 10001,
tls: false
},
dbConf: {
client: 'sqlite3',
connection: {
filename: "./data/ApiClient.sqlite"
},
useNullAsDefault: true
}
}
}
async getConsumers(): Promise<SubscriptionResponse[]> {
const records = await this.knex.select('*').from('consumers')
return records.map(r => JSON.parse(r.JSON))
}
async getSubscriptions(): Promise<SubscriptionResponse[]> {
const records = await this.knex.select('*').from('subscriptions')
return records.map(r => JSON.parse(r.JSON))
}
exportExtraRPCs(): socketioRPC[] {
const quit = async(uid:string) => {return await this.quit(this.getConfigKey('apiConf').apiKey, uid) }
return [
{
name: "subscribe",
func: async <C extends Coin>(coin: C, account: AccountMap[C]) => { return await this.subscribe(this.getConfigKey('apiConf').apiKey, coin, account)},
type: 'call',
visibility: 'private'
},{
name: "subsume",
func: async <C extends Coin>(coin: C, account: AccountMap[C], callback: (tx: TransactionMap[C]) => void) => { return await this.subsume(this.getConfigKey('apiConf').apiKey, coin, account, callback)},
type: 'hook',
unhook: quit,
visibility: 'private'
},{
name: 'unsubscribe',
func: async(uid:string) => { return await this.unsubscribe(this.getConfigKey('apiConf').apiKey, uid) },
type: 'call',
visibility: 'private'
},{
name: 'consume',
func: async<C extends Coin>(uid: string, callback: (tx: TransactionMap[C]) => void) => {return await this.consume(this.getConfigKey('apiConf').apiKey, uid, callback)},
type: 'hook',
unhook: quit,
visibility: 'private'
},{
name: 'quit',
func: quit,
type: 'call',
visibility: 'private'
},{
name: "getSubscriptions",
func: async() => { return await this.getSubscriptions() },
type: "call",
visibility: "private"
},{
name: "getConsumers",
func: async() => { return await this.getConsumers() },
type: "call",
visibility: "private"
}
]
}
private subscribe:FrontblockApiClient['subscribe'] = async (apiKey, coin, account) => {
const res = await this.apiClient.subscribe(apiKey, coin, account)
if(res instanceof SubscriptionResponse){
await this.knex('subscriptions').insert([{uuid: res.uid, JSON: JSON.stringify(res)}])
}
return res
}
private consume:FrontblockApiClient['consume'] = async (apikey, uuid, callback) => {
const res = await this.apiClient.consume(apikey, uuid, callback)
if(res instanceof SubscriptionResponse){
await this.knex('consumers').insert([{uuid: res.uid, JSON: JSON.stringify(res)}])
}
return res
}
private unsubscribe:FrontblockApiClient['unsubscribe'] = async (apikey, uuid) => {
const res = await this.apiClient.unsubscribe(apikey, uuid)
await this.knex('subscriptions')
.where('uuid', uuid)
.del()
return res
}
private quit:FrontblockApiClient['quit'] = async (apikey, uuid) => {
const res = await this.apiClient.quit(apikey, uuid)
await this.knex('consumers')
.where('uuid', uuid)
.del()
return res
}
private subsume:FrontblockApiClient['subsume'] = async (apikey, coin, account, callback) => {
const res = await this.apiClient.subsume(apikey, coin, account, callback)
if(res instanceof SubscriptionResponse){
await Promise.all([
this.knex('consumers')
.insert([{uuid: res.uid, JSON: JSON.stringify(res)}]),
this.knex('subscriptions')
.insert([{uuid: res.uid, JSON: JSON.stringify(new SubscriptionResponse(res.message))}])
])
}
return res
}
private makeApiClient(conf: FrontblockApiConf):FrontblockApiClient{
if(this.apiClient)
this.apiClient.disconnect()
this.apiClient = new FrontblockApiClient(conf)
this.apiClient.connect()
return this.apiClient
}
setConfig(conf:ApiClientPluginConf & { dbConf: knex.Config }):ApiClientPluginConf & { dbConf: knex.Config }{
const currConf = this.getConfig()
if(JSON.stringify(currConf.apiConf) !== JSON.stringify(conf.apiConf)){
this.makeApiClient(conf.apiConf)
}
return super.setConfig(conf)
}
setConfigKey(key:keyof ApiClientPluginConf, value: any):ApiClientPluginConf & { dbConf: knex.Config; }{
if(key === 'apiConf'){
this.makeApiClient(value)
}
return super.setConfigKey(key, value)
}
async start(): Promise<void> {
this.makeApiClient(this.getConfig().apiConf)
super.start()
}
stop(): void {
this.apiClient.disconnect()
super.stop()
}
protected getTableDefinitions(): TableDefiniton[] {
return [{
name: 'subscriptions',
tableBuilder: function (table) {
table.string('uuid').primary();
table.string('JSON');
}
},{
name: 'consumers',
tableBuilder: function (table) {
table.string('uuid').primary();
table.string('JSON');
}
}]
}
}
@@ -1,38 +0,0 @@
const nodeExternals = require('webpack-node-externals');
const TerserPlugin = require('terser-webpack-plugin');
var webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'production',
target: "node",
entry: path.resolve(__dirname, 'Plugin.ts'),
output: {
path: path.resolve(__dirname, '../../dist'),
filename: 'Plugin.js',
libraryTarget: 'commonjs',
},
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
{ test: /\.ts?$/, loader: "ts-loader" }
]
},
optimization: {
minimize: false
},
node: {
global: true,
process: true,
__filename: false,
__dirname: false,
Buffer: true,
},
externals:{
knex: "../node_modules/knex"
}
}
@@ -1,10 +0,0 @@
{
"extends": "./tsconfig.json",
"include": ["src/frontend"],
"exclude": [
"src/backend"
],
"compilerOptions": {
"sourceMap": true
}
}
@@ -1,18 +0,0 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"alwaysStrict": false,
"noImplicitAny": false,
"target": "ES2015",
"module": "commonjs",
"declaration": true,
"strict": true,
"outDir": ".",
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"importHelpers": true,
"strictNullChecks": false,
},
"include": ["src/backend"],
"exclude": []
}
+13 -2
View File
@@ -2,13 +2,24 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ErrorDisplayComponent } from './error-display/error-display.component';
import { SettingsComponent } from './settings/settings.component';
const routes: Routes = [{
path: "apiclient",
loadChildren: () => import('./apiclient/module').then(mod => mod.ApiclientModule)
},{
path: "pluginmanager",
loadChildren: () => import('./pluginmanager/module').then(mod => mod.PluginModule)
loadChildren: () => import('./pluginmanager/module').then(mod => mod.PluginmanagerModule)
},{
path: "settings",
component: SettingsComponent
},{
path: "home",
component: HomeComponent
},{
path: "",
component: HomeComponent
pathMatch: "full",
redirectTo: "home",
},{
path: "**",
component: ErrorDisplayComponent
+7 -4
View File
@@ -20,13 +20,16 @@ export class AppComponent implements AfterContentInit {
}
ngAfterContentInit(){
const _this = this;
(function awaitSidebar(){
if(_this.sidebar != null){
_this.sidebarService.setSidebar(_this.sidebar)
let awaitSidebar: { (): void; (...args: any[]): void; }
(awaitSidebar = () => {
if(this.sidebar != null){
this.sidebarService.setSidebar(this.sidebar)
return
}
setTimeout(awaitSidebar,25)
})()
}
}
+9 -1
View File
@@ -17,8 +17,12 @@ import { HomeComponent } from './home/home.component';
import { ErrorDisplayComponent } from './error-display/error-display.component';
import { HeaderBarComponent } from './header-bar/header-bar.component';
import { SubnavComponent } from './subnav/subnav.component';
import { KnexConfigComponent } from './knex-config/knex-config.component';
import { ApiclientFormComponent } from './apiclient/apiclient-settings-form.component';
import { SettingsComponent } from './settings/settings.component';
import { ApiclientModule } from './apiclient/module';
import { WalletsModule } from './wallets/module';
import { environment } from 'src/environments/environment';
export function createCompiler(fn: CompilerFactory): Compiler {
return fn.createCompiler();
@@ -34,16 +38,20 @@ const declarations = [
HeaderBarComponent,
ErrorDisplayComponent,
SubnavComponent,
KnexConfigComponent,
SettingsComponent
]
@NgModule({
declarations: declarations,
imports: [
WalletsModule,
FormsModule,
BrowserModule,
BrowserAnimationsModule,
ClarityModule,
AppRoutingModule,
ApiclientModule
],
entryComponents: [],
providers: [
@@ -22,7 +22,6 @@ import * as btcHdkey from "btc-hdkey"
import * as bitcoinjslib from "bitcoinjs-lib"
import * as fetch from "node-fetch"
import * as coinselect from "coinselect/accumulative"
import * as KnexModule from '../knex-config/knex-config.module'
SystemJS.set('@clr/angular', SystemJS.newModule(clarityModule));
SystemJS.set('@angular/router', SystemJS.newModule(angularRouter));
@@ -40,7 +39,6 @@ SystemJS.set('btc-hdkey', SystemJS.newModule(btcHdkey))
SystemJS.set('bitcoinjs-lib', SystemJS.newModule(bitcoinjslib))
SystemJS.set('node-fetch', SystemJS.newModule(fetch))
SystemJS.set('coinselect/accumulative', SystemJS.newModule(coinselect))
SystemJS.set('knexconfig', SystemJS.newModule(KnexModule))
SystemJS.config({ meta: { '*': { authorization: true } } });
/** --------- */
@@ -53,7 +51,7 @@ import { environment } from "../../environments/environment"
const fb = environment.production ? window["fb"] : {
Admin: {
getLoadedPluginNames: () => ["ApiClient", "Wallet"]
getLoadedPluginNames: () => []
}
}
@@ -88,6 +86,7 @@ export class DynamicLoaderComponent implements AfterViewInit {
private async installWidget(pluginName: string) {
let module;
if (!angularCore.isDevMode()){
console.log("Loading "+pluginName+" from backend")
module = await SystemJS.import("plugins/" + pluginName + ".js");
}else{
if(environment.loadLocal){
@@ -99,7 +98,18 @@ export class DynamicLoaderComponent implements AfterViewInit {
}
}
this.injectPlugin(module)
this.compileModule(module)
}
compileModule(module:any){
}
injectPlugin(module:any):void{
const plugin:FrontendPlugin = new module['PluginModule']()
if(!plugin.getSidebarEntry) return
const entry: SidebarEntries | SidebarEntry = plugin.getSidebarEntry()
const rc = this.router.config
@@ -1,12 +1,17 @@
<clr-header class="header header-1">
<clr-header class="header header-7">
<div class="branding">
<span class="title">Frontblock</span>
<a class="nav-link" [routerLink]="'home'">
<img src="assets/logo_real.png" class="clr-icon" />
<span class="title">&nbsp;&nbsp;Frontblock</span>
</a>
</div>
<div class="header-actions">
<a class="nav-link nav-icon-text">
<clr-icon shape="bug"></clr-icon>
<span class="nav-text">ALPHA {{devmode}}</span>
<div class="header-actions" style="z-index:2">
<a class="nav-link nav-icon" aria-label="bell" routerLinkActive="active" [routerLink]="'notifications'">
<clr-icon class="has-badge" shape="bell"> </clr-icon>
</a>
<a class="nav-link nav-icon" routerLinkActive="active" [routerLink]="'settings'">
<clr-icon class="" shape="cog" ></clr-icon>
</a>
</div>
</clr-header>
@@ -1 +1 @@
<p>home works!</p>
<ng-container #dynamicWidgets></ng-container>
+24 -4
View File
@@ -1,15 +1,35 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, ViewChild, ViewContainerRef, Injector, ComponentFactoryResolver, AfterViewInit } from '@angular/core';
import { FrontendPlugin } from 'frontblock-generic/Plugin';
import { ApiclientModule } from '../apiclient/module';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
export class HomeComponent implements AfterViewInit {
constructor() { }
@ViewChild('dynamicWidgets', {read: ViewContainerRef, static: false})
settingsContainer: ViewContainerRef
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector
) { }
ngAfterViewInit() {
this.injectModule(new ApiclientModule())
ngOnInit() {
}
injectModule(module:FrontendPlugin<any>) {
if(!module.getSettingsComponent) return
//@ts-ignore
const factory = this.componentFactoryResolver.resolveComponentFactory(module.getWidget())
const component = factory.create(this.injector)
setTimeout(() => this.settingsContainer.insert(component.hostView), 1)
}
}
@@ -1 +0,0 @@
<p>knex-config works!</p>
@@ -1,6 +1,6 @@
import { Component, OnInit, Input } from '@angular/core';
declare const fb
declare const fb : { Admin: { getConfig:()=>any , setConfigKey:(knex:"dbConf", conf:KnexConfig)=>any } }
@Component({
selector: 'knex-config',
@@ -78,7 +78,7 @@ declare const fb
<form clrForm clrLayout="horizontal">
<clr-input-container >
<label class="clr-col-12 clr-col-md-4">file</label>
<input class="clr-col-12 clr-col-md-8" clrInput type="text" name="sqlite-file" [(ngModel)]="sqlite3['connection']['filename']" placeholder="./data/benis.sqlite" required />
<input class="clr-col-12 clr-col-md-8" clrInput type="text" name="sqlite-file" [(ngModel)]="sqlite3['connection']['filename']" placeholder="./data/db.sqlite" required />
</clr-input-container>
</form>
</clr-tab-content>
@@ -98,8 +98,6 @@ declare const fb
})
export class KnexConfigComponent implements OnInit{
@Input("fbPlugin")
fbPlugin:string
conf: KnexConfig
pg:PgConfig = {
@@ -138,10 +136,17 @@ export class KnexConfigComponent implements OnInit{
}
async ngOnInit(){
if(!fb[this.fbPlugin]){
throw new Error("Knex config component doesn't have fb."+this.fbPlugin)
}
const c = await fb[this.fbPlugin].getConfig()
await new Promise((resolve, reject)=>{
let awaitAdmin: { (): void; (...args: any[]): void; }
(awaitAdmin = () => {
if(fb.Admin != null){
resolve()
}
setTimeout(awaitAdmin,25)
})()
})
const c = await fb.Admin.getConfig()
this.conf = c.dbConf
Object.keys(this.mode).forEach(knexType => {
@@ -155,9 +160,13 @@ export class KnexConfigComponent implements OnInit{
window['knex'] = this
}
getSettingsComponentClassName(){
return KnexConfigComponent
}
save(){
const modeName = Object.entries(this.mode).find(([key, active]) => active)[0]
fb[this.fbPlugin].setConfigKey('dbConf', this[modeName]).then(console.log)
fb.Admin.setConfigKey('dbConf', this[modeName]).then(console.log)
}
}
@@ -1,23 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { KnexConfigComponent } from './knex-config.component';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
CommonModule,
FormsModule,
ClarityModule,
],
exports: [
KnexConfigComponent
],
declarations: [
KnexConfigComponent
],
entryComponents: [],
providers: []
})
export class KnexConfigModule{}
+1 -1
View File
@@ -27,5 +27,5 @@ import { FormsModule } from '@angular/forms';
PluginsComponent
]
})
export class PluginModule{
export class PluginmanagerModule{
}
@@ -0,0 +1,4 @@
<knex-config></knex-config>
<ng-container #dynamicSettings></ng-container>
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SettingsComponent } from './settings.component';
describe('SettingsComponent', () => {
let component: SettingsComponent;
let fixture: ComponentFixture<SettingsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SettingsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,32 @@
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector, AfterViewInit } from '@angular/core';
import { ApiclientModule } from '../apiclient/module'
import { FrontendPlugin } from 'frontblock-generic/Plugin';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent implements AfterViewInit {
ngAfterViewInit(): void {
this.injectModule(new ApiclientModule())
// this.injectModule(new ApiclientModule())
}
@ViewChild('dynamicSettings', {read: ViewContainerRef, static: false})
settingsContainer: ViewContainerRef
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector
) {
}
injectModule(module:FrontendPlugin<any>) {
if(!module.getSettingsComponent) return
const factory = this.componentFactoryResolver.resolveComponentFactory(module.getSettingsComponent())
const component = factory.create(this.injector)
setTimeout(() => this.settingsContainer.insert(component.hostView), 1)
}
}
@@ -1,3 +1,26 @@
<!--
<nav class="sidenav">
<section class="sidenav-content">
<a *ngFor="let entry of entries" routerLinkActive="active" [routerLink]="entry.route" class="nav-link">
<clr-icon [attr.shape]="entry.icon" class="is-solid" clrVerticalNavIcon></clr-icon>{{entry.text}}
</a>
<section class="nav-group" *ngFor="let e of multientires" >
<label>
<clr-icon [attr.shape]="e.icon" class="is-solid"></clr-icon>
{{e.text}}
</label>
<ul class="nav-list">
<li *ngFor="let l of e.links"><a class="nav-link" routerLinkActive="active" [routerLink]="l.route" >{{l.text}}</a></li>
</ul>
</section>
</section>
</nav>
-->
<clr-vertical-nav [clr-nav-level]="1" class="nav-trigger--bottom" style="height: 100%;" [clrVerticalNavCollapsible]="true" [(clrVerticalNavCollapsed)]="collapsed" >
<clr-vertical-nav-group *ngFor="let e of multientires" routerLinkActive="active">
<clr-icon [attr.shape]="e.icon" class="is-solid" clrVerticalNavIcon></clr-icon>
@@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { SidebarEntries, SidebarEntry } from 'frontblock-generic/Plugin';
import { ApiclientModule } from '../apiclient/module';
@Component({
selector: 'sidebar',
@@ -10,23 +11,47 @@ export class SidebarComponent implements OnInit {
collapsed = true
entries:SidebarEntry[] = [
]
entries:SidebarEntry[] = []
multientires:SidebarEntries[] = [{
icon: "bundle",
text: "Update Manager",
parentRoute: "pluginmanager",
links: [{
route: "pluginmanager/debug",
text: "DEBUG"
},{
route: "pluginmanager/admin",
text: "Dashboard"
},{
route: "pluginmanager/plugins",
text: "Plugins"
}]
multientires:SidebarEntries[] = [
<SidebarEntries> new ApiclientModule().getSidebarEntry(),
{
icon: "bundle",
text: "Update Manager",
parentRoute: "pluginmanager",
links: [{
route: "pluginmanager/debug",
text: "DEBUG"
},{
route: "pluginmanager/admin",
text: "Dashboard"
},{
route: "pluginmanager/plugins",
text: "Plugins"
}]
},{
icon: "wallet",
text: "Wallets",
parentRoute: "wallet",
links: [{
route: "btc",
text: "BTC Wallet"
},{
route: "ltc",
text: "LTC Wallet"
},{
route: "eth",
text: "ETH Wallet"
},{
route: "xrp",
text: "XRP Wallet"
},{
route: "xlm",
text: "XLM Wallet"
},{
route: "settings",
text: "Settings"
}]
}]
constructor() { }
@@ -1,7 +1,17 @@
<!--
<nav class="subnav">
<ul class="nav">
<li class="nav-item">
<a class="nav-link active" href="#">Dashboard</a>
<a class="header-nav" routerLinkActive="active" [routerLink]="'home'">
<clr-icon class="is-solid" shape="home" size="24"></clr-icon>
</a>
<a class="header-nav" routerLinkActive="active" [routerLink]="'settings'">
<clr-icon class="is-solid" shape="cog" size="24"></clr-icon>
</a>
<a class="header-nav" routerLinkActive="active" [routerLink]="'notifications'">
<clr-icon class="is-solid has-badge" shape="bell" size="24"></clr-icon>
</a>
</li>
</ul>
</nav>
</nav>
-->
-59
View File
@@ -1,59 +0,0 @@
kind: pipeline
name: default
steps:
- name: restore cache
image: drillster/drone-volume-cache
settings:
restore: true
mount:
- ./node_modules
volumes:
- name: cache
path: /cache
- name: npm install
image: node:12
commands:
- npm install
- name: npm run build
image: node:12
commands:
- npm run build
- name: rebuild cache
image: drillster/drone-volume-cache
settings:
rebuild: true
mount:
- ./node_modules
volumes:
- name: cache
path: /cache
- name: deploy plugin
image: node:12
commands:
- git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
- git config --global user.name "${DRONE_COMMIT_AUTHOR}"
- git clone https://gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git
- cp -r ./dist/* ./${DRONE_REPO_NAME}
- cd ./${DRONE_REPO_NAME}
- git add -A
- git commit --allow-empty -m "drone tagged as version ${DRONE_TAG}"
- git tag ${DRONE_TAG}
- git push https://$GIT_USER:$GIT_PASSWORD@gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git master ${DRONE_TAG}
environment:
GIT_USER:
from_secret: git_user
GIT_PASSWORD:
from_secret: git_password
when:
event:
- tag
volumes:
- name: cache
host:
path: /tmp
-11
View File
@@ -1,11 +0,0 @@
dist
kfs
.rpt2_cache
node_modules
lib
*.d.ts
*.js
*.ts
!src/**/*
File diff suppressed because it is too large Load Diff
-57
View File
@@ -1,57 +0,0 @@
{
"name": "frontblock",
"version": "0.9.10",
"description": "frontblock shop-side library ",
"main": "FrontblockApiClient.js",
"scripts": {
"build-backend": "webpack --config src/backend/webpack.prod.js --progress --colors",
"build-frontend": "webpack --config src/frontend/webpack.prod.js --progress --colors",
"build": "npm run clean; npm run build-backend; npm run build-frontend",
"clean": "rm -rf *.js *.ts backend frontend .rpt2_cache lib dist",
"update-frontblock": "rm -rf node_modules/frontblock*; npm install"
},
"keywords": [
"api",
"client",
"frontblock"
],
"author": "",
"license": "ISC",
"dependencies": {
"@angular/common": "^8.2.1",
"@angular/core": "^8.2.1",
"@angular/forms": "^8.2.1",
"@angular/platform-browser": "^8.2.1",
"@angular/router": "^8.2.1",
"@clr/angular": "^2.1.1",
"@types/node": "^11.13.10",
"adm-zip": "^0.4.13",
"angular": "^1.7.8",
"bip39": "^3.0.2",
"bitcoinjs-lib": "^5.0.4",
"bsock": "^0.1.9",
"btc-hdkey": "0.0.17",
"coinselect": "^3.1.11",
"easy-unzip": "^1.1.0",
"express": "^4.16.4",
"frontblock-generic": "^0.28.4",
"hdkey": "^1.1.1",
"key-file-storage": "^2.2.1",
"knex": "^0.19.2",
"log4js": "^4.3.1",
"minimist": "^1.2.0",
"node-fetch": "^2.5.0",
"original-fs": "^1.1.0",
"rxjs": "^6.5.2",
"rxjs-compat": "^6.5.2",
"unzip": "^0.1.11",
"uuid": "^3.3.2"
},
"devDependencies": {
"ts-loader": "^6.0.4",
"typescript": "^3.4.5",
"webpack": "^4.39.2",
"webpack-cli": "^3.3.6",
"webpack-node-externals": "^1.7.2"
}
}
@@ -1,118 +0,0 @@
import { socketioRPC } from "frontblock-generic/RPC";
import { DatabasePlugin, TableDefiniton } from "frontblock-generic/DatabasePlugin"
import { Coin } from "frontblock-generic/Types";
import * as knex from 'knex';
import * as Logger from 'log4js'
Logger.configure({
appenders: {
"frontblock-wallet": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories: {
default: { appenders: [ 'frontblock-wallet' ], level: 'debug' }
}
})
const logger = Logger.getLogger("frontblock-wallet")
export type PublicNode = {
coin: Coin
net: 'testnet' | 'mainnet'
url: URL
}
export type WalletPluginConf = {
btcConf: {
publicNodes: PublicNode[]
}
}
export type Key = {
name: string
type: 'plain' | 'encrypted'
value: string
coin: Coin
}
export default class WalletPlugin extends DatabasePlugin<WalletPluginConf>{
constructor(){
super("Wallet")
}
getDefaultConfig(): WalletPluginConf & { dbConf: knex.Config; } {
return {
btcConf: {
publicNodes: [
{coin: 'BTC', net: 'testnet', url: new URL('https://testnet-api.smartbit.com.au/v1/blockchain')},
{coin: 'BTC', net: 'mainnet', url: new URL('https://api.smartbit.com.au/v1/blockchain')}
]
},
dbConf: {
client: 'sqlite3',
connection: {
filename: "./data/Wallet.sqlite"
},
useNullAsDefault: true
}
}
}
protected exportExtraRPCs(): socketioRPC[] {
return [
{
name: "putKey",
func: async(
name: string,
type: 'plain' | 'encrypted',
value: string,
coin: Coin
) => {
return await this.putKey({
name: name,
type:type,
value:value,
coin:coin
})
},
type: 'call',
visibility: 'private'
},{
name: "delKey",
func: async(name: string) => { return await this.delKey(name) },
type: 'call',
visibility: 'private'
},{
name: "getKeys",
func: async() => { return await this.getKeys() },
type: 'call',
visibility: 'private'
}
]
}
public async putKey(key: Key){
return await this.knex('keys').insert([key])
}
public async delKey(name: string){
return await this.knex('keys')
.where('name', name)
.del()
}
public async getKeys(): Promise<Key[]>{
return await this.knex.select('*').from('keys')
}
protected getTableDefinitions(): TableDefiniton[] {
return [{
name: 'keys',
tableBuilder: function (table) {
table.string('name').primary();
table.string('type');
table.string('value');
table.string('coin');
}
}]
}
}
@@ -1,37 +0,0 @@
const nodeExternals = require('webpack-node-externals');
const TerserPlugin = require('terser-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'production',
target: "node",
entry: path.resolve(__dirname, 'Plugin.ts'),
output: {
path: path.resolve(__dirname, '../../dist'),
filename: 'Plugin.js',
libraryTarget: 'commonjs',
},
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
{ test: /\.ts?$/, loader: "ts-loader" }
]
},
optimization: {
minimize: false
},
node: {
global: true,
process: true,
__filename: false,
__dirname: false,
Buffer: true,
},
externals:{
knex: "../node_modules/knex"
}
}
@@ -1,41 +0,0 @@
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
target: "web",
entry: path.resolve(__dirname, 'module.ts'),
output: {
path: path.resolve(__dirname, '../../dist'),
filename: 'FrontendPlugin.js',
libraryTarget: 'commonjs',
},
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: [".ts", ".tsx", ".js"]
},
module: {
rules: [
{ test: /\.ts?$/, loader: "ts-loader" }
]
},
optimization: {
minimizer: [
new TerserPlugin({
exclude: [
/\.\/(.*)\/.ts/,
/\.\/(.*).ts/,
],
}),
],
},
externals: {
'@angular/core': '@angular/core',
'@angular/common': '@angular/common',
'@angular/router': '@angular/router',
'@angular/animations': '@angular/animations',
'@angular/forms': '@angular/forms',
'@clr/angular': '@clr/angular',
'../../../knex-config/knex-config.module': 'knexconfig'
}
}
@@ -1,10 +0,0 @@
{
"extends": "./tsconfig.json",
"include": ["src/frontend"],
"exclude": [
"src/backend"
],
"compilerOptions": {
"sourceMap": true
}
}
-19
View File
@@ -1,19 +0,0 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"alwaysStrict": false,
"noImplicitAny": false,
"target": "ES2015",
"module": "commonjs",
"declaration": false,
"strict": true,
"outDir": "./lib",
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"importHelpers": true,
"strictNullChecks": false,
},
"include": ["src/backend"],
"exclude": []
}
@@ -1,4 +1,4 @@
import { Component, OnInit, isDevMode, ViewChild, AfterViewInit, OnDestroy, HostListener } from '@angular/core';
import { Component, OnInit, isDevMode, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';
import { WalletGeneratorComponent } from './wallet-generator.component';
import { WalletPickerComponent } from './wallet-picker.component';
import { HDKey } from "btc-hdkey"
@@ -8,7 +8,7 @@ const fetch = require("node-fetch")
import { TransactionBuilder, Signer } from 'bitcoinjs-lib'
import { networks } from 'bitcoinjs-lib'
import { SubscriptionResponse, ErrorResponse } from 'frontblock-generic/Types';
let feeRate = 5 // satoshis per byte
let feeRate = 55 // satoshis per byte
const hdkey = new HDKey()
declare const fb
@@ -247,44 +247,45 @@ export class PluginsComponent implements AfterViewInit, OnDestroy {
@ViewChild(WalletPickerComponent, { static: false })
WalletPickerComponent: WalletPickerComponent
@HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
this.unsubscribe()
return true
}
async ngAfterViewInit(): Promise<void> {
this.WalletGeneratorComponent.setParent(this)
this.WalletPickerComponent.setParent(this)
this.AddressViewerComponent.setParent(this)
this.apiClientPlugin = fb.ApiClient != null
window['btc'] = this
const keys: any[] = await fb.Wallet.getKeys()
const keys: any[] = []
/*
if(this.apiClientPlugin){
window.addEventListener('beforeunload', async (e) => {
await Promise.all(this.subscriptions.map(async s => {
const RR = await fb.ApiClient.unsubscribe(s)
console.log(RR)
}))
})
}
*/
keys.filter(key => key.coin === "BTC").forEach(key => {
keys.filter(key => key.type === "BTC").forEach(key => {
this.wallets[key.name] = {key: key.value, name: key.name}
})
this.WalletPickerComponent.setWallets(this.wallets)
this.timerRefresh = setInterval(async () => { if (this.autorefresh && !this.refreshing && !this.loading) this.refresh() }, 30000)
this.loadKey("tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak")
}
ngOnDestroy(): void {
this.unsubscribe()
clearInterval(this.timerRefresh)
}
unsubscribe(){
this.subscriptions.map(s => {
fb.ApiClient.unsubscribe(s)
})
}
addWallet(wallet: {key:string, name:string}) {
fb.Wallet.putKey(wallet.name, "plain", wallet.key, "BTC")
/* store somewhere */
//fb.Wallet.putKey(wallet.name, "plain", wallet.key, "BTC")
this.wallets[wallet.name] = wallet
this.WalletPickerComponent.setWallets(this.wallets)
}
showWizard() {
@@ -311,19 +312,21 @@ export class PluginsComponent implements AfterViewInit, OnDestroy {
this.loadingN = 1
await this.refresh()
/*
if (fb.ApiClient) {
const res: (SubscriptionResponse | ErrorResponse)[] = await Promise.all([
...this.regaddresses,
...this.changeaddresses
].map(async a => await fb.ApiClient.subsume("BTC", { address: a.address }, tx => { this.transactions.unshift(tx) })))
this.subscriptions = this.subscriptions.concat(res.map(r => {
this.subscriptions.push(res.map(r => {
if (r.result = "Success") {
return r.message
} else {
return
}
})).filter(s => !(!s))
}))
}
*/
this.loading = false
}
@@ -592,7 +595,9 @@ They are completely safe and mean no more addresses were found`)
vout: t.n,
value: Math.floor(parseFloat(t.value) * 100000000)
}
}).filter(utx => utx.value > 0 && utx.address != target)
}).filter(utx => utx.value > 0)
console.log(utxos, target, value, feeRate)
let { inputs, outputs, fee } = coinselect(utxos, [{ address: target, value: Math.floor(parseFloat(<any>value) * 100000000) }], feeRate)
@@ -6,26 +6,20 @@ import { PluginsComponent } from './btc/btc.component';
import { WalletPickerComponent } from './btc/wallet-picker.component';
import { AddressViewerComponent } from './btc/address-viewer.component';
import { FrontendPlugin, SidebarEntries } from 'frontblock-generic/Plugin';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { WalletGeneratorComponent } from './btc/wallet-generator.component';
import { WalletSettingsComponent } from './settings.component';
// @ts-ignore
import { KnexConfigModule } from "../../../knex-config/knex-config.module";
@NgModule({
imports: [
FormsModule,
ClarityModule,
KnexConfigModule,
CommonModule,
RouterModule.forChild([
{path: "btc", component: PluginsComponent},
{path: "settings", component: WalletSettingsComponent},
]),
],
exports: [RouterModule],
@@ -35,33 +29,9 @@ import { KnexConfigModule } from "../../../knex-config/knex-config.module";
WalletPickerComponent,
AddressViewerComponent,
WalletGeneratorComponent
],
entryComponents: [
WalletSettingsComponent
]
})
export class PluginModule implements FrontendPlugin{
getSidebarEntry(): SidebarEntries {
return {
icon: "wallet",
text: "Wallets",
parentRoute: "wallet",
links: [{
route: "btc",
text: "BTC Wallet"
},{
route: "ltc",
text: "LTC Wallet"
},{
route: "eth",
text: "ETH Wallet"
},{
route: "xrp",
text: "XRP Wallet"
},{
route: "xlm",
text: "XLM Wallet"
},{
route: "settings",
text: "Settings"
}]
}
}
}
export class WalletsModule{}
@@ -7,7 +7,7 @@ declare const fb
template:
`
<knex-config fbPlugin="Wallet"></knex-config>
WALLET CONFIG XD
`
})
export class WalletSettingsComponent implements OnInit {
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+1 -2
View File
@@ -10,8 +10,7 @@
"src/app/**/*/node_modules/**/*",
"src/app/**/*/dist/**/*",
"src/app/**/*/backend/**/*",
"src/app/**/*/frontend/**/*",
"src/app/apiclient/**/*",
"src/app/**/*/frontend/**/*"
]
}