Authored by 陈峰

comit

Showing 678 changed files with 476 additions and 2 deletions
app.js 100644 → 100755
No preview for this file type
const schedule = require('node-schedule');
const _ = require('lodash');
const ZookeeperModel = require('../web/models/zookeeperModel');
const Operation = require('../logger/operation');
const {client} = require('../../lib/redis');
module.exports = () => {
let zookeeperModel = new ZookeeperModel();
schedule.scheduleJob('* * * * * *', async () => {
const appsData = await client.getAsync('degradeSSRKeys');
const apps = JSON.parse(appsData || '[]');
_.forEach(apps, async app => {
const zkDegradePath = `/wap/webapp/${app}-degrade`;
const degradeKey = `${app}:degradessr`;
const isDegradeKey = `${degradeKey}:isdegrade`;
const [degradeJson, isDegrade] = await Promise.all([client.getAsync(degradeKey), client.getAsync(isDegradeKey)]);
const zkIsDegrade = await zookeeperModel.getPath(zkDegradePath)
const degrades = JSON.parse(degradeJson || '[]');
if (!isDegrade && zkIsDegrade === 'true') { // isdegrade的redis key已过期,且没有触发限制刷新key过期时间,则降级开关关闭
zookeeperModel.setPath(zkDegradePath, false);
console.log('SSR降级恢复')
Operation.action({
_id: 0,
username: 'ssr-degrade'
}, 'SSR降级恢复', '降级恢复' , {app});
}
_.forEach(degrades, async item => {
const key = `${degradeKey}:${item.time}`;
const result = await client.getAsync(key);
if (item.time * item.tick < +result) { // 大于qps限制,降级
const degradeData = {
...item,
maxTick: +result
};
console.log('触发降级策略')
Operation.action({
_id: 0,
username: 'ssr-degrade'
}, 'SSR降级', '触发降级策略' ,`${isDegradeKey},${JSON.stringify(degradeData)}`);
client.setexAsync(isDegradeKey, item.delay * 60, JSON.stringify(degradeData));
client.delAsync(key);
zookeeperModel.setPath(zkDegradePath, true);
}
});
})
});
}
\ No newline at end of file
... ...
const Router = require('koa-router');
const SsrDegradeModel = require('../models/ssrDegradeModel');
let r = new Router();
const ssrDegrade = {
async index(ctx, next) {
const ssrDegradeModel = new SsrDegradeModel();
const apps = await ssrDegradeModel.getApps();
return ctx.render('action/ssr_degrade', {
list: apps || []
});
},
async editPage(ctx, next) {
const ssrDegradeModel = new SsrDegradeModel();
const app = ctx.request.query.app;
if (!app) {
return ctx.render('action/ssr_degrade_edit', {
data: {}
});
}
const appData = await ssrDegradeModel.getApp(app);
return ctx.render('action/ssr_degrade_edit', {
data: appData
});
},
async edit(ctx, next) {
const ssrDegradeModel = new SsrDegradeModel();
const data = ctx.request.body;
if (!data.app || !Array.isArray(data.degrades)) {
return ctx.body = {
code: 400,
message: '参数错误'
};
}
const result = ssrDegradeModel.editApp(data);
if (result) {
return ctx.body = {
code: 200,
};
}
return ctx.body = {
code: 500,
message: '失败'
};
},
async delete(ctx, next) {
const ssrDegradeModel = new SsrDegradeModel();
const app = ctx.request.query.app;
if (!app) {
return ctx.body = {
code: 400,
message: '参数错误'
};
}
const result = ssrDegradeModel.deleteApp({app});
if (result) {
return ctx.body = {
code: 200,
};
}
return ctx.body = {
code: 500,
message: '失败'
};
}
};
r.get('/index', ssrDegrade.index);
r.get('/edit', ssrDegrade.editPage);
r.post('/edit', ssrDegrade.edit);
r.get('/delete', ssrDegrade.delete);
module.exports = r;
... ...
... ... @@ -14,6 +14,7 @@ const routers = require('./routers');
const collectData = require('./actions/collect_data');
const profile = require('./actions/profile');
const redis = require('../../lib/redis');
const degradeSsrTask = require('../tasks/degrade-ssr-task');
const {
normalMenus,
... ... @@ -47,6 +48,9 @@ const mastersUrl = [
// 服务器监控数据采集
// collectData.collect();
// SSR自适应降级监控
degradeSsrTask();
app.use(async(ctx, next) => {
ctx.redis = redis.client;
... ...
const model = require('../../../lib/model');
const {client} = require('../../../lib/redis');
const ZookeeperModel = require('../../web/models/zookeeperModel');
const _ = require('lodash');
class SsrDegradeModel extends model {
constructor(ctx) {
super(ctx);
}
async getApps() {
const appsData = await client.getAsync('degradeSSRKeys');
const apps = JSON.parse(appsData || '[]');
return Promise.all(_.map(apps, this.getApp));
}
async getApp(app) {
const zookeeperModel = new ZookeeperModel();
const [degradeJson, isDegradeJson] = await Promise.all([
client.getAsync(`${app}:degradessr`),
client.getAsync(`${app}:degradessr:isdegrade`)]
);
const zkIsDegrade = await zookeeperModel.getPath(`/wap/webapp/${app}-degrade`);
const degrades = JSON.parse(degradeJson || '[]');
let isDegrade;
if (isDegradeJson) {
isDegrade = JSON.parse(isDegradeJson);
_.forEach(degrades, d => {
if (d.time === isDegrade.time && d.tick === isDegrade.tick) {
d.status = true;
d.maxTick = d.maxTick;
}
})
}
return {
app,
degrades,
status: zkIsDegrade === 'true'
};
}
async editApp(data) {
const zookeeperModel = new ZookeeperModel();
const appsData = await client.getAsync('degradeSSRKeys');
const apps = JSON.parse(appsData || '[]');
if (!apps.some(app => app === data.app)) {
apps.push(data.app);
client.setAsync('degradeSSRKeys', JSON.stringify(apps));
}
zookeeperModel.setPath(`/wap/webapp/${data.app}-degrade`, false);
client.delAsync(`${data.app}:degradessr:isdegrade`);
const degrades = _.map(data.degrades, d => {
return {
time: d.time,
tick: d.tick,
delay: d.delay
}
})
return await client.setAsync(`${data.app}:degradessr`, JSON.stringify(degrades));
}
async deleteApp(data) {
const zookeeperModel = new ZookeeperModel();
const appsData = await client.getAsync('degradeSSRKeys');
const apps = JSON.parse(appsData || '[]');
const saveApps = apps.filter(app => app !== data.app);
client.setAsync('degradeSSRKeys', JSON.stringify(saveApps));
client.delAsync(`${data.app}:degradessr`);
client.delAsync(`${data.app}:degradessr:isdegrade`);
zookeeperModel.setPath(`/wap/webapp/${data.app}-degrade`, false);
return true;
}
}
module.exports = SsrDegradeModel;
... ...
... ... @@ -29,6 +29,7 @@ const noAuth = new Router();
const base = new Router();
const file = require('./actions/file');
const riskManagement = require('./actions/risk_management');
const ssrDegrade = require('./actions/ssr_degrade');
const logs = require('./actions/logs');
const spa = require('./actions/spa');
const upload = require('./actions/upload');
... ... @@ -80,6 +81,7 @@ module.exports = function(app) {
// base.use('', index.routes(), index.allowedMethods());
base.use('/risk_management', riskManagement.routes(), riskManagement.allowedMethods());
base.use('/ssr_degrade', ssrDegrade.routes(), ssrDegrade.allowedMethods());
base.use('/logs', logs.routes(), logs.allowedMethods());
base.use('/upload', upload.routes(), upload.allowedMethods());
base.use('', spa.routes(), spa.allowedMethods());
... ...
<div class="pageheader">
<div class="media">
<div class="pageicon pull-left">
<i class="fa fa-th-list"></i>
</div>
<div class="media-body">
<ul class="breadcrumb">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
<li>SSR自适应降级</li>
</ul>
<h4>配置列表</h4>
</div>
</div>
<!-- media -->
</div>
<style>
.degrades-ul {
list-style: none;
margin: 0;
padding: 0;
}
</style>
<!-- pageheader -->
<style>
.li-degrade, .td-degrade {
color: red;
}
</style>
<div class="contentpanel servers-page">
<button type="button" class="btn btn-success add">新增配置</button>
<table class="table table-hover table-striped table-bordered" style="margin-top: 2rem">
<thead>
<tr>
<th>应用名称</th>
<th>降级条件</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{#list}}
<tr>
<td>{{app}}</td>
<td>
<ul class="degrades-ul">
{{#degrades}}
<li class="{{#if status}}li-degrade{{/if}}">
{{time}}秒内平均qps大于{{tick}}{{delay}}分钟内未触发则恢复{{#if status}}-[已触发]{{/if}}
</li>
{{/degrades}}
</ul>
</td>
<td class="{{#if status}}td-degrade{{/if}}">{{#if status}}已降级{{^}}正常{{/if}}</td>
<td><button data-app="{{app}}" type="button" class="btn btn-success edit">编辑</button><button data-app="{{app}}" type="button" class="btn btn-danger ml10 del">删除</button></td>
</tr>
{{/list}}
{{#if noData}}
<tr>
<td colspan="4" class="text-center">无数据</td>
</tr>
{{/if}}
</tbody>
</table>
</div>
<script>
$('.add').on('click', function() {
location.href = '/ssr_degrade/edit';
});
$('.del').on('click', function() {
var app = $(this).data('app');
var confirmed = confirm('是否删除?')
if (confirmed) {
$.get('/ssr_degrade/delete', {
app
}, function(ret) {
if (ret.code === 200) {
alert('删除成功!');
location.reload();
} else {
alert('删除失败!');
}
});
}
});
$('.edit').on('click', function() {
var app = $(this).data('app');
location.href = '/ssr_degrade/edit?app=' + app;
});
</script>
\ No newline at end of file
... ...
{{#data}}
<div class="pageheader">
<div class="media">
<div class="pageicon pull-left">
<i class="fa fa-th-list"></i>
</div>
<div class="media-body">
<ul class="breadcrumb">
<li><a href="/"><i class="glyphicon glyphicon-home"></i></a></li>
<li><a href="/ssr_degrade/index">SSR自适应降级</a></li>
<li>编辑配置</li>
</ul>
<h4>编辑配置</h4>
</div>
</div>
<!-- media -->
</div>
<!-- pageheader -->
<div class="contentpanel servers-page form-horizontal">
<div class="form-group">
<label for="app" class="col-sm-2 control-label text-center">应用名称:</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="app" placeholder="应用名称" value="{{app}}" {{#if app}}readonly{{/if}}>
</div>
</div>
<div class="form-group">
<label for="router" class="col-sm-2 control-label text-center">降级条件:</label>
<div class="col-sm-10">
<div id="degrades">
{{#degrades}}
<div class="form-inline degrade" style="margin-top: 10px;">
<input type="text" class="form-control data-time" style="width: 50px" placeholder="" value="{{time}}">秒内平均qps大于
<input type="text" class="form-control data-tick" style="width: 50px" placeholder="" value="{{tick}}">
<input type="text" class="form-control data-delay" style="width: 50px" placeholder="" value="{{delay}}">分钟内未触发则恢复
<a href="javascript:;" class="delete">删除</a>
</div>
{{/degrades}}
</div>
<div style="padding-top: 20px;">
<a href="javascript:;" id="add" class="">新增</a>
</div>
</div>
</div>
<div class="row">
<label class="col-sm-2"></label>
<div class="col-sm-10">
<button type="button" class="btn btn-success save">保存</button>
<button type="button" class="btn btn-default cancel">取消</button>
</div>
</div>
</div>
<div style="display: none;" id="template">
<div class="form-inline degrade" style="margin-top: 10px;">
<input type="text" class="form-control data-time" style="width: 50px" placeholder="秒" value="">秒内平均qps大于
<input type="text" class="form-control data-tick" style="width: 50px" placeholder="qps" value="">
<input type="text" class="form-control data-delay" style="width: 50px" placeholder="qps" value="">分钟内未触发则恢复
<a href="javascript:;" class="delete">删除</a>
</div>
</div>
{{/data}}
<script>
let $app = $('#app'),
$router = $('#router'),
$interval = $('#interval'),
$requests = $('#requests');
function getData() {
const app = $('#app').val();
if (!app) {
alert('请输入应用名称');
return false;
}
const $degrades = $('#degrades').find('.degrade');
const data = $degrades.map((index, el) => {
const $el = $(el);
const time = +$el.find('.data-time').val();
const tick = +$el.find('.data-tick').val();
const delay = +$el.find('.data-delay').val();
return {
time,
tick,
delay
};
}).toArray();
let valid = true;
data.forEach(degrade => {
if (!degrade.time || !Number.isInteger(+degrade.time)) {
alert('请输入时间!');
valid = false;
return false;
}
if (!degrade.tick || !Number.isInteger(+degrade.tick)) {
alert('请输入qps!');
valid = false;
return false;
}
if (!degrade.delay || !Number.isInteger(+degrade.delay)) {
alert('请输入恢复时间!');
valid = false;
return false;
}
});
if (!valid) {
return false;
}
return {
app,
degrades: data
};
}
$('#add').on('click', function() {
var template = $('#template').html();
var $degrade = $(template);
$degrade.find('.delete').on('click', function () {
$(this).parent().remove();
});
$('#degrades').append($degrade);
});
$('.delete').on('click', function() {
$(this).parent().remove();
});
$('.save').on('click', function() {
const data = getData();
if (!data) {
return;
}
$.post('/ssr_degrade/edit', data, function(ret) {
if (ret.code === 200) {
alert('添加成功!');
location.href = '/ssr_degrade/index';
} else {
alert('添加失败!');
}
});
});
$('.cancel').on('click', function() {
location.href = '/ssr_degrade/index';
});
</script>
\ No newline at end of file
... ...
... ... @@ -56,7 +56,7 @@ const creator = (server, path, value, iscover) => new Promise((resolve, reject)
client.close();
resolve(true);
} else {
client.mkdirp(path, new Buffer(value), (err, path) => {
client.mkdirp(path, new Buffer(value.toString()), (err, path) => {
client.close();
if (err) {
console.log('Node %s create err', path, err.stack);
... ...
... ... @@ -14,7 +14,7 @@ const defaults = {
},
redis: {
connect: {
host: '192.168.102.49', //'127.0.0.1',
host: '127.0.0.1', //'127.0.0.1',
port: '6379',
retry_strategy: options => {
if (options.error && options.error.code === 'ECONNREFUSED') {
... ...
... ... @@ -59,6 +59,10 @@ const normalMenus = [
title: '风险控制',
link: '/risk_management/risk_management',
isClassic: true
}, {
title: 'SSR自适应降级',
link: '/ssr_degrade/index',
isClassic: true
}]
},
{
... ...
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type
No preview for this file type

563 Bytes | W: | H:

563 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

660 Bytes | W: | H:

660 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
No preview for this file type

2.89 KB | W: | H:

2.89 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.9 KB | W: | H:

2.9 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
No preview for this file type

3.83 KB | W: | H:

3.83 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.91 KB | W: | H:

2.91 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.08 KB | W: | H:

3.08 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.91 KB | W: | H:

2.91 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.23 KB | W: | H:

3.23 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

76 Bytes | W: | H:

76 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

10.1 KB | W: | H:

10.1 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.08 KB | W: | H:

3.08 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.15 KB | W: | H:

3.15 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.07 KB | W: | H:

3.07 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

78 Bytes | W: | H:

78 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

984 Bytes | W: | H:

984 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.99 KB | W: | H:

2.99 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
No preview for this file type

2.86 KB | W: | H:

2.86 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.86 KB | W: | H:

2.86 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.08 KB | W: | H:

1.08 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.88 KB | W: | H:

2.88 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
No preview for this file type

595 Bytes | W: | H:

595 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.45 KB | W: | H:

3.45 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

19.4 KB | W: | H:

19.4 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

6.07 KB | W: | H:

6.07 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

7.1 KB | W: | H:

7.1 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

12.1 KB | W: | H:

12.1 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.11 KB | W: | H:

3.11 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

4.48 KB | W: | H:

4.48 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

11.2 KB | W: | H:

11.2 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

9.35 KB | W: | H:

9.35 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.6 KB | W: | H:

2.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.64 KB | W: | H:

1.64 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

10.2 KB | W: | H:

10.2 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.99 KB | W: | H:

2.99 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

6.2 KB | W: | H:

6.2 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

14.2 KB | W: | H:

14.2 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

13.9 KB | W: | H:

13.9 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

6.9 KB | W: | H:

6.9 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.37 KB | W: | H:

2.37 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.59 KB | W: | H:

1.59 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

4.84 KB | W: | H:

4.84 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

4.66 KB | W: | H:

4.66 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.32 KB | W: | H:

1.32 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

8.3 KB | W: | H:

8.3 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.3 KB | W: | H:

1.3 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.32 KB | W: | H:

2.32 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.21 KB | W: | H:

2.21 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

8.58 KB | W: | H:

8.58 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.09 KB | W: | H:

1.09 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.46 KB | W: | H:

3.46 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.17 KB | W: | H:

2.17 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

9.03 KB | W: | H:

9.03 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
No preview for this file type

10.5 KB | W: | H:

10.5 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

5.71 KB | W: | H:

5.71 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

91 KB | W: | H:

91 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

336 KB | W: | H:

336 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

272 KB | W: | H:

272 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

209 KB | W: | H:

209 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

232 KB | W: | H:

232 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

290 KB | W: | H:

290 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

361 KB | W: | H:

361 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

253 KB | W: | H:

253 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

9.09 KB | W: | H:

9.09 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

17.3 KB | W: | H:

17.3 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

17.9 KB | W: | H:

17.9 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

16.6 KB | W: | H:

16.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

17.8 KB | W: | H:

17.8 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

16.6 KB | W: | H:

16.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin
No preview for this file type

1.5 KB | W: | H:

1.5 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

6.18 KB | W: | H:

6.18 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

6.53 KB | W: | H:

6.53 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.33 KB | W: | H:

1.33 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.34 KB | W: | H:

1.34 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.07 KB | W: | H:

1.07 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.13 KB | W: | H:

1.13 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.1 KB | W: | H:

3.1 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.81 KB | W: | H:

1.81 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

613 Bytes | W: | H:

613 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

845 Bytes | W: | H:

845 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

3.62 KB | W: | H:

3.62 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

7.3 KB | W: | H:

7.3 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

13 KB | W: | H:

13 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

6.6 KB | W: | H:

6.6 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

9.99 KB | W: | H:

9.99 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

33.7 KB | W: | H:

33.7 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.07 KB | W: | H:

2.07 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

763 Bytes | W: | H:

763 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.56 KB | W: | H:

1.56 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

260 Bytes | W: | H:

260 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

172 Bytes | W: | H:

172 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

4.31 KB | W: | H:

4.31 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

17.5 KB | W: | H:

17.5 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

9.99 KB | W: | H:

9.99 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

33.7 KB | W: | H:

33.7 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

261 Bytes | W: | H:

261 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

824 Bytes | W: | H:

824 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.75 KB | W: | H:

1.75 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.47 KB | W: | H:

1.47 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

1.58 KB | W: | H:

1.58 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

2.27 KB | W: | H:

2.27 KB | W: | H:

  • 2-up
  • Swipe
  • Onion skin

736 Bytes | W: | H:

736 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

728 Bytes | W: | H:

728 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin

953 Bytes | W: | H:

953 Bytes | W: | H:

  • 2-up
  • Swipe
  • Onion skin
No preview for this file type