shadowsocks-manager
Version:
A shadowsocks manager tool for multi user and traffic control.
690 lines (684 loc) • 26 kB
JavaScript
const app = angular.module('app');
app.controller('AdminAccountController', ['$scope', '$state', '$stateParams', '$http', 'accountSortDialog','$interval', 'adminApi', '$localStorage', 'accountSortTool',
($scope, $state, $stateParams, $http, accountSortDialog, $interval, adminApi, $localStorage, accountSortTool) => {
$scope.setTitle('账号');
$scope.setMenuRightButton('sort_by_alpha');
$scope.setMenuSearchButton('search');
if(!$localStorage.admin.accountFilterSettings) {
$localStorage.admin.accountFilterSettings = {
sort: 'port_asc',
filter: {
expired: true,
unexpired: true,
unlimit: true,
mac: true,
},
};
}
$scope.accountMethod = $localStorage.admin.accountFilterSettings;
$scope.accountInfo = {};
$scope.macAccountInfo = {};
$scope.sortAndFilter = () => {
accountSortTool($scope.accountInfo, $scope.accountMethod);
};
if(!$localStorage.admin.accountInfo) {
$localStorage.admin.accountInfo = {
time: Date.now(),
data: [],
};
}
if(!$localStorage.admin.macAccountInfo) {
$localStorage.admin.macAccountInfo = {
time: Date.now(),
data: [],
};
}
$scope.accountInfo.originalAccount = $localStorage.admin.accountInfo.data;
$scope.accountInfo.account = angular.copy($scope.accountInfo.originalAccount);
$scope.macAccountInfo.originalAccount = $localStorage.admin.macAccountInfo.data;
$scope.macAccountInfo.account = angular.copy($scope.macAccountInfo.originalAccount);
$scope.sortAndFilter();
const getAccountInfo = () => {
adminApi.getAccount().then(accounts => {
$localStorage.admin.accountInfo = {
time: Date.now(),
data: accounts,
};
$scope.accountInfo.originalAccount = accounts;
$scope.accountInfo.account = angular.copy($scope.accountInfo.originalAccount);
$scope.sortAndFilter();
return adminApi.getMacAccount();
}).then(macAccounts => {
$localStorage.admin.macAccountInfo = {
time: Date.now(),
data: macAccounts,
};
// $scope.macAccount = macAccounts;
$scope.macAccountInfo.originalAccount = macAccounts;
$scope.macAccountInfo.account = angular.copy($scope.macAccountInfo.originalAccount);
});
};
getAccountInfo();
$scope.$on('visibilitychange', (event, status) => {
if(status === 'visible') {
if($localStorage.admin.accountInfo && Date.now() - $localStorage.admin.accountInfo.time >= 20 * 1000) {
getAccountInfo();
}
}
});
$scope.setInterval($interval(() => {
if($localStorage.admin.accountInfo && Date.now() - $localStorage.admin.accountInfo.time >= 90 * 1000) {
getAccountInfo();
}
}, 15 * 1000));
$scope.setFabButton($scope.id === 1 ? () => {
$state.go('admin.addAccount');
} : null);
$scope.toAccount = id => {
$state.go('admin.accountPage', { accountId: id });
};
$scope.toMacAccount = userId => {
$state.go('admin.userPage', { userId });
};
$scope.sortAndFilterDialog = () => {
accountSortDialog.show($scope.accountMethod, $scope.accountInfo);
};
$scope.$on('RightButtonClick', () => {
$scope.sortAndFilterDialog();
});
const accountFilter = () => {
accountSortTool($scope.accountInfo, $scope.accountMethod);
$scope.accountInfo.account = $scope.accountInfo.account.filter(f => {
return (f.port + (f.user ? f.user : '')).indexOf($scope.menuSearch.text) >= 0;
});
$scope.macAccountInfo.account = $scope.macAccountInfo.originalAccount.filter(f => {
return (f.port + f.mac).indexOf($scope.menuSearch.text.replace(/-/g, '').replace(/:/g, '').toLowerCase()) >= 0;
});
};
$scope.$on('cancelSearch', () => {
accountSortTool($scope.accountInfo, $scope.accountMethod);
});
$scope.$watch('menuSearch.text', () => {
if(!$scope.menuSearch.input) {
return;
}
if(!$scope.menuSearch.text) {
accountSortTool($scope.accountInfo, $scope.accountMethod);
return;
}
accountFilter();
});
$scope.accountColor = account => {
if(account.type === 1) {
return {
background: 'blue-50', 'border-color': 'blue-300',
};
} else if(account.data && account.data.expire <= Date.now()) {
return {
background: 'red-50', 'border-color': 'red-300',
};
} else if(account.autoRemove) {
return {
background: 'lime-50', 'border-color': 'lime-300',
};
}
return {};
};
}
])
.controller('AdminAccountPageController', ['$scope', '$state', '$stateParams', '$http', '$mdMedia', '$q', 'adminApi', '$timeout', '$interval', 'qrcodeDialog', 'ipDialog', '$mdBottomSheet',
($scope, $state, $stateParams, $http, $mdMedia, $q, adminApi, $timeout, $interval, qrcodeDialog, ipDialog, $mdBottomSheet) => {
$scope.setTitle('账号');
$scope.setMenuButton('arrow_back', 'admin.account');
$scope.accountId = +$stateParams.accountId;
$scope.account = { port: '...' };
$q.all([
$http.get(`/api/admin/account/${ $scope.accountId }`),
$http.get('/api/admin/server'),
$http.get('/api/admin/setting/account'),
]).then(success => {
$scope.account = success[0].data;
$scope.servers = success[1].data.map(server => {
if(server.host.indexOf(':') >= 0) {
const hosts = server.host.split(':');
const number = Math.ceil(Math.random() * (hosts.length - 1));
server.host = hosts[number];
}
return server;
});
$scope.getServerPortData($scope.servers[0], $scope.accountId);
$scope.isMultiServerFlow = !!$scope.account.multiServerFlow;
}).catch(err => {
console.log(err);
$state.go('admin.account');
});
let currentServerId;
$scope.getServerPortData = (server, accountId) => {
const serverId = server.id;
currentServerId = serverId;
$scope.serverPortFlow = 0;
$scope.lastConnect = 0;
adminApi.getServerPortData(serverId, accountId).then(success => {
$scope.serverPortFlow = success.serverPortFlow;
$scope.lastConnect = success.lastConnect;
let maxFlow = 0;
if($scope.account.data) {
server.isFlowOutOfLimit = (($scope.account.data.flow + $scope.account.data.flowPack) <= $scope.serverPortFlow);
}
});
$scope.getChartData(serverId);
$scope.servers.forEach((server, index) => {
if(server.id === serverId) { return; }
$timeout(() => {
adminApi.getServerPortData(serverId, accountId);
}, index * 1000);
});
$scope.server = $scope.servers.filter(f => {
return f.id === serverId;
})[0];
};
$scope.setInterval($interval(() => {
const serverId = currentServerId;
adminApi.getServerPortData(serverId, $scope.accountId).then(success => {
if(serverId !== currentServerId) { return; }
$scope.lastConnect = success.lastConnect;
$scope.serverPortFlow = success.serverPortFlow;
});
}, 60 * 1000));
const base64Encode = str => {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
};
$scope.createQrCode = (method, password, host, port, serverName) => {
return 'ss://' + base64Encode(method + ':' + password + '@' + host + ':' + port);
};
$scope.showQrcodeDialog = (method, password, host, port, serverName) => {
const ssAddress = $scope.createQrCode(method, password, host, port, serverName);
qrcodeDialog.show(serverName, ssAddress);
};
$scope.editAccount = id => {
$state.go('admin.editAccount', { accountId: id });
};
$scope.getQrCodeSize = () => {
if($mdMedia('xs')) {
return 230;
} else if ($mdMedia('lg')) {
return 240;
}
return 180;
};
$scope.flowType = {
value: 'day',
};
const flowTime = {
hour: Date.now(),
day: Date.now(),
week: Date.now(),
};
const flowLabel = {
hour: ['0', '', '', '15', '', '', '30', '', '', '45', '', ''],
day: ['0', '', '', '', '', '', '6', '', '', '', '', '', '12', '', '', '', '', '', '18', '', '', '', '', '', ],
week: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
};
const scaleLabel = (number) => {
if(number < 1) {
return number.toFixed(1) +' B';
} else if (number < 1000) {
return number.toFixed(0) +' B';
} else if (number < 1000000) {
return (number/1000).toFixed(0) +' KB';
} else if (number < 1000000000) {
return (number/1000000).toFixed(0) +' MB';
} else if (number < 1000000000000) {
return (number/1000000000).toFixed(1) +' GB';
} else {
return number;
}
};
const setChart = (lineData, pieData) => {
$scope.pieChart = {
data: pieData.map(m => m.flow),
labels: pieData.map(m => m.name),
options: {
responsive: false,
tooltips: {
enabled: true,
mode: 'single',
callbacks: {
label: function(tooltipItem, data) {
const label = data.labels[tooltipItem.index];
const datasetLabel = data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index];
return label + ': ' + scaleLabel(datasetLabel);
}
}
},
},
};
$scope.lineChart = {
data: [lineData],
labels: flowLabel[$scope.flowType.value],
series: 'day',
datasetOverride: [{ yAxisID: 'y-axis-1' }],
options: {
responsive: false,
tooltips: {
callbacks: {
label: function(tooltipItem) {
return scaleLabel(tooltipItem.yLabel);
}
}
},
scales: {
yAxes: [
{
id: 'y-axis-1',
type: 'linear',
display: true,
position: 'left',
ticks: {
callback: scaleLabel,
},
}
]
}
},
};
};
$scope.getChartData = serverId => {
adminApi.getAccountChartData(serverId, $scope.accountId, $scope.flowType.value, flowTime[$scope.flowType.value])
.then(success => {
$scope.sumFlow = success[0].data.reduce((a, b) => {
return a + b;
}, 0);
setChart(success[0].data, success[1].data);
});
if($scope.flowType.value === 'hour') {
$scope.time = moment(flowTime[$scope.flowType.value]).format('YYYY-MM-DD HH:00');
}
if($scope.flowType.value === 'day') {
$scope.time = moment(flowTime[$scope.flowType.value]).format('YYYY-MM-DD');
}
if($scope.flowType.value === 'week') {
$scope.time = moment(flowTime[$scope.flowType.value]).day(0).format('YYYY-MM-DD') + ' / ' + moment(flowTime[$scope.flowType.value]).day(6).format('YYYY-MM-DD');
}
};
$scope.changeFlowTime = (serverId, number) => {
const time = {
hour: 3600 * 1000,
day: 24 * 3600 * 1000,
week: 7 * 24 * 3600 * 1000,
};
flowTime[$scope.flowType.value] += number * time[$scope.flowType.value];
$scope.getChartData(serverId);
};
$scope.resetFlowTime = serverId => {
flowTime[$scope.flowType.value] = Date.now();
$scope.getChartData(serverId);
};
$scope.getChartSize = () => {
if($mdMedia('xs')) {
return {
line: [ 320, 170 ],
pie: [ 170, 170 ],
};
} else if($mdMedia('sm')) {
return {
line: [ 360, 190 ],
pie: [ 190, 190 ],
};
} else if($mdMedia('md')) {
return {
line: [ 360, 180 ],
pie: [ 180, 180 ],
};
} else if($mdMedia('gt-md')) {
return {
line: [ 540, 240 ],
pie: [ 240, 240 ],
};
}
};
$scope.fontColor = account => {
if(account.data.expire >= Date.now()) {
return {
color: '#333',
};
}
return {
color: '#a33',
};
};
$scope.toUserPage = userId => {
if(!userId) { return; }
$state.go('admin.userPage', { userId });
};
$scope.clientIp = (serverId, accountId) => {
ipDialog.show(serverId, accountId);
};
$scope.cycleStyle = account => {
let percent = 0;
if(account.type !== 1) {
percent = ((Date.now() - account.data.from) / (account.data.to - account.data.from) * 100).toFixed(0);
}
if(percent > 100) {
percent = 100;
}
return {
background: `linear-gradient(90deg, rgba(0,0,0,0.12) ${ percent }%, rgba(0,0,0,0) 0%)`
};
};
$scope.setFabButton($scope.id === 1 ? () => {
$scope.editAccount($scope.account.id);
} : null, 'mode_edit');
$scope.setExpireTime = number => {
$scope.expireTimeShift += number;
};
$scope.expireTimeSheet = time => {
if($scope.id !== 1) { return; }
if(!time) { return; }
$scope.expireTimeShift = 0;
$mdBottomSheet.show({
templateUrl: '/public/views/admin/setExpireTime.html',
preserveScope: true,
scope: $scope,
}).catch(() => {
$http.put(`/api/admin/account/${ $scope.accountId }/time`, {
time: $scope.expireTimeShift,
check: true,
}).then(success => {
$http.get(`/api/admin/account/${ $scope.accountId }`).then(success => {
$scope.account = success.data;
});
});
});
};
}
])
.controller('AdminAddAccountController', ['$scope', '$state', '$stateParams', '$http', '$mdBottomSheet', 'alertDialog', '$filter',
($scope, $state, $stateParams, $http, $mdBottomSheet, alertDialog, $filter) => {
$scope.setTitle('添加账号');
$scope.setMenuButton('arrow_back', 'admin.account');
$http.get('/api/admin/order').then(success => {
$scope.orders = success.data.filter(f => !f.baseId);
$scope.account.orderId = $scope.orders[0].id;
});
$http.get('/api/admin/account/newPort').then(success => {
$scope.account.port = success.data.port;
$scope.account.password = Math.random().toString().substr(2, 10);
});
$scope.typeList = [
{key: '不限量', value: 1},
{key: '按周', value: 2},
{key: '按月', value: 3},
{key: '按天', value: 4},
{key: '小时', value: 5},
];
$scope.timeLimit = {
'2': 7 * 24 * 3600000,
'3': 30 * 24 * 3600000,
'4': 24 * 3600000,
'5': 3600000,
};
$scope.account = {
type: 1,
fromOrder: 0,
time: Date.now(),
limit: 1,
flow: 100000000,
autoRemove: 0,
autoRemoveDelay: 0,
autoRemoveDelayStr: '0',
multiServerFlow: 0,
accountServer: false,
accountServerObj: {},
};
$scope.account.flowStr = $filter('flowNum2Str')($scope.account.flow);
const selectOrder = () => {
if(!$scope.account.fromOrder) { return; }
const orderInfo = $scope.orders.filter(f => +f.id === +$scope.account.orderId)[0];
$scope.account.type = orderInfo.type;
$scope.account.flow = orderInfo.flow;
$scope.account.limit = orderInfo.cycle;
$scope.account.autoRemove = orderInfo.autoRemove;
$scope.account.autoRemoveDelay = orderInfo.autoRemoveDelay;
$scope.account.multiServerFlow = orderInfo.multiServerFlow;
$scope.account.accountServer = !!orderInfo.server;
$scope.account.flowStr = $filter('flowNum2Str')($scope.account.flow);
$scope.account.autoRemoveDelayStr = $filter('timeNum2Str')(orderInfo.autoRemoveDelay);
if(orderInfo.server) {
$scope.servers.forEach(server => {
if(JSON.parse(orderInfo.server).indexOf(server.id) >= 0) {
$scope.account.accountServerObj[server.id] = true;
} else {
$scope.account.accountServerObj[server.id] = false;
}
});
}
};
$scope.$watch('account.orderId', selectOrder);
$scope.$watch('account.fromOrder', selectOrder);
$scope.cancel = () => {
$state.go('admin.account');
};
$scope.confirm = () => {
$scope.account.autoRemoveDelay = $filter('timeStr2Num')($scope.account.autoRemoveDelayStr);
alertDialog.loading();
$scope.account.flow = $filter('flowStr2Num')($scope.account.flowStr);
if($scope.account.server) {
$scope.servers.forEach(server => {
if($scope.account.server.indexOf(server.id) >= 0) {
$scope.account.accountServerObj[server.id] = true;
} else {
$scope.account.accountServerObj[server.id] = false;
}
});
}
const server = Object.keys($scope.account.accountServerObj).map(m => $scope.account.accountServerObj[m] ? +m : null).filter(f => f);
$http.post('/api/admin/account', {
type: +$scope.account.type,
orderId: $scope.account.fromOrder ? +$scope.account.orderId : 0,
port: +$scope.account.port,
password: $scope.account.password,
time: $scope.account.time,
limit: +$scope.account.limit,
flow: +$scope.account.flow,
autoRemove: $scope.account.autoRemove ? 1 : 0,
autoRemoveDelay: $scope.account.autoRemoveDelay,
multiServerFlow: $scope.account.multiServerFlow ? 1 : 0,
server: $scope.account.accountServer ? server : null,
}).then(success => {
alertDialog.show('添加账号成功', '确定');
$state.go('admin.account');
}).catch(() => {
alertDialog.show('添加账号失败', '确定');
});
};
$scope.pickTime = () => {
$mdBottomSheet.show({
templateUrl: '/public/views/admin/pickTime.html',
preserveScope: true,
scope: $scope,
});
};
$scope.setStartTime = (number) => {
$scope.account.time += number;
};
$scope.setLimit = (number) => {
$scope.account.limit += number;
if($scope.account.limit < 1) {
$scope.account.limit = 1;
}
};
$http.get('/api/admin/server').then(success => {
$scope.servers = success.data;
});
}
])
.controller('AdminEditAccountController', ['$scope', '$state', '$stateParams', '$http', '$mdBottomSheet', 'confirmDialog', 'alertDialog', '$filter', '$q',
($scope, $state, $stateParams, $http, $mdBottomSheet, confirmDialog, alertDialog, $filter, $q) => {
$scope.setTitle('编辑账号');
$scope.setMenuButton('arrow_back', function() {
$state.go('admin.accountPage', { accountId: $stateParams.accountId });
});
$http.get('/api/admin/order').then(success => {
$scope.orders = success.data.filter(f => !f.baseId);
$scope.account.orderId = success.data[0].id;
});
$scope.typeList = [
{key: '不限量', value: 1},
{key: '按周', value: 2},
{key: '按月', value: 3},
{key: '按天', value: 4},
{key: '小时', value: 5},
];
$scope.timeLimit = {
'2': 7 * 24 * 3600000,
'3': 30 * 24 * 3600000,
'4': 24 * 3600000,
'5': 3600000,
};
$scope.account = {
fromOrder: 0,
time: Date.now(),
limit: 1,
flow: 100,
autoRemove: 0,
};
const selectOrder = (newValue, oldValue) => {
if(newValue === oldValue) { return; }
if(!$scope.account.fromOrder) { return; }
let orderInfo = $scope.orders.filter(f => +f.id === +$scope.account.orderId)[0];
if(!orderInfo) {
orderInfo = $scope.orders[0];
$scope.account.orderId = orderInfo.id;
}
let expire;
if($scope.account.fixedExpire) {
expire = $scope.account.time + $scope.timeLimit[$scope.account.type] * $scope.account.limit;
}
$scope.account.type = orderInfo.type;
$scope.account.flow = orderInfo.flow;
$scope.account.limit = orderInfo.cycle;
$scope.account.autoRemove = orderInfo.autoRemove;
$scope.account.autoRemoveDelay = orderInfo.autoRemoveDelay;
$scope.account.multiServerFlow = orderInfo.multiServerFlow;
$scope.account.accountServer = !!orderInfo.server;
$scope.account.flowStr = $filter('flowNum2Str')($scope.account.flow);
$scope.account.autoRemoveDelayStr = $filter('timeNum2Str')(orderInfo.autoRemoveDelay);
if($scope.account.fixedExpire) {
$scope.account.time = expire - $scope.timeLimit[$scope.account.type] * $scope.account.limit;
while($scope.account.time >= Date.now()) {
$scope.account.time -= $scope.timeLimit[$scope.account.type];
$scope.account.limit++;
}
}
if(orderInfo.server) {
$scope.servers.forEach(server => {
if(JSON.parse(orderInfo.server).indexOf(server.id) >= 0) {
$scope.account.accountServerObj[server.id] = true;
} else {
$scope.account.accountServerObj[server.id] = false;
}
});
}
};
const accountId = $stateParams.accountId;
$q.all([
$http.get('/api/admin/server'),
$http.get(`/api/admin/account/${ accountId }`),
]).then(success => {
$scope.servers = success[0].data;
$scope.account.type = success[1].data.type;
if(success[1].data.orderId) {
$scope.account.fromOrder = 1;
}
$scope.account.orderId = success[1].data.orderId;
$scope.account.port = success[1].data.port;
$scope.account.password = success[1].data.password;
$scope.account.cleanFlow = false;
$scope.account.autoRemove = success[1].data.autoRemove;
$scope.account.autoRemoveDelay = success[1].data.autoRemoveDelay;
$scope.account.autoRemoveDelayStr = $filter('timeNum2Str')($scope.account.autoRemoveDelay);;
$scope.account.multiServerFlow = success[1].data.multiServerFlow;
if(success[1].data.type >= 2 && success[1].data.type <= 5) {
$scope.account.time = success[1].data.data.create;
$scope.account.limit = success[1].data.data.limit;
$scope.account.flow = success[1].data.data.flow;
$scope.account.flowStr = $filter('flowNum2Str')($scope.account.flow);
}
$scope.account.server = success[1].data.server;
$scope.account.accountServer = !!$scope.account.server;
$scope.account.accountServerObj = {};
if($scope.account.server) {
$scope.servers.forEach(server => {
if($scope.account.server.indexOf(server.id) >= 0) {
$scope.account.accountServerObj[server.id] = true;
} else {
$scope.account.accountServerObj[server.id] = false;
}
});
}
$scope.$watch('account.orderId', selectOrder);
$scope.$watch('account.fromOrder', selectOrder);
});
$scope.cancel = () => {
$state.go('admin.accountPage', { accountId: $stateParams.accountId });
};
$scope.confirm = () => {
$scope.account.autoRemoveDelay = $filter('timeStr2Num')($scope.account.autoRemoveDelayStr);
alertDialog.loading();
$scope.account.flow = $filter('flowStr2Num')($scope.account.flowStr);
const server = Object.keys($scope.account.accountServerObj).map(m => $scope.account.accountServerObj[m] ? +m : null).filter(f => f);
$http.put(`/api/admin/account/${ accountId }/data`, {
type: +$scope.account.type,
orderId: $scope.account.fromOrder ? +$scope.account.orderId : 0,
port: +$scope.account.port,
password: $scope.account.password,
time: $scope.account.time,
limit: +$scope.account.limit,
flow: +$scope.account.flow,
cleanFlow: $scope.account.cleanFlow,
autoRemove: $scope.account.autoRemove ? 1 : 0,
autoRemoveDelay: $scope.account.autoRemoveDelay,
multiServerFlow: $scope.account.multiServerFlow ? 1 : 0,
server: $scope.account.accountServer ? server : null,
}).then(success => {
alertDialog.show('修改账号成功', '确定');
$state.go('admin.accountPage', { accountId: $stateParams.accountId });
}).catch(() => {
alertDialog.show('修改账号失败', '确定');
});
};
$scope.pickTime = () => {
$mdBottomSheet.show({
templateUrl: '/public/views/admin/pickTime.html',
preserveScope: true,
scope: $scope,
});
};
$scope.setStartTime = (number) => {
$scope.account.time += number;
};
$scope.setStartTimeToCurrentTime = () => {
$scope.account.time = Date.now();
};
$scope.setLimit = (number) => {
$scope.account.limit += number;
if($scope.account.limit < 1) {
$scope.account.limit = 1;
}
};
$scope.deleteAccount = () => {
confirmDialog.show({
text: '真的要删除账号吗?',
cancel: '取消',
confirm: '删除',
error: '删除账号失败',
fn: function () { return $http.delete('/api/admin/account/' + accountId); },
}).then(() => {
$state.go('admin.account');
});
};
}
]);