kumahq / kuma-gui Goto Github PK
View Code? Open in Web Editor NEWπ» A GUI built on Vue.js for use with Kuma.
Home Page: https://kuma.io/
License: Apache License 2.0
π» A GUI built on Vue.js for use with Kuma.
Home Page: https://kuma.io/
License: Apache License 2.0
Probably a tooltip or a link
Multi zone deployment. Post completion of the multi-zone deployment guide. Everything else seems functional.
Chrome console log:
chunk-vendors.js?t=f515544d:47 [Violation] 'requestAnimationFrame' handler took 227ms
chunk-vendors.js?t=f515544d:47 [Violation] 'requestAnimationFrame' handler took 70ms
chunk-vendors.js?t=f515544d:47 [Violation] 'requestAnimationFrame' handler took 65ms
chunk-vendors.js?t=f515544d:47 TypeError: Cannot read property 'toLowerCase' of undefined
at global-overview.js?t=2c7321c1:1
at Array.map (<anonymous>)
at a.dataAdapter (global-overview.js?t=2c7321c1:1)
at t.apply (chunk-vendors.js?t=f515544d:58)
at e.get [as data] (chunk-vendors.js?t=f515544d:65)
at e.validateData (chunk-vendors.js?t=f515544d:65)
at e.validateData (global-overview.js?t=2c7321c1:1)
at t.update (chunk-vendors.js?t=f515544d:47)
at chunk-vendors.js?t=f515544d:47
Firefox console log:
TypeError: can't access property "toLowerCase", e is undefined
dataAdapter http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
dataAdapter http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
apply http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
get http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validateData http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validateData http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
update http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
setPropertyValue http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
set http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
validateDataElement http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validateDataElements http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
_systemUpdate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
_systemUpdate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
update http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
l http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
update http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
setPropertyValue http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
set http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
validateDataElement http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validateDataElements http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
_systemUpdate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
_systemUpdate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
update http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
l http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
update http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
setPropertyValue http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
set http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
validateDataElement http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validateDataElements http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
_systemUpdate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
_systemUpdate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
update http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
l http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:58
update http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
u http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
requestFrame http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
invalidate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
setPropertyValue http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
set http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:47
validateDataElement http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validateDataElements http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/chunk-vendors.js?t=f515544d:65
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
validate http://localhost:5681/gui/js/global-overview.js?t=2c7321c1:1
chunk-vendors.js:47:129301
Configuration:
{
"apiServer": {
"auth": {
"allowFromLocalhost": true,
"clientCertsDir": ""
},
"catalog": {
"bootstrap": {
"url": ""
}
},
"corsAllowedDomains": [
".*"
],
"http": {
"enabled": true,
"interface": "0.0.0.0",
"port": 5681
},
"https": {
"enabled": true,
"interface": "0.0.0.0",
"port": 5682,
"tlsCertFile": "/var/run/secrets/kuma.io/tls-cert/tls.crt",
"tlsKeyFile": "/var/run/secrets/kuma.io/tls-cert/tls.key"
},
"readOnly": true
},
"bootstrapServer": {
"apiVersion": "v2",
"params": {
"adminAccessLogPath": "/dev/null",
"adminAddress": "127.0.0.1",
"adminPort": 0,
"xdsConnectTimeout": "1s",
"xdsHost": "",
"xdsPort": 5678
}
},
"defaults": {
"skipMeshCreation": false
},
"diagnostics": {
"debugEndpoints": false,
"serverPort": 5680
},
"dnsServer": {
"CIDR": "240.0.0.0/4",
"domain": "mesh",
"port": 5653
},
"dpServer": {
"auth": {
"type": "serviceAccountToken"
},
"hds": {
"checkDefaults": {
"healthyThreshold": 1,
"interval": "1s",
"noTrafficInterval": "1s",
"timeout": "2s",
"unhealthyThreshold": 1
},
"enabled": false,
"interval": "5s",
"refreshInterval": "10s"
},
"port": 5678,
"tlsCertFile": "/var/run/secrets/kuma.io/tls-cert/tls.crt",
"tlsKeyFile": "/var/run/secrets/kuma.io/tls-cert/tls.key"
},
"environment": "kubernetes",
"general": {
"dnsCacheTTL": "10s",
"tlsCertFile": "/var/run/secrets/kuma.io/tls-cert/tls.crt",
"tlsKeyFile": "/var/run/secrets/kuma.io/tls-cert/tls.key"
},
"guiServer": {
"apiServerUrl": ""
},
"metrics": {
"dataplane": {
"enabled": true,
"subscriptionLimit": 10
},
"mesh": {
"maxResyncTimeout": "20s",
"minResyncTimeout": "1s"
},
"zone": {
"enabled": true,
"subscriptionLimit": 10
}
},
"mode": "global",
"monitoringAssignmentServer": {
"assignmentRefreshInterval": "1s",
"grpcPort": 5676
},
"multizone": {
"global": {
"kds": {
"grpcPort": 5685,
"refreshInterval": "1s",
"tlsCertFile": "/var/run/secrets/kuma.io/tls-cert/tls.crt",
"tlsKeyFile": "/var/run/secrets/kuma.io/tls-cert/tls.key",
"zoneInsightFlushInterval": "10s"
},
"pollTimeout": "500ms"
},
"remote": {
"kds": {
"refreshInterval": "1s",
"rootCaFile": ""
}
}
},
"reports": {
"enabled": true
},
"runtime": {
"kubernetes": {
"admissionServer": {
"address": "",
"certDir": "/var/run/secrets/kuma.io/tls-cert",
"port": 5443
},
"controlPlaneServiceName": "kuma-control-plane",
"injector": {
"caCertFile": "/var/run/secrets/kuma.io/tls-cert/ca.crt",
"cniEnabled": false,
"exceptions": {
"labels": {
"openshift.io/build.name": "*",
"openshift.io/deployer-pod-for.name": "*"
}
},
"initContainer": {
"image": "kong-docker-kuma-docker.bintray.io/kuma-init:1.0.7"
},
"sidecarContainer": {
"adminPort": 9901,
"drainTime": "30s",
"gid": 5678,
"image": "kong-docker-kuma-docker.bintray.io/kuma-dp:1.0.7",
"livenessProbe": {
"failureThreshold": 12,
"initialDelaySeconds": 60,
"periodSeconds": 5,
"timeoutSeconds": 3
},
"readinessProbe": {
"failureThreshold": 12,
"initialDelaySeconds": 1,
"periodSeconds": 5,
"successThreshold": 1,
"timeoutSeconds": 3
},
"redirectPortInbound": 15006,
"redirectPortOutbound": 15001,
"resources": {
"limits": {
"cpu": "1000m",
"memory": "512Mi"
},
"requests": {
"cpu": "50m",
"memory": "64Mi"
}
},
"uid": 5678
},
"sidecarTraffic": {
"excludeInboundPorts": [],
"excludeOutboundPorts": []
},
"virtualProbesEnabled": true,
"virtualProbesPort": 9000
},
"marshalingCacheExpirationTime": "5m0s"
},
"universal": {
"dataplaneCleanupAge": "72h0m0s"
}
},
"sdsServer": {
"dataplaneConfigurationRefreshInterval": "1s"
},
"store": {
"cache": {
"enabled": true,
"expirationTime": "1s"
},
"kubernetes": {
"systemNamespace": "kuma-system"
},
"postgres": {
"connectionTimeout": 5,
"dbName": "kuma",
"host": "127.0.0.1",
"maxOpenConnections": 0,
"maxReconnectInterval": "1m0s",
"minReconnectInterval": "10s",
"password": "*****",
"port": 15432,
"tls": {
"caPath": "",
"certPath": "",
"keyPath": "",
"mode": "disable"
},
"user": "kuma"
},
"type": "kubernetes",
"upsert": {
"conflictRetryBaseBackoff": "100ms",
"conflictRetryMaxTimes": 5
}
},
"xdsServer": {
"dataplaneConfigurationRefreshInterval": "1s",
"dataplaneStatusFlushInterval": "10s"
}
}
Thanks!
Import from: kumahq/kuma#2596
When a user is on the Zones view and has a zone with an ingress, there is information that there is no ingress for that zone.
We've agreed on renaming this it's at least present in the cp list
The GUI version of: kumahq/kuma#3488
There's currently nothing with releases in GUI. The day we need to do a backported fix it will be problematic...
When accessing a dp in /gui/#/mesh/all/standard-dataplanes
it would be good to also see the dataplane-insights to debug issues.
This is done for Zones and it's very useful
It would be nice to be able to filter the list of dataplane by tag, at least by kuma.io/service.
Virtual Outbounds should be visible in the GUI.
With kumahq/kuma#4203 we added compatibility checks to kuma CP which validate CP<->DP and Global CP<->Zone CP version compatibility. These are now stored within insights. The GUI still consumes the DP<->Envoy compatibility matrix to check if the DP is compatible with the CP.
With some colors for example: green for 3/3, orange for 2/3 etc.
As charts will have the labels inside the charts (ref: #129), currently there was no work done, to make sure that the charts will look fine if there will be not enough space for the label to fully be visible in the chart
My current idea is to place the label outside the chart if there is not enough space for it, and if there will be too many labels, which could overlap between each other, we could think about maybe introducing legend or something else.
Overall, there needs to be done some work to improve this.
Today, the animation needs to complete first in order for the user to see the text/instructions.
We have to change this in such a way that the text shows up immediately, and then in the background, the animation plays.
I noticed that the KLoader is using a copied version of an old component. We moved the Full screen loader to a kongponent (which resolves the >100 progress bug):
https://github.com/Kong/kongponents/blob/master/packages/KSkeleton/FullScreenKongSkeleton.vue#L54
https://github.com/kumahq/kuma-gui/blob/master/src/components/KLoader.vue#L43
Possible solution is that the KLoader can receive an asset name so that the assets can be customized so you could pass ../assets/images/kuma-loader-v1.gif?external
to KSkeleton, and then have kuma-gui use the kskeleton kongponent.
The library which we are using for charts (amcharts) is very complex and when I was implementing the OverviewCharts
component I created some technical debt, with duplicated and overall untidy/messy code. This should be addressed and improved.
The Vuex store is getting large in size and needs to be split up accordingly. Vuex's documentation explains how to split up a store into modules.
Design needs to be defined kumahq/kuma#3460 - based on PR kumahq/kuma#3462
I used the mesh creation wizard and enabled but didn't enter any names for the logging, metrics, etc backends. I expected that the UI would use default names, but it generated an invalid mesh resource:
$ echo "type: Mesh
name: demo
mtls:
enabledBackend: null
backends:
- name: null
type: builtin
enabled: true
tracing:
defaultBackend: null
backends:
- name: null
type: zipkin
conf:
sampling: 100
url: null
logging:
backends:
- name: null
format: '{ "destination": "%KUMA_DESTINATION_SERVICE%", "destinationAddress": "%UPSTREAM_LOCAL_ADDRESS%", "source": "%KUMA_SOURCE_SERVICE%", "sourceAddress": "%KUMA_SOURCE_ADDRESS%", "bytesReceived": "%BYTES_RECEIVED%", "bytesSent": "%BYTES_SENT%"}'
type: tcp
conf:
address: '127.0.0.1:5000'
metrics:
enabledBackend: null
backends:
- name: null
type: prometheus
conf:
port: 5670
path: /metrics" | kumactl apply -f -
Error: Could not create a resource (Resource is not valid)
* logging.backends[0].name: cannot be empty
* tracing.backends[0].name: cannot be empty
* tracing.backends[0].config.url: cannot be empty
There's ZoneIngress: yes
there should be ZoneEgress: yes
It would be great to update our GUI to use the newest version of Tailwind.
Ref:
Rework the onboarding view to make it simpler and more accurate.
Our GUI is currently using Vue 2, and as we are days/maybe weeks before Vue 3 will become officially released as production ready, I want to start a discussion about moving from JS to TS (TypeScript).
I would love to know what you all think about this idea
My current thoughts:
Benefits:
Drawbacks:
vue add typescript
Kubernetes YAMLs are missing spec branch. We see
kind: Mesh
mtls: ...
whereas we should see
kind: Mesh
spec:
mtls: ...
Universal does not have this branch.
In line with the CNCF guidelines found in kumahq/kuma#3050, we should disable these collections since 1) they are published to Kong's DataDog account and 2) it is not opt-in unless I've missed something about how the initialization works.
cc @jpeach
We have many places that display yaml or json it would be good to have a container for these that enable easy collapsing some nested elements or doing search in the content.
Something similar to what a code editor would do with these formats.
Reported by a user on Kuma Slack
Total updates
looks wrong
For comparison, the output of kumactl inspect dataplanes
looks like this:
It's best practice to run multiple instances of a zone ingress/egress for resiliency. However in the UI each of these instances show up as separate line items:
Would be really nice if each zone was a line item with maybe a drop-down sub-level of line items below with the instances for that zone?
The dataplane install wizard generates this kuma-dp
commandline:
kuma-dp run \
--name=example-g68sex \
--mesh=default \
--cp-address=https://127.0.0.1:5678/ \
--dataplane="type: Dataplane
mesh: default
name: example-g68sex
networking:
address: 192.168.1.100
inbound:
- port: 80
servicePort: 6060
serviceAddress: 127.0.0.1
tags:
kuma.io/service: example
kuma.io/protocol: tcp" \
--dataplane-token-file=kuma-token-example-g68sex
But kuma-dp
doesn't allow --mesh
if it is also set in the dataplane resource. The wizard should omit the flag in this case.
Working through the dataplane wizard, it suggests the following to generate a token:
kumactl generate dataplane-token --dataplane=example-g68sex > kuma-token-example-g68sex
However, the --dataplane
flag ought to be --name
.
Dataplane Wizard still says that we need to apply a Dataplane first and then start kuma-dp. We changed the flow that you can pass a Dataplane definition when running kuma-dp and the wizard should promote this approach
Design needs to be defined kumahq/kuma#3486 - based on PR kumahq/kuma#3568
We need a proper way to write new functionality in the GUI for enterprise extensions of Kuma
kumahq/kuma#1111 introduces "locality" enablement on Mesh resource level. This will be indicated on kumactl get mesh
CLI and will be great to have this also on the GUI.
@bartsmykla has built in rulers in his eyes and noticed that ;)
We make the UI readOnly to motivate IaaC and version-control. We should explain this in the UI.
There's limited point in having multi-zone without mTLS let's warn about this.
The UI should be very similar to ZoneIngress as it's roughly the same thing.
Gateway introduces a few policies. We should add them to the ui and only display them if the feature flag is on.
Some of the charts are redirecting to different parts of the application, and currently after hovering over it the cursor will change to pointer (when #121 will be merged), but there is no information about where it will redirect us after the click.
Because it's not a "normal" HTML element, it should probably be implemented using functionality of the amcharts
Currently we are sending requests to multiple endpoints to get the data which we are displaying on the Overview (counters of resources, charts).
There is a better way and we should use the mesh insights endpoint which includes most of data we need, without unnecessary ones
For test purposes, when doing npm run serve
we are mocking some of the api endpoints, to see how the GUI will behave. After introducing mesh-insights in place of many different requests in Overview, we stopped using and displaying mock data.
To fix/update it, we have to introduce another endpoint/s for mesh-insights in our mock implementations
As whole container with charts is managed by amcharts, and it's actually a very complicated SVG, I couldn't find a quick way to make them responsive.
It's not a big problem with 2 charts, but we will be introducing another ones in the near future, so it would be good to make sure they are being rendered nicely on smaller resolutions.
KongPonents are updated and we should start using them
When I was doing port-forward Kuma control plane to localhost 5681 port, Firefox is not working properly and throwing timeout. You can find logs below.
This issue seems to be related to kuma dns. Sometimes I don't see it if I don't run kumactl install dns | kubectl apply -f -
but most of time I get it.
Kuma 1.2.2
Kubernetes 1.21.1 deployed with Kind
kind create cluster
kumactl install control-plane | kubectl apply -f -
kumactl install dns | kubectl apply -f -
kubectl port-forward -n kuma-system kuma-control-plane-54ddb8fb9f-lv8m9 5681:5681
Now when I access localhost:5681
on Firefox, I see below log
...
Handling connection for 5681
Handling connection for 5681
E0726 17:22:53.624700 4352 portforward.go:372] error copying from remote stream to local connection: readfrom tcp4 127.0.0.1:5681->127.0.0.1:57821: write tcp4 127.0.0.1:5681->127.0.0.1:57821: write: broken pipe
Handling connection for 5681
Handling connection for 5681
Handling connection for 5681
E0726 17:23:23.683242 4352 portforward.go:340] error creating error stream for port 5681 -> 5681: Timeout occurred
Handling connection for 5681
Handling connection for 5681
Handling connection for 5681
Handling connection for 5681
E0726 17:23:23.974291 4352 portforward.go:372] error copying from remote stream to local connection: readfrom tcp4 127.0.0.1:5681->127.0.0.1:57829: write tcp4 127.0.0.1:5681->127.0.0.1:57829: write: broken pipe
E0726 17:23:24.064839 4352 portforward.go:372] error copying from remote stream to local connection: readfrom tcp4 127.0.0.1:5681->127.0.0.1:57837: write tcp4 127.0.0.1:5681->127.0.0.1:57837: write: broken pipe
Handling connection for 5681
E0726 17:23:53.830541 4352 portforward.go:340] error creating error stream for port 5681 -> 5681: Timeout occurred
Handling connection for 5681
E0726 17:23:54.027372 4352 portforward.go:362] error creating forwarding stream for port 5681 -> 5681: Timeout occurred
Handling connection for 5681
E0726 17:23:54.088768 4352 portforward.go:362] error creating forwarding stream for port 5681 -> 5681: Timeout occurred
Handling connection for 5681
E0726 17:24:23.836355 4352 portforward.go:340] error creating error stream for port 5681 -> 5681: Timeout occurred
Handling connection for 5681
E0726 17:24:24.033499 4352 portforward.go:362] error creating forwarding stream for port 5681 -> 5681: Timeout occurred
Handling connection for 5681
E0726 17:24:24.094866 4352 portforward.go:362] error creating forwarding stream for port 5681 -> 5681: Timeout occurred
Handling connection for 5681
E0726 17:24:53.845064 4352 portforward.go:340] error creating error stream for port 5681 -> 5681: Timeout occurred
I don't see such issue when I tested on Chrome/Safari/Edge
I'd like to be able to view how many instances of Standalone, Global, and Zone CP instances are online in the GUI
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.