Commit add38974 authored by Benjamin Bouvier's avatar Benjamin Bouvier

Merge branch 'master' into build-incoming

parents c6a2355c e1d0f8db
{
"presets": [
["env", { "modules": false }],
"react",
"stage-2"
[
"@babel/preset-env",
{
"modules": false
}
],
"@babel/preset-react"
],
"env": {
"tests": {
"presets": ["env", "react", "stage-2"]
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions"
]
},
"production": {
"plugins": [
["transform-react-remove-prop-types", {
"mode": "remove",
"removeImport": true
}]
[
"transform-react-remove-prop-types",
{
"mode": "remove",
"removeImport": true
}
]
]
}
}
},
"plugins": [
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-import-meta",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-json-strings",
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-function-sent",
"@babel/plugin-proposal-export-namespace-from",
"@babel/plugin-proposal-numeric-separator",
"@babel/plugin-proposal-throw-expressions"
]
}
......@@ -8,6 +8,3 @@ shared/locales
# ignore kresus binary
bin/kresus.js
# ignore tests
tests/
......@@ -22,13 +22,20 @@
.project
.settings
.tmproj
.vscode
nbproject
Thumbs.db
yarn.lock
package-lock.json
jsconfig.json
# Misc folders
logs
!client/components/settings/logs
node_modules/
build/
!scripts/build
# Linter cache
.eslintcache
.stylelintcache
# Note: We use --silent argument on npm invocations to keep the output simple
# Note: We use --silent argument on yarn invocations to keep the output simple
# and readable through email notifications.
image: node:6
image: node:8
cache:
key: 'node-modules-kresus'
......@@ -17,12 +17,16 @@ stages:
lint:
stage: lint
before_script:
# Reset the status of the cache.
- rm -fr ./node_modules yarn.lock
- yarn install
script:
# Lint JS code
- npm run --silent check:lint-full
# And check banks and locales are correct
- npm run --silent check:banks && npm run --silent check:locales
# JS code lint.
- yarn run --silent check:lint-full
# Domain specific checks.
- yarn run --silent check:banks
- yarn run --silent check:locales
- yarn run --silent check:config
# Build the server and client code in dev and production
build:
......@@ -31,7 +35,7 @@ build:
- yarn install
script:
# Check everything builds correctly
- npm run --silent build:ci
- yarn run --silent build:ci
cache:
policy: pull
......@@ -41,20 +45,19 @@ test:
before_script:
- yarn install
# Install weboob
- apt-get update && apt-get install -y python-pip python-lxml python-yaml python-dev
- apt-get update && apt-get install -y python-pip python-lxml python-yaml python-dev python-enum34
- mkdir ./weboob
# Fix for outdated pip and setuptools.
# See https://github.com/bigchaindb/bigchaindb/issues/236
- pip install --upgrade setuptools pip
- pip install --upgrade requests
- pip install weboob --target=./weboob
- pip install pylint
script:
# Lint Python code
- KRESUS_WEBOOB_DIR=./weboob npm run --silent check:python
- KRESUS_WEBOOB_DIR=./weboob yarn run --silent check:python
# Run the CI test suite without weboob installed
- npm run --silent check:test
- yarn run --silent check:test
# Run the CI test suite again with weboob installed
- KRESUS_WEBOOB_DIR=./weboob npm run --silent check:test
- KRESUS_WEBOOB_DIR=./weboob yarn run --silent check:test
cache:
policy: pull
......@@ -64,9 +67,6 @@ test:
lint_history:
stage: test
before_script:
# Install up to date Git version
- echo 'deb http://deb.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/backports.list
- apt-get update && apt-get -t jessie-backports install -y git
# Install JS deps
- yarn install
# Give it a Git identity for the rebase to work
......@@ -77,8 +77,9 @@ lint_history:
# Abort any merge in progress
- git merge --abort 2> /dev/null || true
# Install weboob
- apt-get update && apt-get install -y python-pip python-lxml python-yaml python-dev
- apt-get update && apt-get install -y python-pip python-lxml python-yaml python-dev python-enum34
- mkdir ./weboob
- pip install requests
- pip install weboob --target=./weboob
script:
- npm run --silent check:full-history
- yarn run --silent check:full-history
server/
shared/
static/
# For a reason I can't understand, client/ doesn't suffice to remove the client
# directory. And client/* isn't enough to remove the client/main.js file, which
# remains even if there's an explicit rule to disable it. Oh well.
client/*
config.ini
dataDir/
tests/
scripts/
.babelrc
.eslintignore
.eslintrc.yml
.gitlab-ci.yml
.pylintrc
.stylelintrc
Makefile
webpack.config.js
.gitlab/
yarn.lock
**/*.pyc
......@@ -351,7 +351,7 @@ max-bool-expr=5
max-branches=12
# Maximum number of locals for function / method body
max-locals=15
max-locals=20
# Maximum number of parents for a class (see R0901).
max-parents=7
......
{
"extends": "stylelint-config-standard",
"rules": {
"indentation": 4,
"color-hex-length": "long",
"color-hex-case": "upper",
"no-descending-specificity": null,
"string-quotes": "single",
"selector-attribute-quotes": "always",
"function-url-quotes": "always",
"at-rule-empty-line-before": null
}
}
\ No newline at end of file
.PHONY: help install build watch dev lint test check release docker-release docker-nightly
.PHONY: help install prod build watch dev pretty config lint test check release docker-release docker-nightly
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
......@@ -7,31 +7,34 @@ install: ## Globally install a prebuilt version of kresus.
npm -g install kresus
prod: ## Builds in prod mode. Transpiles ES6 files to ES5, moves files and concatenate them to obtain a usable build.
npm run build:prod
yarn run build:prod
build: ## Builds in dev mode. Transpiles ES6 files to ES5, moves files and concatenate them to obtain a usable build.
npm run build:dev
yarn run build:dev
watch: ## As build, but retriggers incremental compilation as the files are changed on disk.
npm run watch
yarn run watch
dev: ## Runs servers that get relaunched whenever a built file changes.
npm run dev
yarn run dev
pretty:
npm run fix:lint
yarn run fix:lint
config: ## Creates an example configuration file that's up to date.
yarn run fix:config
lint: ## Runs the linter for the server and the client, without warnings.
npm run check:lint
yarn run check:lint
test: ## Runs all the tests.
npm run check:test
yarn run check:test
check: ## Runs all tests and style checks.
npm run check
yarn run check
release: ## Prepares for a release. To be done only on the `builds` branch.
npm run release
yarn run release
docker-release: ## Prepares for a Docker release. Must be done after make release.
docker build -t bnjbvr/kresus -f docker/Dockerfile-stable .
......
......@@ -17,7 +17,7 @@ For up to date installation instructions, please refer to the
[kresus.org](https://kresus.org/install.html) website.
Note that this website is opensource as well and you can
[contribute](https://framagit.org/bnjbvr/kresus.org/blob/master/content/pages/01-install.md)
[contribute](https://framagit.org/kresusapp/kresus.org/blob/master/content/pages/01-install.md)
to improve installation instructions.
......@@ -25,7 +25,7 @@ to improve installation instructions.
See the [contributing file](CONTRIBUTING.md).
A big thank you to all of our [contributors](https://framagit.org/bnjbvr/kresus/graphs/master)!
A big thank you to all of our [contributors](https://framagit.org/kresusapp/kresus/graphs/master)!
## Code of conduct
......@@ -39,5 +39,4 @@ to report abuses, etc.
If you like the work we're doing, consider making a donation!
[![Make a recurrent donation](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/bnjbvr/donate)
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=bnj&url=https://github.com/bnjbvr/kresus&title=Kresus&language=&tags=github&category=software)
[![Make a recurrent donation](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/Kresus/donate)
......@@ -3,7 +3,7 @@
// Pollute global scope with Babel polyfills prior to anything else.
// Note: eslint doesn't like unassigned imports.
/* eslint-disable */
require('babel-polyfill');
require('@babel/polyfill');
/* eslint-enable */
var path = require('path');
......@@ -19,26 +19,64 @@ function help(binaryName) {
process.exit(0);
}
var explainedChmodError = false;
function tryChmod(pathname, mode) {
try {
fs.chmodSync(pathname, mode);
} catch (err) {
if (!explainedChmodError) {
console.warn('To help ensuring your private data is safe, Kresus tried to ' +
'chmod the data directory (datadir in config.ini, or KRESUS_DATA_DIR as environment ' +
'variable) with predefined restrictive settings, but an error occurred:');
explainedChmodError = true;
}
console.warn('Unable to chmod', pathname);
}
}
function recursiveChmod(pathname, fileMode, dirMode) {
var stats = fs.statSync(pathname);
if (stats.isFile()) {
if (stats.mode !== fileMode) {
tryChmod(pathname, fileMode);
}
return;
}
if (stats.isDirectory(pathname)) {
if (stats.mode !== dirMode) {
tryChmod(pathname, dirMode);
}
fs.readdirSync(pathname).forEach(function(dir) {
recursiveChmod(path.join(pathname, dir), fileMode, dirMode);
});
}
}
// In the stats retrieved from a file, the rights are the last 9 bits :
// user rights / group rights / other rights
var ACLMask = 0x1FF;
var configFileACLMask = 0x1ff;
function readConfigFromFile(path) {
function readConfigFromFile(pathname) {
var content = null;
try {
var mode = fs.statSync(path).mode;
var mode = fs.statSync(pathname).mode;
var rights = mode & ACLMask;
var rights = mode & configFileACLMask;
// Allow:
// - readable by user
// - writeable by user
// - readable by group
var allowedFlags = fs.constants.S_IRUSR | fs.constants.S_IWUSR | fs.constants.S_IRGRP;
// In production, check the config file has r or rw rights for the owner.
if (process.env.NODE_ENV === 'production' &&
rights !== (rights & fs.constants.S_IRUSR) &&
rights !== (rights & (fs.constants.S_IRUSR | fs.constants.S_IWUSR))) {
console.error('For security reasons, config file', path, 'should be +r or +rw (not +rwx) for its owner only.');
if (process.env.NODE_ENV === 'production' && (rights & ~allowedFlags) !== 0) {
console.error('For security reasons, the configuration file', pathname, 'should be at most readable by its owner and group, writable by its owner. Please make sure to restrict permissions on this file using the chmod command.');
process.exit(-1);
}
content = fs.readFileSync(path, { encoding: 'utf8' });
content = fs.readFileSync(pathname, { encoding: 'utf8' });
} catch (e) {
console.error('Error when trying to read the configuration file (does the file at this path exist?)', e.toString(), '\n\n', e.stack);
process.exit(-1);
......@@ -57,9 +95,11 @@ function readConfigFromFile(path) {
// First two args are [node, binaryname]
var numActualArgs = Math.max(process.argv.length - 2, 0);
function actualArg(n) { return process.argv[2 + n]; }
function actualArg(n) {
return process.argv[2 + n];
}
var config = null;
var config = {};
if (numActualArgs >= 1) {
var binaryName = actualArg(-1);
var arg = actualArg(0);
......@@ -67,7 +107,7 @@ if (numActualArgs >= 1) {
help(binaryName);
} else if (['-c', '--config'].includes(arg)) {
if (numActualArgs < 2) {
console.error('Missing config file path.')
console.error('Missing config file path.');
help(binaryName);
process.exit(-1);
}
......@@ -83,19 +123,30 @@ if (numActualArgs >= 1) {
// First, define process.kresus.
var root = path.join(path.dirname(fs.realpathSync(__filename)), '..', 'build');
require(path.join(root, 'server', 'apply-config.js'))(config);
require(path.join(root, 'server', 'config.js')).apply(config);
// Then only, import the server.
var server = require(path.join(root, 'server'));
var mainDir = process.kresus.dataDir;
if (!fs.existsSync(mainDir)) {
fs.mkdirSync(mainDir);
var dataDir = process.kresus.dataDir;
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir);
}
process.chdir(mainDir);
// The server should only create files with +rw permissions for the current
// user.
var processUmask = 0o0077;
process.umask(processUmask);
// Ensure the data directory contains files only the current user can read and
// write.
recursiveChmod(dataDir,
fs.constants.S_IRUSR | fs.constants.S_IWUSR,
fs.constants.S_IRUSR | fs.constants.S_IWUSR | fs.constants.S_IXUSR);
process.chdir(dataDir);
var defaultDbPath = path.join(mainDir, 'db');
var defaultDbPath = path.join(dataDir, 'db');
var opts = {
root: root,
......
......@@ -5,6 +5,7 @@ import { translate as $t } from '../../helpers';
import ExternalLink from '../ui/external-link';
import dependencies from './dependenciesLicenses.json';
import { repository } from '../../../package.json';
const AboutKresus = () => {
return (
......@@ -37,7 +38,7 @@ const AboutKresus = () => {
</ExternalLink>
</li>
<li>
<ExternalLink href="https://framagit.org/bnjbvr/kresus">
<ExternalLink href={repository.url}>
<span className="fa fa-code" />
<span className="label">{$t('client.about.sources')}</span>
</ExternalLink>
......@@ -61,9 +62,18 @@ const About = () => {
if (dependency.website) {
maybeDepLink = <ExternalLink href={dependency.website}>{dep}</ExternalLink>;
}
let maybeAuthor = null;
if (dependency.author) {
maybeAuthor = (
<span>
{$t('client.about.by')} {dependency.author}
</span>
);
}
thanksItems.push(
<li key={dep}>
{maybeDepLink} ({$t('client.about.license', { license: dependency.license })})
{maybeDepLink} {maybeAuthor} (
{$t('client.about.license', { license: dependency.license })})
</li>
);
}
......
This diff is collapsed.
......@@ -3,9 +3,9 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { actions } from '../../store';
import { get, actions } from '../../store';
import { round2 } from '../../helpers';
import { round2, translate as $t } from '../../helpers';
import AmountInput from '../ui/amount-input';
......@@ -14,7 +14,7 @@ function computeAmountRatio(amount, threshold) {
return 0;
}
let ratio = 100 * amount / threshold;
let ratio = (100 * amount) / threshold;
if (ratio < 0) {
ratio -= 100;
......@@ -23,7 +23,7 @@ function computeAmountRatio(amount, threshold) {
return round2(ratio);
}
export function getBars(threshold, amount, warningThresholdInPct) {
function getBars(threshold, amount, warningThresholdInPct) {
const amountPct = computeAmountRatio(amount, threshold);
let bars = new Map();
if (threshold === 0) {
......@@ -43,7 +43,7 @@ export function getBars(threshold, amount, warningThresholdInPct) {
}
bars.set('successRange', {
classes: `progress-bar-${state}`,
classes: `stacked-progress-part-${state}`,
width: amountPct !== 0 ? Math.min(100, Math.abs(amountPct)) : 100
});
} else {
......@@ -76,7 +76,7 @@ export function getBars(threshold, amount, warningThresholdInPct) {
// From 0 to warningThresholdInPct
if (successRange > 0) {
bars.set('successRange', {
classes: 'progress-bar-success',
classes: 'stacked-progress-part-success',
width: successRange
});
}
......@@ -85,7 +85,7 @@ export function getBars(threshold, amount, warningThresholdInPct) {
if (warningRange > 0) {
let progressive = dangerRange ? 'progressive' : '';
bars.set('warningRange', {
classes: `progress-bar-warning ${progressive}`.trim(),
classes: `stacked-progress-part-warning ${progressive}`.trim(),
width: warningRange
});
}
......@@ -93,7 +93,7 @@ export function getBars(threshold, amount, warningThresholdInPct) {
// From 100 to amount in percent
if (dangerRange > 0) {
bars.set('dangerRange', {
classes: 'progress-bar-danger',
classes: 'stacked-progress-part-danger',
width: dangerRange
});
}
......@@ -102,29 +102,34 @@ export function getBars(threshold, amount, warningThresholdInPct) {
return bars;
}
export const testing = {
getBars
};
class BudgetListItem extends React.Component {
handleChange = threshold => {
if (this.props.cat.threshold === threshold || Number.isNaN(threshold)) {
if (this.props.budget.threshold === threshold || Number.isNaN(threshold)) {
return;
}
let category = {
title: this.props.cat.title,
color: this.props.cat.color,
let budget = {
categoryId: this.props.budget.categoryId,
year: this.props.budget.year,
month: this.props.budget.month,
threshold
};
this.props.updateCategory(this.props.cat, category);
this.props.updateBudget(this.props.budget, budget);
};
handleViewOperations = () => {
this.props.showOperations(this.props.cat.id);
this.props.showOperations(this.props.category.id);
this.props.showSearchDetails();
};
render() {
let { cat: category, amount } = this.props;
let threshold = category.threshold;
let { category, amount, budget } = this.props;
let threshold = budget.threshold;
const amountPct = computeAmountRatio(amount, threshold);
let amountText = amount;
......@@ -142,7 +147,7 @@ class BudgetListItem extends React.Component {
remainingText = `${remainingToSpendPct.toFixed(2)}%`;
} else {
thresholdText = <span className="hidden-lg">{`/${threshold}`}</span>;
thresholdText = <span className="threshold">{`/${threshold}`}</span>;
remainingText = round2(amount - threshold);
}
......@@ -156,14 +161,14 @@ class BudgetListItem extends React.Component {
<div
key={key}
role="progressbar"
className={`progress-bar ${values.classes}`}
className={`${values.classes}`}
style={{ width: `${values.width}%` }}
/>
);
}
return (
<tr key={category.id}>
<tr>
<td className="category-name">
<span className="color-block-small" style={{ backgroundColor: category.color }}>
&nbsp;
......@@ -171,9 +176,9 @@ class BudgetListItem extends React.Component {
{category.title}
</td>
<td className="category-amount">
<div className="progress budget">