Compare commits

..

1 Commits

Author SHA1 Message Date
Julien Fontanet
ab4cd5b261 v0.1.1 2018-02-06 12:01:16 +01:00
852 changed files with 11843 additions and 109962 deletions

View File

@@ -1,27 +1,15 @@
module.exports = {
extends: ['standard', 'standard-jsx'],
globals: {
__DEV__: true,
$Dict: true,
$Diff: true,
$Exact: true,
$Keys: true,
$PropertyType: true,
$Shape: true,
},
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
legacyDecorators: true,
},
},
rules: {
'extends': [
'standard',
],
'parser': 'babel-eslint',
'rules': {
'comma-dangle': ['error', 'always-multiline'],
indent: 'off',
'no-var': 'error',
'node/no-extraneous-import': 'error',
'node/no-extraneous-require': 'error',
'node/no-missing-import': 'error',
'node/no-missing-require': 'error',
'prefer-const': 'error',
'react/jsx-indent': 'off',
},
}

View File

@@ -8,8 +8,6 @@
[lints]
[options]
esproposal.decorators=ignore
esproposal.optional_chaining=enable
include_warnings=true
module.use_strict=true

11
.gitignore vendored
View File

@@ -1,4 +1,3 @@
/_book/
/coverage/
/node_modules/
/lerna-debug.log
@@ -9,21 +8,11 @@
/packages/*/dist/
/packages/*/node_modules/
/packages/vhd-cli/src/commands/index.js
/packages/xen-api/examples/node_modules/
/packages/xen-api/plot.dat
/packages/xo-server/.xo-server.*
/packages/xo-server/src/api/index.js
/packages/xo-server/src/xapi/mixins/index.js
/packages/xo-server/src/xo-mixins/index.js
/packages/xo-server-auth-ldap/ldap.cache.conf
/packages/xo-web/src/common/intl/locales/index.js
/packages/xo-web/src/common/themes/index.js
npm-debug.log
npm-debug.log.*
pnpm-debug.log

View File

@@ -1,5 +0,0 @@
module.exports = {
semi: false,
singleQuote: true,
trailingComma: 'es5',
}

View File

@@ -1,18 +1,12 @@
language: node_js
node_js:
#- stable # disable for now due to an issue of indirect dep upath with Node 9
- stable
- 8
- 6
# Use containers.
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
sudo: false
addons:
apt:
packages:
- qemu-utils
- blktap-utils
- vmdk-stream-converter
before_install:
- curl -o- -L https://yarnpkg.com/install.sh | bash
@@ -20,7 +14,3 @@ before_install:
cache:
yarn: true
script:
- yarn run test
- yarn run test-integration

View File

@@ -1,67 +0,0 @@
'use strict'
const PLUGINS_RE = /^(?:@babel\/|babel-)plugin-.+$/
const PRESETS_RE = /^@babel\/preset-.+$/
const NODE_ENV = process.env.NODE_ENV || 'development'
const __PROD__ = NODE_ENV === 'production'
const __TEST__ = NODE_ENV === 'test'
const configs = {
'@babel/plugin-proposal-decorators': {
legacy: true,
},
'@babel/plugin-proposal-pipeline-operator': {
proposal: 'minimal',
},
'@babel/preset-env' (pkg) {
return {
debug: !__TEST__,
// disabled until https://github.com/babel/babel/issues/8323 is resolved
// loose: true,
shippedProposals: true,
targets: (() => {
let node = (pkg.engines || {}).node
if (node !== undefined) {
const trimChars = '^=>~'
while (trimChars.includes(node[0])) {
node = node.slice(1)
}
}
return { browsers: pkg.browserslist, node }
})(),
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
}
},
}
const getConfig = (key, ...args) => {
const config = configs[key]
return config === undefined
? {}
: typeof config === 'function'
? config(...args)
: config
}
module.exports = function (pkg, plugins, presets) {
plugins === undefined && (plugins = {})
presets === undefined && (presets = {})
Object.keys(pkg.devDependencies || {}).forEach(name => {
if (!(name in presets) && PLUGINS_RE.test(name)) {
plugins[name] = getConfig(name, pkg)
} else if (!(name in presets) && PRESETS_RE.test(name)) {
presets[name] = getConfig(name, pkg)
}
})
return {
comments: !__PROD__,
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
plugins: Object.keys(plugins).map(plugin => [plugin, plugins[plugin]]),
presets: Object.keys(presets).map(preset => [preset, presets[preset]]),
}
}

View File

@@ -1,11 +0,0 @@
{
"private": true,
"name": "@xen-orchestra/babel-config",
"version": "0.0.0",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/babel-config",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
}
}

View File

@@ -1,114 +0,0 @@
#!/usr/bin/env node
const defer = require('golike-defer').default
const { NULL_REF, Xapi } = require('xen-api')
const pkg = require('./package.json')
Xapi.prototype.getVmDisks = async function (vm) {
const disks = { __proto__: null }
await Promise.all([
...vm.VBDs.map(async vbdRef => {
const vbd = await this.getRecord('VBD', vbdRef)
let vdiRef
if (vbd.type === 'Disk' && (vdiRef = vbd.VDI) !== NULL_REF) {
disks[vbd.userdevice] = await this.getRecord('VDI', vdiRef)
}
}),
])
return disks
}
defer(async function main ($defer, args) {
if (args.length === 0 || args.includes('-h') || args.includes('--help')) {
const cliName = Object.keys(pkg.bin)[0]
return console.error(
'%s',
`
Usage: ${cliName} <source XAPI URL> <source snapshot UUID> <target XAPI URL> <target VM UUID> <backup job id> <backup schedule id>
${cliName} v${pkg.version}
`
)
}
const [
srcXapiUrl,
srcSnapshotUuid,
tgtXapiUrl,
tgtVmUuid,
jobId,
scheduleId,
] = args
const srcXapi = new Xapi({
allowUnauthorized: true,
url: srcXapiUrl,
watchEvents: false,
})
await srcXapi.connect()
defer.call(srcXapi, 'disconnect')
const tgtXapi = new Xapi({
allowUnauthorized: true,
url: tgtXapiUrl,
watchEvents: false,
})
await tgtXapi.connect()
defer.call(tgtXapi, 'disconnect')
const [srcSnapshot, tgtVm] = await Promise.all([
srcXapi.getRecordByUuid('VM', srcSnapshotUuid),
tgtXapi.getRecordByUuid('VM', tgtVmUuid),
])
const srcVm = await srcXapi.getRecord('VM', srcSnapshot.snapshot_of)
const metadata = {
'xo:backup:datetime': srcSnapshot.snapshot_time,
'xo:backup:job': jobId,
'xo:backup:schedule': scheduleId,
'xo:backup:vm': srcVm.uuid,
}
const [srcDisks, tgtDisks] = await Promise.all([
srcXapi.getVmDisks(srcSnapshot),
tgtXapi.getVmDisks(tgtVm),
])
const userDevices = Object.keys(tgtDisks)
const tgtSr = await tgtXapi.getRecord(
'SR',
tgtDisks[Object.keys(tgtDisks)[0]].SR
)
await Promise.all([
srcXapi.setFieldEntries(srcSnapshot, 'other_config', metadata),
tgtXapi.setField(
tgtVm,
'name_label',
`${srcVm.name_label} (${srcSnapshot.snapshot_time})`
),
tgtXapi.setFieldEntries(tgtVm, 'other_config', metadata),
tgtXapi.setFieldEntries(tgtVm, 'other_config', {
'xo:backup:sr': tgtSr.uuid,
'xo:copy_of': srcSnapshotUuid,
}),
tgtXapi.setFieldEntries(tgtVm, 'blocked_operations', {
start:
'Start operation for this vm is blocked, clone it if you want to use it.',
}),
Promise.all(
userDevices.map(userDevice => {
const srcDisk = srcDisks[userDevice]
const tgtDisk = tgtDisks[userDevice]
return tgtXapi.setFieldEntry(
tgtDisk,
'other_config',
'xo:copy_of',
srcDisk.uuid
)
})
),
])
})(process.argv.slice(2)).catch(console.error.bind(console, 'Fatal error:'))

View File

@@ -1,20 +0,0 @@
{
"name": "@xen-orchestra/cr-seed-cli",
"version": "0.1.0",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/cr-seed-cli",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"engines": {
"node": ">=8"
},
"bin": {
"xo-cr-seed": "./index.js"
},
"dependencies": {
"golike-defer": "^0.4.1",
"xen-api": "^0.18.0"
}
}

View File

@@ -1,3 +1,41 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)
'use strict'
const NODE_ENV = process.env.NODE_ENV || 'development'
const __PROD__ = NODE_ENV === 'production'
const __TEST__ = NODE_ENV === 'test'
const pkg = require('./package')
let nodeCompat = (pkg.engines || {}).node
if (nodeCompat === undefined) {
nodeCompat = '6'
} else {
const trimChars = '^=>~'
while (trimChars.includes(nodeCompat[0])) {
nodeCompat = nodeCompat.slice(1)
}
}
module.exports = {
comments: !__PROD__,
ignore: __TEST__ ? undefined : [/\.spec\.js$/],
plugins: ['lodash'],
presets: [
[
'@babel/env',
{
debug: !__TEST__,
loose: true,
shippedProposals: true,
targets: __PROD__
? {
browsers: '>2%',
node: nodeCompat,
}
: { node: 'current' },
useBuiltIns: '@babel/polyfill' in (pkg.dependencies || {}) && 'usage',
},
],
'@babel/flow',
],
}

View File

@@ -10,6 +10,25 @@ Installation of the [npm package](https://npmjs.org/package/@xen-orchestra/cron)
> npm install --save @xen-orchestra/cron
```
## Usage
```js
import { createSchedule } from '@xen-orchestra/cron'
const schedule = createSchedule('0 0 * * sun', 'America/New_York')
schedule.next(2)
// [ 2018-02-11T05:00:00.000Z, 2018-02-18T05:00:00.000Z ]
const job = schedule.createJob(() => {
console.log(new Date())
})
job.start()
job.stop()
```
### Pattern syntax
```
@@ -41,75 +60,6 @@ Step values can be used in conjunctions with ranges. For instance,
> [cron](https://github.com/kelektiv/node-cron), it does not appear to
> be very standard though.
### API
`createSchedule(pattern: string, zone: string = 'utc'): Schedule`
> Create a new schedule.
- `pattern`: the pattern to use, see [the syntax](#pattern-syntax)
- `zone`: the timezone to use, use `'local'` for the local timezone
```js
import { createSchedule } from '@xen-orchestra/cron'
const schedule = createSchedule('0 0 * * sun', 'America/New_York')
```
`Schedule#createJob(fn: Function): Job`
> Create a new job from this schedule.
- `fn`: function to execute, if it returns a promise, it will be
awaited before scheduling the next run.
```js
const job = schedule.createJob(() => {
console.log(new Date())
})
```
`Schedule#next(n: number): Array<Date>`
> Returns the next dates matching this schedule.
- `n`: number of dates to return
```js
schedule.next(2)
// [ 2018-02-11T05:00:00.000Z, 2018-02-18T05:00:00.000Z ]
```
`Schedule#startJob(fn: Function): () => void`
> Start a new job from this schedule and return a function to stop it.
- `fn`: function to execute, if it returns a promise, it will be
awaited before scheduling the next run.
```js
const stopJob = schedule.startJob(() => {
console.log(new Date())
})
stopJob()
```
`Job#start(): void`
> Start this job.
```js
job.start()
```
`Job#stop(): void`
> Stop this job.
```js
job.stop()
```
## Development
```
@@ -136,7 +86,7 @@ the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
- report any [issue](https://github.com/vatesfr/xo-web/issues)
you've encountered;
- fork and create a pull request.

View File

@@ -1,6 +1,6 @@
{
"name": "@xen-orchestra/cron",
"version": "1.0.3",
"version": "0.1.1",
"license": "ISC",
"description": "Focused, well maintained, cron parser/scheduler",
"keywords": [
@@ -14,8 +14,8 @@
"scheduling",
"task"
],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/cron",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/packages/@xen-orchestra/cron",
"bugs": "https://github.com/vatesfr/xo-web/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
@@ -30,21 +30,18 @@
"files": [
"dist/"
],
"browserslist": [
">2%"
],
"engines": {
"node": ">=6"
},
"dependencies": {
"lodash": "^4.17.4",
"moment-timezone": "^0.5.14"
"luxon": "^0.4.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"@babel/cli": "7.0.0-beta.39",
"@babel/core": "7.0.0-beta.39",
"@babel/preset-env": "7.0.0-beta.39",
"@babel/preset-flow": "7.0.0-beta.39",
"cross-env": "^5.1.3",
"rimraf": "^2.6.2"
},

View File

@@ -1,36 +1,23 @@
import moment from 'moment-timezone'
import { DateTime } from 'luxon'
import next from './next'
import parse from './parse'
const MAX_DELAY = 2 ** 31 - 1
class Job {
constructor (schedule, fn) {
const wrapper = () => {
const result = fn()
let then
if (result != null && typeof (then = result.then) === 'function') {
then.call(result, scheduleNext, scheduleNext)
} else {
scheduleNext()
const wrapper = scheduledRun => {
if (scheduledRun) {
fn()
}
this._timeout = setTimeout(wrapper, schedule.next() - Date.now(), true)
}
const scheduleNext = () => {
const delay = schedule._nextDelay()
this._timeout =
delay < MAX_DELAY
? setTimeout(wrapper, delay)
: setTimeout(scheduleNext, MAX_DELAY)
}
this._scheduleNext = scheduleNext
this._fn = wrapper
this._timeout = undefined
}
start () {
this.stop()
this._scheduleNext()
this._fn()
}
stop () {
@@ -41,12 +28,7 @@ class Job {
class Schedule {
constructor (pattern, zone = 'utc') {
this._schedule = parse(pattern)
this._createDate =
zone.toLowerCase() === 'utc'
? moment.utc
: zone === 'local'
? moment
: () => moment.tz(zone)
this._dateTimeOpts = { zone }
}
createJob (fn) {
@@ -56,23 +38,12 @@ class Schedule {
next (n) {
const dates = new Array(n)
const schedule = this._schedule
let date = this._createDate()
let date = DateTime.fromObject(this._dateTimeOpts)
for (let i = 0; i < n; ++i) {
dates[i] = (date = next(schedule, date)).toDate()
dates[i] = (date = next(schedule, date)).toJSDate()
}
return dates
}
_nextDelay () {
const now = this._createDate()
return next(this._schedule, now) - now
}
startJob (fn) {
const job = this.createJob(fn)
job.start()
return job.stop.bind(job)
}
}
export const createSchedule = (...args) => new Schedule(...args)

View File

@@ -1,49 +1,50 @@
import moment from 'moment-timezone'
import sortedIndex from 'lodash/sortedIndex'
import { DateTime } from 'luxon'
const NEXT_MAPPING = {
month: { year: 1 },
date: { month: 1 },
day: { week: 1 },
day: { month: 1 },
weekday: { week: 1 },
hour: { day: 1 },
minute: { hour: 1 },
}
const getFirst = values => (values !== undefined ? values[0] : 0)
const getFirst = values => values !== undefined ? values[0] : 0
const setFirstAvailable = (date, unit, values) => {
if (values === undefined) {
return
return date
}
const curr = date.get(unit)
const next = values[sortedIndex(values, curr) % values.length]
if (curr === next) {
return
return date
}
const timestamp = +date
date.set(unit, next)
if (timestamp > +date) {
date.add(NEXT_MAPPING[unit])
}
return true
const newDate = date.set({ [unit]: next })
return newDate > date ? newDate : newDate.plus(NEXT_MAPPING[unit])
}
// returns the next run, after the passed date
export default (schedule, fromDate) => {
let date = moment(fromDate)
let date = fromDate
.set({
second: 0,
millisecond: 0,
})
.add({ minute: 1 })
.plus({ minute: 1 })
const { minute, hour, dayOfMonth, month, dayOfWeek } = schedule
setFirstAvailable(date, 'minute', minute)
date = setFirstAvailable(date, 'minute', minute)
if (setFirstAvailable(date, 'hour', hour)) {
date.set('minute', getFirst(minute))
let tmp
tmp = setFirstAvailable(date, 'hour', hour)
if (tmp !== date) {
date = tmp.set({
minute: getFirst(minute),
})
}
let loop
@@ -51,30 +52,30 @@ export default (schedule, fromDate) => {
do {
loop = false
if (setFirstAvailable(date, 'month', month)) {
date.set({
date: 1,
tmp = setFirstAvailable(date, 'month', month)
if (tmp !== date) {
date = tmp.set({
day: 1,
hour: getFirst(hour),
minute: getFirst(minute),
})
}
let newDate = date.clone()
if (dayOfMonth === undefined) {
if (dayOfWeek !== undefined) {
setFirstAvailable(newDate, 'day', dayOfWeek)
tmp = setFirstAvailable(date, 'weekday', dayOfWeek)
}
} else if (dayOfWeek === undefined) {
setFirstAvailable(newDate, 'date', dayOfMonth)
tmp = setFirstAvailable(date, 'day', dayOfMonth)
} else {
const dateDay = newDate.clone()
setFirstAvailable(dateDay, 'date', dayOfMonth)
setFirstAvailable(newDate, 'day', dayOfWeek)
newDate = moment.min(dateDay, newDate)
tmp = DateTime.min(
setFirstAvailable(date, 'day', dayOfMonth),
setFirstAvailable(date, 'weekday', dayOfWeek)
)
}
if (+date !== +newDate) {
loop = date.month() !== newDate.month()
date = newDate.set({
if (tmp !== date) {
loop = tmp.month !== date.month
date = tmp.set({
hour: getFirst(hour),
minute: getFirst(minute),
})

View File

@@ -1,15 +1,17 @@
/* eslint-env jest */
import mapValues from 'lodash/mapValues'
import moment from 'moment-timezone'
import { DateTime } from 'luxon'
import next from './next'
import parse from './parse'
const N = (pattern, fromDate = '2018-04-09T06:25') => {
const iso = next(parse(pattern), moment.utc(fromDate)).toISOString()
return iso.slice(0, iso.lastIndexOf(':'))
}
const N = (pattern, fromDate = '2018-04-09T06:25') =>
next(parse(pattern), DateTime.fromISO(fromDate, { zone: 'utc' })).toISO({
includeOffset: false,
suppressMilliseconds: true,
suppressSeconds: true,
})
describe('next()', () => {
mapValues(
@@ -41,8 +43,4 @@ describe('next()', () => {
'no solutions found for this schedule'
)
})
it('select the first sunday of the month', () => {
expect(N('* * * * 0', '2018-03-31T00:00')).toBe('2018-04-01T00:00')
})
})

View File

@@ -90,7 +90,7 @@ const createParser = ({ fields: [...fields], presets: { ...presets } }) => {
if (!match('/')) {
return
}
;[start, end] = field.range
[start, end] = field.range
step = parseInteger()
} else {
start = parseValue()
@@ -173,13 +173,18 @@ export default createParser({
{
aliases: 'jan feb mar apr may jun jul aug sep oct nov dec'.split(' '),
name: 'month',
range: [0, 11],
range: [1, 12],
// this function is applied to numeric entries (not steps)
//
// currently parse month 0-11
post: value => value + 1,
},
{
aliases: 'sun mon tue wen thu fri sat'.split(' '),
aliases: 'mon tue wen thu fri sat sun'.split(' '),
name: 'dayOfWeek',
post: value => (value === 7 ? 0 : value),
range: [0, 6],
post: value => (value === 0 ? 7 : value),
range: [1, 7],
},
],
presets: {

View File

@@ -8,27 +8,29 @@ describe('parse()', () => {
minute: [0],
hour: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
dayOfMonth: [1, 11, 21, 31],
month: [0, 2, 4, 7, 10],
month: [1, 3, 5, 8, 11],
})
})
it('correctly parse months', () => {
expect(parse('* * * 0,11 *')).toEqual({
month: [0, 11],
month: [1, 12],
})
expect(parse('* * * jan,dec *')).toEqual({
month: [0, 11],
month: [1, 12],
})
})
it('correctly parse days', () => {
expect(parse('* * * * mon,sun')).toEqual({
dayOfWeek: [0, 1],
dayOfWeek: [1, 7],
})
})
it('reports missing integer', () => {
expect(() => parse('*/a')).toThrow('minute: missing integer at character 2')
expect(() => parse('*/a')).toThrow(
'minute: missing integer at character 2'
)
expect(() => parse('*')).toThrow('hour: missing integer at character 1')
})
@@ -40,10 +42,10 @@ describe('parse()', () => {
it('dayOfWeek: 0 and 7 bind to sunday', () => {
expect(parse('* * * * 0')).toEqual({
dayOfWeek: [0],
dayOfWeek: [7],
})
expect(parse('* * * * 7')).toEqual({
dayOfWeek: [0],
dayOfWeek: [7],
})
})
})

View File

@@ -1,3 +0,0 @@
module.exports = require('../../@xen-orchestra/babel-config')(
require('./package.json')
)

View File

@@ -1,52 +0,0 @@
{
"name": "@xen-orchestra/fs",
"version": "0.3.0",
"license": "AGPL-3.0",
"description": "The File System for Xen Orchestra backups.",
"keywords": [],
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/fs",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"preferGlobal": true,
"main": "dist/",
"bin": {},
"files": [
"dist/"
],
"engines": {
"node": ">=6"
},
"dependencies": {
"@marsaud/smb2": "^0.9.0",
"execa": "^1.0.0",
"fs-extra": "^7.0.0",
"get-stream": "^4.0.0",
"lodash": "^4.17.4",
"promise-toolbox": "^0.10.1",
"through2": "^2.0.3",
"tmp": "^0.0.33",
"xo-remote-parser": "^0.5.0"
},
"devDependencies": {
"@babel/cli": "7.0.0",
"@babel/core": "7.0.0",
"@babel/plugin-proposal-function-bind": "7.0.0",
"@babel/preset-env": "7.0.0",
"@babel/preset-flow": "7.0.0",
"babel-plugin-lodash": "^3.3.2",
"cross-env": "^5.1.3",
"index-modules": "^0.3.0",
"rimraf": "^2.6.2"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel --source-maps --out-dir=dist/ src/",
"clean": "rimraf dist/",
"dev": "cross-env NODE_ENV=development babel --watch --source-maps --out-dir=dist/ src/",
"prebuild": "yarn run clean",
"predev": "yarn run clean",
"prepare": "yarn run build"
}
}

View File

@@ -1,307 +0,0 @@
// @flow
import getStream from 'get-stream'
import { randomBytes } from 'crypto'
import { fromCallback, fromEvent, ignoreErrors } from 'promise-toolbox'
import { type Readable, type Writable } from 'stream'
import { parse } from 'xo-remote-parser'
import { createChecksumStream, validChecksumOfReadStream } from './checksum'
type Data = Buffer | Readable | string
type FileDescriptor = {| fd: mixed, path: string |}
type LaxReadable = Readable & Object
type LaxWritable = Writable & Object
type File = FileDescriptor | string
const checksumFile = file => file + '.checksum'
export default class RemoteHandlerAbstract {
_remote: Object
constructor (remote: any) {
this._remote = { ...remote, ...parse(remote.url) }
if (this._remote.type !== this.type) {
throw new Error('Incorrect remote type')
}
}
get type (): string {
throw new Error('Not implemented')
}
/**
* Asks the handler to sync the state of the effective remote with its' metadata
*/
async sync (): Promise<mixed> {
return this._sync()
}
async _sync (): Promise<mixed> {
throw new Error('Not implemented')
}
/**
* Free the resources possibly dedicated to put the remote at work, when it is no more needed
*/
async forget (): Promise<void> {
await this._forget()
}
async _forget (): Promise<void> {
throw new Error('Not implemented')
}
async test (): Promise<Object> {
const testFileName = `${Date.now()}.test`
const data = await fromCallback(cb => randomBytes(1024 * 1024, cb))
let step = 'write'
try {
await this.outputFile(testFileName, data)
step = 'read'
const read = await this.readFile(testFileName)
if (data.compare(read) !== 0) {
throw new Error('output and input did not match')
}
return {
success: true,
}
} catch (error) {
return {
success: false,
step,
file: testFileName,
error: error.message || String(error),
}
} finally {
ignoreErrors.call(this.unlink(testFileName))
}
}
async outputFile (file: string, data: Data, options?: Object): Promise<void> {
return this._outputFile(file, data, {
flags: 'wx',
...options,
})
}
async _outputFile (file: string, data: Data, options?: Object): Promise<void> {
const stream = await this.createOutputStream(file, options)
const promise = fromEvent(stream, 'finish')
stream.end(data)
await promise
}
async read (
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
return this._read(file, buffer, position)
}
_read (
file: File,
buffer: Buffer,
position?: number
): Promise<{| bytesRead: number, buffer: Buffer |}> {
throw new Error('Not implemented')
}
async readFile (file: string, options?: Object): Promise<Buffer> {
return this._readFile(file, options)
}
_readFile (file: string, options?: Object): Promise<Buffer> {
return this.createReadStream(file, options).then(getStream.buffer)
}
async rename (
oldPath: string,
newPath: string,
{ checksum = false }: Object = {}
) {
let p = this._rename(oldPath, newPath)
if (checksum) {
p = Promise.all([
p,
this._rename(checksumFile(oldPath), checksumFile(newPath)),
])
}
return p
}
async _rename (oldPath: string, newPath: string) {
throw new Error('Not implemented')
}
async list (
dir: string = '.',
{
filter,
prependDir = false,
}: { filter?: (name: string) => boolean, prependDir?: boolean } = {}
): Promise<string[]> {
let entries = await this._list(dir)
if (filter !== undefined) {
entries = entries.filter(filter)
}
if (prependDir) {
entries.forEach((entry, i) => {
entries[i] = dir + '/' + entry
})
}
return entries
}
async _list (dir: string): Promise<string[]> {
throw new Error('Not implemented')
}
createReadStream (
file: string,
{ checksum = false, ignoreMissingChecksum = false, ...options }: Object = {}
): Promise<LaxReadable> {
const path = typeof file === 'string' ? file : file.path
const streamP = this._createReadStream(file, options).then(stream => {
// detect early errors
let promise = fromEvent(stream, 'readable')
// try to add the length prop if missing and not a range stream
if (
stream.length === undefined &&
options.end === undefined &&
options.start === undefined
) {
promise = Promise.all([
promise,
ignoreErrors.call(
this.getSize(file).then(size => {
stream.length = size
})
),
])
}
return promise.then(() => stream)
})
if (!checksum) {
return streamP
}
// avoid a unhandled rejection warning
ignoreErrors.call(streamP)
return this.readFile(checksumFile(path)).then(
checksum =>
streamP.then(stream => {
const { length } = stream
stream = (validChecksumOfReadStream(
stream,
String(checksum).trim()
): LaxReadable)
stream.length = length
return stream
}),
error => {
if (ignoreMissingChecksum && error && error.code === 'ENOENT') {
return streamP
}
throw error
}
)
}
async _createReadStream (
file: string,
options?: Object
): Promise<LaxReadable> {
throw new Error('Not implemented')
}
async openFile (path: string, flags?: string): Promise<FileDescriptor> {
return { fd: await this._openFile(path, flags), path }
}
async _openFile (path: string, flags?: string): Promise<mixed> {
throw new Error('Not implemented')
}
async closeFile (fd: FileDescriptor): Promise<void> {
await this._closeFile(fd.fd)
}
async _closeFile (fd: mixed): Promise<void> {
throw new Error('Not implemented')
}
async refreshChecksum (path: string): Promise<void> {
const stream = (await this.createReadStream(path)).pipe(
createChecksumStream()
)
stream.resume() // start reading the whole file
await this.outputFile(checksumFile(path), await stream.checksum)
}
async createOutputStream (
file: File,
{ checksum = false, ...options }: Object = {}
): Promise<LaxWritable> {
const path = typeof file === 'string' ? file : file.path
const streamP = this._createOutputStream(file, {
flags: 'wx',
...options,
})
if (!checksum) {
return streamP
}
const checksumStream = createChecksumStream()
const forwardError = error => {
checksumStream.emit('error', error)
}
const stream = await streamP
stream.on('error', forwardError)
checksumStream.pipe(stream)
// $FlowFixMe
checksumStream.checksumWritten = checksumStream.checksum
.then(value => this.outputFile(checksumFile(path), value))
.catch(forwardError)
return checksumStream
}
async _createOutputStream (
file: mixed,
options?: Object
): Promise<LaxWritable> {
throw new Error('Not implemented')
}
async unlink (file: string, { checksum = true }: Object = {}): Promise<void> {
if (checksum) {
ignoreErrors.call(this._unlink(checksumFile(file)))
}
await this._unlink(file)
}
async _unlink (file: mixed): Promise<void> {
throw new Error('Not implemented')
}
async getSize (file: mixed): Promise<number> {
return this._getSize(file)
}
async _getSize (file: mixed): Promise<number> {
throw new Error('Not implemented')
}
}

View File

@@ -1,100 +0,0 @@
// @flow
// $FlowFixMe
import through2 from 'through2'
import { createHash } from 'crypto'
import { defer, fromEvent } from 'promise-toolbox'
import { invert } from 'lodash'
import { type Readable, type Transform } from 'stream'
// Format: $<algorithm>$<salt>$<encrypted>
//
// http://man7.org/linux/man-pages/man3/crypt.3.html#NOTES
const ALGORITHM_TO_ID = {
md5: '1',
sha256: '5',
sha512: '6',
}
const ID_TO_ALGORITHM = invert(ALGORITHM_TO_ID)
// Create a through stream which computes the checksum of all data going
// through.
//
// The `checksum` attribute is a promise which resolves at the end of the stream
// with a string representation of the checksum.
//
// const source = ...
// const checksumStream = source.pipe(createChecksumStream())
// checksumStream.resume() // make the data flow without an output
// console.log(await checksumStream.checksum)
export const createChecksumStream = (
algorithm: string = 'md5'
): Transform & { checksum: Promise<string> } => {
const algorithmId = ALGORITHM_TO_ID[algorithm]
if (!algorithmId) {
throw new Error(`unknown algorithm: ${algorithm}`)
}
const hash = createHash(algorithm)
const { promise, resolve, reject } = defer()
const stream = through2(
(chunk, enc, callback) => {
hash.update(chunk)
callback(null, chunk)
},
callback => {
resolve(`$${algorithmId}$$${hash.digest('hex')}`)
callback()
}
).once('error', reject)
stream.checksum = promise
return stream
}
// Check if the checksum of a readable stream is equals to an expected checksum.
// The given stream is wrapped in a stream which emits an error event
// if the computed checksum is not equals to the expected checksum.
export const validChecksumOfReadStream = (
stream: Readable,
expectedChecksum: string
): Readable & { checksumVerified: Promise<void> } => {
const algorithmId = expectedChecksum.slice(
1,
expectedChecksum.indexOf('$', 1)
)
if (!algorithmId) {
throw new Error(`unknown algorithm: ${algorithmId}`)
}
const hash = createHash(ID_TO_ALGORITHM[algorithmId])
const wrapper: any = stream.pipe(
through2(
{ highWaterMark: 0 },
(chunk, enc, callback) => {
hash.update(chunk)
callback(null, chunk)
},
callback => {
const checksum = `$${algorithmId}$$${hash.digest('hex')}`
callback(
checksum !== expectedChecksum
? new Error(
`Bad checksum (${checksum}), expected: ${expectedChecksum}`
)
: null
)
}
)
)
stream.on('error', error => wrapper.emit('error', error))
wrapper.checksumVerified = fromEvent(wrapper, 'end')
return wrapper
}

View File

@@ -1,26 +0,0 @@
/* eslint-env jest */
import rimraf from 'rimraf'
import tmp from 'tmp'
import { pFromCallback } from 'promise-toolbox'
import { getHandler } from '.'
const initialDir = process.cwd()
beforeEach(async () => {
const dir = await pFromCallback(cb => tmp.dir(cb))
process.chdir(dir)
})
afterEach(async () => {
const tmpDir = process.cwd()
process.chdir(initialDir)
await pFromCallback(cb => rimraf(tmpDir, cb))
})
test("fs test doesn't crash", async () => {
const handler = getHandler({ url: 'file://' + process.cwd() })
const result = await handler.test()
expect(result.success).toBeTruthy()
})

View File

@@ -1,26 +0,0 @@
// @flow
import type RemoteHandler from './abstract'
import RemoteHandlerLocal from './local'
import RemoteHandlerNfs from './nfs'
import RemoteHandlerSmb from './smb'
export type { default as RemoteHandler } from './abstract'
export type Remote = { url: string }
const HANDLERS = {
file: RemoteHandlerLocal,
smb: RemoteHandlerSmb,
nfs: RemoteHandlerNfs,
}
export const getHandler = (remote: Remote): RemoteHandler => {
// FIXME: should be done in xo-remote-parser.
const type = remote.url.split('://')[0]
const Handler = HANDLERS[type]
if (!Handler) {
throw new Error('Unhandled remote type')
}
return new Handler(remote)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,46 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at julien.fontanet@vates.fr. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

View File

@@ -1,27 +0,0 @@
<!--
Welcome to the issue section of Xen Orchestra!
Here you can:
- report an issue
- propose an enhancement
- ask a question
Please, respect this template as much as possible, it helps us sort
the issues :)
-->
### Context
- **XO origin**: the sources / XO Appliance
- **Versions**:
- Node: **FILL HERE**
- xo-web: **FILL HERE**
- xo-server: **FILL HERE**
### Expected behavior
<!-- What you expect to happen -->
### Current behavior
<!-- What is actually happening -->

View File

@@ -1,17 +0,0 @@
### Check list
> Check items when done or if not relevant
- [ ] PR reference the relevant issue (e.g. `Fixes #007`)
- [ ] if UI changes, a screenshot has been added to the PR
- [ ] CHANGELOG:
- enhancement/bug fix entry added
- list of packages to release updated (`${name} v${new version}`)
- [ ] documentation updated
### Process
1. create a PR as soon as possible
1. mark it as `WiP:` (Work in Progress) if not ready to be merged
1. when you want a review, add a reviewer
1. if necessary, update your PR, and re- add a reviewer

View File

@@ -1,11 +1,7 @@
# Xen Orchestra [![Chat with us](https://storage.crisp.im/plugins/images/936925df-f37b-4ba8-bab0-70cd2edcb0be/badge.svg)](https://go.crisp.im/chat/embed/?website_id=-JzqzzwddSV7bKGtEyAQ) [![Build Status](https://travis-ci.org/vatesfr/xen-orchestra.png?branch=master)](https://travis-ci.org/vatesfr/xen-orchestra)
Almost all dev for Xen Orchestra is happening in this repository.
![](http://i.imgur.com/tRffA5y.png)
Because transition is still underway,
[xo-web](https://github.com/vatesfr/xo-web) is still developed in
their own repositories.
## Installation
XOA or manual install procedure is [available here](https://xen-orchestra.com/docs/installation.html)
## License
AGPL3 © [Vates SAS](http://vates.fr)
For now, all issues are still to be reported in [xo-web's tracker](https://github.com/vatesfr/xo-web/issues).

View File

@@ -1,5 +0,0 @@
module.exports = {
// Necessary for jest to be able to find the `.babelrc.js` closest to the file
// instead of only the one in this directory.
babelrcRoots: true,
}

View File

@@ -1,9 +0,0 @@
{
"gitbook": ">=3.0.0",
"root": "./docs",
"plugins": [
"anchors",
"-edit-link"
],
"pluginsConfig": {}
}

View File

@@ -1,31 +0,0 @@
# Xen Orchestra
## Introduction
Welcome to the official Xen Orchestra (XO) documentation.
XO is a web interface to visualize and administer your XenServer (or XAPI enabled) hosts. **No agent** is required for it to work.
It aims to be easy to use on any device supporting modern web technologies (HTML 5, CSS 3, JavaScript), such as your desktop computer or your smartphone.
![](https://pbs.twimg.com/profile_images/601775622675898368/xWbbafyO_400x400.png)
## XOA quick deploy
SSH to your XenServer, and execute the following:
```
bash -c "$(curl -s http://xoa.io/deploy)"
```
### XOA credentials
* Web UI: `admin@admin.net` / `admin`
* Console/SSH: `xoa` / `xoa` (first login)
## Must read
* [XOA installation](xoa.md)
* [Main features](features.md)
* [Pro Support](support.md)

View File

@@ -1,77 +0,0 @@
# Summary
* [Introduction](README.md)
* [Architecture](architecture.md)
* [xo-server](xo-server.md)
* [xo-web](xo-web.md)
* [xo-cli](xo-cli.md)
* [others](others.md)
* [Installation](installation.md)
* [XOA](xoa.md)
* [Updater](updater.md)
* [Trial activation](trial_activation.md)
* [Plugins](plugins.md)
* [Logs](logs.md)
* [Compatibility](supported-version.md)
* [Troubleshooting](troubleshooting.md)
* [From the sources](from_the_sources.md)
* [Configuration](configuration.md)
* [Features](features.md)
* [Administration](administration.md)
* [Home view](user_interface.md)
* [Search and filters](search.md)
* [VM management](vm_management.md)
* [VM creation](vm_creation.md)
* [VM import and export](vm_import_export.md)
* [XenServer Patching](patching.md)
* [Docker support](docker_support.md)
* [Backup and DR](backups.md)
* [Full backups](full_backups.md)
* [Rolling snapshots](rolling_snapshots.md)
* [Continuous Delta backups](delta_backups.md)
* [Continuous Replication](continuous_replication.md)
* [Disaster recovery](disaster_recovery.md)
* [Smart Backup](smart_backup.md)
* [File level Restore](file_level_restore.md)
* [Backup Concurrency](concurrency.md)
* [Configure backup reports](backup_reports.md)
* [Backup troubleshooting](backup_troubleshooting.md)
* [User authentication](authentication.md)
* [Built-in](built-in.md)
* [LDAP](ldap.md)
* [SAML](saml.md)
* [GitHub](github.md)
* [Google](google.md)
* [Resources delegation](resources_delegation.md)
* [ACLs](acls.md)
* [CloudInit](cloudinit.md)
* [Self Service](self_service.md)
* [Visualizations](visualizations.md)
* [Health](health.md)
* [Job manager](scheduler.md)
* [Alerts](alerts.md)
* [Load balancing](load_balancing.md)
* [Auto scalability](auto_scalability.md)
* [Forecaster](forecaster.md)
* [Recipes](recipes.md)
* [Reverse proxy](reverse_proxy.md)
* [How to contribute?](contributing.md)
* [Support](support.md)
* [Roadmap](roadmap.md)
* [Purchase](purchase.md)
* [Direct purchase](directpurchase.md)
* [Through purchase department](through_purchase_department.md)
* [Reseller](reseller.md)
* [Editions](editions.md)
* [Trial](trial.md)
* [Invoices](invoices.md)
* [Upgrade](upgrade.md)
* [XOSAN](xosan.md)
* [Requirements](xosan_requirements.md)
* [Types](xosan_types.md)
* [Replicated](xosan_replicated.md)
* [Disperse](xosan_disperse.md)
* [Creation](xosan_create.md)
* [Trial](xosan_trial.md)
* [General Troubleshooting](general-troubleshooting.md)
* [Glossary](glossary.md)

View File

@@ -1,72 +0,0 @@
# ACLs
> ACLs are permissions that apply to pre-existing objects. Only a super admin (XO administrator) can create objects.
ACLs are the permissions for your users or groups. The ACLs view can be accessed in the "Settings" panel.
1. Select the user or group you want to apply permissions on
2. Select the object on which the permission will apply
3. Choose the role for this ACL
4. Click on "Create"
![](./assets/createacl.png)
> Pro tip: you can click to add multiple objects at the same time!
Your ACL is now available in the right list:
![](./assets/acllist.png)
You can edit/remove existing ACLs here.
## Roles
There are 3 different roles for your users:
* Admin
* Operator
* Viewer
### Admin
An object admin can do everything on it, even destroy it. E.g with its admin VM:
* remove it
* migrate it (to a host with admin permission on it)
* modify the VM resources, name and description
* clone it
* copy it
* convert it into a template
* snapshot it (even revert from a snapshot)
* export it
* attach/add visible disks
* same for network cards
### Operator
An operator can make everyday operations on assigned objects. E.g on a VM:
* eject a CD
* insert a CD (if he can view the ISO storage repository)
* start, restart, shutdown, suspend/resume it
All other operations are forbidden.
### Viewer
A viewer can only see the VM state and its metrics. That's all!
## Inheritance
Objects have a hierarchy: a Pool contains all its hosts, containing itself all its VMs.
If you give a *view* permission to a user (or a group) on a pool, he will automatically see all the objects inside this pool (SRs, hosts, VMs).
## Examples
### Allow a user to install an OS
If the OS install needs an ISO, you need to give this user 2 permissions:
* *Operate* on the VM (e.g to start it)
* *View* on the ISO Storage containing the needed ISO.

View File

@@ -1,12 +0,0 @@
# Administration
This section contains everyday XenServer administration tasks.
* [Home view](user_interface.md)
* [Search and filters](search.md)
* [VM management](vm_management.md)
* [VM creation](vm_creation.md)
* [VM import and export](vm_import_export.md)
* [XenServer Patching](patching.md)
![](./assets/xo5homevms.png)

View File

@@ -1,35 +0,0 @@
# Alerts
Alerts are a way to warn the administrator about various events. The first kind of alerts will be emails and also in a dedicated area of `xo-web` to display them.
## Performances alerts
The administrator will configure alerts based on performance thresholds.
The configurable metrics are:
* CPU usage (VM, host)
* RAM usage (VM, host)
* network bandwidth (VM, host)
* load average (host)
* disk IO (VM)
* total IO (SR, only for XenServer Dundee and higher)
If any configured values exceed the threshold during a selected period of time, an alert will be sent.
Those alerts will be also stored and accessible in the web interface, and also later for the load balancing feature (helping it to solve those performance problems).
## Updates alerts
When your XOA detects new packages, you'll be notified by email.
## Backup alerts
Same story for backups: if a backup fails, you'll receive an email.
You can choose to be notified only if it fails or even after each backup job.
Current supported alerts system:
* Email
* XMPP

View File

@@ -1,28 +0,0 @@
# Architecture
Xen Orchestra (XO) is software built with a server and clients, such as the web client `xo-web`, but also a CLI capable client, called `xo-cli`.
> XO is totally agent-less: you don't have to install any program on your hosts to get it working!
## XOA
*Xen Orchestra Virtual Appliance* (XOA) is a virtual machine with Xen Orchestra already installed, thus working out-of-the-box.
This is the easiest way to try Xen Orchestra quickly.
Your XOA is connected to all your hosts, or the pool master only if you are using Pools in XenServer:
![](./assets/partner2.jpg)
## Xen Orchestra (XO)
![](./assets/xo-arch.jpg)
Xen Orchestra itself is built as a modular solution. Each part has its role:
- The core is "[xo-server](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-server/)" - a daemon dealing directly with XenServer or XAPI capable hosts. This is where users are stored, and it's the center point for talking to your whole Xen infrastructure.
- The web interface is "[xo-web](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-web)" - it runs directly from your browser. The connection with ```xo-server``` is done via *WebSockets*.
- "[xo-cli](https://github.com/vatesfr/xen-orchestra/tree/master/packages/xo-cli)" is a module allowing you to send commands directly from the command line.
We have other modules as well (like the LDAP plugin for example). It allows us to use this modular architecture to add further parts as we need them. It's completely flexible, allowing us to adapt Xen Orchestra to every existing workflow.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show More