Giter Club home page Giter Club logo

paas-admin's Introduction

GOV.UK PaaS Admin

⚠️ When merging pull requests, please use the gds-cli or github_merge_sign ⚠️

Overview

A web UI for interacting with GOV.UK PaaS (CloudFoundry).

It aims to be a progressive enhanced, well tested and user researched tool that tenants can use to complement their use of the CLI.

Usage

npm run build              # compile the build to ./dist
npm run test               # run all the tests and linters
npm run test:unit          # only unit tests
npm run lint               # run code linters
npm run fix                # try to autofix problems with js/css
npm run start              # rebuild and start the server
npm run push               # rebuild and push to cloudfoundry
npm run clean              # destroy the ./dist build dir

Prerequisites

You will need to add a UAA client to your CloudFoundry deployment manifest, for example:

paas-admin:
  override: true
  authorized-grant-types: authorization_code,client_credentials,refresh_token
  autoapprove: true
  secret: [CF_CLIENT_SECRET]
  scope: cloud_controller.read,cloud_controller.admin_read_only,cloud_controller.global_auditor,cloud_controller.write,scim.me,openid,profile,uaa.user,cloud_controller.admincloud_controller.read,cloud_controller.admin_read_only,cloud_controller.global_auditor,cloud_controller.write,scim.me,openid,profile,uaa.user,cloud_controller.admin
  authorities: scim.userids,scim.invite,scim.read
  redirect-uri: "https://[pass-admin-domain.com]/auth/login/callback"

If you get problems with "Invalid redirect", use uaac to modify the redirect-uri:

uaac target https://uaa.my.environment
uaac token client get admin -s my-uaa-admin-client-secret
uaac client update paas-admin --redirect-uri http://localhost:3000/auth/login/callback

Requirements

  • Node.js version ~ 14 LTS - consider using NVM (nvm use in this repo) for version management
  • npm versions > 7.x.x

Getting Started

Clone this repository and then use npm to install the project dependencies:

npm install

Execute the unit tests to ensure everything looks good:

npm test

Executing the acceptance tests against dev environment:

export PAAS_ADMIN_BASE_URL=https://admin.${DEPLOY_ENV}.dev.cloudpipeline.digital
export CF_API_BASE_URL=https://api.${DEPLOY_ENV}.dev.cloudpipeline.digital
export ACCOUNTS_API_BASE_URL=https://accounts.${DEPLOY_ENV}.dev.cloudpipeline.digital
export ACCOUNTS_USERNAME=admin
export ACCOUNTS_PASSWORD= # get this value from credhub using `credhub get -n /concourse/main/create-cloudfoundry/paas_accounts_password`

export ADMIN_USERNAME=admin
export ADMIN_PASSWORD= # get this value from credhub using `credhub get -n /${DEPLOY_ENV}/${DEPLOY_ENV}/cf_admin_password`

npm run test:acceptance

Start the server pointing at stubbed APIs

# In one terminal tab
npm run start:stub-api

# In a second terminal tab
npm run start:with-stub-api

Start the server pointing at real APIs in development mode

Run

gds aws paas-dev-admin -- npm run start:dev:with-cf-dev-env  $DEPLOY_ENV

and follow on-screen instructions.

You should be able to edit files in the ./src directory and the changes will automatically be updated.

Quick local start for review/troubleshooting

Provided you have logged in CF and have AWS credentials

  1. Update uaa user:
DEPLOY_ENV=...
uaac target https://uaa.${DEPLOY_ENV}.dev.cloudpipeline.digital
uaac token client get admin -s $(aws s3 cp s3://gds-paas-${DEPLOY_ENV}-state/cf-vars-store.yml  - | grep uaa_admin_client_secret | cut -f2 -d " ")
uaac client update paas-admin --redirect-uri http://localhost:3000/auth/login/callback
  1. Get the command to start the server:
cf target -o admin -s public
cf env paas-admin | awk '/User-Provided/ { printing=1; next} /^$/ { printing=0 ; next} printing  {gsub(": ","=\""); gsub("$", "\" \\"); print; next  } END { print "npm start"}'

  1. Undo the paas-admin client change once done:
uaac client update paas-admin --redirect-uri https://admin.${DEPLOY_ENV}.dev.cloudpipeline.digital/auth/login/callback

Production builds

The NODE_ENV environment variable alters the build process to bundle all dependencies into the ./dist/ directory.

NODE_ENV=production npm run build

The ./dist folder should now be a distributable without the need for the node_modules folder and should be executable on any environment that has a supported version of Node.js, e.g., cf push.

To push the build to CloudFoundry there is a helper script that creates a production build and calls cf push:

npm run push

Alternatives

This project is fairly young and may not be a right fit for different needs yet.

You may be interested in investigating other tools, such as Stratos which may become an official tool some day.

paas-admin's People

Contributors

0atman avatar 46bit avatar alext avatar ap-hunt avatar cadmiumcat avatar camelpunch avatar chrisfarms avatar corlettb avatar dcarley avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar fearoffish avatar gidsg avatar govuk-paas-ci-user avatar henrytk avatar keymon avatar kr8n3r avatar leeporte avatar markpeterbuckley avatar mogds avatar nimalank7 avatar paroxp avatar richardtowers avatar risicle avatar samcrang avatar schmie avatar tlwr avatar whi-tw avatar whpearson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

paas-admin's Issues

<body> element should not have govuk-body class

Hey folks,

Just noticed that the skip link is rendered strangely in the PaaS admin app, with an unexpected bottom margin:

Screenshot 2019-09-27 at 16 39 47BST

From digging in, it looks like this is happening because you're applying the govuk-body class to the body

{% set bodyClasses = "govuk-body" %}

I can totally understand from the name why that might have happened (!) but the govuk-body class is intended to be applied to paragraphs. The only class we expect to be on the <body> is govuk-template__body (which is there)

This is setting a bottom margin on the <body> which is being inherited by the skip link (and possibly other elements on the page) – the fact that the margin is being inherited at all is kind of weird and I'm going to raise an issue about that over on our side.

Note that this will also currently be setting the font on everything within the <body>, so when you remove this there's a chance you might end up with some parts of the UI not using the font, if they don't have the correct classes.

Replace EOL sass-lint

replace with perhaps stylelint or eslint?
one of it's deep dependency has a vulnerable package

Organisations list mislabels Trial orgs as Billable

The org view of https://admin.london.cloud.service.gov.uk/organisations/a8e32de1-b0a0-4a00-b36b-6324a0ba35cf says it is a Trial org:

Screen Shot 2019-10-02 at 10 57 41

But on the homepage's list of organisations it is said to be a Billable org:

Screen Shot 2019-10-02 at 10 58 33

In fact, the homepage's list of organisations describes every org as Billable 😮

I glanced at the underlying code and it looked OK, but I suspect the comparison is subtly hiding a problem. The comparison in both locations is if quota name equals 'default' then say 'Trial' else say 'Billable'. If the quota name isn't actually set then it would say Billable for every org, just like we're seeing on the homepage…

Look at replacing Enzyme

We use React 18, but there is only an adapter for react 16 (with not even 17 in sight), resulting with failing builds now

hile resolving: [email protected]
npm ERR! Found: [email protected]
npm ERR! node_modules/react
npm ERR!   react@"^18.1.0" from the root project
npm ERR!   peer react@"^18.1.0" from [email protected]
npm ERR!   node_modules/react-dom
npm ERR!     react-dom@"^18.1.0" from the root project
npm ERR!   1 more (react-markdown)
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^16.0.0-0" from [email protected]
npm ERR! node_modules/enzyme-adapter-react-16
npm ERR!   dev enzyme-adapter-react-16@"^1.15.6" from the root project
npm ERR! 
npm ERR! Conflicting peer dependency: [email protected]
npm ERR! node_modules/react
npm ERR!   peer react@"^16.0.0-0" from [email protected]
npm ERR!   node_modules/enzyme-adapter-react-16
npm ERR!     dev enzyme-adapter-react-16@"^1.15.6" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.

Best alternative is React Testing library https://dev.to/wojtekmaj/enzyme-is-dead-now-what-ekl but different approaches to testing

Missing file /assets/init.js

I get this error running it:

   2018-10-08T16:48:04.56+0100 [APP/PROC/WEB/1] OUT {"level":50,"time":1539013684563,"msg":"unregistered route: /assets/init.js","pid":17,"hostname":"b6d3b30b-fea8-4006-65c0-fa58","req":{"method":"GET","url":"/assets/init.js"},"type":"Error","stack":"NotFoundError: unregistered route: /assets/init.js\n    at n.default.find (/home/vcap/app/dist/webpack:/src/lib/router/router.ts:17:13)\n    at find (/home/vcap/app/dist/webpack:/src/components/app/router-middleware.ts:34:22)\n    at Layer.handle [as handle_request] (/home/vcap/deps/0/node_modules/express/lib/router/layer.js:95:5)\n    at trim_prefix (/home/vcap/deps/0/node_modules/express/lib/router/index.js:317:13)\n    at /home/vcap/deps/0/node_modules/express/lib/router/index.js:284:7\n    at Function.process_params (/home/vcap/deps/0/node_modules/express/lib/router/index.js:335:12)\n    at next (/home/vcap/deps/0/node_modules/express/lib/router/index.js:275:10)\n    at next (/home/vcap/app/dist/webpack:/src/components/app/router-middleware.ts:28:5)\n    at Layer.handle [as handle_request] (/home/vcap/deps/0/node_modules/express/lib/router/layer.js:95:5)\n    at trim_prefix (/home/vcap/deps/0/node_modules/express/lib/router/index.js:317:13)\n    at /home/vcap/deps/0/node_modules/express/lib/router/index.js:284:7\n    at Function.process_params (/home/vcap/deps/0/node_modules/express/lib/router/index.js:335:12)\n    at next (/home/vcap/deps/0/node_modules/express/lib/router/index.js:275:10)\n    at expressInit (/home/vcap/deps/0/node_modules/express/lib/middleware/init.js:40:5)\n    at Layer.handle [as handle_request] (/home/vcap/deps/0/node_modules/express/lib/router/layer.js:95:5)\n    at trim_prefix (/home/vcap/deps/0/node_modules/express/lib/router/index.js:317:13)\n    at /home/vcap/deps/0/node_modules/express/lib/router/index.js:284:7\n    at Function.process_params (/home/vcap/deps/0/node_modules/express/lib/router/index.js:335:12)\n    at next (/home/vcap/deps/0/node_modules/express/lib/router/index.js:275:10)\n    at query (/home/vcap/deps/0/node_modules/express/lib/middleware/query.js:45:5)\n    at Layer.handle [as handle_request] (/home/vcap/deps/0/node_modules/express/lib/router/layer.js:95:5)\n    at trim_prefix (/home/vcap/deps/0/node_modules/express/lib/router/index.js:317:13)","name":"NotFoundError","v":1}
   2018-10-08T16:48:04.56+0100 [RTR/0] OUT admin.alext.dev.cloudpipeline.digital - [2018-10-08T15:48:04.522+0000] "GET /assets/init.js HTTP/1.1" 404 0 3177 "https://admin.alext.dev.cloudpipeline.digital/organisations/ef19aa52-a2ff-4dae-b069-3132b43cd9f8/users/invite" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0" "127.0.0.1:11270" "10.0.32.4:61112" x_forwarded_for:"213.86.153.236, 127.0.0.1" x_forwarded_proto:"https" vcap_request_id:"1c6244ee-3e06-42c8-50ca-7b600af45a6c" response_time:0.046086528 app_id:"ebe8aa35-7350-4bf4-8519-4d64b3ffd12f" app_index:"1" x_b3_traceid:"fe3293705cf876c9" x_b3_spanid:"fe3293705cf876c9" x_b3_parentspanid:"-"

I guess webpack needs to be updated to include that file

Replace deprecated packages

npm WARN deprecated [email protected]: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (debug-js/debug#797)
npm WARN deprecated [email protected]: request has been deprecated, see request/request#3142
npm WARN deprecated [email protected]: this library is no longer supported
npm WARN deprecated [email protected]: request has been deprecated, see request/request#3142
npm WARN deprecated [email protected]: request-promise has been deprecated because it extends the now deprecated request package, see request/request#3142

Update aws-sdk packages

they seem to be deprecated or renamed

Renamed

@aws-sdk/[email protected]: The package @aws-sdk/client-cloudwatch-node has been renamed to @aws-sdk/client-cloudwatch. Please install the renamed package.

Deprecated

@aws-sdk/[email protected]: The package @aws-sdk/core-handler was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/region-provider was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/retry-middleware was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/middleware-serializer was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/stream-collector-node was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/signing-middleware was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/query-error-unmarshaller was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/query-builder was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/protocol-query was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/xml-body-parser was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/protocol-timestamp was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/response-metadata-extractor was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/is-iterable was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/util-error-constructor was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/json-parser was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/protocol-json-rpc was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/json-builder was released in Developer Preview. It is no longer supported.

@aws-sdk/[email protected]: The package @aws-sdk/json-error-unmarshaller was released in Developer Preview. It is no longer supported.

Move to webpack 5

we're on latest webpack 4 version To keep receiving updated we need to upgrade.

Calculator doesn't work for 22+ services

The pricing calculator passes around the service data in its GET query string. This hits haproxy's size limit. This bug was already recorded on paas-billing, but fixing it would require changes to both.

Ideally a fix would allow all these things:

  • Pricing calculator links are still sharable;
  • Pricing calculator doesn't require big changes (i.e., no JavaScript);
  • Can use arbitrary numbers of services.

Ideas for how to fix this:

  • Remove haproxy or increase its 8K GET header size limit (involves bigger platform infra changes);
  • Switch to POST for these requests and accept URLs won't be sharable (stops url being sharable);
  • Compress and base64 the data using JavaScript (requires JavaScript and changes to both the calculator and paas-billing);
  • Use POST but then have the data saved in a database and the user redirected to a sharable URL (disadvantage: another database to maintain.)

Quota on organisations page includes stopped apps

When viewing the organisation quota page, the total running/percentage used includes stopped apps. This doesn't actually stop you from staging/launching new apps so isn't as scary as it looks but it feels like a bug if I'm allowed to use >100% of my quota.

Screenshot from 2019-10-01 18-26-12 - 1

Error resending user invitation

I clicked 'Resend user invite' for a user and received the 'Sorry an error occurred' message. The logs showed this:

{\"errors\":[{\"error\":\"BadRequestError\",\"message\":\"Missing personalisation: location\"}],\"status_code\":400}

Upgrade to Jest 28

ts-jest, jest-babel, types/jest, jest-puppeteer and expect all need to be compatible with 28 which is a major version

`npm run push`

npm run push fails with:

Pushing from manifest to org admin / space public as admin...
Using manifest file /Users/henryknott/dev/go/src/github.com/alphagov/paas-admin/manifest.yml
Getting app info...
No app files found in '/Users/henryknott/dev/go/src/github.com/alphagov/paas-admin'

I haven't had time to investigate why, but just recording it here.

A user with global auditing can reach the "user details" page but it would fail

What?

A user with Global Auditor role in a CF installation can go to an org, see all the org users, and then click on any of these users. But when displaying the user page, it fails.

Expected

The auditor should not be able to "click" on the user details if it has no permissions, or a more informative error should be displayed.

Upgrade openid-client to major version 5

What

Update openid-client package to the next major version, version 5 and update application code to address breaking changes.

Why

We want to be using latest stable version of packages so that we have an easier upgrade path and we're less vulnerable to exploits

It’s done when

If we don’t do this

It will become more difficult to upgrade later on.

Figure of what to do with Jest and ES Modules

What

TLDR; We need to find a way to make ES module dependencies work with Jest and TS-jest without affecting our javascript bundle meant for the client-side browser.

We have an increasing number of dependencies moving to ES Modules
They link to Sindre Sorhus's write-up

Tests now break in Jest test with

Jest encountered an unexpected token

 Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

By default "node_modules" folder is ignored by transformers.

 Here's what you can do:
     • If you are trying to use ECMAScript Modules, see https://jestjs.io/docs/ecmascript-modules for how to enable it.
     • To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
     • If you need a custom transformation specify a "transform" option in your config.
     • If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.

You'll find more details and examples of these config options in the docs:
 - https://jestjs.io/docs/configuration
 - For information about custom transformations, see:
 - https://jestjs.io/docs/code-transformation

For use with Typescript use use a jest wrapper, ts-jestand they have docs on ES module support

In most cases we already use import instead of require.

Why

So that we can update dependencies that have move to ES modules

It’s done when

  • tests pass and update pull requests are merged

If we don’t do this

We'll be stuck with out of date dependencies.

Tests running stangely on my machine

Creating this issue to track my investigation.
I think this is likely a problem with my machine, but we'll see.

The problem is this test fails with a en-US-like string:

    expect(received).toContain(expected) // indexOf

    Expected substring: "Monday, February 24, 2020"
    Received string:    "Monday, 24 February 2020"

      280 |     const input = '2020-02-24T16:32:03.000Z';
      281 |     const output = formatDate(new Date(input),{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
    > 282 |     expect(output).toContain('Monday, February 24, 2020');

But my machine seems to be recognised by node as en-GB:

λ node
> new Intl.DateTimeFormat().resolvedOptions().locale
'en-GB'

Ways to fix this:

  1. Remove the test
  2. fix @0atman's odd locale

"Org Billing Manager" role gets removed when editing the unique user with that role.

Summary

When a org has only one user with "Org Billing Manager", if you click on edit the user and then save, the role gets remove without reason.

Expected result

The role should remain unless un-tick

How to reproduce:

  1. Given an empty org with no bill managers
  2. Add a new user with "Org Bill manager"
  3. Go back to edit users and click on the new user. The checkbox of "Org Billing Manager" appears as grey and can not be changed.
  4. Click on save user.
  5. Go back to the user, the "Org Billing Manager" role is gone

Replace RDS from AWS-SDK with @aws-sdk/client-rds

What

Replace whole of aws-sdk in favour of the modular @aws-sdk/client-rds modular package. This will require a bit of a rewrite where we use it

  • src/components/services/controllers.tsx
  • src/components/services/views.tsx
  • src/components/services/controllers.mocked.test.tsx

Why

aws-sdk is v2 and it's a monolith package. V3 has been split into modular packages and we're already using those as of #1742
Replacing this remaining instance will allow as to fully remove v2 of aws-sdk.

It’s done when

  • aws-sdk and @types/aws-sdk are removed
  • @aws-sdk/client-rds is used for RDS stuff

If we don’t do this

  • we'll be using v2 and v3 and Amazon will at some point deprecate v2

User invitation says invitations are sent for users who already existed - but nothing gets sent

If you invite a user using a link such as: https://admin.system-domain/organisations/02d9229a-004c-48f0-93ea-6041996cedb1/users/invite and the user already exists, but is not already a user in the org, the message reads:

Success!
We've have sent your invitation

Even though no invitation is sent, because they already exist. The permissions do get correctly added to the orgs/spaces though.

Cannot display list of org members when a user is deleted from UAA but not from CF

When a member of your org has been deleted from UAA but still exists in CF, paas-admin returns a HTTP 500 error for the org's members page.

Here's the error message from my environment:

2019-08-09T11:19:20.89+0100 [APP/PROC/WEB/1] OUT {"level":50,"time":1565345960892,"pid":12,"hostname":"e1f8d666-5451-4965-573b-9d75","req":{"method":"GET","url":"/organisations/b4989f51-7709-4533-ab50-b4410600b589/users"},"msg":"UAAClient: get /Users/badb45db-273d-45fa-b543-fecb23486530 failed with status 404 and data {\"error_description\":\"User badb45db-273d-45fa-b543-fecb23486530 does not exist\",\"error\":\"scim_resource_not_found\",\"message\":\"User badb45db-273d-45fa-b543-fecb23486530 does not exist\"}","stack":"Error: UAAClient: get /Users/badb45db-273d-45fa-b543-fecb23486530 failed with status 404 and data {\"error_description\":\"User badb45db-273d-45fa-b543-fecb23486530 does not exist\",\"error\":\"scim_resource_not_found\",\"message\":\"User badb45db-273d-45fa-b543-fecb23486530 does not exist\"}\n    at /home/vcap/app/dist/webpack:/src/lib/uaa/uaa.ts:203:11\n    at Generator.next (<anonymous>)\n    at fulfilled (/home/vcap/deps/0/node_modules/tslib/tslib.js:107:62)\n    at process._tickCallback (internal/process/next_tick.js:68:7)","type":"Error","v":1}

Find an alternative to Zombie

we use zombie browser in acceptance-tests/main.test.ts

package hasn't been update since 2018 and its dependencies are using an outdated version of acorn

alternatives:

Puppeteer
CasperJS
NW.js (w/xvfb)
Nightwatch.js
WebdriverIO
QUnit

As the test is using MOCHA, perhaps we can also move to jest-puppeteer, seeing we already use Jest for other tests?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.