Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 29c2fccbe5 | |||
| 975c07fc2e | |||
| ff45beac09 | |||
| 24eb709293 | |||
| 49e2146152 | |||
| cdaf64b3cf | |||
| 49af31776e | |||
| 0880823576 | |||
| 1fa1ecb8ec | |||
| d6108c9836 | |||
|
|
cc344fa60e | ||
|
|
5e468359a1 | ||
|
|
2317dd2d4c | ||
|
|
b92647c52e | ||
|
|
54024ee222 | ||
| 60d6474a42 | |||
| 8c52326550 | |||
| 52aba347a8 | |||
|
|
61a0fd2aa6 | ||
|
|
d604d739e3 | ||
|
|
a57f53fdb4 | ||
|
|
f276cdf697 | ||
|
|
7fb46a4c0b | ||
|
|
fc993a95d7 | ||
| c6ff7e7ba5 | |||
| 3f9a3fb1cd | |||
| 7ae4f7dbd0 | |||
| dbb44ec30a | |||
| 7f4fc42a7a | |||
| 01db4540b1 | |||
| 5c0c096a79 | |||
| c1719a057a | |||
| e969eacd06 | |||
| 8ed3439cdc | |||
|
|
5241dfddd4 | ||
|
|
6e0572b48a | ||
|
|
9a4a39b19a | ||
|
|
8abf113715 | ||
| 25c997fec5 | |||
|
|
1546bf7f87 | ||
|
|
02d4adcff0 | ||
|
|
147c889b53 | ||
|
|
f6e7644284 | ||
|
|
582da6d7f0 | ||
|
|
3a337b7b0a | ||
|
|
2953dd0172 | ||
|
|
08f0620305 | ||
|
|
ec22a8e0ec | ||
|
|
be4d2547e4 | ||
|
|
a7f8893399 | ||
|
|
d0b52f40b7 | ||
| 3117d288b1 | |||
|
|
6ecd190de8 | ||
|
|
dacd6d5afb | ||
| 299f6dea06 | |||
| ed025f9acb | |||
|
|
de0e66a94a | ||
|
|
e86504ecd5 | ||
|
|
1b192328b2 | ||
|
|
77a77ff87e | ||
|
|
439dff4a64 | ||
|
|
f8cb6f25f7 | ||
| c4ea066927 | |||
| 5f9c6ff2bb | |||
| 8965f7ae89 | |||
|
|
c157c86600 | ||
| 9a931b65e2 | |||
| 2c149507f6 | |||
|
|
34dea39c52 | ||
|
|
f1a4547961 | ||
|
|
8ae56a8df6 | ||
| cb0110ed20 | |||
| 2bbf814003 | |||
|
86e3fdb910
|
|||
| b731c4c388 | |||
| e929ce029d | |||
| 5160096a1a | |||
| f219a4041a | |||
| 674eae1c11 | |||
| f62369bd01 | |||
| 7a6660ebd8 | |||
| e20d1bdcc5 | |||
| 3e309b6379 | |||
| ae4e92e090 | |||
| 7b69719f0e | |||
| 1d631fec21 | |||
| 690b1b68c3 | |||
| 6e619622d2 |
187
aiprompts/reflection.md
Normal file
187
aiprompts/reflection.md
Normal file
@@ -0,0 +1,187 @@
|
||||
## Compile time reflection
|
||||
|
||||
$ is used as a prefix for compile time (also referred to as 'comptime') operations.
|
||||
|
||||
Having built-in JSON support is nice, but V also allows you to create efficient serializers for any data format. V has compile time if and for constructs:
|
||||
|
||||
.fields
|
||||
You can iterate over struct fields using .fields, it also works with generic types (e.g. T.fields) and generic arguments (e.g. param.fields where fn gen[T](param T) {).
|
||||
|
||||
struct User {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
$for field in User.fields {
|
||||
$if field.typ is string {
|
||||
println('${field.name} is of type string')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// name is of type string
|
||||
.values
|
||||
You can read Enum values and their attributes.
|
||||
|
||||
enum Color {
|
||||
red @[RED] // first attribute
|
||||
blue @[BLUE] // second attribute
|
||||
}
|
||||
|
||||
fn main() {
|
||||
$for e in Color.values {
|
||||
println(e.name)
|
||||
println(e.attrs)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// red
|
||||
// ['RED']
|
||||
// blue
|
||||
// ['BLUE']
|
||||
.attributes
|
||||
You can read Struct attributes.
|
||||
|
||||
@[COLOR]
|
||||
struct Foo {
|
||||
a int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
$for e in Foo.attributes {
|
||||
println(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// StructAttribute{
|
||||
// name: 'COLOR'
|
||||
// has_arg: false
|
||||
// arg: ''
|
||||
// kind: plain
|
||||
// }
|
||||
.variants
|
||||
You can read variant types from Sum type.
|
||||
|
||||
type MySum = int | string
|
||||
|
||||
fn main() {
|
||||
$for v in MySum.variants {
|
||||
$if v.typ is int {
|
||||
println('has int type')
|
||||
} $else $if v.typ is string {
|
||||
println('has string type')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// has int type
|
||||
// has string type
|
||||
.methods
|
||||
You can retrieve information about struct methods.
|
||||
|
||||
struct Foo {
|
||||
}
|
||||
|
||||
fn (f Foo) test() int {
|
||||
return 123
|
||||
}
|
||||
|
||||
fn (f Foo) test2() string {
|
||||
return 'foo'
|
||||
}
|
||||
|
||||
fn main() {
|
||||
foo := Foo{}
|
||||
$for m in Foo.methods {
|
||||
$if m.return_type is int {
|
||||
print('${m.name} returns int: ')
|
||||
println(foo.$method())
|
||||
} $else $if m.return_type is string {
|
||||
print('${m.name} returns string: ')
|
||||
println(foo.$method())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// test returns int: 123
|
||||
// test2 returns string: foo
|
||||
.params
|
||||
You can retrieve information about struct method params.
|
||||
|
||||
struct Test {
|
||||
}
|
||||
|
||||
fn (t Test) foo(arg1 int, arg2 string) {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
$for m in Test.methods {
|
||||
$for param in m.params {
|
||||
println('${typeof(param.typ).name}: ${param.name}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Output:
|
||||
// int: arg1
|
||||
// string: arg2
|
||||
|
||||
## Example
|
||||
|
||||
```v
|
||||
// An example deserializer implementation
|
||||
|
||||
struct User {
|
||||
name string
|
||||
age int
|
||||
}
|
||||
|
||||
fn main() {
|
||||
data := 'name=Alice\nage=18'
|
||||
user := decode[User](data)
|
||||
println(user)
|
||||
}
|
||||
|
||||
fn decode[T](data string) T {
|
||||
mut result := T{}
|
||||
// compile-time `for` loop
|
||||
// T.fields gives an array of a field metadata type
|
||||
$for field in T.fields {
|
||||
$if field.typ is string {
|
||||
// $(string_expr) produces an identifier
|
||||
result.$(field.name) = get_string(data, field.name)
|
||||
} $else $if field.typ is int {
|
||||
result.$(field.name) = get_int(data, field.name)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fn get_string(data string, field_name string) string {
|
||||
for line in data.split_into_lines() {
|
||||
key_val := line.split('=')
|
||||
if key_val[0] == field_name {
|
||||
return key_val[1]
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
fn get_int(data string, field string) int {
|
||||
return get_string(data, field).int()
|
||||
}
|
||||
|
||||
// `decode<User>` generates:
|
||||
// fn decode_User(data string) User {
|
||||
// mut result := User{}
|
||||
// result.name = get_string(data, 'name')
|
||||
// result.age = get_int(data, 'age')
|
||||
// return result
|
||||
// }
|
||||
```
|
||||
2
cli/.gitignore
vendored
2
cli/.gitignore
vendored
@@ -1 +1,3 @@
|
||||
hero
|
||||
compile
|
||||
compile_upload
|
||||
|
||||
22
cli/hero.v
22
cli/hero.v
@@ -19,6 +19,26 @@ fn playcmds_do(path string) ! {
|
||||
}
|
||||
|
||||
fn do() ! {
|
||||
|
||||
if ! core.is_osx()! {
|
||||
if os.getenv('SUDO_COMMAND') != '' || os.getenv('SUDO_USER') != '' {
|
||||
println('Error: Please do not run this program with sudo!')
|
||||
exit(1) // Exit with error code
|
||||
}
|
||||
}
|
||||
|
||||
if os.getuid() == 0 {
|
||||
if core.is_osx()! {
|
||||
eprintln("please do not run hero as root in osx.")
|
||||
exit(1)
|
||||
}
|
||||
} else {
|
||||
if ! core.is_osx()! {
|
||||
eprintln("please do run hero as root, don't use sudo.")
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if os.args.len == 2 {
|
||||
mypath := os.args[1]
|
||||
if mypath.to_lower().ends_with('.hero') {
|
||||
@@ -31,7 +51,7 @@ fn do() ! {
|
||||
mut cmd := Command{
|
||||
name: 'hero'
|
||||
description: 'Your HERO toolset.'
|
||||
version: '1.0.6'
|
||||
version: '1.0.14'
|
||||
}
|
||||
|
||||
// herocmds.cmd_run_add_flags(mut cmd)
|
||||
|
||||
2
examples/biztools/bizmodel/example/.gitignore
vendored
Normal file
2
examples/biztools/bizmodel/example/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
bizmodel
|
||||
dest
|
||||
41
examples/biztools/bizmodel/example/bizmodel.vsh
Executable file
41
examples/biztools/bizmodel/example/bizmodel.vsh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
//#!/usr/bin/env -S v -cg -enable-globals run
|
||||
import freeflowuniverse.herolib.data.doctree
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.core.playcmds
|
||||
import freeflowuniverse.herolib.web.mdbook
|
||||
import os
|
||||
|
||||
const wikipath = os.dir(@FILE) + '/wiki'
|
||||
const summarypath = os.dir(@FILE) + '/wiki/summary.md'
|
||||
|
||||
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
|
||||
|
||||
mut m := bizmodel.getset('example')!
|
||||
m.workdir = wikipath
|
||||
m.play(mut playbook.new(path: wikipath)!)!
|
||||
m.export_sheets()!
|
||||
bizmodel.set(m)
|
||||
|
||||
// // execute the actions so we have the info populated
|
||||
// // playcmds.run(mut plb,false)!
|
||||
|
||||
// // just run the doctree & mdbook and it should
|
||||
// // load the doctree, these are all collections
|
||||
// mut tree := doctree.new(name: 'bizmodel')!
|
||||
// tree.scan(path: wikipath)!
|
||||
// tree.export(dest: buildpath, reset: true)!
|
||||
|
||||
// // mut bm:=bizmodel.get("test")!
|
||||
// // println(bm)
|
||||
|
||||
// mut mdbooks := mdbook.get()!
|
||||
// mdbooks.generate(
|
||||
// name: 'bizmodel'
|
||||
// summary_path: summarypath
|
||||
// doctree_path: buildpath
|
||||
// title: 'bizmodel example'
|
||||
// )!
|
||||
// mdbook.book_open('bizmodel')!
|
||||
10
examples/biztools/bizmodel/example/load.md
Normal file
10
examples/biztools/bizmodel/example/load.md
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
## Loader instructions
|
||||
|
||||
this will make sure we load the appropriate biz model
|
||||
|
||||
|
||||
```js
|
||||
!!bizmodel.load name:'default' url:'https://github.com/freeflowuniverse/herolib/tree/development/bizmodel/example/data'
|
||||
```
|
||||
|
||||
1
examples/biztools/bizmodel/example/wiki/.collection
Normal file
1
examples/biztools/bizmodel/example/wiki/.collection
Normal file
@@ -0,0 +1 @@
|
||||
name:bizmodel_example
|
||||
10
examples/biztools/bizmodel/example/wiki/bizmodel.md
Normal file
10
examples/biztools/bizmodel/example/wiki/bizmodel.md
Normal file
@@ -0,0 +1,10 @@
|
||||

|
||||
|
||||
# bizmodel
|
||||
|
||||
OurWorld has developed a tool to generate and keep business models up to date.
|
||||
|
||||
Our aim is to make it easy for ourworld to track changes in planning over the multiple projects and even be able to aggregated them. Because the input for such a plan is text (as you can see in this ebook) its easy to see how the modelling and parameters change over time.
|
||||
|
||||
This is a very flexible tool which will be extended for budgetting, cashflow management, shareholder tables, ...
|
||||
|
||||
4
examples/biztools/bizmodel/example/wiki/debug.md
Normal file
4
examples/biztools/bizmodel/example/wiki/debug.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Debug
|
||||
|
||||
Some tools and info to help debug the bizmodel simulator.
|
||||
|
||||
8
examples/biztools/bizmodel/example/wiki/employees.md
Normal file
8
examples/biztools/bizmodel/example/wiki/employees.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# Hr Overview
|
||||
|
||||
|
||||
|
||||
!!!bizmodel.employees_wiki bizname:'test'
|
||||
|
||||
|
||||
> note: Nr People like 0:5,20:5 means, month 0 (start) is 5, month 20 its 5 people
|
||||
5
examples/biztools/bizmodel/example/wiki/hr/cto.md
Normal file
5
examples/biztools/bizmodel/example/wiki/hr/cto.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# CTO
|
||||
|
||||
!!!bizmodel.employee_wiki bizname:'test' name:'despiegk'
|
||||
|
||||
!!wiki.include page:cto_description.md
|
||||
@@ -0,0 +1,3 @@
|
||||
## CTO Description
|
||||
|
||||
this is a page to test nested includes
|
||||
1
examples/biztools/bizmodel/example/wiki/img/.done
Normal file
1
examples/biztools/bizmodel/example/wiki/img/.done
Normal file
@@ -0,0 +1 @@
|
||||
ms1bmodel.png
|
||||
BIN
examples/biztools/bizmodel/example/wiki/img/ms1bmodel.png
Normal file
BIN
examples/biztools/bizmodel/example/wiki/img/ms1bmodel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
52
examples/biztools/bizmodel/example/wiki/overview.md
Normal file
52
examples/biztools/bizmodel/example/wiki/overview.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# This is our business model planner
|
||||
|
||||
## P&L Overview
|
||||
|
||||
|
||||
<!-- period is in months, 3 means every quarter -->
|
||||
|
||||
|
||||
!!bizmodel.graph_bar_row rowname:revenue_total unit:million title:'A Title' title_sub:'Sub' sheetname:'bizmodel_test'
|
||||
|
||||
Unit is in Million USD.
|
||||
|
||||
!!bizmodel.graph_bar_row rowname:revenue_total unit:million sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.graph_line_row rowname:revenue_total unit:million sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.graph_pie_row rowname:revenue_total unit:million size:'80%' sheetname:'bizmodel_test'
|
||||
|
||||
## FUNDING
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:'funding' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'REVENUE' includefilter:rev sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'Revenue Total' includefilter:'revtotal' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'REVENUE' includefilter:'revtotal2' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'COGS' includefilter:'cogs' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'Margin' includefilter:'margin' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'HR Teams' includefilter:'hrnr' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'HR Costs' includefilter:'hrcost' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'COSTS' includefilter:'ocost' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'HR Costs' includefilter:'hrcost' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'P&L Overview' includefilter:'pl' sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'P&L Overview' includefilter:'pl' sheetname:'bizmodel_test'
|
||||
|
||||
## Some Details
|
||||
|
||||
> show how we can do per month
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:'pl' period_months:1 sheetname:'bizmodel_test'
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# HR Params
|
||||
|
||||
## Engineering
|
||||
|
||||
Costs can be grouped in cost centers which can then be used to futher process e.g. transcactions between companies.
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.costcenter_define bizname:'test'
|
||||
name:'tfdmcc'
|
||||
descr:'TFDMCC executes on near source agreement for TFTech'
|
||||
min_month:'10000USD'
|
||||
max_month:'100000USD'
|
||||
end_date:'1/1/2026' //when does agreement stop
|
||||
|
||||
!!bizmodel.costcenter_define bizname:'test'
|
||||
name:'cs_tftech'
|
||||
descr:'Nearsource agreement for TFTech towards Codescalers'
|
||||
min_month:'10000USD'
|
||||
max_month:'100000USD'
|
||||
end_date:'1/1/2026'
|
||||
|
||||
!!bizmodel.costcenter_define bizname:'test'
|
||||
name:'cs_tfcloud'
|
||||
descr:'Nearsource agreement for TFCloud towards Codescalers'
|
||||
min_month:'10000USD'
|
||||
max_month:'100000USD'
|
||||
end_date:'1/1/2026'
|
||||
|
||||
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
# Generic Overhead Costs
|
||||
|
||||
possible parameters
|
||||
|
||||
- name
|
||||
- descr: description of the cost
|
||||
- cost: is 'month:amount,month:amount, ...', no extrapolation
|
||||
- cost_growth: is 'month:amount,month:amount, ..., or just a nr', will extrapolate
|
||||
- type: travel, admin, legal, varia, office
|
||||
- cost_percent_revenue e.g. 4%, will make sure the cost will be at least 4% of revenue
|
||||
- indexation, e.g. 2%
|
||||
|
||||
Other financial flows can be mentioned here as well.
|
||||
|
||||
|
||||
```js
|
||||
!!bizmodel.cost_define bizname:'test'
|
||||
name:'rental'
|
||||
descr:'Office Rental in BE.'
|
||||
cost:'5000'
|
||||
indexation:'2%'
|
||||
type:'office'
|
||||
|
||||
!!bizmodel.cost_define bizname:'test'
|
||||
name:'oneoff'
|
||||
descr:'Event in Z.'
|
||||
cost_one:'3:50000'
|
||||
type:'event'
|
||||
|
||||
!!bizmodel.cost_define bizname:'test'
|
||||
name:'cloud'
|
||||
descr:'Datacenter and Cloud Costs'
|
||||
cost:'2000eur'
|
||||
cost_percent_revenue:'2%'
|
||||
type:'cloud'
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Department Params
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.department_define bizname:'test'
|
||||
name:'ops'
|
||||
title:'Operations'
|
||||
order:5
|
||||
|
||||
!!bizmodel.department_define bizname:'test'
|
||||
name:'coordination'
|
||||
title:'Coordination'
|
||||
order:1
|
||||
|
||||
!!bizmodel.department_define bizname:'test'
|
||||
name:'engineering'
|
||||
title:'Engineering'
|
||||
order:4
|
||||
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
# Funding Params
|
||||
|
||||
possible parameters
|
||||
|
||||
- name, e.g. for a specific person
|
||||
- descr: description of the funding
|
||||
- investment is month:amount,month:amount, ...
|
||||
- type: loan or capital
|
||||
|
||||
Other financial flows can be mentioned here as well.
|
||||
|
||||
|
||||
```js
|
||||
!!bizmodel.funding_define bizname:'test'
|
||||
name:'our_investor'
|
||||
descr:'A fantastic super investor.'
|
||||
investment:'3:1000000EUR'
|
||||
type:'capital'
|
||||
|
||||
!!bizmodel.funding_define bizname:'test'
|
||||
name:'a_founder'
|
||||
descr:'Together Are Strong'
|
||||
investment:'2000000'
|
||||
type:'loan'
|
||||
|
||||
|
||||
|
||||
```
|
||||
|
||||
73
examples/biztools/bizmodel/example/wiki/params/hr_params.md
Normal file
73
examples/biztools/bizmodel/example/wiki/params/hr_params.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# HR Params
|
||||
|
||||
## Engineering
|
||||
|
||||
possible parameters
|
||||
|
||||
- descr, description of the function (e.g. master architect)
|
||||
- cost, any currency eg. 1000usd
|
||||
- in case cost changes over time e.g. 1:10000USD,20:20000USD,60:30000USD
|
||||
- indexation, e.g. 2%
|
||||
- department
|
||||
- name, e.g. for a specific person
|
||||
- nrpeople: how many people per month, growth over time notation e.g. 1:10,60:20 means 10 in month 1 growing to 20 month 60
|
||||
- cost_percent_revenue e.g. 4%, will make sure the cost will be at least 4% of revenue
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
sid:2
|
||||
descr:'Senior Engineer'
|
||||
cost:'1:12000,12:14000' //cost is always per person
|
||||
department:'engineering'
|
||||
nrpeople:'0:5,20:5'
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
name:'despiegk'
|
||||
title: 'CTO and crazy inventor.'
|
||||
sid:3
|
||||
descr:'CTO'
|
||||
cost:'12000EUR' //the salary is the cost independent of the fulltime status
|
||||
indexation:'10%'
|
||||
department:'coordination'
|
||||
page:'cto.md'
|
||||
fulltime: "50%" //100% means yes
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Senior Architect'
|
||||
cost:'10000USD' indexation:'5%'
|
||||
department:'engineering'
|
||||
nrpeople:'0:5,20:10'
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Junior Engineer'
|
||||
cost:'4000USD' indexation:'5%'
|
||||
department:'engineering'
|
||||
nrpeople:'0:5,20:10'
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Operations
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Ops Manager'
|
||||
cost:'1:8000,12:14000'
|
||||
department:'ops'
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Support Junior'
|
||||
cost:'2000EUR' indexation:'5%'
|
||||
department:'ops'
|
||||
nrpeople:'7:5,18:10'
|
||||
cost_percent_revenue:'1%'
|
||||
!!bizmodel.employee_define bizname:'test'
|
||||
descr:'Support Senior'
|
||||
cost:'5000EUR' indexation:'5%'
|
||||
department:'ops'
|
||||
nrpeople:'3:5,20:10'
|
||||
cost_percent_revenue:'1%'
|
||||
costcenter:'tfdmcc:25,cs_tfcloud:75'
|
||||
generate_page:'../employees/support_senior.md'
|
||||
```
|
||||
14
examples/biztools/bizmodel/example/wiki/params/params.md
Normal file
14
examples/biztools/bizmodel/example/wiki/params/params.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Bizmodel Params
|
||||
|
||||
In this section we can find all the parameters for the bizmodel.
|
||||
|
||||
## how to use and read
|
||||
|
||||
The params are defined in the different instruction files e.g. revenue_params.md
|
||||
|
||||
Often you will see something like `revenue_growth:'10:1000,20:1100'` this can be read as month 10 it 1000, month 20 its 1100.
|
||||
|
||||
The software will extrapolate.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# HR Params
|
||||
|
||||
## Revenue Items (non recurring)
|
||||
|
||||
This company is a cloud company ...
|
||||
|
||||
```js
|
||||
!!bizmodel.revenue_define bizname:'test'
|
||||
descr:'OEM Deals'
|
||||
revenue_time:'10:1000000EUR,15:3333,20:1200000'
|
||||
cogs_perc: '1:5%,20:10%'
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test'
|
||||
descr:'License Deals'
|
||||
revenue_growth:'10:1000,20:1100'
|
||||
cogs_perc: '10%'
|
||||
rev_delay_month: 1
|
||||
|
||||
!!bizmodel.revenue_define bizname:'test'
|
||||
descr:'3NODE License Sales 1 Time'
|
||||
//means revenue is 100 month 1, 200 month 60
|
||||
revenue_item:'1:100,60:200'
|
||||
revenue_nr:'10:1000,24:2000,60:40000'
|
||||
cogs_perc: '10%'
|
||||
rev_delay_month: 1
|
||||
```
|
||||
|
||||
## Revenue Items Recurring
|
||||
|
||||
possible parameters
|
||||
|
||||
- name, e.g. for a specific project
|
||||
- descr, description of the revenue line item
|
||||
- revenue_setup, revenue for 1 item '1000usd'
|
||||
- revenue_monthly, revenue per month for 1 item
|
||||
- revenue_setup_delay, how many months before revenue comes in after sales
|
||||
- revenue_monthly_delay, how many months before monthly revenue starts
|
||||
- cogs_setup, cost of good for 1 item at setup
|
||||
- cogs_setup_perc: what is percentage of the cogs (can change over time) for setup e.g. 0:50%
|
||||
- cogs_monthly, cost of goods for the monthly per 1 item
|
||||
- cogs_monthly_perc: what is percentage of the cogs (can change over time) for monthly e.g. 0:5%,12:10%
|
||||
- nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200)
|
||||
- nr_months: how many months is recurring
|
||||
|
||||
if currency not specified then is always in USD
|
||||
|
||||
```js
|
||||
|
||||
!!bizmodel.revenue_recurring_define bizname:'test'
|
||||
name: '3node_lic'
|
||||
descr:'3NODE License Sales Recurring Basic'
|
||||
revenue_setup:'1:100,60:50'
|
||||
// revenue_setup:'5'
|
||||
revenue_monthly_delay:3
|
||||
revenue_monthly:'1:1,60:1'
|
||||
// cogs_setup:'1:0'
|
||||
cogs_setup_perc:'50%'
|
||||
revenue_setup_delay:1
|
||||
cogs_monthly_perc:'50%'
|
||||
nr_sold:'10:1000,24:2000,60:40000'
|
||||
60 is the default
|
||||
nr_months:60
|
||||
```
|
||||
|
||||
13
examples/biztools/bizmodel/example/wiki/revenue.md
Normal file
13
examples/biztools/bizmodel/example/wiki/revenue.md
Normal file
@@ -0,0 +1,13 @@
|
||||
## Revenue
|
||||
|
||||
Overview of achieved revenue.
|
||||
|
||||
Unit is in Million USD.
|
||||
|
||||
!!bizmodel.sheet_wiki title:'REVENUE' includefilter:rev sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.graph_bar_row rowname:revenue_total unit:million sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.graph_line_row rowname:revenue_total unit:million sheetname:'bizmodel_test'
|
||||
|
||||
!!bizmodel.graph_pie_row rowname:revenue_total unit:million size:'80%' sheetname:'bizmodel_test'
|
||||
13
examples/biztools/bizmodel/example/wiki/summary.md
Normal file
13
examples/biztools/bizmodel/example/wiki/summary.md
Normal file
@@ -0,0 +1,13 @@
|
||||
- [bizmodel](bizmodel_example/bizmodel.md)
|
||||
- [Revenue](bizmodel_example/revenue.md)
|
||||
- [Result](bizmodel_example/overview.md)
|
||||
- [parameters](bizmodel_example/params.md)
|
||||
- [revenue_params](bizmodel_example/params/revenue_params.md)
|
||||
- [funding_params](bizmodel_example/params/funding_params.md)
|
||||
- [hr_params](bizmodel_example/params/hr_params.md)
|
||||
- [costs_params](bizmodel_example/params/costs_params.md)
|
||||
- [rows overview](bizmodel_example/rows_overview.md)
|
||||
- [employees](bizmodel_example/employees.md)
|
||||
- [debug](bizmodel_example/debug.md)
|
||||
- [worksheet](bizmodel_example/worksheet.md)
|
||||
|
||||
4
examples/biztools/bizmodel/example/wiki/worksheet.md
Normal file
4
examples/biztools/bizmodel/example/wiki/worksheet.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# Overview of the rows in the biz model sheet
|
||||
|
||||
|
||||
!!bizmodel.sheet_wiki sheetname:'bizmodel_test'
|
||||
48
examples/biztools/bizmodel/tf9_biz.vsh
Executable file
48
examples/biztools/bizmodel/tf9_biz.vsh
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env -S v -cg -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
// #!/usr/bin/env -S v -cg -enable-globals run
|
||||
import freeflowuniverse.herolib.data.doctree
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.biz.bizmodel
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.core.playcmds
|
||||
import freeflowuniverse.herolib.web.mdbook
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
import os
|
||||
|
||||
const name = 'tf9_budget'
|
||||
|
||||
const wikipath = '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/info_ourworld/collections/${name}'
|
||||
const summarypath = '${wikipath}/summary.md'
|
||||
|
||||
// mut sh := spreadsheet.sheet_new(name: 'test2') or { panic(err) }
|
||||
// println(sh)
|
||||
// sh.row_new(descr: 'this is a description', name: 'something', growth: '0:100aed,55:1000eur')!
|
||||
// println(sh)
|
||||
// println(sh.wiki()!)
|
||||
|
||||
// exit(0)
|
||||
|
||||
// execute the actions so we have the info populated
|
||||
// mut plb:=playbook.new(path: wikipath)!
|
||||
// playcmds.run(mut plb,false)!
|
||||
|
||||
buildpath := '${os.home_dir()}/hero/var/mdbuild/bizmodel'
|
||||
|
||||
// just run the doctree & mdbook and it should
|
||||
// load the doctree, these are all collections
|
||||
mut tree := doctree.new(name: name)!
|
||||
tree.scan(path: wikipath)!
|
||||
tree.export(dest: buildpath, reset: true)!
|
||||
|
||||
// mut bm:=bizmodel.get("test")!
|
||||
// println(bm)
|
||||
|
||||
mut mdbooks := mdbook.get()!
|
||||
mdbooks.generate(
|
||||
name: 'bizmodel'
|
||||
summary_path: summarypath
|
||||
doctree_path: buildpath
|
||||
title: 'bizmodel ${name}'
|
||||
)!
|
||||
mdbook.book_open('bizmodel')!
|
||||
12
examples/biztools/bizmodel/todo.md
Normal file
12
examples/biztools/bizmodel/todo.md
Normal file
@@ -0,0 +1,12 @@
|
||||
need to find where the manual is
|
||||
|
||||
- [manual](bizmodel_example/configuration.md)
|
||||
- [widgets](bizmodel_example/widgets.md)
|
||||
- [graph_bar_row](bizmodel_example/graph_bar_row.md)
|
||||
- [sheet_tables](bizmodel_example/sheet_tables.md)
|
||||
- [widget_args](bizmodel_example/widget_args.md)
|
||||
- [params](bizmodel_example/configuration.md)
|
||||
- [revenue params](bizmodel_example/revenue_params.md)
|
||||
- [funding params](bizmodel_example/funding_params.md)
|
||||
- [hr params](bizmodel_example/hr_params.md)
|
||||
- [costs params](bizmodel_example/costs_params.md)
|
||||
11
examples/biztools/investor_tool.vsh
Executable file
11
examples/biztools/investor_tool.vsh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env -S v -gc none -no-retry-compilation -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.biz.investortool
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import os
|
||||
|
||||
mut plbook := playbook.new(
|
||||
path: '${os.home_dir()}/code/git.ourworld.tf/ourworld_holding/investorstool/output'
|
||||
)!
|
||||
mut it := investortool.play(mut plbook)!
|
||||
it.check()!
|
||||
108
examples/clients/mycelium.vsh
Executable file
108
examples/clients/mycelium.vsh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer
|
||||
import freeflowuniverse.herolib.osal
|
||||
import time
|
||||
import os
|
||||
import encoding.base64
|
||||
|
||||
const server1_port = 9001
|
||||
const server2_port = 9002
|
||||
|
||||
fn terminate(port int) ! {
|
||||
// Step 1: Run lsof to get process details
|
||||
res := os.execute('lsof -i:${port}')
|
||||
if res.exit_code != 0 {
|
||||
return error('no service running at port ${port} due to: ${res.output}')
|
||||
}
|
||||
|
||||
// Step 2: Parse the output to extract the PID
|
||||
lines := res.output.split('\n')
|
||||
if lines.len < 2 {
|
||||
return error('no process found running on port ${port}')
|
||||
}
|
||||
|
||||
// The PID is the second column in the output
|
||||
fields := lines[1].split(' ')
|
||||
if fields.len < 2 {
|
||||
return error('failed to parse lsof output')
|
||||
}
|
||||
pid := fields[1]
|
||||
|
||||
// Step 3: Kill the process using the PID
|
||||
kill_res := os.execute('kill ${pid}')
|
||||
if kill_res.exit_code != 0 {
|
||||
return error('failed to kill process ${pid}: ${kill_res.output}')
|
||||
}
|
||||
|
||||
println('Successfully terminated process ${pid} running on port ${port}')
|
||||
}
|
||||
|
||||
// Check if not installed install it.
|
||||
mut installer := mycelium_installer.get()!
|
||||
installer.install()!
|
||||
|
||||
mycelium.delete()!
|
||||
|
||||
spawn fn () {
|
||||
os.execute('mkdir -p /tmp/mycelium_server1 && cd /tmp/mycelium_server1 && mycelium --peers tcp://188.40.132.242:9651 quic://[2a01:4f8:212:fa6::2]:9651 tcp://185.69.166.7:9651 quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651 tcp://65.21.231.58:9651 quic://[2a01:4f9:5a:1042::2]:9651 tcp://[2604:a00:50:17b:9e6b:ff:fe1f:e054]:9651 quic://5.78.122.16:9651 tcp://[2a01:4ff:2f0:3621::1]:9651 quic://142.93.217.194:9651 --tun-name tun2 --tcp-listen-port 9652 --quic-listen-port 9653 --api-addr 127.0.0.1:${server1_port}')
|
||||
}()
|
||||
|
||||
spawn fn () {
|
||||
os.execute('mkdir -p /tmp/mycelium_server2 && cd /tmp/mycelium_server2 && mycelium --peers tcp://188.40.132.242:9651 quic://[2a01:4f8:212:fa6::2]:9651 tcp://185.69.166.7:9651 quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651 tcp://65.21.231.58:9651 quic://[2a01:4f9:5a:1042::2]:9651 tcp://[2604:a00:50:17b:9e6b:ff:fe1f:e054]:9651 quic://5.78.122.16:9651 tcp://[2a01:4ff:2f0:3621::1]:9651 quic://142.93.217.194:9651 --tun-name tun3 --tcp-listen-port 9654 --quic-listen-port 9655 --api-addr 127.0.0.1:${server2_port}')
|
||||
}()
|
||||
|
||||
defer {
|
||||
terminate(server1_port) or {}
|
||||
terminate(server2_port) or {}
|
||||
}
|
||||
|
||||
time.sleep(2 * time.second)
|
||||
|
||||
mut client1 := mycelium.get()!
|
||||
client1.server_url = 'http://localhost:${server1_port}'
|
||||
client1.name = 'client1'
|
||||
println(client1)
|
||||
|
||||
mut client2 := mycelium.get()!
|
||||
client2.server_url = 'http://localhost:${server2_port}'
|
||||
client2.name = 'client2'
|
||||
println(client2)
|
||||
|
||||
inspect1 := mycelium.inspect(key_file_path: '/tmp/mycelium_server1/priv_key.bin')!
|
||||
inspect2 := mycelium.inspect(key_file_path: '/tmp/mycelium_server2/priv_key.bin')!
|
||||
|
||||
println('Server 1 public key: ${inspect1.public_key}')
|
||||
println('Server 2 public key: ${inspect2.public_key}')
|
||||
|
||||
// Send a message to a node by public key
|
||||
// Parameters: public_key, payload, topic, wait_for_reply
|
||||
msg := client1.send_msg(
|
||||
public_key: inspect2.public_key // destination public key
|
||||
payload: 'Sending a message from the client 1 to the client 2' // message payload
|
||||
topic: 'testing' // optional topic
|
||||
)!
|
||||
|
||||
println('Sent message ID: ${msg.id}')
|
||||
println('send succeeded')
|
||||
|
||||
// Receive messages
|
||||
// Parameters: wait_for_message, peek_only, topic_filter
|
||||
received := client2.receive_msg(wait: true, peek: false, topic: 'testing')!
|
||||
println('Received message from: ${received.src_pk}')
|
||||
println('Message payload: ${base64.decode_str(received.payload)}')
|
||||
|
||||
// Reply to a message
|
||||
// client1.reply_msg(
|
||||
// id: received.id
|
||||
// public_key: received.src_pk
|
||||
// payload: 'Got your message!'
|
||||
// topic: 'greetings'
|
||||
// )!
|
||||
|
||||
// // // Check message status
|
||||
// // status := client.get_msg_status(msg.id)!
|
||||
// // println('Message status: ${status.state}')
|
||||
// // println('Created at: ${status.created}')
|
||||
// // println('Expires at: ${status.deadline}')
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.develop.gittools
|
||||
import freeflowuniverse.herolib.osal
|
||||
import time
|
||||
|
||||
mut gs_default := gittools.new()!
|
||||
|
||||
println(gs_default)
|
||||
|
||||
// // Initializes the Git structure with the coderoot path.
|
||||
// coderoot := '/tmp/code'
|
||||
// mut gs_tmo := gittools.new(coderoot: coderoot)!
|
||||
|
||||
// // Retrieve the specified repository.
|
||||
// mut repo := gs_default.get_repo(name: 'herolib')!
|
||||
|
||||
// println(repo)
|
||||
14
examples/develop/gittools/gittools_path_get.vsh
Executable file
14
examples/develop/gittools/gittools_path_get.vsh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.develop.gittools
|
||||
import freeflowuniverse.herolib.osal
|
||||
import time
|
||||
|
||||
mut gs := gittools.new()!
|
||||
mydocs_path := gs.get_path(
|
||||
pull: true
|
||||
reset: false
|
||||
url: 'https://git.ourworld.tf/tfgrid/info_docs_depin/src/branch/main/docs'
|
||||
)!
|
||||
|
||||
println(mydocs_path)
|
||||
@@ -1 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
@@ -1 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
|
||||
import freeflowuniverse.herolib.osal
|
||||
|
||||
coredns_installer.install()!
|
||||
// coredns_installer.delete()!
|
||||
mut installer := coredns_installer.get()!
|
||||
installer.build()!
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.daguserver
|
||||
import freeflowuniverse.herolib.installers.infra.zinit
|
||||
|
||||
// make sure zinit is there and running, will restart it if needed
|
||||
mut z := zinit.get()!
|
||||
z.destroy()!
|
||||
z.start()!
|
||||
|
||||
// mut ds := daguserver.get()!
|
||||
// ds.destroy()!
|
||||
// ds.start()!
|
||||
|
||||
// println(ds)
|
||||
8
examples/installers/db/meilisearch.vsh
Executable file
8
examples/installers/db/meilisearch.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.db.meilisearch_installer
|
||||
|
||||
mut meilisearch := meilisearch_installer.get()!
|
||||
meilisearch.install()!
|
||||
meilisearch.start()!
|
||||
meilisearch.destroy()!
|
||||
9
examples/installers/db/postgresql.vsh
Executable file
9
examples/installers/db/postgresql.vsh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.db.postgresql as postgresql_installer
|
||||
|
||||
mut db := postgresql_installer.get()!
|
||||
|
||||
db.install()!
|
||||
db.start()!
|
||||
db.destroy()!
|
||||
9
examples/installers/db/zerodb.vsh
Executable file
9
examples/installers/db/zerodb.vsh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.db.zerodb as zerodb_installer
|
||||
|
||||
mut db := zerodb_installer.get()!
|
||||
|
||||
db.install()!
|
||||
db.start()!
|
||||
db.destroy()!
|
||||
@@ -1,16 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.gitea as gitea_installer
|
||||
|
||||
mut installer := gitea_installer.get(name: 'test')!
|
||||
|
||||
// if you want to configure using heroscript
|
||||
gitea_installer.play(
|
||||
heroscript: "
|
||||
!!gitea.configure name:test
|
||||
passwd:'something'
|
||||
domain: 'docs.info.com'
|
||||
"
|
||||
)!
|
||||
|
||||
installer.start()!
|
||||
8
examples/installers/infra/gitea.vsh
Executable file
8
examples/installers/infra/gitea.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.gitea as gitea_installer
|
||||
|
||||
mut gitea := gitea_installer.get()!
|
||||
gitea.install()!
|
||||
gitea.start()!
|
||||
gitea.destroy()!
|
||||
8
examples/installers/infra/livekit.vsh
Executable file
8
examples/installers/infra/livekit.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.livekit as livekit_installer
|
||||
|
||||
mut livekit := livekit_installer.get()!
|
||||
livekit.install()!
|
||||
livekit.start()!
|
||||
livekit.destroy()!
|
||||
8
examples/installers/infra/screen.vsh
Executable file
8
examples/installers/infra/screen.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.screen as screen_installer
|
||||
|
||||
mut screen := screen_installer.get()!
|
||||
|
||||
screen.install()!
|
||||
screen.destroy()!
|
||||
8
examples/installers/infra/zinit_installer.vsh
Executable file
8
examples/installers/infra/zinit_installer.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.zinit_installer
|
||||
|
||||
mut installer := zinit_installer.get()!
|
||||
installer.install()!
|
||||
installer.start()!
|
||||
// installer.destroy()!
|
||||
6
examples/installers/lang/golang.vsh
Executable file
6
examples/installers/lang/golang.vsh
Executable file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.golang
|
||||
|
||||
mut golang_installer := golang.get()!
|
||||
golang_installer.install()!
|
||||
7
examples/installers/lang/nodejs.vsh
Executable file
7
examples/installers/lang/nodejs.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.nodejs
|
||||
|
||||
mut nodejs_installer := nodejs.get()!
|
||||
// nodejs_installer.install()!
|
||||
nodejs_installer.destroy()!
|
||||
7
examples/installers/lang/python.vsh
Executable file
7
examples/installers/lang/python.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.python as python_module
|
||||
|
||||
mut python_installer := python_module.get()!
|
||||
// python_installer.install()!
|
||||
python_installer.destroy()!
|
||||
7
examples/installers/lang/rust.vsh
Executable file
7
examples/installers/lang/rust.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.lang.rust as rust_module
|
||||
|
||||
mut rust_installer := rust_module.get()!
|
||||
// rust_installer.install()!
|
||||
rust_installer.destroy()!
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.net.mycelium as mycelium_installer
|
||||
|
||||
mycelium_installer.start()!
|
||||
42
examples/installers/net/mycelium.vsh
Executable file
42
examples/installers/net/mycelium.vsh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.net.mycelium_installer
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
mut installer := mycelium_installer.get()!
|
||||
installer.start()!
|
||||
|
||||
mut r := mycelium.inspect()!
|
||||
println(r)
|
||||
|
||||
mut client := mycelium.get()!
|
||||
|
||||
// Send a message to a node by public key
|
||||
// Parameters: public_key, payload, topic, wait_for_reply
|
||||
msg := client.send_msg(
|
||||
public_key: 'abc123...' // destination public key
|
||||
payload: 'Hello World' // message payload
|
||||
topic: 'greetings' // optional topic
|
||||
wait: true // wait for reply
|
||||
)!
|
||||
println('Sent message ID: ${msg.id}')
|
||||
|
||||
// Receive messages
|
||||
// Parameters: wait_for_message, peek_only, topic_filter
|
||||
received := client.receive_msg(wait: true, peek: false, topic: 'greetings')!
|
||||
println('Received message from: ${received.src_pk}')
|
||||
println('Message payload: ${received.payload}')
|
||||
|
||||
// Reply to a message
|
||||
client.reply_msg(
|
||||
id: received.id // original message ID
|
||||
public_key: received.src_pk // sender's public key
|
||||
payload: 'Got your message!' // reply payload
|
||||
topic: 'greetings' // topic
|
||||
)!
|
||||
|
||||
// Check message status
|
||||
status := client.get_msg_status(msg.id)!
|
||||
println('Message status: ${status.state}')
|
||||
println('Created at: ${status.created}')
|
||||
println('Expires at: ${status.deadline}')
|
||||
7
examples/installers/net/wireguard.vsh
Executable file
7
examples/installers/net/wireguard.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.net.wireguard_installer as wireguard
|
||||
|
||||
mut wireguard_installer := wireguard.get()!
|
||||
wireguard_installer.install()!
|
||||
wireguard_installer.destroy()!
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import time
|
||||
import freeflowuniverse.herolib.installers.db.postgresql
|
||||
|
||||
mut db := postgresql.get()!
|
||||
|
||||
// db.destroy()!
|
||||
db.start()!
|
||||
|
||||
// db.db_create('my_new_db')!
|
||||
// db.stop()!
|
||||
// db.start()!
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.actrunner
|
||||
import freeflowuniverse.herolib.installers.virt.herocontainers
|
||||
// import freeflowuniverse.herolib.installers.virt.herocontainers
|
||||
|
||||
actrunner.install()!
|
||||
mut actrunner_ := actrunner.get()!
|
||||
actrunner_.install()!
|
||||
// herocontainers.start()!
|
||||
8
examples/installers/sysadmintools/garage_s3.vsh
Executable file
8
examples/installers/sysadmintools/garage_s3.vsh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.garage_s3 as garage_s3_installer
|
||||
|
||||
mut garage_s3 := garage_s3_installer.get()!
|
||||
garage_s3.install()!
|
||||
garage_s3.start()!
|
||||
garage_s3.destroy()!
|
||||
7
examples/installers/sysadmintools/rclone.vsh
Executable file
7
examples/installers/sysadmintools/rclone.vsh
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.rclone as rclone_installer
|
||||
|
||||
mut rclone := rclone_installer.get()!
|
||||
rclone.install()!
|
||||
rclone.destroy()!
|
||||
@@ -4,3 +4,4 @@ import freeflowuniverse.herolib.installers.threefold.griddriver
|
||||
|
||||
mut griddriver_installer := griddriver.get()!
|
||||
griddriver_installer.install()!
|
||||
griddriver_installer.destroy()!
|
||||
12
examples/installers/traefik.vsh
Executable file
12
examples/installers/traefik.vsh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.installers.web.traefik as traefik_installer
|
||||
|
||||
traefik_installer.delete()!
|
||||
mut installer := traefik_installer.get()!
|
||||
|
||||
installer.password = 'planet'
|
||||
traefik_installer.set(installer)!
|
||||
|
||||
installer.start()!
|
||||
9
examples/installers/virt/dagu.vsh
Executable file
9
examples/installers/virt/dagu.vsh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.sysadmintools.daguserver
|
||||
import freeflowuniverse.herolib.installers.infra.zinit_installer
|
||||
|
||||
mut ds := daguserver.get()!
|
||||
ds.install()!
|
||||
ds.start()!
|
||||
ds.destroy()!
|
||||
11
examples/installers/virt/pacman.vsh
Executable file
11
examples/installers/virt/pacman.vsh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.virt.pacman as pacman_installer
|
||||
|
||||
mut pacman := pacman_installer.get()!
|
||||
|
||||
// To install
|
||||
pacman.install()!
|
||||
|
||||
// To remove
|
||||
pacman.destroy()!
|
||||
60
examples/osal/coredns/example.vsh
Executable file
60
examples/osal/coredns/example.vsh
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.installers.infra.coredns as coredns_installer
|
||||
import freeflowuniverse.herolib.osal.coredns
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
|
||||
// coredns_installer.delete()!
|
||||
mut installer := coredns_installer.get()!
|
||||
// coredns_installer.fix()!
|
||||
installer.start()!
|
||||
|
||||
mut script := "
|
||||
!!dns.a_record
|
||||
sub_domain: 'host1'
|
||||
ip: '1.2.3.4'
|
||||
ttl: 300
|
||||
|
||||
!!dns.aaaa_record
|
||||
sub_domain: 'host1'
|
||||
ip: '2001:db8::1'
|
||||
ttl: 300
|
||||
|
||||
!!dns.mx_record
|
||||
sub_domain: '*'
|
||||
host: 'mail.example.com'
|
||||
preference: 10
|
||||
ttl: 300
|
||||
|
||||
!!dns.txt_record
|
||||
sub_domain: '*'
|
||||
text: 'v=spf1 mx ~all'
|
||||
ttl: 300
|
||||
|
||||
!!dns.srv_record
|
||||
service: 'ssh'
|
||||
protocol: 'tcp'
|
||||
host: 'host1'
|
||||
target: 'sip.example.com'
|
||||
port: 5060
|
||||
priority: 10
|
||||
weight: 100
|
||||
ttl: 300
|
||||
|
||||
!!dns.ns_record
|
||||
host: 'ns1.example.com'
|
||||
ttl: 300
|
||||
|
||||
!!dns.soa_record
|
||||
mbox: 'hostmaster.example.com'
|
||||
ns: 'ns1.example.com'
|
||||
refresh: 44
|
||||
retry: 55
|
||||
expire: 66
|
||||
minttl: 100
|
||||
ttl: 300
|
||||
"
|
||||
|
||||
mut plbook := playbook.new(text: script)!
|
||||
mut set := coredns.play_dns(mut plbook)!
|
||||
set.set(key_prefix: 'dns:', domain: 'heroexample.com')!
|
||||
24
examples/osal/tun.vsh
Executable file
24
examples/osal/tun.vsh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.osal.tun
|
||||
|
||||
// Check if TUN is available
|
||||
if available := tun.available() {
|
||||
if available {
|
||||
println('TUN is available on this system')
|
||||
|
||||
// Get a free TUN interface name
|
||||
if interface_name := tun.free() {
|
||||
println('Found free TUN interface: ${interface_name}')
|
||||
|
||||
// Example: Now you could use this interface name
|
||||
// to set up your tunnel
|
||||
} else {
|
||||
println('Error finding free interface: ${err}')
|
||||
}
|
||||
} else {
|
||||
println('TUN is not available on this system')
|
||||
}
|
||||
} else {
|
||||
println('Error checking TUN availability: ${err}')
|
||||
}
|
||||
4
examples/virt/podman_buildah/.gitignore
vendored
Normal file
4
examples/virt/podman_buildah/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
buildah_example
|
||||
buildah_run_clean
|
||||
buildah_run_mdbook
|
||||
buildah_run
|
||||
@@ -1,15 +1,14 @@
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.virt.herocontainers
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.osal
|
||||
import freeflowuniverse.herolib.installers.virt.pacman
|
||||
import freeflowuniverse.herolib.installers.virt.podman as podman_installer
|
||||
|
||||
mut installer := pacman.get()!
|
||||
|
||||
// installer.destroy()!
|
||||
// installer.install()!
|
||||
mut podman_installer0 := podman_installer.get()!
|
||||
// podman_installer0.destroy()!
|
||||
podman_installer0.install()!
|
||||
|
||||
// exit(0)
|
||||
|
||||
@@ -17,13 +16,13 @@ mut installer := pacman.get()!
|
||||
|
||||
mut engine := herocontainers.new(install: true, herocompile: false)!
|
||||
|
||||
engine.reset_all()!
|
||||
// engine.reset_all()!
|
||||
|
||||
mut builder_gorust := engine.builder_go_rust()!
|
||||
// mut builder_gorust := engine.builder_go_rust()!
|
||||
|
||||
// will build nodejs, python build & herolib, hero
|
||||
// mut builder_hero := engine.builder_hero(reset:true)!
|
||||
|
||||
// mut builder_web := engine.builder_heroweb(reset:true)!
|
||||
|
||||
builder_gorust.shell()!
|
||||
// builder_gorust.shell()!
|
||||
|
||||
@@ -7,14 +7,22 @@ import freeflowuniverse.herolib.core.base
|
||||
import time
|
||||
import os
|
||||
|
||||
mut pm := herocontainers.new(herocompile: true, install: false)!
|
||||
// herocompile means we do it for the host system
|
||||
mut pm := herocontainers.new(herocompile: false, install: false)!
|
||||
|
||||
mut mybuildcontainer := pm.builder_get('builder_heroweb')!
|
||||
// pm.builder_base(reset:true)!
|
||||
|
||||
mut builder := pm.builder_get('base')!
|
||||
builder.shell()!
|
||||
|
||||
println(builder)
|
||||
|
||||
// builder.install_zinit()!
|
||||
|
||||
// bash & python can be executed directly in build container
|
||||
|
||||
// any of the herocommands can be executed like this
|
||||
mybuildcontainer.run(cmd: 'installers -n heroweb', runtime: .herocmd)!
|
||||
// mybuildcontainer.run(cmd: 'installers -n heroweb', runtime: .herocmd)!
|
||||
|
||||
// //following will execute heroscript in the buildcontainer
|
||||
// mybuildcontainer.run(
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import freeflowuniverse.herolib.virt.herocontainers
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.core.base
|
||||
// import freeflowuniverse.herolib.builder
|
||||
import time
|
||||
import os
|
||||
|
||||
@@ -5,16 +5,15 @@ import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
// Create a new docusaurus factory
|
||||
mut docs := docusaurus.new(
|
||||
// build_path: '/tmp/docusaurus_build'
|
||||
build_path: '/tmp/docusaurus_build'
|
||||
)!
|
||||
|
||||
// Create a new docusaurus site
|
||||
mut site := docs.dev(
|
||||
url:'https://git.ourworld.tf/despiegk/docs_kristof'
|
||||
url: 'https://git.ourworld.tf/despiegk/docs_kristof'
|
||||
)!
|
||||
|
||||
|
||||
//FOR FUTURE TO ADD CONTENT FROM DOCTREE
|
||||
// FOR FUTURE TO ADD CONTENT FROM DOCTREE
|
||||
|
||||
// Create a doctree for content
|
||||
// mut tree := doctree.new(name: 'content')!
|
||||
@@ -34,10 +33,10 @@ mut site := docs.dev(
|
||||
// )!
|
||||
|
||||
// Build the docusaurus site
|
||||
//site.build()!
|
||||
// site.build()!
|
||||
|
||||
// Generate the static site
|
||||
//site.generate()!
|
||||
// site.generate()!
|
||||
|
||||
// Optionally open the site in a browser
|
||||
// site.open()!
|
||||
|
||||
@@ -4,7 +4,7 @@ set -e
|
||||
|
||||
os_name="$(uname -s)"
|
||||
arch_name="$(uname -m)"
|
||||
version='1.0.6'
|
||||
version='1.0.14'
|
||||
|
||||
|
||||
# Base URL for GitHub releases
|
||||
|
||||
@@ -181,7 +181,7 @@ function os_update {
|
||||
fi
|
||||
#apt install apt-transport-https ca-certificates curl software-properties-common -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes
|
||||
package_install "apt-transport-https ca-certificates curl wget software-properties-common tmux"
|
||||
package_install "rclone rsync mc redis-server screen net-tools git dnsutils htop ca-certificates screen lsb-release binutils pkg-config"
|
||||
package_install "rclone rsync mc redis-server screen net-tools git dnsutils htop ca-certificates screen lsb-release binutils pkg-config libssl-dev iproute2"
|
||||
|
||||
elif [[ "${OSNAME}" == "darwin"* ]]; then
|
||||
if command -v brew >/dev/null 2>&1; then
|
||||
|
||||
155
lib/biz/bizmodel/act.v
Normal file
155
lib/biz/bizmodel/act.v
Normal file
@@ -0,0 +1,155 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook, Action }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.data.paramsparser {Params}
|
||||
import freeflowuniverse.herolib.biz.spreadsheet {RowGetArgs, UnitType, PeriodType}
|
||||
|
||||
pub fn (mut m BizModel) act(action Action) !Action {
|
||||
return match texttools.snake_case(action.name) {
|
||||
'funding_define' {
|
||||
m.funding_define_action(action)!
|
||||
}
|
||||
'revenue_define' {
|
||||
m.funding_define_action(action)!
|
||||
}
|
||||
'costcenter_define' {
|
||||
m.costcenter_define_action(action)!
|
||||
}
|
||||
'cost_define' {
|
||||
m.cost_define_action(action)!
|
||||
}
|
||||
'department_define' {
|
||||
m.department_define_action(action)!
|
||||
}
|
||||
'employee_define' {
|
||||
m.employee_define_action(action)!
|
||||
}
|
||||
'export_report' {
|
||||
m.new_report_action(action)!
|
||||
}
|
||||
'sheet_wiki' {
|
||||
m.export_sheet_action(action)!
|
||||
}
|
||||
'graph_bar_row' {
|
||||
m.export_graph_bar_action(action)!
|
||||
}
|
||||
'graph_pie_row' {
|
||||
m.export_graph_pie_action(action)!
|
||||
}
|
||||
'graph_line_row' {
|
||||
m.export_graph_line_action(action)!
|
||||
}
|
||||
'row_overview' {
|
||||
m.export_overview_action(action)!
|
||||
}
|
||||
else {
|
||||
return error('Unknown operation: ${action.name}')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut m BizModel) export_sheet_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
fn (mut m BizModel) export_graph_title_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_title_chart(row_args_from_params(action.params)!), action)
|
||||
}
|
||||
|
||||
fn (mut m BizModel) export_graph_line_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_line_chart(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
fn (mut m BizModel) export_graph_bar_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_bar_chart(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
pub fn (mut m BizModel) export_graph_pie_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_pie_chart(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
pub fn (mut m BizModel) export_overview_action(action Action) !Action {
|
||||
return m.export_action(m.sheet.wiki_row_overview(row_args_from_params(action.params)!)!, action)
|
||||
}
|
||||
|
||||
fn (mut m BizModel) new_report_action(action Action) !Action {
|
||||
m.new_report(action.params.decode[Report]()!)!
|
||||
return action
|
||||
}
|
||||
|
||||
// fetches args for getting row from params
|
||||
pub fn row_args_from_params(p Params) !RowGetArgs {
|
||||
rowname := p.get_default('rowname', '')!
|
||||
namefilter := p.get_list_default('namefilter', [])!
|
||||
includefilter := p.get_list_default('includefilter', [])!
|
||||
excludefilter := p.get_list_default('excludefilter', [])!
|
||||
size := p.get_default('size', '')!
|
||||
title_sub := p.get_default('title_sub', '')!
|
||||
title := p.get_default('title', '')!
|
||||
unit := p.get_default('unit', 'normal')!
|
||||
unit_e := match unit {
|
||||
'thousand' { UnitType.thousand }
|
||||
'million' { UnitType.million }
|
||||
'billion' { UnitType.billion }
|
||||
else { UnitType.normal }
|
||||
}
|
||||
period_type := p.get_default('period_type', 'year')!
|
||||
if period_type !in ['year', 'month', 'quarter'] {
|
||||
return error('period type needs to be in year,month,quarter')
|
||||
}
|
||||
period_type_e := match period_type {
|
||||
'year' { PeriodType.year }
|
||||
'month' { PeriodType.month }
|
||||
'quarter' { PeriodType.quarter }
|
||||
else { PeriodType.error }
|
||||
}
|
||||
if period_type_e == .error {
|
||||
return error('period type needs to be in year,month,quarter')
|
||||
}
|
||||
|
||||
rowname_show := p.get_default_true('rowname_show')
|
||||
descr_show := p.get_default_true('descr_show')
|
||||
|
||||
return RowGetArgs{
|
||||
rowname: rowname
|
||||
namefilter: namefilter
|
||||
includefilter: includefilter
|
||||
excludefilter: excludefilter
|
||||
period_type: period_type_e
|
||||
unit: unit_e
|
||||
title_sub: title_sub
|
||||
title: title
|
||||
size: size
|
||||
rowname_show: rowname_show
|
||||
descr_show: descr_show
|
||||
}
|
||||
}
|
||||
|
||||
// creates the name for a file being exported given the params of the export action
|
||||
fn (m BizModel) export_action(content string, action Action) !Action {
|
||||
// determine name of file being exported
|
||||
name := if action.params.exists('name') { action.params.get('name')! } else {
|
||||
if action.params.exists('title') { action.params.get('title')! } else {
|
||||
// if no name or title, name is ex: revenue_total_graph_bar_row
|
||||
rowname := action.params.get_default('rowname', '')!
|
||||
'${rowname}_${action.name}'
|
||||
}
|
||||
}
|
||||
|
||||
// by default exports to working dir of bizmodel
|
||||
destination := action.params.get_default('destination', m.workdir)!
|
||||
|
||||
mut path := pathlib.get_file(
|
||||
path: os.join_path(destination, name)
|
||||
increment: true
|
||||
empty: action.params.get_default_false('overwrite')
|
||||
)!
|
||||
|
||||
path.write(content)!
|
||||
return action
|
||||
}
|
||||
90
lib/biz/bizmodel/export.v
Normal file
90
lib/biz/bizmodel/export.v
Normal file
@@ -0,0 +1,90 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
pub struct Export {
|
||||
pub:
|
||||
path string
|
||||
overwrite bool
|
||||
format ExportFormat
|
||||
}
|
||||
|
||||
pub enum ExportFormat {
|
||||
docusaurus
|
||||
mdbook
|
||||
}
|
||||
|
||||
pub struct Report {
|
||||
pub:
|
||||
name string
|
||||
title string
|
||||
description string
|
||||
path string
|
||||
sections []ReportSection
|
||||
}
|
||||
|
||||
pub enum ReportSection {
|
||||
revenue_model
|
||||
cost_structure
|
||||
human_resources
|
||||
}
|
||||
|
||||
pub fn (b BizModel) new_report(report Report) !Report {
|
||||
name := if report.name != '' {report.name} else { texttools.snake_case(report.title) }
|
||||
path := pathlib.get_dir(
|
||||
path: os.join_path(os.home_dir(), '/hero/var/bizmodel/reports/${name}')
|
||||
)!
|
||||
return Report {
|
||||
...report,
|
||||
name: name
|
||||
path: path.path
|
||||
}
|
||||
// b.export_summary()
|
||||
// b.export_business_description()
|
||||
// b.export_market_analysis()
|
||||
// b.export_business_model()
|
||||
// b.export_revenue_model(export)!
|
||||
// b.export_cost_structure(export)
|
||||
// b.export_operational_plan(export)!
|
||||
// b.export_fundraising(export)
|
||||
}
|
||||
|
||||
pub fn (r Report) export(export Export) ! {
|
||||
match export.format {
|
||||
.docusaurus {
|
||||
mut factory := docusaurus.new()!
|
||||
mut site := factory.get(
|
||||
name: r.name
|
||||
path: r.path
|
||||
publish_path: export.path
|
||||
config: docusaurus.Config {} //TODO: is this needed
|
||||
)!
|
||||
site.build()!
|
||||
}
|
||||
.mdbook {panic('MDBook export not fully implemented')}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (b BizModel) export_operational_plan(export Export) ! {
|
||||
mut hr_page := pathlib.get_file(path: '${export.path}/human_resources.md')!
|
||||
hr_page.template_write('./templates/human_resources.md', export.overwrite)!
|
||||
|
||||
for key, employee in b.employees {
|
||||
mut employee_page := pathlib.get_file(path: '${export.path}/${texttools.snake_case(employee.name)}.md')!
|
||||
employee_page.template_write('./templates/employee.md', export.overwrite)!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (b BizModel) export_revenue_model(export Export) ! {
|
||||
println('begin')
|
||||
mut overview_page := pathlib.get_file(path: '${export.path}/revenue_overview.md')!
|
||||
overview_page.template_write('./templates/overview.md', export.overwrite)!
|
||||
|
||||
for key, product in b.products {
|
||||
mut product_page := pathlib.get_file(path: '${export.path}/${texttools.snake_case(product.name)}.md')!
|
||||
product_page.template_write('./templates/product.md', export.overwrite)!
|
||||
}
|
||||
}
|
||||
14
lib/biz/bizmodel/export_test.v
Normal file
14
lib/biz/bizmodel/export_test.v
Normal file
@@ -0,0 +1,14 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.web.docusaurus
|
||||
|
||||
const bizmodel_name = 'test'
|
||||
const export_path = os.join_path(os.dir(@FILE), 'testdata')
|
||||
|
||||
pub fn test_export_report() ! {
|
||||
model := getset(bizmodel_name)!
|
||||
model.export_report(Report{
|
||||
title: 'My Business Model'
|
||||
}, path: export_path)!
|
||||
}
|
||||
@@ -11,8 +11,8 @@ pub fn get(name string) !&BizModel {
|
||||
if name in bizmodels {
|
||||
return bizmodels[name] or { panic('bug') }
|
||||
}
|
||||
return error("cann't find biz model:'${name}' in global bizmodels ${bizmodels.keys()}")
|
||||
}
|
||||
return error("cann't find biz model:'${name}' in global bizmodels")
|
||||
}
|
||||
|
||||
// get bizmodel from global
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
module bizmodel
|
||||
|
||||
import os
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
pub struct BizModel {
|
||||
pub mut:
|
||||
name string
|
||||
workdir string = '${os.home_dir()}/hero/var/bizmodel'
|
||||
sheet &spreadsheet.Sheet
|
||||
employees map[string]&Employee
|
||||
departments map[string]&Department
|
||||
|
||||
@@ -1,89 +1,51 @@
|
||||
module bizmodel
|
||||
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook }
|
||||
import arrays
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.core.playbook { PlayBook, Action }
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
// import freeflowuniverse.herolib.core.texttools
|
||||
// import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.biz.spreadsheet
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
// first make sure we find a run action to know the name
|
||||
mut actions4 := plbook.actions_find(actor: 'bizmodel')!
|
||||
|
||||
if actions4.len == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
knownactions := ['revenue_define', 'employee_define', 'department_define', 'funding_define',
|
||||
'costcenter_define', 'cost_define']
|
||||
|
||||
for action in actions4 {
|
||||
// biz name needs to be specified in the the bizmodel hero actions
|
||||
bizname := action.params.get('bizname') or {
|
||||
return error("Can't find param: 'bizname' for ${action.actor}.${action.name} macro, is a requirement argument.")
|
||||
}
|
||||
mut sim := getset(bizname)!
|
||||
|
||||
if action.name !in knownactions {
|
||||
return error("Can't find macro with name: ${action.name} for macro's for bizmodel.")
|
||||
}
|
||||
|
||||
console.print_debug(action.name)
|
||||
match action.name {
|
||||
'revenue_define' {
|
||||
sim.revenue_action(action)!
|
||||
}
|
||||
'funding_define' {
|
||||
sim.funding_define_action(action)!
|
||||
}
|
||||
'costcenter_define' {
|
||||
sim.costcenter_define_action(action)!
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
console.print_debug('TOTALS for bizmodel play')
|
||||
// now we have processed the macro's, we can calculate the totals
|
||||
rlock bizmodels {
|
||||
for _, mut sim in bizmodels {
|
||||
// sim.hr_total()!
|
||||
sim.cost_total()!
|
||||
sim.revenue_total()!
|
||||
sim.funding_total()!
|
||||
}
|
||||
}
|
||||
|
||||
for action in actions4 {
|
||||
console.print_debug(action.name)
|
||||
// biz name needs to be specified in the the bizmodel hero actions
|
||||
bizname := action.params.get('bizname') or {
|
||||
return error("Can't find param: 'bizname' for bizmodel macro, is a requirement argument.")
|
||||
}
|
||||
|
||||
mut sim := get(bizname)!
|
||||
|
||||
if action.name !in knownactions {
|
||||
return error("Can't find macro with name: ${action.name} for macro's for bizmodel.")
|
||||
}
|
||||
|
||||
match action.name {
|
||||
'cost_define' {
|
||||
sim.cost_define_action(action)!
|
||||
}
|
||||
'department_define' {
|
||||
sim.department_define_action(action)!
|
||||
}
|
||||
'employee_define' {
|
||||
sim.employee_define_action(action)!
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
// mut sim:=get("test")!
|
||||
// //println(sim.sheet.rows.keys())
|
||||
// //println(spreadsheet.sheets_keys())
|
||||
// println(spreadsheet.sheet_get('bizmodel_test')!)
|
||||
// if true{panic("sss")}
|
||||
const action_priorities = {
|
||||
0: ['revenue_define', 'costcenter_define', 'funding_define']
|
||||
1: ['cost_define', 'department_define', 'employee_define']
|
||||
2: ['sheet_wiki', 'graph_bar_row', 'graph_pie_row', 'graph_line_row', 'row_overview']
|
||||
}
|
||||
|
||||
pub fn play(mut plbook PlayBook) ! {
|
||||
// group actions by which bizmodel they belong to
|
||||
actions_by_biz := arrays.group_by[string, &Action](
|
||||
plbook.actions_find(actor: 'bizmodel')!,
|
||||
fn (a &Action) string {
|
||||
return a.params.get('bizname') or {'default'}
|
||||
}
|
||||
)
|
||||
|
||||
// play actions for each biz in playbook
|
||||
for biz, actions in actions_by_biz {
|
||||
mut model := getset(biz)!
|
||||
model.play(mut plbook)!
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut m BizModel) play(mut plbook PlayBook) ! {
|
||||
mut actions := plbook.actions_find(actor: 'bizmodel')!
|
||||
|
||||
for action in actions.filter(it.name in action_priorities[0]) {
|
||||
m.act(*action)!
|
||||
}
|
||||
|
||||
m.cost_total()!
|
||||
m.revenue_total()!
|
||||
m.funding_total()!
|
||||
|
||||
for action in actions.filter(it.name in action_priorities[1]) {
|
||||
m.act(*action)!
|
||||
}
|
||||
|
||||
for action in actions.filter(it.name in action_priorities[2]) {
|
||||
m.act(*action)!
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ module bizmodel
|
||||
import freeflowuniverse.herolib.core.playbook { Action }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
fn (mut m BizModel) cost_define_action(action Action) ! {
|
||||
fn (mut m BizModel) cost_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -73,6 +73,7 @@ fn (mut m BizModel) cost_define_action(action Action) ! {
|
||||
)!
|
||||
m.sheet.row_delete('tmp3')
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
fn (mut sim BizModel) cost_total() ! {
|
||||
|
||||
@@ -3,7 +3,7 @@ module bizmodel
|
||||
import freeflowuniverse.herolib.core.playbook { Action }
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
|
||||
fn (mut m BizModel) costcenter_define_action(action Action) ! {
|
||||
fn (mut m BizModel) costcenter_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -20,4 +20,5 @@ fn (mut m BizModel) costcenter_define_action(action Action) ! {
|
||||
department: department
|
||||
}
|
||||
m.costcenters[name] = &cc
|
||||
return action
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import freeflowuniverse.herolib.core.texttools
|
||||
// - descr: description of the funding .
|
||||
// - investment is month:amount,month:amount, ... .
|
||||
// - type: loan or capital .
|
||||
fn (mut m BizModel) funding_define_action(action Action) ! {
|
||||
fn (mut m BizModel) funding_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -29,6 +29,7 @@ fn (mut m BizModel) funding_define_action(action Action) ! {
|
||||
descr: descr
|
||||
extrapolate: false
|
||||
)!
|
||||
return action
|
||||
}
|
||||
|
||||
fn (mut sim BizModel) funding_total() ! {
|
||||
|
||||
@@ -15,7 +15,7 @@ import freeflowuniverse.herolib.core.texttools
|
||||
// department:'engineering'
|
||||
// cost_percent_revenue e.g. 4%, will make sure the cost will be at least 4% of revenue
|
||||
|
||||
fn (mut m BizModel) employee_define_action(action Action) ! {
|
||||
fn (mut m BizModel) employee_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -107,10 +107,7 @@ fn (mut m BizModel) employee_define_action(action Action) ! {
|
||||
fulltime_perc: action.params.get_percentage_default('fulltime', '100%')!
|
||||
}
|
||||
|
||||
// println(employee)
|
||||
|
||||
// todo: use existing id gen
|
||||
|
||||
if name != '' {
|
||||
// sid = smartid.sid_new('')!
|
||||
// // TODO: this isn't necessary if sid_new works correctly
|
||||
@@ -120,9 +117,10 @@ fn (mut m BizModel) employee_define_action(action Action) ! {
|
||||
// }
|
||||
m.employees[name] = &employee
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
fn (mut m BizModel) department_define_action(action Action) ! {
|
||||
fn (mut m BizModel) department_define_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -141,6 +139,8 @@ fn (mut m BizModel) department_define_action(action Action) ! {
|
||||
if name != '' {
|
||||
m.departments[name] = &department
|
||||
}
|
||||
|
||||
return action
|
||||
}
|
||||
|
||||
// fn (mut sim BizModel) hr_total() ! {
|
||||
|
||||
@@ -21,7 +21,7 @@ import freeflowuniverse.herolib.core.texttools
|
||||
// - nr_sold: how many do we sell per month (is in growth format e.g. 10:100,20:200)
|
||||
// - nr_months_recurring: how many months is recurring, if 0 then no recurring
|
||||
//
|
||||
fn (mut m BizModel) revenue_action(action Action) ! {
|
||||
fn (mut m BizModel) revenue_action(action Action) !Action {
|
||||
mut name := action.params.get_default('name', '')!
|
||||
mut descr := action.params.get_default('descr', '')!
|
||||
if descr.len == 0 {
|
||||
@@ -312,6 +312,7 @@ fn (mut m BizModel) revenue_action(action Action) ! {
|
||||
// panic("sdsd")
|
||||
|
||||
// }
|
||||
return action
|
||||
}
|
||||
|
||||
// revenue_total calculates and aggregates the total revenue and cost of goods sold (COGS) for the business model
|
||||
|
||||
9
lib/biz/bizmodel/templates/human_resources.md
Normal file
9
lib/biz/bizmodel/templates/human_resources.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Human Resources
|
||||
|
||||
| Name | Title | Nr People |
|
||||
|------|-------|-------|
|
||||
@for employee in model.employees.values().filter(it.department == dept.name)
|
||||
| @{employee_names[employee.name]} | @{employee.title} | @{employee.nrpeople} |
|
||||
@end
|
||||
|
||||
@end
|
||||
@@ -2,53 +2,48 @@
|
||||
|
||||
## FUNDING
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:'funding'
|
||||
@{bizmodel.sheet.wiki(includefilter:'funding')!}
|
||||
|
||||
## REVENUE vs COGS
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:rev
|
||||
@{bizmodel.sheet.wiki(includefilter:'rev')!}
|
||||
|
||||
#### Revenue Lines
|
||||
|
||||
!!bizmodel.sheet_wiki title:'Revenue Total' includefilter:'revtotal'
|
||||
@{bizmodel.sheet.wiki(title:'Revenue Total', includefilter:'revtotal')!}
|
||||
|
||||
#### COGS Lines
|
||||
|
||||
!!bizmodel.sheet_wiki title:'COGS' includefilter:'cogstotal'
|
||||
@{bizmodel.sheet.wiki(title:'COGS', includefilter:'cogstotal')!}
|
||||
|
||||
## HR
|
||||
!!bizmodel.sheet_wiki title:'HR Teams' includefilter:'hrnr'
|
||||
|
||||
!!bizmodel.sheet_wiki title:'HR Costs' includefilter:'hrcost'
|
||||
@{bizmodel.sheet.wiki(title:'HR Teams', includefilter:'hrnr')!}
|
||||
|
||||
@{bizmodel.sheet.wiki(title:'HR Costs', includefilter:'hrcost')!}
|
||||
|
||||
## Operational Costs
|
||||
|
||||
!!bizmodel.sheet_wiki title:'COSTS' includefilter:'ocost'
|
||||
|
||||
@{bizmodel.sheet.wiki(title:'COSTS', includefilter:'ocost')!}
|
||||
|
||||
## P&L Overview
|
||||
|
||||
<!-- period is in months, 3 means every quarter -->
|
||||
|
||||
!!bizmodel.sheet_wiki title:'P&L Overview' includefilter:'pl'
|
||||
@{bizmodel.sheet.wiki(title:'P&L Overview', includefilter:'pl')!}
|
||||
|
||||
|
||||
!!bizmodel.graph_bar_row rowname:revenue_total unit:million title:'A Title' title_sub:'Sub'
|
||||
@{bizmodel.graph_bar_row(rowname:'revenue_total', unit:'million', title:'A Title', title_sub:'Sub')!}
|
||||
|
||||
Unit is in Million USD.
|
||||
|
||||
!!bizmodel.graph_bar_row rowname:revenue_total unit:million
|
||||
@{bizmodel.graph_bar_row(rowname:'revenue_total', unit:'million')!}
|
||||
|
||||
!!bizmodel.graph_line_row rowname:revenue_total unit:million
|
||||
|
||||
!!bizmodel.graph_pie_row rowname:revenue_total unit:million size:'80%'
|
||||
@{bizmodel.graph_line_row(rowname:'revenue_total', unit:'million')!}
|
||||
|
||||
@{bizmodel.graph_pie_row(rowname:'revenue_total', unit:'million', size:'80%')!}
|
||||
|
||||
## Some Details
|
||||
|
||||
> show how we can do per month
|
||||
|
||||
!!bizmodel.sheet_wiki includefilter:'pl' period_months:1
|
||||
|
||||
|
||||
|
||||
@{bizmodel.sheet_wiki(includefilter:'pl', period_months:1)!}
|
||||
68
lib/biz/bizmodel/templates/product.md
Normal file
68
lib/biz/bizmodel/templates/product.md
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
# @{product.title}
|
||||
|
||||
@{product.description}
|
||||
|
||||
#### parameters for the product
|
||||
|
||||
@if product.has_oneoffs
|
||||
|
||||
Product ${name1} has revenue events (one offs)
|
||||
|
||||
!!!spreadsheet.sheet_wiki
|
||||
namefilter:'${name1}_revenue,${name1}_cogs,${name1}_cogs_perc,${name1}_maintenance_month_perc' sheetname:'bizmodel_tf9
|
||||
|
||||
- COGS = Cost of Goods Sold (is our cost to deliver the product/service)
|
||||
- maintenance is fee we charge to the customer per month in relation to the revenue we charged e.g. 1% of a product which was sold for 1m EUR means we charge 1% of 1 m EUR per month.
|
||||
|
||||
@end //one offs
|
||||
|
||||
@if product.has_items
|
||||
|
||||
Product sold and its revenue/cost of goods
|
||||
|
||||
!!!spreadsheet.sheet_wiki
|
||||
namefilter:'${name1}_nr_sold,${name1}_revenue_setup,${name1}_revenue_monthly,${name1}_cogs_setup,${name1}_cogs_setup_perc,${name1}_cogs_monthly,${name1}_cogs_monthly_perc'
|
||||
sheetname:'bizmodel_tf9
|
||||
|
||||
- nr sold, is the nr sold per month of ${name1}
|
||||
- revenue setup is setup per item for ${name1}, this is the money we receive. Similar there is a revenue monthly.
|
||||
- cogs = Cost of Goods Sold (is our cost to deliver the product)
|
||||
- can we as a setup per item, or per month per item
|
||||
|
||||
@if product.nr_months_recurring>1
|
||||
|
||||
This product ${name1} is recurring, means customer pays per month ongoing, the period customer is paying for in months is: **${product.nr_months_recurring}**
|
||||
|
||||
@end //recurring
|
||||
|
||||
@end
|
||||
|
||||
#### the revenue/cogs calculated
|
||||
|
||||
|
||||
!!!spreadsheet.sheet_wiki
|
||||
namefilter:'${name1}_nr_sold_recurring'
|
||||
sheetname:'bizmodel_tf9
|
||||
|
||||
This results in following revenues and cogs:
|
||||
|
||||
!!!spreadsheet.sheet_wiki
|
||||
namefilter:'${name1}_revenue_setup_total,${name1}_revenue_monthly_total,${name1}_cogs_setup_total,${name1}_cogs_monthly_total,${name1}_cogs_setup_from_perc,${name1}_cogs_monthly_from_perc,${name1}_maintenance_month,
|
||||
${name1}_revenue_monthly_recurring,${name1}_cogs_monthly_recurring'
|
||||
sheetname:'bizmodel_tf9
|
||||
|
||||
resulting revenues:
|
||||
!!!spreadsheet.sheet_wiki
|
||||
namefilter:'${name1}_revenue_total,${name1}_cogs_total'
|
||||
sheetname:'bizmodel_tf9
|
||||
|
||||
|
||||
!!!spreadsheet.graph_line_row rowname:'${name1}_cogs_total' unit:million sheetname:'bizmodel_tf9'
|
||||
|
||||
!!!spreadsheet.graph_line_row rowname:'${name1}_revenue_total' unit:million sheetname:'bizmodel_tf9'
|
||||
|
||||
|
||||
@end //product has_revenue
|
||||
|
||||
@end //loop
|
||||
@@ -124,9 +124,4 @@ fn test_curr() {
|
||||
console.print_debug(sh.rows['something'].cells[0])
|
||||
assert sh.rows['something']!.cells[0].val == 25.0
|
||||
assert sh.rows['something']!.cells[60 - 1].val == 900.0
|
||||
|
||||
// TODO: we need to create tests for it
|
||||
|
||||
console.print_debug(sh)
|
||||
panic('test1')
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ pub fn (mut r Row) cell_get(colnr int) !&Cell {
|
||||
return &r.cells[colnr]
|
||||
}
|
||||
|
||||
pub fn (mut r Row) values_get() []f64 {
|
||||
pub fn (r Row) values_get() []f64 {
|
||||
mut out := []f64{}
|
||||
for cell in r.cells {
|
||||
out << cell.val
|
||||
|
||||
@@ -259,13 +259,13 @@ pub fn (mut s Sheet) json() string {
|
||||
}
|
||||
|
||||
// find row, report error if not found
|
||||
pub fn (mut s Sheet) row_get(name string) !&Row {
|
||||
mut row := s.rows[name] or { return error('could not find row with name: ${name}') }
|
||||
pub fn (s Sheet) row_get(name string) !&Row {
|
||||
row := s.rows[name] or { return error('could not find row with name: ${name}, available rows: ${s.rows.keys()}') }
|
||||
return row
|
||||
}
|
||||
|
||||
pub fn (mut s Sheet) values_get(name string) ![]f64 {
|
||||
mut r := s.row_get(name)!
|
||||
pub fn (s Sheet) values_get(name string) ![]f64 {
|
||||
r := s.row_get(name)!
|
||||
vs := r.values_get()
|
||||
return vs
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string {
|
||||
mut s2 := s
|
||||
|
||||
if args.period_type == .year {
|
||||
s.toyear(
|
||||
s2 = s.toyear(
|
||||
name: args.rowname
|
||||
namefilter: args.namefilter
|
||||
includefilter: args.includefilter
|
||||
@@ -129,7 +129,7 @@ pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string {
|
||||
)!
|
||||
}
|
||||
if args.period_type == .quarter {
|
||||
s.toquarter(
|
||||
s2 = s.toquarter(
|
||||
name: args.rowname
|
||||
namefilter: args.namefilter
|
||||
includefilter: args.includefilter
|
||||
@@ -141,13 +141,13 @@ pub fn (mut s Sheet) data_get_as_string(args RowGetArgs) !string {
|
||||
// console.print_debug(s2.row_get(args.rowname)!)
|
||||
mut vals := s2.values_get(args.rowname)!
|
||||
if args.period_type == .year && vals.len != nryears {
|
||||
return error('${err_pre}Vals.len need to be 6, for year.\nhere:\n${vals}')
|
||||
return error('${err_pre}Vals.len need to be ${nryears}, for year.\nhere:\n${vals}')
|
||||
}
|
||||
if args.period_type == .quarter && vals.len != nryears * 4 {
|
||||
return error('${err_pre}vals.len need to be 6*4, for quarter.\nhere:\n${vals}')
|
||||
return error('${err_pre}vals.len need to be ${nryears}*4, for quarter.\nhere:\n${vals}')
|
||||
}
|
||||
if args.period_type == .month && vals.len != nryears * 12 {
|
||||
return error('${err_pre}vals.len need to be 6*12, for month.\nhere:\n${vals}')
|
||||
return error('${err_pre}vals.len need to be ${nryears}*12, for month.\nhere:\n${vals}')
|
||||
}
|
||||
|
||||
for mut val in vals {
|
||||
|
||||
152
lib/clients/livekit/access_token.v
Normal file
152
lib/clients/livekit/access_token.v
Normal file
@@ -0,0 +1,152 @@
|
||||
module livekit
|
||||
|
||||
// import time
|
||||
// import rand
|
||||
// import crypto.hmac
|
||||
// import crypto.sha256
|
||||
// import encoding.base64
|
||||
// import json
|
||||
|
||||
// // Define AccessTokenOptions struct
|
||||
// pub struct AccessTokenOptions {
|
||||
// pub mut:
|
||||
// ttl int | string // TTL in seconds or a time span (e.g., '2d', '5h')
|
||||
// name string // Display name for the participant
|
||||
// identity string // Identity of the user
|
||||
// metadata string // Custom metadata to be passed to participants
|
||||
// }
|
||||
|
||||
// // Struct representing grants
|
||||
// pub struct ClaimGrants {
|
||||
// pub mut:
|
||||
// video VideoGrant
|
||||
// iss string
|
||||
// exp i64
|
||||
// nbf int
|
||||
// sub string
|
||||
// name string
|
||||
// }
|
||||
|
||||
// // VideoGrant struct placeholder
|
||||
// pub struct VideoGrant {
|
||||
// pub mut:
|
||||
// room string
|
||||
// room_join bool @[json: 'roomJoin']
|
||||
// can_publish bool @[json: 'canPublish']
|
||||
// can_publish_data bool @[json: 'canPublishData']
|
||||
// can_subscribe bool @[json: 'canSubscribe']
|
||||
// }
|
||||
|
||||
// // SIPGrant struct placeholder
|
||||
// struct SIPGrant {}
|
||||
|
||||
// // AccessToken class
|
||||
// pub struct AccessToken {
|
||||
// mut:
|
||||
// api_key string
|
||||
// api_secret string
|
||||
// grants ClaimGrants
|
||||
// identity string
|
||||
// ttl int | string
|
||||
// }
|
||||
|
||||
// // Constructor for AccessToken
|
||||
// pub fn new_access_token(api_key string, api_secret string, options AccessTokenOptions) !AccessToken {
|
||||
// if api_key == '' || api_secret == '' {
|
||||
// return error('API key and API secret must be set')
|
||||
// }
|
||||
|
||||
// ttl := if options.ttl is int { options.ttl } else { 21600 } // Default TTL of 6 hours (21600 seconds)
|
||||
|
||||
// return AccessToken{
|
||||
// api_key: api_key
|
||||
// api_secret: api_secret
|
||||
// identity: options.identity
|
||||
// ttl: ttl
|
||||
// grants: ClaimGrants{
|
||||
// exp: time.now().unix()+ttl
|
||||
// iss: api_key
|
||||
// sub: options.name
|
||||
// name: options.name
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Method to add a video grant to the token
|
||||
// pub fn (mut token AccessToken) add_video_grant(grant VideoGrant) {
|
||||
// token.grants.video = grant
|
||||
// }
|
||||
|
||||
// // Method to generate a JWT token
|
||||
// pub fn (token AccessToken) to_jwt() !string {
|
||||
// // Create JWT payload
|
||||
// payload := json.encode(token.grants)
|
||||
|
||||
// println('payload: ${payload}')
|
||||
|
||||
// // Create JWT header
|
||||
// header := '{"alg":"HS256","typ":"JWT"}'
|
||||
|
||||
// // Encode header and payload in base64
|
||||
// header_encoded := base64.url_encode_str(header)
|
||||
// payload_encoded := base64.url_encode_str(payload)
|
||||
|
||||
// // Create the unsigned token
|
||||
// unsigned_token := '${header_encoded}.${payload_encoded}'
|
||||
|
||||
// // Create the HMAC-SHA256 signature
|
||||
// signature := hmac.new(token.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, sha256.block_size)
|
||||
|
||||
// // Encode the signature in base64
|
||||
// signature_encoded := base64.url_encode(signature)
|
||||
|
||||
// // Create the final JWT
|
||||
// jwt := '${unsigned_token}.${signature_encoded}'
|
||||
// return jwt
|
||||
// }
|
||||
|
||||
// // TokenVerifier class
|
||||
// pub struct TokenVerifier {
|
||||
// api_key string
|
||||
// api_secret string
|
||||
// }
|
||||
|
||||
// // Constructor for TokenVerifier
|
||||
// pub fn new_token_verifier(api_key string, api_secret string) !TokenVerifier {
|
||||
// if api_key == '' || api_secret == '' {
|
||||
// return error('API key and API secret must be set')
|
||||
// }
|
||||
// return TokenVerifier{
|
||||
// api_key: api_key
|
||||
// api_secret: api_secret
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Method to verify the JWT token
|
||||
// pub fn (verifier TokenVerifier) verify(token string) !ClaimGrants {
|
||||
// // Split the token into parts
|
||||
// parts := token.split('.')
|
||||
// if parts.len != 3 {
|
||||
// return error('Invalid token')
|
||||
// }
|
||||
|
||||
// // Decode header, payload, and signature
|
||||
// payload_encoded := parts[1]
|
||||
// signature_encoded := parts[2]
|
||||
|
||||
// // Recompute the HMAC-SHA256 signature
|
||||
// unsigned_token := '${parts[0]}.${parts[1]}'
|
||||
// expected_signature := hmac.new(verifier.api_secret.bytes(), unsigned_token.bytes(), sha256.sum, sha256.block_size)
|
||||
// expected_signature_encoded := base64.url_encode(expected_signature)
|
||||
|
||||
// // Verify the signature
|
||||
// if signature_encoded != expected_signature_encoded {
|
||||
// return error('Invalid token signature')
|
||||
// }
|
||||
|
||||
// // Decode the payload
|
||||
// payload_json := base64.url_decode_str(payload_encoded)
|
||||
|
||||
// // Parse and return the claims as ClaimGrants
|
||||
// return json.decode(ClaimGrants, payload_json)
|
||||
// }
|
||||
199
lib/clients/livekit/server_client.v
Normal file
199
lib/clients/livekit/server_client.v
Normal file
@@ -0,0 +1,199 @@
|
||||
module livekit
|
||||
|
||||
import net.http
|
||||
import json
|
||||
|
||||
// // pub struct Client {
|
||||
// // pub:
|
||||
// // host string
|
||||
// // token string
|
||||
// // }
|
||||
|
||||
// // pub struct Room {
|
||||
// // pub mut:
|
||||
// // sid string
|
||||
// // name string
|
||||
// // empty_timeout string
|
||||
// // max_participants string
|
||||
// // creation_time string
|
||||
// // turn_password string
|
||||
// // metadata string
|
||||
// // num_participants u32
|
||||
// // active_recording bool
|
||||
// // }
|
||||
|
||||
// pub struct ParticipantInfo {
|
||||
// pub mut:
|
||||
// sid string
|
||||
// identity string
|
||||
// name string
|
||||
// state string
|
||||
// tracks []TrackInfo
|
||||
// metadata string
|
||||
// joined_at i64
|
||||
// permission ParticipantPermission
|
||||
// is_publisher bool
|
||||
// }
|
||||
|
||||
// pub struct TrackInfo {
|
||||
// pub mut:
|
||||
// sid string
|
||||
// typ string @[json: 'type']
|
||||
// source string
|
||||
// name string
|
||||
// mime_type string
|
||||
// muted bool
|
||||
// width u32
|
||||
// height u32
|
||||
// simulcast bool
|
||||
// disable_dtx bool
|
||||
// layers []VideoLayer
|
||||
// }
|
||||
|
||||
// pub struct ParticipantPermission {
|
||||
// pub mut:
|
||||
// can_subscribe bool
|
||||
// can_publish bool
|
||||
// can_publish_data bool
|
||||
// }
|
||||
|
||||
// pub struct VideoLayer {
|
||||
// pub mut:
|
||||
// quality string
|
||||
// width u32
|
||||
// height u32
|
||||
// }
|
||||
|
||||
// // Helper method to make POST requests to LiveKit API
|
||||
// fn (client Client) make_post_request(url string, body string) !http.Response {
|
||||
// mut headers := http.new_header()
|
||||
// headers.add_custom('Authorization', 'Bearer ${client.token}')!
|
||||
// headers.add_custom('Content-Type', 'application/json')!
|
||||
|
||||
// req := http.Request{
|
||||
// method: http.Method.post
|
||||
// url: url
|
||||
// data: body
|
||||
// header: headers
|
||||
// }
|
||||
// return req.do()!
|
||||
// }
|
||||
|
||||
// pub struct CreateRoomArgs {
|
||||
// pub:
|
||||
// name string
|
||||
// empty_timeout u32
|
||||
// max_participants u32
|
||||
// metadata string
|
||||
// }
|
||||
|
||||
// // RoomService API methods
|
||||
// pub fn (client Client) create_room(args CreateRoomArgs) !Room {
|
||||
// body := json.encode(args)
|
||||
// url := '${client.host}/twirp/livekit.RoomService/CreateRoom'
|
||||
// response := client.make_post_request(url, body)!
|
||||
|
||||
// return json.decode(Room, response.body)!
|
||||
// }
|
||||
|
||||
// // pub fn (client Client) list_rooms(names []string) ![]Room {
|
||||
// // body := json.encode({
|
||||
// // 'names': names
|
||||
// // })
|
||||
// // url := '${client.host}/twirp/livekit.RoomService/ListRooms'
|
||||
// // response := client.make_post_request(url, body)!
|
||||
|
||||
// // return json.decode([]Room, response.body)!
|
||||
// // }
|
||||
|
||||
// pub fn (client Client) delete_room(room_name string) ! {
|
||||
// body := json.encode({
|
||||
// 'room': room_name
|
||||
// })
|
||||
// url := '${client.host}/twirp/livekit.RoomService/DeleteRoom'
|
||||
// _ := client.make_post_request(url, body)!
|
||||
// }
|
||||
|
||||
// pub fn (client Client) list_participants(room_name string) ![]ParticipantInfo {
|
||||
// body := json.encode({
|
||||
// 'room': room_name
|
||||
// })
|
||||
// url := '${client.host}/twirp/livekit.RoomService/ListParticipants'
|
||||
// response := client.make_post_request(url, body)!
|
||||
|
||||
// return json.decode([]ParticipantInfo, response.body)!
|
||||
// }
|
||||
|
||||
// pub fn (client Client) get_participant(room_name string, identity string) !ParticipantInfo {
|
||||
// body := json.encode({
|
||||
// 'room': room_name
|
||||
// 'identity': identity
|
||||
// })
|
||||
// url := '${client.host}/twirp/livekit.RoomService/GetParticipant'
|
||||
// response := client.make_post_request(url, body)!
|
||||
|
||||
// return json.decode(ParticipantInfo, response.body)!
|
||||
// }
|
||||
|
||||
// pub fn (client Client) remove_participant(room_name string, identity string) ! {
|
||||
// body := json.encode({
|
||||
// 'room': room_name
|
||||
// 'identity': identity
|
||||
// })
|
||||
// url := '${client.host}/twirp/livekit.RoomService/RemoveParticipant'
|
||||
// _ := client.make_post_request(url, body)!
|
||||
// }
|
||||
|
||||
// pub struct MutePublishedTrackArgs {
|
||||
// pub:
|
||||
// room_name string
|
||||
// identity string
|
||||
// track_sid string
|
||||
// muted bool
|
||||
// }
|
||||
|
||||
// pub fn (client Client) mute_published_track(args MutePublishedTrackArgs) ! {
|
||||
// body := json.encode(args)
|
||||
// url := '${client.host}/twirp/livekit.RoomService/MutePublishedTrack'
|
||||
// _ := client.make_post_request(url, body)!
|
||||
// }
|
||||
|
||||
// pub struct UpdateParticipantArgs {
|
||||
// pub:
|
||||
// room_name string @[json: 'room']
|
||||
// identity string
|
||||
// metadata string
|
||||
// permission ParticipantPermission
|
||||
// }
|
||||
|
||||
// pub fn (client Client) update_participant(args UpdateParticipantArgs) ! {
|
||||
// body := json.encode(args)
|
||||
// url := '${client.host}/twirp/livekit.RoomService/UpdateParticipant'
|
||||
// _ := client.make_post_request(url, body)!
|
||||
// }
|
||||
|
||||
// pub struct UpdateRoomMetadataArgs {
|
||||
// pub:
|
||||
// room_name string @[json: 'room']
|
||||
// metadata string
|
||||
// }
|
||||
|
||||
// pub fn (client Client) update_room_metadata(args UpdateRoomMetadataArgs) ! {
|
||||
// body := json.encode(args)
|
||||
// url := '${client.host}/twirp/livekit.RoomService/UpdateRoomMetadata'
|
||||
// _ := client.make_post_request(url, body)!
|
||||
// }
|
||||
|
||||
// pub struct SendDataArgs {
|
||||
// pub:
|
||||
// room_name string @[json: 'room']
|
||||
// data []u8
|
||||
// kind string
|
||||
// destination_identities []string
|
||||
// }
|
||||
|
||||
// pub fn (client Client) send_data(args SendDataArgs) ! {
|
||||
// body := json.encode(args)
|
||||
// url := '${client.host}/twirp/livekit.RoomService/SendData'
|
||||
// _ := client.make_post_request(url, body)!
|
||||
// }
|
||||
@@ -1,39 +1,65 @@
|
||||
module mycelium
|
||||
|
||||
import json
|
||||
import encoding.base64
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
|
||||
// Represents a destination for a message, can be either IP or public key
|
||||
pub struct MessageDestination {
|
||||
pub:
|
||||
pk string
|
||||
ip string @[omitempty] // IP in the subnet of the receiver node
|
||||
pk string @[omitempty] // hex encoded public key of the receiver node
|
||||
}
|
||||
|
||||
// Body of a message to be sent
|
||||
pub struct PushMessageBody {
|
||||
pub:
|
||||
dst MessageDestination
|
||||
payload string
|
||||
topic ?string // optional message topic
|
||||
payload string // base64 encoded message
|
||||
}
|
||||
|
||||
// Response containing message ID after pushing
|
||||
pub struct PushMessageResponseId {
|
||||
pub:
|
||||
id string // hex encoded message ID
|
||||
}
|
||||
|
||||
// A message received by the system
|
||||
pub struct InboundMessage {
|
||||
pub:
|
||||
id string
|
||||
src_ip string @[json: 'srcIP']
|
||||
src_pk string @[json: 'srcPk']
|
||||
dst_ip string @[json: 'dstIp']
|
||||
dst_pk string @[json: 'dstPk']
|
||||
payload string
|
||||
src_ip string @[json: 'srcIp'] // Sender overlay IP address
|
||||
src_pk string @[json: 'srcPk'] // Sender public key, hex encoded
|
||||
dst_ip string @[json: 'dstIp'] // Receiver overlay IP address
|
||||
dst_pk string @[json: 'dstPk'] // Receiver public key, hex encoded
|
||||
topic string // Optional message topic
|
||||
payload string // Message payload, base64 encoded
|
||||
}
|
||||
|
||||
// Information about an outbound message
|
||||
pub struct MessageStatusResponse {
|
||||
pub:
|
||||
id string
|
||||
dst string
|
||||
state string
|
||||
created string
|
||||
deadline string
|
||||
msg_len string @[json: 'msgLen']
|
||||
dst string // IP address of receiving node
|
||||
state string // pending, received, read, aborted or sending object
|
||||
created i64 // Unix timestamp of creation
|
||||
deadline i64 // Unix timestamp of expiry
|
||||
msg_len int @[json: 'msgLen'] // Length in bytes
|
||||
}
|
||||
|
||||
// General information about a node
|
||||
pub struct Info {
|
||||
pub:
|
||||
node_subnet string @[json: 'nodeSubnet'] // subnet owned by node
|
||||
}
|
||||
|
||||
// Response containing public key for a node IP
|
||||
pub struct PublicKeyResponse {
|
||||
pub:
|
||||
node_pub_key string @[json: 'NodePubKey'] // hex encoded public key
|
||||
}
|
||||
|
||||
// Get connection to mycelium server
|
||||
pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection {
|
||||
mut c := self.conn or {
|
||||
mut c2 := httpconnection.new(
|
||||
@@ -47,30 +73,63 @@ pub fn (mut self Mycelium) connection() !&httpconnection.HTTPConnection {
|
||||
return c
|
||||
}
|
||||
|
||||
pub fn (mut self Mycelium) send_msg(pk string, payload string, wait bool) !InboundMessage {
|
||||
@[params]
|
||||
pub struct SendMessageArgs {
|
||||
pub mut:
|
||||
public_key string @[required]
|
||||
payload string @[required]
|
||||
topic ?string
|
||||
wait bool
|
||||
}
|
||||
|
||||
// Send a message to a node identified by public key
|
||||
pub fn (mut self Mycelium) send_msg(args SendMessageArgs) !InboundMessage {
|
||||
mut conn := self.connection()!
|
||||
mut params := {
|
||||
'dst': json.encode(MessageDestination{ pk: pk })
|
||||
'payload': payload
|
||||
mut body := PushMessageBody{
|
||||
dst: MessageDestination{
|
||||
pk: args.public_key
|
||||
ip: ''
|
||||
}
|
||||
payload: base64.encode_str(args.payload)
|
||||
topic: if v := args.topic {
|
||||
base64.encode_str(v)
|
||||
} else {
|
||||
none
|
||||
}
|
||||
}
|
||||
mut prefix := ''
|
||||
if wait {
|
||||
prefix = '?reply_timeout=120'
|
||||
mut prefix := '/api/v1/messages'
|
||||
if args.wait {
|
||||
prefix += '?reply_timeout=120'
|
||||
}
|
||||
return conn.post_json_generic[InboundMessage](
|
||||
method: .post
|
||||
prefix: prefix
|
||||
params: params
|
||||
data: json.encode(body)
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage {
|
||||
@[params]
|
||||
pub struct ReceiveMessageArgs {
|
||||
pub mut:
|
||||
topic ?string
|
||||
wait bool
|
||||
peek bool
|
||||
}
|
||||
|
||||
// Receive a message from the queue
|
||||
pub fn (mut self Mycelium) receive_msg(args ReceiveMessageArgs) !InboundMessage {
|
||||
mut conn := self.connection()!
|
||||
mut prefix := ''
|
||||
if wait {
|
||||
prefix = '?timeout=60'
|
||||
mut prefix := '/api/v1/messages?peek=${args.peek}&'
|
||||
|
||||
if args.wait {
|
||||
prefix += 'timeout=120&'
|
||||
}
|
||||
|
||||
if v := args.topic {
|
||||
prefix += 'topic=${base64.encode_str(v)}'
|
||||
}
|
||||
|
||||
return conn.get_json_generic[InboundMessage](
|
||||
method: .get
|
||||
prefix: prefix
|
||||
@@ -78,17 +137,9 @@ pub fn (mut self Mycelium) receive_msg(wait bool) !InboundMessage {
|
||||
)!
|
||||
}
|
||||
|
||||
pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage {
|
||||
mut conn := self.connection()!
|
||||
mut prefix := ''
|
||||
if wait {
|
||||
prefix = '?timeout=60'
|
||||
}
|
||||
res := conn.get_json_generic[InboundMessage](
|
||||
method: .get
|
||||
prefix: prefix
|
||||
dataformat: .json
|
||||
) or {
|
||||
// Optional version of receive_msg that returns none on 204
|
||||
pub fn (mut self Mycelium) receive_msg_opt(args ReceiveMessageArgs) ?InboundMessage {
|
||||
res := self.receive_msg(args) or {
|
||||
if err.msg().contains('204') {
|
||||
return none
|
||||
}
|
||||
@@ -97,25 +148,62 @@ pub fn (mut self Mycelium) receive_msg_opt(wait bool) ?InboundMessage {
|
||||
return res
|
||||
}
|
||||
|
||||
// Get status of a message by ID
|
||||
pub fn (mut self Mycelium) get_msg_status(id string) !MessageStatusResponse {
|
||||
mut conn := self.connection()!
|
||||
return conn.get_json_generic[MessageStatusResponse](
|
||||
method: .get
|
||||
prefix: 'status/${id}'
|
||||
prefix: '/api/v1/messages/status/${id}'
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
pub fn (mut self Mycelium) reply_msg(id string, pk string, payload string) ! {
|
||||
@[params]
|
||||
pub struct ReplyMessageArgs {
|
||||
pub mut:
|
||||
id string @[required]
|
||||
public_key string @[required]
|
||||
payload string @[required]
|
||||
topic ?string
|
||||
}
|
||||
|
||||
// Reply to a message
|
||||
pub fn (mut self Mycelium) reply_msg(args ReplyMessageArgs) ! {
|
||||
mut conn := self.connection()!
|
||||
mut params := {
|
||||
'dst': json.encode(MessageDestination{ pk: pk })
|
||||
'payload': payload
|
||||
mut body := PushMessageBody{
|
||||
dst: MessageDestination{
|
||||
pk: args.public_key
|
||||
ip: ''
|
||||
}
|
||||
payload: base64.encode_str(args.payload)
|
||||
topic: if v := args.topic { base64.encode_str(v) } else { none }
|
||||
}
|
||||
conn.post_json_generic[json.Any](
|
||||
_ := conn.post_json_str(
|
||||
method: .post
|
||||
prefix: 'reply/${id}'
|
||||
params: params
|
||||
prefix: '/api/v1/messages/reply/${args.id}'
|
||||
data: json.encode(body)
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
// curl -v -H 'Content-Type: application/json' -d '{"dst": {"pk": "be4bf135d60b7e43a46be1ad68f955cdc1209a3c55dc30d00c4463b1dace4377"}, "payload": "xuV+"}' http://localhost:8989/api/v1/messages\
|
||||
|
||||
// Get node info
|
||||
pub fn (mut self Mycelium) get_info() !Info {
|
||||
mut conn := self.connection()!
|
||||
return conn.get_json_generic[Info](
|
||||
method: .get
|
||||
prefix: '/api/v1/admin'
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
// Get public key for a node IP
|
||||
pub fn (mut self Mycelium) get_pubkey_from_ip(ip string) !PublicKeyResponse {
|
||||
mut conn := self.connection()!
|
||||
return conn.get_json_generic[PublicKeyResponse](
|
||||
method: .get
|
||||
prefix: '/api/v1/pubkey/${ip}'
|
||||
dataformat: .json
|
||||
)!
|
||||
}
|
||||
|
||||
71
lib/clients/mycelium/mycelium_check.v
Normal file
71
lib/clients/mycelium/mycelium_check.v
Normal file
@@ -0,0 +1,71 @@
|
||||
module mycelium
|
||||
|
||||
import freeflowuniverse.herolib.osal
|
||||
import freeflowuniverse.herolib.core
|
||||
import freeflowuniverse.herolib.installers.lang.rust
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.core.texttools
|
||||
import freeflowuniverse.herolib.osal.screen
|
||||
import freeflowuniverse.herolib.ui
|
||||
import freeflowuniverse.herolib.sysadmin.startupmanager
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
|
||||
pub fn check() bool {
|
||||
// if core.is_osx()! {
|
||||
// mut scr := screen.new(reset: false) or {return False}
|
||||
// name := 'mycelium'
|
||||
// if !scr.exists(name) {
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
|
||||
// if !(osal.process_exists_byname('mycelium') or {return False}) {
|
||||
// return false
|
||||
// }
|
||||
|
||||
// TODO: might be dangerous if that one goes out
|
||||
ping_result := osal.ping(address: '40a:152c:b85b:9646:5b71:d03a:eb27:2462', retry: 2) or {
|
||||
return false
|
||||
}
|
||||
if ping_result == .ok {
|
||||
console.print_debug('could reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
|
||||
return true
|
||||
}
|
||||
console.print_stderr('could not reach 40a:152c:b85b:9646:5b71:d03a:eb27:2462')
|
||||
return false
|
||||
}
|
||||
|
||||
pub struct MyceliumInspectResult {
|
||||
pub:
|
||||
public_key string @[json: publicKey]
|
||||
address string
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct MyceliumInspectArgs {
|
||||
pub:
|
||||
key_file_path string = '/root/hero/cfg/priv_key.bin'
|
||||
}
|
||||
|
||||
pub fn inspect(args MyceliumInspectArgs) !MyceliumInspectResult {
|
||||
command := 'mycelium inspect --key-file ${args.key_file_path} --json'
|
||||
result := os.execute(command)
|
||||
|
||||
if result.exit_code != 0 {
|
||||
return error('Command failed: ${result.output}')
|
||||
}
|
||||
|
||||
inspect_result := json.decode(MyceliumInspectResult, result.output) or {
|
||||
return error('Failed to parse JSON: ${err}')
|
||||
}
|
||||
|
||||
return inspect_result
|
||||
}
|
||||
|
||||
// if returns empty then probably mycelium is not installed
|
||||
pub fn ipaddr() string {
|
||||
r := inspect() or { MyceliumInspectResult{} }
|
||||
return r.address
|
||||
}
|
||||
@@ -2,8 +2,6 @@ module mycelium
|
||||
|
||||
import freeflowuniverse.herolib.core.base
|
||||
import freeflowuniverse.herolib.core.playbook
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.data.encoderhero
|
||||
|
||||
__global (
|
||||
mycelium_global map[string]&Mycelium
|
||||
@@ -12,35 +10,71 @@ __global (
|
||||
|
||||
/////////FACTORY
|
||||
|
||||
// set the model in mem and the config on the filesystem
|
||||
@[params]
|
||||
pub struct ArgsGet {
|
||||
pub mut:
|
||||
name string
|
||||
}
|
||||
|
||||
fn args_get(args_ ArgsGet) ArgsGet {
|
||||
mut args := args_
|
||||
if args.name == '' {
|
||||
args.name = 'default'
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
pub fn get(args_ ArgsGet) !&Mycelium {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
mut obj := Mycelium{}
|
||||
if args.name !in mycelium_global {
|
||||
if !exists(args)! {
|
||||
set(obj)!
|
||||
} else {
|
||||
heroscript := context.hero_config_get('mycelium', args.name)!
|
||||
mut obj_ := heroscript_loads(heroscript)!
|
||||
set_in_mem(obj_)!
|
||||
}
|
||||
}
|
||||
return mycelium_global[args.name] or {
|
||||
println(mycelium_global)
|
||||
// bug if we get here because should be in globals
|
||||
panic('could not get config for mycelium with name, is bug:${args.name}')
|
||||
}
|
||||
}
|
||||
|
||||
// register the config for the future
|
||||
pub fn set(o Mycelium) ! {
|
||||
set_in_mem(o)!
|
||||
mut context := base.context()!
|
||||
heroscript := heroscript_dumps(o)!
|
||||
context.hero_config_set('mycelium', o.name, heroscript)!
|
||||
}
|
||||
|
||||
// does the config exists?
|
||||
pub fn exists(args_ ArgsGet) !bool {
|
||||
mut context := base.context()!
|
||||
mut args := args_get(args_)
|
||||
return context.hero_config_exists('mycelium', args.name)
|
||||
}
|
||||
|
||||
pub fn delete(args_ ArgsGet) ! {
|
||||
mut args := args_get(args_)
|
||||
mut context := base.context()!
|
||||
context.hero_config_delete('mycelium', args.name)!
|
||||
if args.name in mycelium_global {
|
||||
// del mycelium_global[args.name]
|
||||
}
|
||||
}
|
||||
|
||||
// only sets in mem, does not set as config
|
||||
fn set_in_mem(o Mycelium) ! {
|
||||
mut o2 := obj_init(o)!
|
||||
mycelium_global[o.name] = &o2
|
||||
mycelium_default = o.name
|
||||
}
|
||||
|
||||
// check we find the config on the filesystem
|
||||
pub fn exists(args_ ArgsGet) bool {
|
||||
mut model := args_get(args_)
|
||||
mut context := base.context() or { panic('bug') }
|
||||
return context.hero_config_exists('mycelium', model.name)
|
||||
}
|
||||
|
||||
// load the config error if it doesn't exist
|
||||
pub fn load(args_ ArgsGet) ! {
|
||||
mut model := args_get(args_)
|
||||
mut context := base.context()!
|
||||
mut heroscript := context.hero_config_get('mycelium', model.name)!
|
||||
play(heroscript: heroscript)!
|
||||
}
|
||||
|
||||
// save the config to the filesystem in the context
|
||||
pub fn save(o Mycelium) ! {
|
||||
mut context := base.context()!
|
||||
heroscript := encoderhero.encode[Mycelium](o)!
|
||||
context.hero_config_set('mycelium', model.name, heroscript)!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PlayArgs {
|
||||
pub mut:
|
||||
@@ -50,21 +84,28 @@ pub mut:
|
||||
}
|
||||
|
||||
pub fn play(args_ PlayArgs) ! {
|
||||
mut model := args_
|
||||
mut args := args_
|
||||
|
||||
if model.heroscript == '' {
|
||||
model.heroscript = heroscript_default()!
|
||||
}
|
||||
mut plbook := model.plbook or { playbook.new(text: model.heroscript)! }
|
||||
mut plbook := args.plbook or { playbook.new(text: args.heroscript)! }
|
||||
|
||||
mut configure_actions := plbook.find(filter: 'mycelium.configure')!
|
||||
if configure_actions.len > 0 {
|
||||
for config_action in configure_actions {
|
||||
mut p := config_action.params
|
||||
mycfg := cfg_play(p)!
|
||||
console.print_debug('install action mycelium.configure\n${mycfg}')
|
||||
set(mycfg)!
|
||||
save(mycfg)!
|
||||
mut install_actions := plbook.find(filter: 'mycelium.configure')!
|
||||
if install_actions.len > 0 {
|
||||
for install_action in install_actions {
|
||||
heroscript := install_action.heroscript()
|
||||
mut obj2 := heroscript_loads(heroscript)!
|
||||
set(obj2)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// switch instance to be used for mycelium
|
||||
pub fn switch(name string) {
|
||||
mycelium_default = name
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
@[params]
|
||||
pub struct DefaultConfigArgs {
|
||||
instance string = 'default'
|
||||
}
|
||||
|
||||
@@ -1,39 +1,33 @@
|
||||
module mycelium
|
||||
|
||||
import freeflowuniverse.herolib.data.paramsparser
|
||||
import freeflowuniverse.herolib.core.httpconnection
|
||||
import os
|
||||
import freeflowuniverse.herolib.data.encoderhero
|
||||
|
||||
pub const version = '0.0.0'
|
||||
const singleton = true
|
||||
const default = true
|
||||
|
||||
pub fn heroscript_default() !string {
|
||||
heroscript := "
|
||||
!!mycelium.configure
|
||||
name:'mycelium'
|
||||
"
|
||||
return heroscript
|
||||
}
|
||||
|
||||
@[heap]
|
||||
pub struct Mycelium {
|
||||
pub mut:
|
||||
name string = 'default'
|
||||
server_url string
|
||||
conn ?&httpconnection.HTTPConnection
|
||||
server_url string = 'http://localhost:8989'
|
||||
conn ?&httpconnection.HTTPConnection @[skip; str: skip]
|
||||
}
|
||||
|
||||
fn cfg_play(p paramsparser.Params) ! {
|
||||
mut mycfg := Mycelium{
|
||||
name: p.get_default('name', 'default')!
|
||||
server_url: p.get_default('server_url', 'http://localhost:8989/api/v1/messages')!
|
||||
}
|
||||
set(mycfg)!
|
||||
// your checking & initialization code if needed
|
||||
fn obj_init(mycfg_ Mycelium) !Mycelium {
|
||||
mut mycfg := mycfg_
|
||||
return mycfg
|
||||
}
|
||||
|
||||
fn obj_init(obj_ Mycelium) !Mycelium {
|
||||
// never call get here, only thing we can do here is work on object itself
|
||||
mut obj := obj_
|
||||
/////////////NORMALLY NO NEED TO TOUCH
|
||||
|
||||
pub fn heroscript_dumps(obj Mycelium) !string {
|
||||
return encoderhero.encode[Mycelium](obj)!
|
||||
}
|
||||
|
||||
pub fn heroscript_loads(heroscript string) !Mycelium {
|
||||
mut obj := encoderhero.decode[Mycelium](heroscript)!
|
||||
return obj
|
||||
}
|
||||
|
||||
602
lib/clients/mycelium/openapi.yaml
Normal file
602
lib/clients/mycelium/openapi.yaml
Normal file
@@ -0,0 +1,602 @@
|
||||
openapi: 3.0.2
|
||||
info:
|
||||
version: '1.0.0'
|
||||
|
||||
title: Mycelium management
|
||||
contact:
|
||||
url: 'https://github.com/threefoldtech/mycelium'
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: 'https://github.com/threefoldtech/mycelium/blob/master/LICENSE'
|
||||
|
||||
description: |
|
||||
This is the specification of the **mycelium** management API. It is used to perform admin tasks on the system, and
|
||||
to perform administrative duties.
|
||||
|
||||
externalDocs:
|
||||
description: For full documentation, check out the mycelium github repo.
|
||||
url: 'https://github.com/threefoldtech/mycelium'
|
||||
|
||||
tags:
|
||||
- name: Admin
|
||||
description: Administrative operations
|
||||
- name: Peer
|
||||
description: Operations related to peer management
|
||||
- name: Route
|
||||
description: Operations related to network routes
|
||||
- name: Message
|
||||
description: Operations on the embedded message subsystem
|
||||
|
||||
servers:
|
||||
- url: 'http://localhost:8989'
|
||||
|
||||
paths:
|
||||
'/api/v1/admin':
|
||||
get:
|
||||
tags:
|
||||
- Admin
|
||||
summary: Get general info about the node
|
||||
description: |
|
||||
Get general info about the node, which is not related to other more specific functionality
|
||||
operationId: getInfo
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Info'
|
||||
|
||||
'/api/v1/admin/peers':
|
||||
get:
|
||||
tags:
|
||||
- Admin
|
||||
- Peer
|
||||
summary: List known peers
|
||||
description: |
|
||||
List all peers known in the system, and info about their connection.
|
||||
This includes the endpoint, how we know about the peer, the connection state, and if the connection is alive the amount
|
||||
of bytes we've sent to and received from the peer.
|
||||
operationId: getPeers
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/PeerStats'
|
||||
post:
|
||||
tags:
|
||||
- Admin
|
||||
- Peer
|
||||
summary: Add a new peer
|
||||
description: |
|
||||
Add a new peer identified by the provided endpoint.
|
||||
The peer is added to the list of known peers. It will eventually be connected
|
||||
to by the standard connection loop of the peer manager. This means that a peer
|
||||
which can't be connected to will stay in the system, as it might be reachable
|
||||
later on.
|
||||
operationId: addPeer
|
||||
responses:
|
||||
'204':
|
||||
description: Peer added
|
||||
'400':
|
||||
description: Malformed endpoint
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
description: Details about why the endpoint is not valid
|
||||
'409':
|
||||
description: Peer already exists
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
description: message saying we already know this peer
|
||||
|
||||
'/api/v1/admin/peers/{endpoint}':
|
||||
delete:
|
||||
tags:
|
||||
- Admin
|
||||
- Peer
|
||||
summary: Remove an existing peer
|
||||
description: |
|
||||
Remove an existing peer identified by the provided endpoint.
|
||||
The peer is removed from the list of known peers. If a connection to it
|
||||
is currently active, it will be closed.
|
||||
operationId: deletePeer
|
||||
responses:
|
||||
'204':
|
||||
description: Peer removed
|
||||
'400':
|
||||
description: Malformed endpoint
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
description: Details about why the endpoint is not valid
|
||||
'404':
|
||||
description: Peer doesn't exist
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
description: message saying we don't know this peer
|
||||
|
||||
'/api/v1/admin/routes/selected':
|
||||
get:
|
||||
tags:
|
||||
- Admin
|
||||
- Route
|
||||
summary: List all selected routes
|
||||
description: |
|
||||
List all selected routes in the system, and their next hop identifier, metric and sequence number.
|
||||
It is possible for a route to be selected and have an infinite metric. This route will however not forward packets.
|
||||
operationId: getSelectedRoutes
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Route'
|
||||
|
||||
'/api/v1/admin/routes/fallback':
|
||||
get:
|
||||
tags:
|
||||
- Admin
|
||||
- Route
|
||||
summary: List all active fallback routes
|
||||
description: |
|
||||
List all fallback routes in the system, and their next hop identifier, metric and sequence number.
|
||||
These routes are available to be selected in case the selected route for a destination suddenly fails, or gets retracted.
|
||||
operationId: getSelectedRoutes
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Route'
|
||||
|
||||
'/api/v1/messages':
|
||||
get:
|
||||
tags:
|
||||
- Message
|
||||
summary: Get a message from the inbound message queue
|
||||
description: |
|
||||
Get a message from the inbound message queue. By default, the message is removed from the queue and won't be shown again.
|
||||
If the peek query parameter is set to true, the message will be peeked, and the next call to this endpoint will show the same message.
|
||||
This method returns immediately by default: a message is returned if one is ready, and if there isn't nothing is returned. If the timeout
|
||||
query parameter is set, this call won't return for the given amount of seconds, unless a message is received
|
||||
operationId: popMessage
|
||||
parameters:
|
||||
- in: query
|
||||
name: peek
|
||||
required: false
|
||||
schema:
|
||||
type: boolean
|
||||
description: Whether to peek the message or not. If this is true, the message won't be removed from the inbound queue when it is read
|
||||
example: true
|
||||
- in: query
|
||||
name: timeout
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 0
|
||||
description: |
|
||||
Amount of seconds to wait for a message to arrive if one is not available. Setting this to 0 is valid and will return
|
||||
a message if present, or return immediately if there isn't
|
||||
example: 60
|
||||
- in: query
|
||||
name: topic
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: byte
|
||||
minLength: 0
|
||||
maxLength: 340
|
||||
description: |
|
||||
Optional filter for loading messages. If set, the system checks if the message has the given string at the start. This way
|
||||
a topic can be encoded.
|
||||
example: example.topic
|
||||
responses:
|
||||
'200':
|
||||
description: Message retrieved
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InboundMessage'
|
||||
'204':
|
||||
description: No message ready
|
||||
post:
|
||||
tags:
|
||||
- Message
|
||||
summary: Submit a new message to the system.
|
||||
description: |
|
||||
Push a new message to the systems outbound message queue. The system will continuously attempt to send the message until
|
||||
it is either fully transmitted, or the send deadline is expired.
|
||||
operationId: pushMessage
|
||||
parameters:
|
||||
- in: query
|
||||
name: reply_timeout
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 0
|
||||
description: |
|
||||
Amount of seconds to wait for a reply to this message to come in. If not set, the system won't wait for a reply and return
|
||||
the ID of the message, which can be used later. If set, the system will wait for at most the given amount of seconds for a reply
|
||||
to come in. If a reply arrives, it is returned to the client. If not, the message ID is returned for later use.
|
||||
example: 120
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PushMessageBody'
|
||||
responses:
|
||||
'200':
|
||||
description: We received a reply within the specified timeout
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InboundMessage'
|
||||
|
||||
'201':
|
||||
description: Message pushed successfully, and not waiting for a reply
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PushMessageResponseId'
|
||||
'408':
|
||||
description: The system timed out waiting for a reply to the message
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PushMessageResponseId'
|
||||
|
||||
'/api/v1/messsages/reply/{id}':
|
||||
post:
|
||||
tags:
|
||||
- Message
|
||||
summary: Reply to a message with the given ID
|
||||
description: |
|
||||
Submits a reply message to the system, where ID is an id of a previously received message. If the sender is waiting
|
||||
for a reply, it will bypass the queue of open messages.
|
||||
operationId: pushMessageReply
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
minLength: 16
|
||||
maxLength: 16
|
||||
example: abcdef0123456789
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PushMessageBody'
|
||||
responses:
|
||||
'204':
|
||||
description: successfully submitted the reply
|
||||
|
||||
'/api/v1/messages/status/{id}':
|
||||
get:
|
||||
tags:
|
||||
- Message
|
||||
summary: Get the status of an outbound message
|
||||
description: |
|
||||
Get information about the current state of an outbound message. This can be used to check the transmission
|
||||
state, size and destination of the message.
|
||||
operationId: getMessageInfo
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: hex
|
||||
minLength: 16
|
||||
maxLength: 16
|
||||
example: abcdef0123456789
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/MessageStatusResponse'
|
||||
'404':
|
||||
description: Message not found
|
||||
|
||||
'/api/v1/pubkey/{mycelium_ip}':
|
||||
get:
|
||||
summary: Get the pubkey from node ip
|
||||
description: |
|
||||
Get the node's public key from it's IP address.
|
||||
operationId: getPublicKeyFromIp
|
||||
parameters:
|
||||
- in: path
|
||||
name: mycelium_ip
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: ipv6
|
||||
example: 5fd:7636:b80:9ad0::1
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PublicKeyResponse'
|
||||
'404':
|
||||
description: Public key not found
|
||||
|
||||
|
||||
components:
|
||||
schemas:
|
||||
Info:
|
||||
description: General information about a node
|
||||
type: object
|
||||
properties:
|
||||
nodeSubnet:
|
||||
description: The subnet owned by the node and advertised to peers
|
||||
type: string
|
||||
example: 54f:b680:ba6e:7ced::/64
|
||||
|
||||
Endpoint:
|
||||
description: Identification to connect to a peer
|
||||
type: object
|
||||
properties:
|
||||
proto:
|
||||
description: Protocol used
|
||||
type: string
|
||||
enum:
|
||||
- 'tcp'
|
||||
- 'quic'
|
||||
example: tcp
|
||||
socketAddr:
|
||||
description: The socket address used
|
||||
type: string
|
||||
example: 192.0.2.6:9651
|
||||
|
||||
PeerStats:
|
||||
description: Info about a peer
|
||||
type: object
|
||||
properties:
|
||||
endpoint:
|
||||
$ref: '#/components/schemas/Endpoint'
|
||||
type:
|
||||
description: How we know about this peer
|
||||
type: string
|
||||
enum:
|
||||
- 'static'
|
||||
- 'inbound'
|
||||
- 'linkLocalDiscovery'
|
||||
example: static
|
||||
connectionState:
|
||||
description: The current state of the connection to the peer
|
||||
type: string
|
||||
enum:
|
||||
- 'alive'
|
||||
- 'connecting'
|
||||
- 'dead'
|
||||
example: alive
|
||||
txBytes:
|
||||
description: The amount of bytes transmitted to this peer
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 0
|
||||
example: 464531564
|
||||
rxBytes:
|
||||
description: The amount of bytes received from this peer
|
||||
type: integer
|
||||
format: int64
|
||||
minimum: 0
|
||||
example: 64645089
|
||||
|
||||
Route:
|
||||
description: Information about a route
|
||||
type: object
|
||||
properties:
|
||||
subnet:
|
||||
description: The overlay subnet for which this is the route
|
||||
type: string
|
||||
example: 469:1348:ab0c:a1d8::/64
|
||||
nextHop:
|
||||
description: A way to identify the next hop of the route, where forwarded packets will be sent
|
||||
type: string
|
||||
example: TCP 203.0.113.2:60128 <-> 198.51.100.27:9651
|
||||
metric:
|
||||
description: The metric of the route, an estimation of how long the packet will take to arrive at its final destination
|
||||
oneOf:
|
||||
- description: A finite metric value
|
||||
type: integer
|
||||
format: int32
|
||||
minimum: 0
|
||||
maximum: 65534
|
||||
example: 13
|
||||
- description: An infinite (unreachable) metric. This is always `infinite`
|
||||
type: string
|
||||
example: infinite
|
||||
seqno:
|
||||
description: the sequence number advertised with this route by the source
|
||||
type: integer
|
||||
format: int32
|
||||
minimum: 0
|
||||
maximum: 65535
|
||||
example: 1
|
||||
|
||||
InboundMessage:
|
||||
description: A message received by the system
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Id of the message, hex encoded
|
||||
type: string
|
||||
format: hex
|
||||
minLength: 16
|
||||
maxLength: 16
|
||||
example: 0123456789abcdef
|
||||
srcIp:
|
||||
description: Sender overlay IP address
|
||||
type: string
|
||||
format: ipv6
|
||||
example: 449:abcd:0123:defa::1
|
||||
srcPk:
|
||||
description: Sender public key, hex encoded
|
||||
type: string
|
||||
format: hex
|
||||
minLength: 64
|
||||
maxLength: 64
|
||||
example: fedbca9876543210fedbca9876543210fedbca9876543210fedbca9876543210
|
||||
dstIp:
|
||||
description: Receiver overlay IP address
|
||||
type: string
|
||||
format: ipv6
|
||||
example: 34f:b680:ba6e:7ced:355f:346f:d97b:eecb
|
||||
dstPk:
|
||||
description: Receiver public key, hex encoded. This is the public key of the system
|
||||
type: string
|
||||
format: hex
|
||||
minLength: 64
|
||||
maxLength: 64
|
||||
example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf
|
||||
topic:
|
||||
description: An optional message topic
|
||||
type: string
|
||||
format: byte
|
||||
minLength: 0
|
||||
maxLength: 340
|
||||
example: hpV+
|
||||
payload:
|
||||
description: The message payload, encoded in standard alphabet base64
|
||||
type: string
|
||||
format: byte
|
||||
example: xuV+
|
||||
|
||||
PushMessageBody:
|
||||
description: A message to send to a given receiver
|
||||
type: object
|
||||
properties:
|
||||
dst:
|
||||
$ref: '#/components/schemas/MessageDestination'
|
||||
topic:
|
||||
description: An optional message topic
|
||||
type: string
|
||||
format: byte
|
||||
minLength: 0
|
||||
maxLength: 340
|
||||
example: hpV+
|
||||
payload:
|
||||
description: The message to send, base64 encoded
|
||||
type: string
|
||||
format: byte
|
||||
example: xuV+
|
||||
|
||||
MessageDestination:
|
||||
oneOf:
|
||||
- description: An IP in the subnet of the receiver node
|
||||
type: object
|
||||
properties:
|
||||
ip:
|
||||
description: The target IP of the message
|
||||
format: ipv6
|
||||
example: 449:abcd:0123:defa::1
|
||||
- description: The hex encoded public key of the receiver node
|
||||
type: object
|
||||
properties:
|
||||
pk:
|
||||
description: The hex encoded public key of the target node
|
||||
type: string
|
||||
minLength: 64
|
||||
maxLength: 64
|
||||
example: bb39b4a3a4efd70f3e05e37887677e02efbda14681d0acd3882bc0f754792c32
|
||||
|
||||
PushMessageResponseId:
|
||||
description: The ID generated for a message after pushing it to the system
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: Id of the message, hex encoded
|
||||
type: string
|
||||
format: hex
|
||||
minLength: 16
|
||||
maxLength: 16
|
||||
example: 0123456789abcdef
|
||||
|
||||
MessageStatusResponse:
|
||||
description: Information about an outbound message
|
||||
type: object
|
||||
properties:
|
||||
dst:
|
||||
description: IP address of the receiving node
|
||||
type: string
|
||||
format: ipv6
|
||||
example: 449:abcd:0123:defa::1
|
||||
state:
|
||||
$ref: '#/components/schemas/TransmissionState'
|
||||
created:
|
||||
description: Unix timestamp of when this message was created
|
||||
type: integer
|
||||
format: int64
|
||||
example: 1649512789
|
||||
deadline:
|
||||
description: Unix timestamp of when this message will expire. If the message is not received before this, the system will give up
|
||||
type: integer
|
||||
format: int64
|
||||
example: 1649513089
|
||||
msgLen:
|
||||
description: Length of the message in bytes
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 27
|
||||
|
||||
TransmissionState:
|
||||
description: The state of an outbound message in it's lifetime
|
||||
oneOf:
|
||||
- type: string
|
||||
enum: ['pending', 'received', 'read', 'aborted']
|
||||
example: 'received'
|
||||
- type: object
|
||||
properties:
|
||||
sending:
|
||||
type: object
|
||||
properties:
|
||||
pending:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 5
|
||||
sent:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 17
|
||||
acked:
|
||||
type: integer
|
||||
minimum: 0
|
||||
example: 3
|
||||
example: 'received'
|
||||
|
||||
PublicKeyResponse:
|
||||
description: Public key requested based on a node's IP
|
||||
type: object
|
||||
properties:
|
||||
NodePubKey:
|
||||
type: string
|
||||
format: hex
|
||||
minLength: 64
|
||||
maxLength: 64
|
||||
example: 02468ace13579bdf02468ace13579bdf02468ace13579bdf02468ace13579bdf
|
||||
@@ -1,6 +1,13 @@
|
||||
# Mycelium Client
|
||||
|
||||
A V client library for interacting with the Mycelium messaging system. This client provides functionality for sending, receiving, and managing messages through a Mycelium server.
|
||||
A V client library for interacting with the Mycelium messaging system. This client provides functionality for configuring and inspecting a Mycelium node.
|
||||
|
||||
## Components
|
||||
|
||||
The Mycelium integration consists of two main components:
|
||||
|
||||
1. **Mycelium Client** (this package) - For interacting with a running Mycelium node
|
||||
2. **Mycelium Installer** (in `installers/net/mycelium/`) - For installing and managing Mycelium nodes
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -11,131 +18,101 @@ The client can be configured either through V code or using heroscript.
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
// Get default client instance
|
||||
mut client := mycelium.get()!
|
||||
|
||||
// By default connects to http://localhost:8989/api/v1/messages
|
||||
// To use a different server:
|
||||
mut client := mycelium.get(name: "custom", server_url: "http://myserver:8989/api/v1/messages")!
|
||||
// Get named client instance
|
||||
mut client := mycelium.get(name: "custom")!
|
||||
```
|
||||
|
||||
### Heroscript Configuration
|
||||
## Core Functions
|
||||
|
||||
```hero
|
||||
!!mycelium.configure
|
||||
name:'custom' # optional, defaults to 'default'
|
||||
server_url:'http://myserver:8989/api/v1/messages' # optional, defaults to localhost:8989
|
||||
```
|
||||
### Inspect Node
|
||||
|
||||
Note: Configuration is not needed if using a locally running Mycelium server with default settings.
|
||||
|
||||
## Example Script
|
||||
|
||||
Save as `mycelium_example.vsh`:
|
||||
Get information about the local Mycelium node:
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
// Get node info including public key and address
|
||||
result := mycelium.inspect()!
|
||||
println('Public Key: ${result.public_key}')
|
||||
println('Address: ${result.address}')
|
||||
|
||||
// Get just the IP address
|
||||
addr := mycelium.ipaddr()
|
||||
println('IP Address: ${addr}')
|
||||
```
|
||||
|
||||
### Check Node Status
|
||||
|
||||
Check if the Mycelium node is running and reachable:
|
||||
|
||||
```v
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
is_running := mycelium.check()
|
||||
if is_running {
|
||||
println('Mycelium node is running and reachable')
|
||||
} else {
|
||||
println('Mycelium node is not running or unreachable')
|
||||
}
|
||||
```
|
||||
|
||||
### Sending and Receiving Messages
|
||||
|
||||
The client provides several functions for sending and receiving messages between nodes:
|
||||
|
||||
```v
|
||||
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.clients.mycelium
|
||||
|
||||
// Initialize client
|
||||
mut client := mycelium.get()!
|
||||
|
||||
// Send a message and wait for reply
|
||||
// Send a message to a node by public key
|
||||
// Parameters: public_key, payload, topic, wait_for_reply
|
||||
msg := client.send_msg(
|
||||
pk: "recipient_public_key"
|
||||
payload: "Hello!"
|
||||
wait: true // wait for reply (timeout 120s)
|
||||
'abc123...', // destination public key
|
||||
'Hello World', // message payload
|
||||
'greetings', // optional topic
|
||||
true // wait for reply
|
||||
)!
|
||||
println('Sent message ID: ${msg.id}')
|
||||
|
||||
// Receive messages
|
||||
// Parameters: wait_for_message, peek_only, topic_filter
|
||||
received := client.receive_msg(true, false, 'greetings')!
|
||||
println('Received message from: ${received.src_pk}')
|
||||
println('Message payload: ${received.payload}')
|
||||
|
||||
// Reply to a message
|
||||
client.reply_msg(
|
||||
received.id, // original message ID
|
||||
received.src_pk, // sender's public key
|
||||
'Got your message!', // reply payload
|
||||
'greetings' // topic
|
||||
)!
|
||||
println('Message sent with ID: ${msg.id}')
|
||||
|
||||
// Check message status
|
||||
status := client.get_msg_status(msg.id)!
|
||||
println('Message status: ${status.state}')
|
||||
|
||||
// Receive messages with timeout
|
||||
if incoming := client.receive_msg_opt(wait: true) {
|
||||
println('Received message: ${incoming.payload}')
|
||||
println('From: ${incoming.src_pk}')
|
||||
|
||||
// Reply to the message
|
||||
client.reply_msg(
|
||||
id: incoming.id
|
||||
pk: incoming.src_pk
|
||||
payload: "Got your message!"
|
||||
)!
|
||||
}
|
||||
println('Created at: ${status.created}')
|
||||
println('Expires at: ${status.deadline}')
|
||||
```
|
||||
|
||||
## API Reference
|
||||
The messaging API supports:
|
||||
- Sending messages to nodes identified by public key
|
||||
- Optional message topics for filtering
|
||||
- Waiting for replies when sending messages
|
||||
- Peeking at messages without removing them from the queue
|
||||
- Tracking message delivery status
|
||||
- Base64 encoded message payloads for binary data
|
||||
|
||||
### Sending Messages
|
||||
## Installation and Management
|
||||
|
||||
```v
|
||||
// Send a message to a specific public key
|
||||
// wait=true means wait for reply (timeout 120s)
|
||||
msg := client.send_msg(pk: "recipient_public_key", payload: "Hello!", wait: true)!
|
||||
For installing and managing Mycelium nodes, use the Mycelium Installer package located in `installers/net/mycelium/`. The installer provides functionality for:
|
||||
|
||||
// Get status of a sent message
|
||||
status := client.get_msg_status(id: "message_id")!
|
||||
```
|
||||
|
||||
### Receiving Messages
|
||||
|
||||
```v
|
||||
// Receive a message (non-blocking)
|
||||
msg := client.receive_msg(wait: false)!
|
||||
|
||||
// Receive a message with timeout (blocking for 60s)
|
||||
msg := client.receive_msg(wait: true)!
|
||||
|
||||
// Receive a message (returns none if no message available)
|
||||
if msg := client.receive_msg_opt(wait: false) {
|
||||
println('Received: ${msg.payload}')
|
||||
}
|
||||
```
|
||||
|
||||
### Replying to Messages
|
||||
|
||||
```v
|
||||
// Reply to a specific message
|
||||
client.reply_msg(
|
||||
id: "original_message_id",
|
||||
pk: "sender_public_key",
|
||||
payload: "Reply message"
|
||||
)!
|
||||
```
|
||||
|
||||
## Message Types
|
||||
|
||||
### InboundMessage
|
||||
```v
|
||||
struct InboundMessage {
|
||||
id string
|
||||
src_ip string
|
||||
src_pk string
|
||||
dst_ip string
|
||||
dst_pk string
|
||||
payload string
|
||||
}
|
||||
```
|
||||
|
||||
### MessageStatusResponse
|
||||
```v
|
||||
struct MessageStatusResponse {
|
||||
id string
|
||||
dst string
|
||||
state string
|
||||
created string
|
||||
deadline string
|
||||
msg_len string
|
||||
}
|
||||
```
|
||||
|
||||
## Heroscript Complete Example
|
||||
|
||||
```hero
|
||||
!!mycelium.configure
|
||||
name:'mycelium'
|
||||
server_url:'http://localhost:8989/api/v1/messages'
|
||||
|
||||
# More heroscript commands can be added here as the API expands
|
||||
- Installing Mycelium nodes
|
||||
- Starting/stopping nodes
|
||||
- Managing node configuration
|
||||
- Setting up TUN interfaces
|
||||
- Configuring peer connections
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module zdb
|
||||
module zerodb_client
|
||||
|
||||
import freeflowuniverse.herolib.core.redisclient
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
@@ -14,11 +14,12 @@ pub mut:
|
||||
// /tmp/redis-default.sock
|
||||
pub fn get(addr string, auth string, namespace string) !ZDB {
|
||||
console.print_header(' ZDB get: addr:${addr} namespace:${namespace}')
|
||||
mut redis := redisclient.get(addr)!
|
||||
mut redis := redisclient.new(addr)!
|
||||
mut zdb := ZDB{
|
||||
redis: redis
|
||||
}
|
||||
|
||||
println('Here..')
|
||||
if auth != '' {
|
||||
zdb.redis.send_expect_ok(['AUTH', auth])!
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user