commit e2d478d65be11489372faac5b051d4602c3227ec Author: Priscila Oliveira Date: Sun Feb 3 11:23:33 2019 +0100 initial commit diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..dfb8d74 --- /dev/null +++ b/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@verdaccio", { "flow": true }]] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..42bfe7a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true + +# 2 space indentation +[{.,}*.{js,jsx,yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e757e5a --- /dev/null +++ b/.eslintignore @@ -0,0 +1,7 @@ +node_modules +coverage/ +wiki/ +static/ +flow-typed/ +website/ +build/ diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..74398c1 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,128 @@ +{ + "plugins": [ + "babel", + "react", + "flowtype", + "jest", + "verdaccio", + "jsx-a11y" + ], + "extends": [ + "eslint:recommended", + "google", + "plugin:flowtype/recommended", + "plugin:jest/recommended", + "plugin:prettier/recommended", + "plugin:react/recommended", + "plugin:verdaccio/recommended", + "plugin:jsx-a11y/recommended" + ], + "settings": { + "react": { + "pragma": "React", + "version": "16.4.2", + "flowVersion": "0.81.0" + } + }, + "parser": "babel-eslint", + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 7, + "ecmaFeatures": { + "impliedStrict": true, + "jsx": true + } + }, + "env": { + "browser": true, + "node": true, + "es6": true, + "jest": true + }, + "globals": { + "__APP_VERSION__": true + }, + "rules": { + "babel/no-invalid-this": 1, + "prettier/prettier": ["error", null, "@prettier"], + "react/no-deprecated": 1, + "react/jsx-no-target-blank": 1, + "react/destructuring-assignment": ["error", "always"], + "react/forbid-component-props": ["warn", { "forbid": ["style"] }], + "react/no-this-in-sfc": ["warn"], + "react/no-unsafe": ["warn"], + "react/sort-comp": ["warn", { + "order": [ + "static-methods", + "lifecycle", + "render", + "everything-else", + "/^on.+$/", + "/^render.+$/" + ] + }], + "react/void-dom-elements-no-children": ["warn"], + "react/no-did-mount-set-state": ["error", "disallow-in-func"], + "react/jsx-wrap-multilines": ["error",{ + "declaration": "parens", + "assignment": "parens", + "return": "parens", + "arrow": "parens", + "condition": "parens", + "logical": "parens", + "prop": "parens" + }], + "react/jsx-boolean-value": ["error", "always"], + "react/jsx-closing-tag-location": ["error"], + "react/jsx-curly-spacing": ["error", "never"], + "react/jsx-equals-spacing": ["error", "never"], + "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], + "react/jsx-handler-names": ["warn"], + "react/jsx-indent": ["error", 2], + "react/jsx-indent-props": ["error", 2], + "react/jsx-key": ["error"], + "react/jsx-max-depth": ["error", { "max": 2}], + "react/jsx-max-props-per-line": ["error", {"maximum": 3, "when": "multiline" }], + "react/jsx-no-bind": ["error"], + "react/jsx-no-comment-textnodes": ["error"], + "react/jsx-no-duplicate-props": ["error"], + "react/jsx-no-literals": ["error"], + "react/jsx-no-undef": ["error"], + "react/jsx-one-expression-per-line": ["error", {"allow": "single-child"}], + "react/jsx-curly-brace-presence": ["error", { "props": "always", "children": "ignore" }], + "react/jsx-pascal-case": ["error"], + "react/jsx-props-no-multi-spaces": ["error"], + "react/jsx-sort-default-props": ["error"], + "react/jsx-sort-props": ["error"], + "react/no-string-refs": ["error"], + "react/no-danger-with-children": ["error"], + "react/jsx-tag-spacing": ["error", { + "closingSlash": "never", + "beforeSelfClosing": "always", + "afterOpening": "allow-multiline", + "beforeClosing": "allow" + }], + "react/prefer-es6-class": [ + 2, + "always" + ], + "semi": ["error"], + "comma-dangle": ["error"], + "camelcase": 0, + "no-useless-escape": ["error"], + "no-invalid-this": 0, + "handle-callback-err": ["error"], + "no-fallthrough": ["error"], + "no-new-require": ["error"], + "max-len": ["error", 160], + "require-jsdoc": 0, + "valid-jsdoc": 0, + "prefer-spread": 1, + "prefer-rest-params": 1, + "linebreak-style": 0, + "quote-props":["error", "as-needed"], + "verdaccio/jsx-no-style": ["warn"], + "verdaccio/jsx-spread": ["warn"], + "jest/expect-expect": 0 + } +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..20e1ceb --- /dev/null +++ b/.flowconfig @@ -0,0 +1,25 @@ +[ignore] +.*/node_modules/.* +.*/test/**/*.json +.*/static/.* +.*/test/unit/partials/.* +.*/.nyc_output/.* +.*/coverage/.* +.*/.vscode/.* +.*/build/.* +.*/docs/.* +.*/scripts/.* +.*/assets/.* +.*/bin/.* +.*/systemd/.* +.*/website/.* +.*/wiki/.* +.*/docs/.* +.*/tools/.* + +[libs] +node_modules/@verdaccio/types/lib/ + +[options] +suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe +module.ignore_non_literal_requires=true diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..df43e6d --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# Compiled script +/static +/dist + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f235af8 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,12 @@ +{ + "useTabs": false, + "printWidth": 160, + "tabWidth": 2, + "singleQuote": true, + "requirePragma": true, + "bracketSpacing": true, + "jsxBracketSameLine": true, + "trailingComma": "es5", + "semi": true, + "parser": "flow" +} diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000..095b2b0 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,40 @@ +{ + "processors": ["stylelint-processor-styled-components"], + "extends": [ + "stylelint-config-recommended" + ], + "rules": { + "at-rule-no-unknown": true, + "block-no-empty": true, + "color-named": "always-where-possible", + "comment-no-empty": true, + "declaration-block-no-duplicate-properties": [ + true, + { + ignore: ["consecutive-duplicates-with-different-values"] + } + ], + "declaration-block-no-shorthand-property-overrides": true, + "font-family-no-duplicate-names": true, + "color-no-invalid-hex": true, + "font-family-no-missing-generic-family-keyword": true, + "function-calc-no-unspaced-operator": true, + "function-linear-gradient-no-nonstandard-direction": true, + "keyframe-declaration-no-important": true, + "property-no-vendor-prefix": true, + "media-feature-name-no-unknown": true, + "no-descending-specificity": [true, { "severity": "warning" }], + "no-duplicate-at-import-rules": true, + "no-duplicate-selectors": true, + "no-empty-source": true, + "no-extra-semicolons": true, + "no-invalid-double-slash-comments": true, + "property-no-unknown": true, + "selector-pseudo-class-no-unknown": true, + "selector-pseudo-element-no-unknown": true, + "selector-type-no-unknown": [true, { "severity": "warning" }], + "string-no-newline": true, + "unit-no-unknown": true + } + +} diff --git a/README.md b/README.md new file mode 100755 index 0000000..5195e51 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +under construction... diff --git a/package.json b/package.json new file mode 100644 index 0000000..f7a31c3 --- /dev/null +++ b/package.json @@ -0,0 +1,197 @@ +{ + "name": "ui", + "version": "1.0.0", + "description": "Verdaccio User Interface", + "author": { + "name": "Verdaccio Core Team" + }, + "repository": { + "type": "git", + "url": "git://github.com/verdaccio/io" + }, + "main": "build/index.js", + "dependencies": { + "@verdaccio/file-locking": "0.0.7", + "@verdaccio/local-storage": "2.0.0-beta.1", + "@verdaccio/streams": "2.0.0-beta.0", + "JSONStream": "1.3.4", + "async": "2.6.1", + "body-parser": "1.18.3", + "bunyan": "1.8.12", + "chalk": "2.4.1", + "commander": "2.18.0", + "compression": "1.7.3", + "cookies": "0.7.2", + "cors": "2.8.4", + "date-fns": "1.29.0", + "express": "4.16.3", + "global": "4.3.2", + "handlebars": "4.0.12", + "http-errors": "1.7.1", + "js-base64": "2.4.9", + "js-string-escape": "1.0.1", + "js-yaml": "3.12.0", + "jsonwebtoken": "8.3.0", + "lockfile": "1.0.4", + "lodash": "4.17.11", + "lunr-mutable-indexes": "2.3.1", + "marked": "0.5.1", + "mime": "2.3.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "mv": "2.1.1", + "pkginfo": "0.4.1", + "request": "2.88.0", + "semver": "5.5.1", + "verdaccio-audit": "1.1.0", + "verdaccio-htpasswd": "1.0.1", + "verror": "1.10.0" + }, + "devDependencies": { + "@commitlint/cli": "7.2.1", + "@commitlint/config-conventional": "7.1.2", + "@material-ui/core": "3.1.0", + "@material-ui/icons": "3.0.1", + "@verdaccio/babel-preset": "0.0.3", + "@verdaccio/types": "4.1.4", + "autosuggest-highlight": "3.1.1", + "bundlesize": "0.17.0", + "codecov": "3.1.0", + "cross-env": "5.2.0", + "css-loader": "0.28.10", + "emotion": "9.2.12", + "enzyme": "3.6.0", + "enzyme-adapter-react-16": "1.5.0", + "eslint": "5.10.0", + "eslint-config-google": "0.11.0", + "eslint-config-prettier": "3.3.0", + "eslint-loader": "2.1.1", + "eslint-plugin-babel": "5.3.0", + "eslint-plugin-flowtype": "3.2.0", + "eslint-plugin-import": "2.14.0", + "eslint-plugin-jest": "22.1.2", + "eslint-plugin-jsx-a11y": "6.1.2", + "eslint-plugin-prettier": "3.0.0", + "eslint-plugin-react": "7.11.1", + "eslint-plugin-verdaccio": "0.0.5", + "file-loader": "2.0.0", + "flow-bin": "0.81.0", + "flow-runtime": "0.17.0", + "friendly-errors-webpack-plugin": "1.7.0", + "github-markdown-css": "2.10.0", + "html-webpack-plugin": "3.2.0", + "husky": "0.15.0-rc.8", + "identity-obj-proxy": "3.0.0", + "in-publish": "2.0.0", + "jest": "23.6.0", + "jest-environment-jsdom": "23.4.0", + "jest-environment-jsdom-global": "1.1.0", + "jest-environment-node": "23.4.0", + "lint-staged": "7.3.0", + "localstorage-memory": "1.0.2", + "mini-css-extract-plugin": "0.4.3", + "node-mocks-http": "1.7.0", + "node-sass": "4.9.3", + "normalize.css": "8.0.0", + "optimize-css-assets-webpack-plugin": "5.0.1", + "ora": "1.4.0", + "prettier": "1.14.3", + "prop-types": "15.6.2", + "puppeteer": "1.8.0", + "react": "16.4.2", + "react-autosuggest": "9.4.2", + "react-dom": "16.4.2", + "react-emotion": "9.2.12", + "react-hot-loader": "4.2.0", + "react-router": "4.3.1", + "react-router-dom": "4.2.2", + "resolve-url-loader": "3.0.0", + "rimraf": "2.6.2", + "sass-loader": "7.1.0", + "source-map-loader": "0.2.4", + "standard-version": "4.4.0", + "style-loader": "0.23.0", + "stylelint": "9.9.0", + "stylelint-config-recommended": "2.1.0", + "stylelint-config-recommended-scss": "3.2.0", + "stylelint-config-styled-components": "0.1.1", + "stylelint-processor-styled-components": "1.5.1", + "stylelint-scss": "3.3.1", + "stylelint-webpack-plugin": "0.10.5", + "supertest": "3.3.0", + "typeface-roboto": "0.0.54", + "url-loader": "1.1.1", + "verdaccio-auth-memory": "0.0.4", + "verdaccio-memory": "2.0.0-beta.0", + "webpack": "4.20.2", + "webpack-bundle-analyzer": "3.0.2", + "webpack-cli": "3.1.1", + "webpack-dev-server": "3.1.14", + "webpack-merge": "4.1.4", + "whatwg-fetch": "3.0.0" + }, + "keywords": [ + "verdaccio", + "verdaccio-ui" + ], + "scripts": { + "flow": "flow check", + "pretest": "npm run code:build", + "test": "npm run test:unit", + "test:clean": "npx jest --clearCache", + "test:unit": "cross-env NODE_ENV=test BABEL_ENV=test TZ=UTC FORCE_COLOR=1 jest --config ./jest.config.js --maxWorkers 2 --passWithNoTests", + "test:functional": "cross-env NODE_ENV=test jest --config ./test/jest.config.functional.js --testPathPattern ./test/functional/index* --passWithNoTests", + "test:e2e": "cross-env BABEL_ENV=test jest --config ./test/jest.config.e2e.js", + "test:size": "bundlesize", + "test:all": "npm run build:webui && npm run test && npm run test:functional && npm run test:e2e && npm run test:size", + "pre:ci": "npm run lint && npm run build:webui", + "coverage:publish": "codecov", + "lint": "npm run flow && npm run lint:js && npm run lint:css", + "lint:js": "eslint .", + "lint:css": "stylelint 'src/webui/**/styles.js'", + "dev:start": "cross-env BABEL_ENV=registry babel-node src/lib/cli", + "code:build": "cross-env BABEL_ENV=registry babel src/ --out-dir build/ --ignore src/webui/ --copy-files", + "code:docker-build": "cross-env BABEL_ENV=registry-docker babel src/ --out-dir build/ --ignore src/webui/ --copy-files", + "pre:webpack": "rimraf static/*", + "dev:webui": "cross-env BABEL_ENV=ui babel-node tools/dev.server.js", + "build:webui": "npm run pre:webpack && cross-env BABEL_ENV=ui webpack --config tools/webpack.prod.config.babel.js", + "build:docker": "docker build -t verdaccio . --no-cache", + "build:docker:rpi": "docker build -f Dockerfile.rpi -t verdaccio:rpi ." + }, + "engines": { + "node": ">=6.12.0", + "npm": ">=3" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged && commitlint -e $GIT_PARAMS" + } + }, + "lint-staged": { + "linters": { + "*.yaml": [ + "prettier --parser yaml --no-config --single-quote --write", + "git add" + ], + "*": [ + "eslint .", + "prettier --write", + "git add" + ] + }, + "ignore": [ + "*.json" + ] + }, + "license": "MIT", + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "collective": { + "type": "opencollective", + "url": "https://opencollective.com/verdaccio", + "logo": "https://opencollective.com/verdaccio/logo.txt" + } +} diff --git a/src/.eslintrc b/src/.eslintrc new file mode 100644 index 0000000..507caf8 --- /dev/null +++ b/src/.eslintrc @@ -0,0 +1,30 @@ +{ + "env": { + "browser": true, + "node": true, + "jest": true, + "es6": true + }, + "globals": { + "__DEBUG__": true + }, + "rules": { + "require-jsdoc": 0, + "camelcase": ["error"], + "no-console": [ + 1, + { + "allow": [ + "log" + ] + } + ], + "no-unused-vars": [ + 2, + { + "vars": "all", + "args": "all" + } + ] + } +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..8888930 --- /dev/null +++ b/src/app.js @@ -0,0 +1,186 @@ +import React, {Component, Fragment} from 'react'; +import isNil from 'lodash/isNil'; + +import storage from './utils/storage'; +import {makeLogin, isTokenExpire} from './utils/login'; + +import Loading from './components/Loading'; +import LoginModal from './components/Login'; +import Header from './components/Header'; +import {Container, Content} from './components/Layout'; +import RouterApp from './router'; +import API from './utils/api'; +import './styles/typeface-roboto.scss'; +import './styles/main.scss'; +import 'normalize.css'; +import Footer from './components/Footer'; + +export const AppContext = React.createContext(); + +export const AppContextProvider = AppContext.Provider; +export const AppContextConsumer = AppContext.Consumer; + +export default class App extends Component { + state = { + error: {}, + logoUrl: window.VERDACCIO_LOGO, + user: {}, + scope: window.VERDACCIO_SCOPE ? `${window.VERDACCIO_SCOPE}:` : '', + showLoginModal: false, + isUserLoggedIn: false, + packages: [], + isLoading: true, + }; + + componentDidMount() { + this.isUserAlreadyLoggedIn(); + this.loadOnHandler(); + } + + // eslint-disable-next-line no-unused-vars + componentDidUpdate(_, prevState) { + const {isUserLoggedIn} = this.state; + if (prevState.isUserLoggedIn !== isUserLoggedIn) { + this.loadOnHandler(); + } + } + + isUserAlreadyLoggedIn = () => { + // checks for token validity + const token = storage.getItem('token'); + const username = storage.getItem('username'); + if (isTokenExpire(token) || isNil(username)) { + this.handleLogout(); + } else { + this.setState({ + user: {username, token}, + isUserLoggedIn: true, + }); + } + }; + + loadOnHandler = async () => { + try { + this.req = await API.request('packages', 'GET'); + this.setState({ + packages: this.req, + isLoading: false, + }); + } catch (error) { + // FIXME: add dialog + console.error({ + title: 'Warning', + message: `Unable to load package list: ${error.message}`, + }); + this.setLoading(false); + } + }; + + setLoading = (isLoading) => + this.setState({ + isLoading, + }); + + /** + * Toggles the login modal + * Required by:
+ */ + handleToggleLoginModal = () => { + this.setState((prevState) => ({ + showLoginModal: !prevState.showLoginModal, + error: {}, + })); + }; + + /** + * handles login + * Required by:
+ */ + handleDoLogin = async (usernameValue, passwordValue) => { + const {username, token, error} = await makeLogin(usernameValue, passwordValue); + + if (username && token) { + this.setLoggedUser(username, token); + storage.setItem('username', username); + storage.setItem('token', token); + } + + if (error) { + this.setState({ + user: {}, + error, + }); + } + }; + + setLoggedUser = (username, token) => { + this.setState({ + user: { + username, + token, + }, + isUserLoggedIn: true, // close login modal after successful login + showLoginModal: false, // set isUserLoggedIn to true + }); + }; + /** + * Logouts user + * Required by:
+ */ + handleLogout = () => { + storage.removeItem('username'); + storage.removeItem('token'); + this.setState({ + user: {}, + isUserLoggedIn: false, + }); + }; + + render() { + const {isLoading, isUserLoggedIn, packages, logoUrl, user, scope} = this.state; + return ( + + {isLoading ? ( + + ) : ( + + {this.renderContent()} + + )} + {this.renderLoginModal()} + + ); + } + + renderLoginModal = () => { + const {error, showLoginModal} = this.state; + return ( + + ); + }; + + renderContent = () => { + return ( + + + + {this.renderHeader()} + + +