├── assets
├── CNAME
└── 404.html
├── docs
├── CNAME
├── fonts
│ ├── ui-grid.eot
│ ├── ui-grid.ttf
│ ├── ui-grid.woff
│ ├── materialdesignicons-webfont.eot
│ ├── materialdesignicons-webfont.ttf
│ ├── materialdesignicons-webfont.woff
│ ├── materialdesignicons-webfont.woff2
│ └── ui-grid.svg
├── assets
│ └── images
│ │ ├── bg.jpg
│ │ └── logo.png
├── 404.html
├── index.html
└── styles
│ ├── app-9c669350e7.css
│ └── app-7900bfb0cd.css
├── .bowerrc
├── gulp
├── .eslintrc
├── scripts.js
├── favicon.js
├── watch.js
├── documentation.js
├── styles.js
├── inject.js
├── server.js
├── conf.js
└── build.js
├── media
├── screen1.png
├── screen2.png
├── screen3.png
├── screen4.jpg
└── screen5.jpg
├── src
├── assets
│ └── images
│ │ ├── bg.jpg
│ │ └── logo.png
├── app
│ ├── table
│ │ ├── table.html
│ │ ├── table.js
│ │ ├── table-container.js
│ │ └── table-container.html
│ ├── base
│ │ ├── 404.html
│ │ ├── breadcrumbs.html
│ │ ├── header.js
│ │ ├── base.html
│ │ ├── sidebar.html
│ │ ├── header.html
│ │ └── sidebar.js
│ ├── index.app.js
│ ├── index.run.js
│ ├── filters
│ │ └── filesize.js
│ ├── services
│ │ ├── http_interceptor.js
│ │ ├── theme.js
│ │ └── api.js
│ ├── login
│ │ ├── login.js
│ │ └── login.html
│ ├── index.route.js
│ ├── index.config.js
│ └── sql
│ │ ├── ace-mode-clickhouse.js
│ │ ├── sql.html
│ │ └── sql.js
├── less
│ ├── theme.less
│ ├── login.less
│ ├── index.less
│ ├── sidebar.less
│ ├── layout.less
│ └── core.less
└── index.html
├── .gitignore
├── README.md
├── .dockerignore
├── .editorconfig
├── Dockerfile
├── gulpfile.js
├── .eslintrc
├── LICENSE.txt
├── bower.json
└── package.json
/assets/CNAME:
--------------------------------------------------------------------------------
1 | guiclickhouse.smi2.ru
--------------------------------------------------------------------------------
/docs/CNAME:
--------------------------------------------------------------------------------
1 | guiclickhouse.smi2.ru
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/gulp/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "node": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/media/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/media/screen1.png
--------------------------------------------------------------------------------
/media/screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/media/screen2.png
--------------------------------------------------------------------------------
/media/screen3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/media/screen3.png
--------------------------------------------------------------------------------
/media/screen4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/media/screen4.jpg
--------------------------------------------------------------------------------
/media/screen5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/media/screen5.jpg
--------------------------------------------------------------------------------
/docs/fonts/ui-grid.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/fonts/ui-grid.eot
--------------------------------------------------------------------------------
/docs/fonts/ui-grid.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/fonts/ui-grid.ttf
--------------------------------------------------------------------------------
/docs/fonts/ui-grid.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/fonts/ui-grid.woff
--------------------------------------------------------------------------------
/src/assets/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/src/assets/images/bg.jpg
--------------------------------------------------------------------------------
/docs/assets/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/assets/images/bg.jpg
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/src/assets/images/logo.png
--------------------------------------------------------------------------------
/docs/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/assets/images/logo.png
--------------------------------------------------------------------------------
/docs/fonts/materialdesignicons-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/fonts/materialdesignicons-webfont.eot
--------------------------------------------------------------------------------
/docs/fonts/materialdesignicons-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/fonts/materialdesignicons-webfont.ttf
--------------------------------------------------------------------------------
/docs/fonts/materialdesignicons-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/fonts/materialdesignicons-webfont.woff
--------------------------------------------------------------------------------
/docs/fonts/materialdesignicons-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/smi2/clickhouse-frontend/HEAD/docs/fonts/materialdesignicons-webfont.woff2
--------------------------------------------------------------------------------
/src/app/table/table.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | .tmp
4 | coverage
5 | help
6 |
7 | # IntelliJ project files
8 | .idea
9 | *.iml
10 | out
11 | gen
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **Project rename TABIX, this repo depreciation**
2 |
3 |
4 | https://tabix.io
5 |
6 | https://tabix.io/doc/
7 |
8 |
9 | https://github.com/smi2/tabix.ui
10 |
11 | -----
12 | -----
13 | -----
14 | -----
15 | -----
16 |
17 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .git/
3 | .tmp/
4 | .dockerignore
5 | Dockerfile*
6 | bower_components/
7 | docker-compose.*
8 |
9 | .gitignore
10 | .env
11 | .editorconfig
12 | .gitlab-ci.yml
13 | .idea/
14 | .vagrant/
15 | .vscode/
16 | todo
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/assets/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | СМИ2 clickhouse
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | СМИ2 clickhouse
6 |
7 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/base/404.html:
--------------------------------------------------------------------------------
1 |
2 |
404 {{'не найдено' | translate}}
3 |
4 |
5 | {{ 'Назад' | translate }}
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:6.7.0
2 |
3 | RUN npm install -g bower && npm update -g bower && \
4 | npm install -g gulp && npm update -g gulp
5 |
6 | COPY . /app
7 |
8 | WORKDIR /app
9 |
10 | RUN npm install && \
11 | bower install --allow-root && \
12 | gulp build
13 |
14 | EXPOSE 3000
15 |
16 | CMD ["gulp", "serve:dist"]
17 |
--------------------------------------------------------------------------------
/src/less/theme.less:
--------------------------------------------------------------------------------
1 | @border: #4d4d4d;
2 | //@light: #dddddd;
3 |
4 | md-input-container {
5 | margin: 4px 0px;
6 | }
7 |
8 | a {
9 | color: inherit;
10 | text-decoration: none;
11 | }
12 |
13 | md-toast.md-dark-theme {
14 | .md-toast-content {
15 | background-color: #666;
16 | }
17 | }
18 |
19 | md-card {
20 | border-radius: 0;
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/base/breadcrumbs.html:
--------------------------------------------------------------------------------
1 |
2 | -
3 |
4 |
5 | -
6 |
7 | {{nav.text}}
8 | {{nav.text}}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/table/table.js:
--------------------------------------------------------------------------------
1 | (( angular, smi2 ) => {
2 | 'use strict';
3 |
4 | angular.module( smi2.app.name ).controller( 'TableController', TableController );
5 | TableController.$inject = [ '$scope', '$stateParams' ];
6 |
7 | /**
8 | * @ngdoc controller
9 | * @name smi2.controller:TableController
10 | * @description Контроллер страницы 1 таблицы БД
11 | */
12 | function TableController( $scope, $stateParams ) {
13 | let { dbName, tableName } = $stateParams;
14 | $scope.vars = {
15 | dbName,
16 | tableName
17 | };
18 |
19 | }
20 | })( angular, smi2 );
21 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Welcome to your gulpfile!
3 | * The gulp tasks are splitted in several files in the gulp directory
4 | * because putting all here was really too long
5 | */
6 |
7 | 'use strict';
8 |
9 | var gulp = require('gulp');
10 | var glob = require('glob');
11 |
12 | /**
13 | * This will load all js or coffee files in the gulp directory
14 | * in order to load all gulp tasks
15 | */
16 | glob.sync('./gulp/**/*.js').forEach(function (file) {
17 | require(file);
18 | });
19 |
20 | /**
21 | * Default task clean temporaries directories and launch the
22 | * main optimization build task
23 | */
24 | gulp.task('default', ['clean'], function () {
25 | gulp.start('build');
26 | });
27 |
--------------------------------------------------------------------------------
/src/app/index.app.js:
--------------------------------------------------------------------------------
1 | (() => {
2 | 'use strict';
3 |
4 | var smi2 = window.smi2 = window.smi2 || {};
5 | smi2.app = {
6 | name: 'SMI2',
7 | version: window.clickhouseGuiVersion || ""
8 | };
9 |
10 | // External libs connection
11 | angular.module(smi2.app.name, [
12 | 'ngAnimate',
13 | 'ui.router',
14 | 'LocalStorageModule',
15 | 'angularScreenfull',
16 | 'ui.ace',
17 | 'ui.grid',
18 | 'ui.grid.autoResize',
19 | 'angularResizable',
20 | 'ngSanitize',
21 | 'ngMaterial',
22 | 'funMetisMenu',
23 | 'ngScrollbars',
24 | 'ngCsv',
25 | 'pascalprecht.translate'
26 | ]);
27 | })();
28 |
--------------------------------------------------------------------------------
/gulp/scripts.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 |
9 | var $ = require('gulp-load-plugins')();
10 |
11 |
12 | gulp.task('scripts-reload', function() {
13 | return buildScripts()
14 | .pipe(browserSync.stream());
15 | });
16 |
17 | gulp.task('scripts', function() {
18 | return buildScripts();
19 | });
20 |
21 | function buildScripts() {
22 | return gulp.src(path.join(conf.paths.src, '/app/**/*.js'))
23 | .pipe($.eslint())
24 | .pipe($.eslint.format())
25 | .pipe($.babel({
26 | presets: ['es2015', 'stage-0']
27 | }))
28 | .on('error', conf.errorHandler('babel'))
29 | .pipe($.size())
30 | };
31 |
--------------------------------------------------------------------------------
/src/app/index.run.js:
--------------------------------------------------------------------------------
1 | (() => {
2 | 'use strict';
3 |
4 | /**
5 | * @ngdoc controller
6 | * @name smi2.controller:Run
7 | * @description Main APP controller
8 | */
9 | angular
10 | .module(smi2.app.name)
11 | .run([
12 | '$rootScope',
13 | '$state',
14 | ($rootScope, $state) => {
15 |
16 | $rootScope.breadcrumbs = [];
17 | $rootScope.currentDatabase = null;
18 |
19 | // Провеярю в чем ошибка перехода на state
20 | var stateChangeErrorUnbind = $rootScope.$on('$stateChangeError', (toState, toParams, fromState, fromParams, error, reason) => {
21 | if (reason == 'notAuthorized') {
22 | $state.go('login');
23 | }
24 | });
25 |
26 | // Требование JSlinter'a (((
27 | $rootScope.$on('$destroy', () => stateChangeErrorUnbind);
28 | }
29 | ]);
30 | })();
31 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | СМИ2 clickhouse
--------------------------------------------------------------------------------
/src/app/base/header.js:
--------------------------------------------------------------------------------
1 | ((angular, smi2) => {
2 | 'use strict';
3 |
4 | angular.module(smi2.app.name).controller('HeaderController', HeaderController);
5 | HeaderController.$inject = ['$scope', '$state', 'API', 'ThemeService'];
6 |
7 | /**
8 | * @ngdoc controller
9 | * @name smi2.controller:HeaderController
10 | * @description Controller for layout header
11 | */
12 | function HeaderController($scope, $state, API, ThemeService) {
13 | $scope.user = API.getConnectionInfo().name;
14 | $scope.themes = ThemeService.list;
15 |
16 | /**
17 | * Logout )
18 | */
19 | $scope.logout = () => {
20 | API.clear();
21 | $state.go('login');
22 | };
23 |
24 | /**
25 | * Change UI theme
26 | * @param theme
27 | */
28 | $scope.setUiTheme = (theme) => ThemeService.set(theme.name);
29 | }
30 | })(angular, smi2);
31 |
--------------------------------------------------------------------------------
/src/app/filters/filesize.js:
--------------------------------------------------------------------------------
1 | ((angular, smi2) => {
2 | 'use strict';
3 |
4 | angular
5 | .module(smi2.app.name)
6 | .filter('filesize', () => {
7 | var units = [
8 | 'bytes',
9 | 'KB',
10 | 'MB',
11 | 'GB',
12 | 'TB',
13 | 'PB'
14 | ];
15 |
16 | return (bytes, precision) => {
17 | if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) {
18 | return '?';
19 | }
20 |
21 | var unit = 0;
22 |
23 | while (bytes >= 1024) {
24 | bytes /= 1024;
25 | unit++;
26 | }
27 |
28 | return bytes.toFixed(+precision) + ' ' + units[unit];
29 | };
30 | });
31 | })(angular, smi2);
32 |
--------------------------------------------------------------------------------
/src/app/services/http_interceptor.js:
--------------------------------------------------------------------------------
1 | ((angular, smi2) => {
2 | 'use strict';
3 |
4 | angular.module(smi2.app.name).service('HttpInterceptor', HttpInterceptor);
5 | HttpInterceptor.$inject = ['$q', '$injector'];
6 |
7 | /**
8 | * @ngdoc service
9 | * @name smi2.service:HttpInterceptor
10 | * @description
11 | * Service from handle HTTP requests
12 | */
13 | function HttpInterceptor($q, $injector) {
14 | return {
15 | /**
16 | * @ngdoc
17 | * @name responseError
18 | * @description Handle HTTP errors
19 | * @param {mixed} rejection
20 | * @methodOf smi2.service:HttpInterceptor
21 | * @return {promise} $q promise
22 | */
23 | responseError: (rejection) => {
24 |
25 | // If not authorized -> go to login page
26 | if (rejection.status == 401) {
27 | $injector.get('$state').go('login');
28 | }
29 | return $q.reject(rejection);
30 | }
31 | };
32 | }
33 | })(angular, smi2);
34 |
--------------------------------------------------------------------------------
/gulp/favicon.js:
--------------------------------------------------------------------------------
1 | var favicons = require('gulp-favicons'),
2 | gutil = require("gulp-util");
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | gulp.task("favicons", function() {
8 | return gulp.src(path.join(conf.paths.src, '/assets/images/test-favicon.png')).pipe(favicons({
9 | path: "favicons/",
10 | display: "standalone",
11 | orientation: "portrait",
12 | online: false,
13 | html: false,
14 | replace: true,
15 | icons: {
16 | android: false,
17 | appleIcon: false,
18 | appleStartup: false,
19 | coast: false,
20 | favicons: true,
21 | firefox: false,
22 | opengraph: false,
23 | twitter: false,
24 | windows: false,
25 | yandex: false
26 | }
27 | }))
28 | .on("error", gutil.log)
29 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/favicons/localhost/')));
30 | });
31 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "plugins": ["angular", "es6-recommended"],
4 | "env": {
5 | "browser": true,
6 | "jasmine": true,
7 | "es6": true
8 | },
9 | "globals": {
10 | "angular": true,
11 | "smi2": true,
12 | "module": true,
13 | "inject": true,
14 | "moment": true
15 | },
16 | "rules": {
17 | "semi": 2,
18 | "angular/window-service":0,
19 | "angular/on-watch":0,
20 |
21 | // Нет объективных причин почему вместо $scope используется this.
22 | // Никаких изменений в производительности, памяти.
23 | // Хотя JohnPapa настаивает на this
24 | // https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#style-y031
25 | "angular/controller-as": 0,
26 | "angular/controller-as-route":0,
27 |
28 | "angular/module-getter":0,
29 | "angular/di":0,
30 | "angular/log":0,
31 | "no-console":0
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2011-2016 Smi2, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/src/less/login.less:
--------------------------------------------------------------------------------
1 | .login-view {
2 | position: relative;
3 | }
4 | .login-view .login-checking {
5 | display: -webkit-box;
6 | display: -ms-flexbox;
7 | display: flex;
8 | -webkit-box-orient: vertical;
9 | -webkit-box-direction: normal;
10 | -ms-flex-direction: column;
11 | flex-direction: column;
12 | -webkit-box-pack: center;
13 | -ms-flex-pack: center;
14 | justify-content: center;
15 | -webkit-box-align: center;
16 | -ms-flex-align: center;
17 | align-items: center;
18 | z-index: 10;
19 | background: rgba(0,0,0,0.6);
20 | text-align: center;
21 | position: absolute;
22 | width: 100%;
23 | height: 100%;
24 | }
25 | .login-view .login-checking .loading-text {
26 | -webkit-animation: pulse 0.8s linear infinite;
27 | animation: pulse 0.8s linear infinite;
28 | }
29 | .login-view .card__header {
30 | border-bottom: 1px solid rgba(0,0,0,0.2);
31 | }
32 | .login-view .login-message {
33 | padding: 8px;
34 | }
35 | .login-view .login-message.error {
36 | background-color: #f44336;
37 | }
38 | .login-view .login-message.success {
39 | background-color: #4caf50;
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/base/base.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{'Открыть таблицу' | translate}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/less/index.less:
--------------------------------------------------------------------------------
1 | @import 'core.less';
2 | @import 'layout.less';
3 | @import 'login.less';
4 | @import 'theme.less';
5 | @import 'sidebar.less';
6 |
7 | .logo {
8 | width: 24px;
9 | height: 24px;
10 | margin-left: 10px;
11 | float: left;
12 | padding-top: 7px;
13 | }
14 |
15 | a {
16 | text-decoration: none;
17 | }
18 |
19 | .sql-table {
20 | th {
21 | background-color: rgba(200,200,200,0.2);
22 | }
23 | td, th {
24 | white-space: nowrap;
25 | text-align: left;
26 | padding: 5px;
27 | }
28 | .sql-table__subheader {
29 | font-size: 80%;
30 | opacity: 0.7;
31 | }
32 | tr:nth-child(even) {
33 | ///background: #f2f2f2;
34 | }
35 | }
36 |
37 | pre {
38 | tab-size: 2;
39 | }
40 |
41 | .fullscreen {
42 | width: 100%;
43 | height: 100%;
44 | &.default {
45 | background-color: rgb(250,250,250);
46 | }
47 | &.dark {
48 | background-color: rgb(48,48,48);
49 | }
50 | }
51 |
52 | *:-webkit-full-screen {
53 | background-color: inherit;
54 | }
55 |
56 | .sql-tabs {
57 | md-tabs-wrapper {
58 | padding-right: 48px;
59 | }
60 | md-next-button {
61 | right: 48px;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/gulp/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 |
9 | function isOnlyChange(event) {
10 | return event.type === 'changed';
11 | }
12 |
13 | gulp.task('watch', ['favicons','inject'], function () {
14 |
15 | gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject-reload']);
16 |
17 | gulp.watch([
18 | path.join(conf.paths.src, '/less/**/*.css'),
19 | path.join(conf.paths.src, '/less/**/*.less')
20 | ], function(event) {
21 | if(isOnlyChange(event)) {
22 | gulp.start('styles-reload');
23 | } else {
24 | gulp.start('inject-reload');
25 | }
26 | });
27 |
28 | gulp.watch(path.join(conf.paths.src, '/app/**/*.js'), function(event) {
29 | if(isOnlyChange(event)) {
30 | gulp.start('scripts-reload');
31 | } else {
32 | gulp.start('inject-reload');
33 | }
34 | });
35 |
36 | gulp.watch(path.join(conf.paths.src, '/app/assets/images/favicons/**/*'), function(event) {
37 | gulp.start('favicons');
38 | });
39 |
40 |
41 | gulp.watch(path.join(conf.paths.src, '/app/**/*.{json,html}'), function(event) {
42 | browserSync.reload(event.path);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/src/app/base/sidebar.html:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/src/less/sidebar.less:
--------------------------------------------------------------------------------
1 | .sidebar-view {
2 | &.login{
3 | z-index:3
4 | }
5 | margin-right: 14px;
6 | margin-top: 40px;
7 |
8 | .sidebar {
9 | overflow: hidden;
10 | height: 100%;
11 | padding-left: 5px;
12 |
13 | .mCSB_container_wrapper {
14 | margin-right: 5px !important;
15 |
16 | .mCSB_container {
17 | padding-right: 0 !important;
18 | }
19 | }
20 |
21 | .metismenu {
22 | -webkit-margin-before: 0;
23 | -webkit-padding-start: 0;
24 |
25 | ul {
26 | list-style: none;
27 |
28 | li {
29 | position: relative;
30 | padding-right: 5px;
31 |
32 | ul.sub-menu {
33 | -webkit-padding-start: 0;
34 | margin-left: 20px;
35 | }
36 |
37 | button {
38 | width: 100%;
39 | text-overflow: ellipsis;
40 | margin: 0;
41 |
42 | i {
43 | margin-right: 5px;
44 | }
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/app/services/theme.js:
--------------------------------------------------------------------------------
1 | ((angular, smi2) => {
2 | 'use strict';
3 |
4 | angular.module(smi2.app.name).service('ThemeService', ThemeService);
5 | ThemeService.$inject = ['localStorageService', '$filter'];
6 |
7 | /**
8 | * @ngdoc service
9 | * @name smi2.service:ThemeService
10 | * @description Theme service
11 | */
12 | function ThemeService(localStorageService, $filter) {
13 |
14 | const themeName = localStorageService.get('themeName') || 'dark';
15 | const list = [{
16 | isDark: false,
17 | name: 'default',
18 | title: $filter('translate')('Светлая тема')
19 | }, {
20 | isDark: true,
21 | name: 'dark',
22 | title: $filter('translate')('Темная тема')
23 | }];
24 | let theme = list.find((item) => (item.name == themeName)) || list[0];
25 |
26 | return {
27 | isDark: () => theme.isDark,
28 | theme: theme.name,
29 | themeObject: theme,
30 | set: (name) => {
31 | if (name != themeName) {
32 | localStorageService.set('themeName', name);
33 | location.href = location.href;
34 | }
35 | },
36 | list
37 | };
38 | }
39 | })(angular, smi2);
40 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SMI2-clickhouse",
3 | "version": "1.0.0",
4 | "dependencies": {
5 | "angular-animate": "~1.5.0",
6 | "jquery": "~2.1.4",
7 | "angular-ui-router": "~0.2.15",
8 | "angular": "~1.4.12",
9 | "angular-ui-ace": "~0.2.3",
10 | "angular-local-storage": "~0.2.7",
11 | "angular-screenfull": "^0.1.1",
12 | "angular-ui-grid": "^3.2.9",
13 | "screenfull": "~3.0.2",
14 | "angular-sanitize": "^1.5.8",
15 | "angular-material": "^1.1.1",
16 | "mdi": "^1.7.22",
17 | "ng-scrollbars": "^0.0.11",
18 | "fun-metis-menu": "*",
19 | "angular-resizable": "^1.2.0",
20 | "ng-csv": "^0.3.6",
21 | "angular-translate": "^2.12.1",
22 | "angular-translate-handler-log": "^2.13.1"
23 | },
24 | "devDependencies": {
25 | "angular-mocks": "~1.5.0"
26 | },
27 | "overrides": {
28 | "screenfull": {
29 | "main": [
30 | "dist/screenfull.min.js"
31 | ]
32 | },
33 | "mdi": {
34 | "main": [
35 | "css/materialdesignicons.css",
36 | "fonts/*"
37 | ]
38 | },
39 | "angular-resizable": {
40 | "main": [
41 | "src/angular-resizable.js",
42 | "src/angular-resizable.css"
43 | ]
44 | }
45 | },
46 | "resolutions": {
47 | "jquery": "~2.1.4",
48 | "angular": "~1.4.12"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/gulp/documentation.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 | var ngdocs = require('gulp-ngdocs');
5 | var conf = require('./conf');
6 | var path = require('path');
7 | var browserSync = require('browser-sync');
8 | var browserSyncSpa = require('browser-sync-spa');
9 |
10 | /**
11 | * Генерация основы ngDoc приложения
12 | */
13 | gulp.task('docs:generate', [], function() {
14 | return gulp.src('./src/**/*.js')
15 | .pipe(ngdocs.process({
16 | scripts: [
17 | 'bower_components/angular/angular.js',
18 | 'bower_components/angular-animate/angular-animate.min.js'
19 | ],
20 | loadDefaults: {},
21 | html5Mode: false,
22 | startPage: '/api',
23 | title: "Документация Clickhouse Frontend",
24 | image: "https://smi2.net/wp-content/uploads/2016/02/icon-smi2.png",
25 | imageLink: "http://smi2.net",
26 | titleLink: "/api"
27 | }))
28 | .pipe(gulp.dest('./' + conf.paths.docs));
29 | });
30 |
31 | /**
32 | * Запуск сервера, по-другому приложение
33 | * запускаться отказалось
34 | */
35 | gulp.task('docs', ['docs:generate'], function() {
36 | browserSync.use(browserSyncSpa({
37 | selector: '[ng-app]' // Only needed for angular apps
38 | }));
39 |
40 | browserSync.instance = browserSync.init({
41 | startPath: '/',
42 | server: {
43 | baseDir: './' + conf.paths.docs
44 | },
45 | port: 3010,
46 | browser: 'default'
47 | });
48 | gulp.watch(path.join(conf.paths.src, '/**/*.js'), function(event) {
49 | gulp.start('docs:generate');
50 | browserSync.reload(event.path);
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/gulp/styles.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 |
7 | var browserSync = require('browser-sync');
8 |
9 | var $ = require('gulp-load-plugins')();
10 |
11 | var wiredep = require('wiredep').stream;
12 | var _ = require('lodash');
13 |
14 | gulp.task('styles-reload', ['styles'], function() {
15 | return buildStyles()
16 | .pipe(browserSync.stream());
17 | });
18 |
19 | gulp.task('styles', function() {
20 | return buildStyles();
21 | });
22 |
23 | var buildStyles = function() {
24 | var lessOptions = {
25 | options: [
26 | 'bower_components',
27 | path.join(conf.paths.src, '/')
28 | ]
29 | };
30 |
31 | var injectFiles = gulp.src([
32 | path.join(conf.paths.src, '/less/*.less'),
33 | path.join('!' + conf.paths.src, '/less/index.less')
34 | ], { read: false });
35 |
36 | var injectOptions = {
37 | transform: function(filePath) {
38 | filePath = filePath.replace(conf.paths.src + '/less/', '');
39 | return '@import "' + filePath + '";';
40 | },
41 | starttag: '// injector',
42 | endtag: '// endinjector',
43 | addRootSlash: false
44 | };
45 |
46 |
47 | return gulp.src([
48 | path.join(conf.paths.src, '/less/index.less')
49 | ])
50 | .pipe($.inject(injectFiles, injectOptions))
51 | .pipe(wiredep(_.extend({}, conf.wiredep)))
52 | .pipe($.sourcemaps.init())
53 | .pipe($.less(lessOptions)).on('error', conf.errorHandler('Less'))
54 | .pipe($.autoprefixer()).on('error', conf.errorHandler('Autoprefixer'))
55 | .pipe($.sourcemaps.write())
56 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve/app/')));
57 | };
58 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | СМИ2 clickhouse
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/gulp/inject.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 | var packageJson = require('../package.json');
7 |
8 | var $ = require('gulp-load-plugins')();
9 |
10 | var wiredep = require('wiredep').stream;
11 | var _ = require('lodash');
12 |
13 | var browserSync = require('browser-sync');
14 |
15 | gulp.task('inject-reload', ['inject'], function() {
16 | browserSync.reload();
17 | });
18 |
19 | gulp.task('inject', ['scripts', 'styles'], function() {
20 | var injectStyles = gulp.src([
21 | path.join(conf.paths.tmp, '/serve/app/**/*.css'),
22 | path.join('!' + conf.paths.tmp, '/serve/app/vendor.css')
23 | ], {
24 | read: false
25 | });
26 |
27 | var injectScripts = gulp.src([
28 | path.join(conf.paths.src, '/app/**/*.app.js'),
29 | path.join(conf.paths.src, '/app/**/*.js'),
30 | path.join('!' + conf.paths.src, '/app/**/*.test.js'),
31 | path.join('!' + conf.paths.src, '/app/**/*.mock.js'),
32 | ])
33 | .pipe($.babel({
34 | presets: ['es2015', 'stage-0']
35 | }))
36 | .on('error', conf.errorHandler('babel'))
37 | .pipe($.angularFilesort())
38 | .on('error', conf.errorHandler('AngularFilesort'));
39 |
40 | var injectOptions = {
41 | ignorePath: [conf.paths.src, path.join(conf.paths.tmp, '/serve')],
42 | addRootSlash: false
43 | };
44 |
45 | return gulp.src(path.join(conf.paths.src, '/*.html'))
46 | .pipe($.inject(injectStyles, injectOptions))
47 | .pipe($.inject(injectScripts, injectOptions))
48 | .pipe($.replace('', ''))
49 | .pipe(wiredep(_.extend({}, conf.wiredep)))
50 | .pipe(gulp.dest(path.join(conf.paths.tmp, '/serve')));
51 | });
52 |
--------------------------------------------------------------------------------
/src/app/base/header.html:
--------------------------------------------------------------------------------
1 |
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "SMI2-clickhouse",
3 | "version": "1.4.8",
4 | "license": "MIT",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/smi2/clickhouse-frontend.git"
8 | },
9 | "dependencies": {
10 | "glob": "^7.0.3",
11 | "gulp-ngdocs": "^0.2.13",
12 | "marked": "^0.3.5"
13 | },
14 | "scripts": {
15 | "test": "gulp test"
16 | },
17 | "devDependencies": {
18 | "babel-preset-es2015": "^6.18.0",
19 | "babel-preset-stage-0": "^6.16.0",
20 | "browser-sync": "~2.9.11",
21 | "browser-sync-spa": "~1.0.3",
22 | "chalk": "~1.1.1",
23 | "del": "~2.0.2",
24 | "eslint-plugin-angular": "~0.12.0",
25 | "eslint-plugin-es6-recommended": "^0.1.2",
26 | "estraverse": "~4.1.0",
27 | "gulp": "~3.9.0",
28 | "gulp-angular-filesort": "~1.1.1",
29 | "gulp-angular-templatecache": "~1.8.0",
30 | "gulp-autoprefixer": "~3.0.2",
31 | "gulp-babel": "^6.1.2",
32 | "gulp-eslint": "~1.0.0",
33 | "gulp-favicons": "^2.2.6",
34 | "gulp-filter": "~3.0.1",
35 | "gulp-flatten": "~0.2.0",
36 | "gulp-inject": "~3.0.0",
37 | "gulp-less": "~3.0.3",
38 | "gulp-load-plugins": "~0.10.0",
39 | "gulp-minify-css": "~1.2.1",
40 | "gulp-minify-html": "~1.0.4",
41 | "gulp-ng-annotate": "~1.1.0",
42 | "gulp-rename": "~1.2.2",
43 | "gulp-replace": "~0.5.4",
44 | "gulp-rev": "~6.0.1",
45 | "gulp-rev-replace": "~0.4.2",
46 | "gulp-size": "~2.0.0",
47 | "gulp-sourcemaps": "~1.6.0",
48 | "gulp-uglify": "~1.4.1",
49 | "gulp-useref": "~1.3.0",
50 | "gulp-util": "~3.0.6",
51 | "lodash": "~3.10.1",
52 | "main-bower-files": "~2.9.0",
53 | "uglify-save-license": "~0.4.1",
54 | "wiredep": "~2.2.2"
55 | },
56 | "engines": {
57 | "node": ">=0.10.0"
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/gulp/server.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 | var browserSync = require('browser-sync');
7 | var browserSyncSpa = require('browser-sync-spa');
8 |
9 | var util = require('util');
10 |
11 | function browserSyncInit(baseDir, browser) {
12 | browser = browser === undefined ? 'default' : browser;
13 |
14 | var routes = null;
15 | if(baseDir === conf.paths.src || (util.isArray(baseDir) && baseDir.indexOf(conf.paths.src) !== -1)) {
16 | routes = {
17 | '/bower_components': 'bower_components',
18 | '/.tmp': '.tmp'
19 | };
20 | }
21 |
22 | var server = {
23 | baseDir: baseDir,
24 | routes: routes
25 | };
26 |
27 | /*
28 | * You can add a proxy to your backend by uncommenting the line below.
29 | * You just have to configure a context which will we redirected and the target url.
30 | * Example: $http.get('/users') requests will be automatically proxified.
31 | *
32 | * For more details and option, https://github.com/chimurai/http-proxy-middleware/blob/v0.9.0/README.md
33 | */
34 | browserSync.instance = browserSync.init({
35 | startPath: '/',
36 | server: server,
37 | browser: browser
38 | });
39 | }
40 |
41 | browserSync.use(browserSyncSpa({
42 | selector: '[ng-app]'// Only needed for angular apps
43 | }));
44 |
45 | gulp.task('serve', ['watch'], function () {
46 | browserSyncInit([path.join(conf.paths.tmp, '/serve'), conf.paths.src]);
47 | });
48 |
49 | gulp.task('serve:dist', ['build'], function () {
50 | browserSyncInit(conf.paths.dist);
51 | });
52 |
53 | gulp.task('serve:e2e', ['inject'], function () {
54 | browserSyncInit([conf.paths.tmp + '/serve', conf.paths.src], []);
55 | });
56 |
57 | gulp.task('serve:e2e-dist', ['build'], function () {
58 | browserSyncInit(conf.paths.dist, []);
59 | });
60 |
--------------------------------------------------------------------------------
/src/app/login/login.js:
--------------------------------------------------------------------------------
1 | (function (angular, smi2) {
2 | 'use strict';
3 |
4 | angular.module(smi2.app.name).controller('LoginController', LoginController);
5 | LoginController.$inject = [
6 | '$scope',
7 | '$state',
8 | 'localStorageService',
9 | 'API',
10 | '$mdToast',
11 | 'ThemeService'
12 | ];
13 |
14 | /**
15 | * @ngdoc controller
16 | * @name smi2.controller:LoginController
17 | * @description Login page controller
18 | */
19 | function LoginController($scope, $state, localStorageService, API, $mdToast, ThemeService) {
20 |
21 | const ALL_BASES_KEY = 'basesConfig';
22 |
23 | $scope.vars = {
24 | bases: localStorageService.get(ALL_BASES_KEY) || [],
25 | db: {},
26 | loading: false,
27 | version: smi2.app.version,
28 | themes: ThemeService.list
29 | };
30 |
31 | /**
32 | * Login with saving connection settings
33 | */
34 | $scope.login = () => {
35 | $scope.vars.loading = true;
36 |
37 | // Saving to LocalStorage
38 | if ($scope.vars.db.id) {
39 | for (var i = 0; i < $scope.vars.bases.length; i++) {
40 | if ($scope.vars.bases[i].id == $scope.vars.db.id) {
41 | $scope.vars.bases[i] = $scope.vars.db;
42 | break;
43 | }
44 | }
45 | } else {
46 | $scope.vars.db.id = (new Date()).getTime();
47 | $scope.vars.bases.push($scope.vars.db);
48 | }
49 | localStorageService.set(ALL_BASES_KEY, $scope.vars.bases);
50 |
51 | API.setConnection($scope.vars.db);
52 | API.query('SELECT \'login success\'').then(
53 | () => $state.go('sql'),
54 | () => {
55 | $scope.vars.loading = false;
56 | $mdToast.show(
57 | $mdToast
58 | .simple()
59 | .content('Ошибка доступа')
60 | .theme(ThemeService.theme)
61 | .position('bottom right')
62 | );
63 | }
64 | );
65 | };
66 |
67 | /**
68 | * Remove connection item
69 | */
70 | $scope.remove = () => {
71 | const index = $scope.vars.bases.findIndex((item) => (item.id == $scope.vars.db.id));
72 | $scope.vars.bases.splice(index);
73 | localStorageService.set(ALL_BASES_KEY, $scope.vars.bases);
74 | $scope.vars.db = {};
75 | };
76 |
77 | /**
78 | * Change UI theme
79 | * @param theme
80 | */
81 | $scope.setUiTheme = (theme) => ThemeService.set(theme.name);
82 | }
83 | })(angular, smi2);
84 |
--------------------------------------------------------------------------------
/src/app/index.route.js:
--------------------------------------------------------------------------------
1 | ((angular, smi2) => {
2 | 'use strict';
3 |
4 | angular
5 | .module(smi2.app.name)
6 | .config(($stateProvider) => {
7 |
8 | $stateProvider
9 |
10 | // Base routing with auth detection
11 | .state('base', {
12 | abstract: true,
13 | resolve: {
14 | session: ['$q', 'API', ($q, API) => {
15 | var defer = $q.defer();
16 | if (angular.isDefined(API.getConnectionInfo().host)) {
17 | defer.resolve();
18 | } else {
19 | defer.reject('notAuthorized');
20 | }
21 | return defer.promise;
22 | }]
23 | },
24 | templateUrl: 'app/base/base.html'
25 | })
26 |
27 | // Design route
28 | .state('layout', {
29 | parent: 'base',
30 | abstract: true,
31 | views: {
32 | header: {
33 | templateUrl: 'app/base/header.html',
34 | controller: 'HeaderController'
35 | },
36 | sidebar: {
37 | templateUrl: 'app/base/sidebar.html',
38 | controller: 'SidebarController'
39 | },
40 | main: {
41 | template: ''
42 | }
43 | }
44 | })
45 |
46 | // Dashboard state. Now it's only redir to sql state
47 | .state('dashboard', {
48 | parent: 'layout',
49 | url: '/',
50 | controller: ['$state', $state => $state.go('sql')]
51 | })
52 |
53 | // Login state
54 | .state('login', {
55 | url: '/login',
56 | templateUrl: 'app/login/login.html',
57 | controller: 'LoginController'
58 | })
59 |
60 | // SQL state
61 | .state('sql', {
62 | parent: 'layout',
63 | url: '/sql',
64 | templateUrl: 'app/sql/sql.html',
65 | controller: 'SqlController'
66 | })
67 |
68 | // Show 1 table
69 | .state('table', {
70 | parent: 'layout',
71 | url: '/database/{dbName}/table/{tableName}',
72 | templateUrl: 'app/table/table.html',
73 | controller: 'TableController'
74 | })
75 |
76 | // 4040 not found handle
77 | .state('404', {
78 | parent: 'layout',
79 | templateUrl: 'app/base/404.html'
80 | });
81 | });
82 | })(angular, smi2);
83 |
--------------------------------------------------------------------------------
/gulp/conf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file contains the variables used in other gulp files
3 | * which defines tasks
4 | * By design, we only put there very generic config values
5 | * which are used in several places to keep good readability
6 | * of the tasks
7 | */
8 |
9 | var gutil = require('gulp-util');
10 |
11 | /**
12 | * The main paths of your project handle these with care
13 | */
14 | exports.paths = {
15 | src: 'src',
16 | dist: 'docs',
17 | tmp: '.tmp',
18 | e2e: 'e2e',
19 | docs: 'help'
20 | };
21 |
22 | /**
23 | * Wiredep is the lib which inject bower dependencies in your project
24 | * Mainly used to inject script tags in the index.html but also used
25 | * to inject css preprocessor deps and js files in karma
26 | */
27 | exports.wiredep = {
28 | exclude: [/\/bootstrap\.js$/, /\/bootstrap\.css/],
29 | directory: 'bower_components',
30 | overrides: {
31 | 'ace-builds': {
32 | main: [
33 | 'src-min-noconflict/ace.js',
34 | 'src-min-noconflict/ext-language_tools.js',
35 | 'src-min-noconflict/ext-beautify.js',
36 | 'src-min-noconflict/ext-statusbar.js',
37 | 'src-min-noconflict/ext-settings_menu.js',
38 | 'src-min-noconflict/theme-ambiance.js',
39 | 'src-min-noconflict/theme-crimson_editor.js',
40 | 'src-min-noconflict/theme-iplastic.js',
41 | 'src-min-noconflict/theme-mono_industrial.js',
42 | 'src-min-noconflict/theme-terminal.js',
43 | 'src-min-noconflict/theme-tomorrow_night.js',
44 | 'src-min-noconflict/theme-chaos.js',
45 | 'src-min-noconflict/theme-dawn.js',
46 | 'src-min-noconflict/theme-katzenmilch.js',
47 | 'src-min-noconflict/theme-monokai.js',
48 | 'src-min-noconflict/theme-textmate.js',
49 | 'src-min-noconflict/theme-twilight.js',
50 | 'src-min-noconflict/theme-chrome.js',
51 | 'src-min-noconflict/theme-dreamweaver.js',
52 | 'src-min-noconflict/theme-kr_theme.js',
53 | 'src-min-noconflict/theme-pastel_on_dark.js',
54 | 'src-min-noconflict/theme-tomorrow.js',
55 | 'src-min-noconflict/theme-vibrant_ink.js',
56 | 'src-min-noconflict/theme-clouds.js',
57 | 'src-min-noconflict/theme-eclipse.js',
58 | 'src-min-noconflict/theme-kuroir.js',
59 | 'src-min-noconflict/theme-solarized_dark.js',
60 | 'src-min-noconflict/theme-tomorrow_night_blue.js',
61 | 'src-min-noconflict/theme-xcode.js',
62 | 'src-min-noconflict/theme-clouds_midnight.js',
63 | 'src-min-noconflict/theme-github.js',
64 | 'src-min-noconflict/theme-merbivore.js',
65 | 'src-min-noconflict/theme-solarized_light.js',
66 | 'src-min-noconflict/theme-tomorrow_night_bright.js',
67 | 'src-min-noconflict/theme-cobalt.js',
68 | 'src-min-noconflict/theme-idle_fingers.js',
69 | 'src-min-noconflict/theme-merbivore_soft.js',
70 | 'src-min-noconflict/theme-sqlserver.js',
71 | 'src-min-noconflict/theme-tomorrow_night_eighties.js'
72 | ]
73 | }
74 | }
75 | };
76 |
77 | /**
78 | * Common implementation for an error handler of a Gulp plugin
79 | */
80 | exports.errorHandler = function(title) {
81 | 'use strict';
82 |
83 | return function(err) {
84 | gutil.log(gutil.colors.red('[' + title + ']'), err.toString());
85 | this.emit('end');
86 | };
87 | };
88 |
--------------------------------------------------------------------------------
/src/app/base/sidebar.js:
--------------------------------------------------------------------------------
1 | (( angular, smi2 ) => {
2 | 'use strict';
3 |
4 | angular.module( smi2.app.name ).controller( 'SidebarController', SidebarController );
5 | SidebarController.$inject = [
6 | '$scope',
7 | '$rootScope',
8 | '$state',
9 | 'API',
10 | 'ThemeService',
11 | '$mdSidenav'
12 | ];
13 |
14 | /**
15 | * @ngdoc controller
16 | * @name smi2.controller:SidebarController
17 | * @description Контроллер бокового меню
18 | */
19 | function SidebarController( $scope, $rootScope, $state, API, ThemeService, $mdSidenav ) {
20 | $scope.vars = {
21 | loaded: false,
22 | databases: [ ]
23 | };
24 |
25 | $scope.vars.scrollConfig = {
26 | autoHideScrollbar: false,
27 | theme: ThemeService.isDark( )
28 | ? 'light'
29 | : 'dark',
30 | scrollButtons: {
31 | enable: false
32 | },
33 | scrollInertia: 400,
34 | advanced: {
35 | updateOnContentResize: true
36 | }
37 | };
38 |
39 | $scope.vars.metis = {
40 | config: {
41 | toggle: true,
42 | preventDefault: false
43 | }
44 | };
45 |
46 | $scope.clickAndSelect = ( database, event ) => {
47 | if ( database.name == $rootScope.currentDatabase ) {
48 | event.stopPropagation( );
49 | return false;
50 | }
51 | $scope.selectDatabase( database );
52 | };
53 |
54 | /**
55 | * Select database
56 | */
57 | $scope.selectDatabase = database => {
58 | $rootScope.currentDatabase = database.name;
59 | $mdSidenav( 'tableSiedenav' ).close( );
60 | };
61 |
62 | /**
63 | * Open table in right sidebar
64 | */
65 | $scope.openTable = ( table ) => {
66 | $mdSidenav( 'tableSiedenav' ).close( );
67 | $rootScope.currentTable = table.name;
68 | $mdSidenav( 'tableSiedenav' ).open( );
69 | };
70 |
71 | API.query( 'SELECT ' +
72 | ' database, ' +
73 | ' name ' +
74 | 'FROM ' +
75 | ' system.tables' ).then(res => {
76 | let data = res.data || [ ];
77 | $scope.vars.databases = data.reduce(( prev, item ) => {
78 | for ( let a of prev ) {
79 | if ( a.name == item.database ) {
80 | a.tables.push({ name: item.name });
81 | return prev;
82 | }
83 | }
84 | return [
85 | ...prev, {
86 | name: item.database,
87 | tables: [
88 | {
89 | name: item.name
90 | }
91 | ]
92 | }
93 | ];
94 | }, [ ]);
95 |
96 | $scope.selectDatabase($scope.vars.databases[0]);
97 | $scope.vars.loaded = true;
98 | });
99 | }
100 | })( angular, smi2 );
101 |
--------------------------------------------------------------------------------
/src/app/table/table-container.js:
--------------------------------------------------------------------------------
1 | (( angular, smi2 ) => {
2 | 'use strict';
3 |
4 | angular.module( smi2.app.name ).controller( 'TableContainerController', TableController );
5 | TableController.$inject = [
6 | '$scope',
7 | '$rootScope',
8 | 'API',
9 | 'ThemeService',
10 | '$stateParams',
11 | '$mdSidenav',
12 | '$mdComponentRegistry'
13 | ];
14 |
15 | /**
16 | * @ngdoc controller
17 | * @name smi2.controller:TableContainerController
18 | * @description Контроллер страницы 1 таблицы БД
19 | */
20 | function TableController( $scope, $rootScope, API, ThemeService, $stateParams, $mdSidenav, $mdComponentRegistry ) {
21 |
22 | $scope.vars = {
23 | columns: {},
24 | data: null,
25 | grid: null,
26 | limit: 100,
27 | offset: 0,
28 | statistics: {},
29 | loading: false,
30 | scrollConfig: {
31 | autoHideScrollbar: false,
32 | theme: ThemeService.isDark( )
33 | ? 'light'
34 | : 'dark',
35 | scrollButtons: {
36 | enable: false
37 | },
38 | scrollInertia: 100,
39 | advanced: {
40 | updateOnContentResize: true
41 | }
42 | }
43 | };
44 | $scope.initContainer = ( ) => {
45 | $rootScope.$watch('currentTable', ( val ) => {
46 | if ( val ) {
47 | $scope.vars.currentTable = $rootScope.currentTable;
48 | $scope.vars.currentDatabase = $rootScope.currentDatabase;
49 | $scope.init( );
50 | }
51 | });
52 | };
53 |
54 | $scope.initOnGo = ( ) => {
55 | if ( $scope.$parent.vars ) {
56 | if ($mdComponentRegistry.get( 'tableSiedenav' )) {
57 | $mdSidenav( 'tableSiedenav' ).close( );
58 | }
59 | $scope.vars.currentTable = $scope.$parent.vars.tableName;
60 | $scope.vars.currentDatabase = $scope.$parent.vars.dbName;
61 | $scope.init( );
62 |
63 | }
64 | };
65 |
66 | /**
67 | * Загрузка данных
68 | */
69 | $scope.load = ( ) => {
70 | $scope.vars.data = -1;
71 | API.query( `
72 | select *
73 | from ${ $scope.vars.currentDatabase }.${ $scope.vars.currentTable }
74 | limit ${ $scope.vars.offset }, ${ $scope.vars.limit }
75 | ` ).then( function ( data ) {
76 | $scope.vars.data = API.dataToHtml( data );
77 | $scope.vars.loading = false;
78 | }, function ( response ) {
79 | $scope.vars.loading = false;
80 | console.error( 'Ошибка ' + response );
81 | });
82 | };
83 |
84 | $scope.init = ( ) => {
85 | $scope.vars.loading = true;
86 |
87 | /**
88 | * Запрос полей таблицы
89 | */
90 | API.query( 'describe table ' + $scope.vars.currentDatabase + '.' + $scope.vars.currentTable ).then( data => $scope.vars.columns = data );
91 |
92 | /**
93 | * Запрос статистики по таблице
94 | */
95 | API.query( 'SELECT ' +
96 | ' table, ' +
97 | ' formatReadableSize(sum(bytes)) as size, ' +
98 | ' sum(bytes) as sizeBytes, ' +
99 | ' min(min_date) as minDate, ' +
100 | ' max(max_date) as maxDate ' +
101 | 'FROM ' +
102 | ' system.parts ' +
103 | 'WHERE ' +
104 | ' database = \'' + $scope.vars.currentDatabase + '\' AND ' + ' ( ' + ' table = \'' + $scope.vars.currentTable + '\' OR ' + ' table = \'' + $scope.vars.currentTable + '_sharded\'' + ' ) ' + 'GROUP BY ' + ' table ' ).then(response => $scope.vars.statistics = (response && response.data.length && response.data[0]) || {});
105 |
106 | $scope.load( );
107 | };
108 |
109 | /**
110 | * Шаг вперед по данным
111 | */
112 | $scope.loadNext = ( ) => {
113 | $scope.vars.offset += $scope.vars.limit;
114 | $scope.load( );
115 | };
116 |
117 | /**
118 | * Шаг назад по данным
119 | */
120 | $scope.loadPrev = ( ) => {
121 | if ( $scope.vars.offset > 0 ) {
122 | $scope.vars.offset -= $scope.vars.limit;
123 | $scope.load( );
124 | }
125 | };
126 | }
127 | })( angular, smi2 );
128 |
--------------------------------------------------------------------------------
/gulp/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var gulp = require('gulp');
5 | var conf = require('./conf');
6 | var packageJson = require('../package.json');
7 |
8 | var $ = require('gulp-load-plugins')({
9 | pattern: ['gulp-*', 'main-bower-files', 'uglify-save-license', 'del']
10 | });
11 |
12 | gulp.task('partials', function () {
13 | return gulp.src([
14 | path.join(conf.paths.src, '/app/**/*.html'),
15 | path.join(conf.paths.src, '/app/**/*.json'),
16 | path.join(conf.paths.tmp, '/serve/app/**/*.html'),
17 | path.join(conf.paths.tmp, '/serve/app/**/*.json')
18 | ])
19 | .pipe($.minifyHtml({
20 | empty: true,
21 | spare: true,
22 | quotes: true
23 | }))
24 | .pipe($.angularTemplatecache('templateCacheHtml.js', {
25 | module: 'SMI2',
26 | root: 'app'
27 | }))
28 | .pipe(gulp.dest(conf.paths.tmp + '/partials/'));
29 | });
30 |
31 | gulp.task('html', ['inject', 'partials'], function () {
32 | var partialsInjectFile = gulp.src([
33 | path.join(conf.paths.src, '/app/**/*.app.js'),
34 | path.join(conf.paths.tmp, '/partials/templateCacheHtml.js')
35 | ], {
36 | read: false
37 | });
38 | var partialsInjectOptions = {
39 | starttag: '',
40 | ignorePath: path.join(conf.paths.tmp, '/partials'),
41 | addRootSlash: false
42 | };
43 |
44 | var htmlFilter = $.filter('*.html', {
45 | restore: true
46 | });
47 | var jsFilter = $.filter('**/*.js', {
48 | restore: true
49 | });
50 | var jsVendor = $.filter(['**/*.js', '!**/vendor-*.js'], {
51 | restore: true
52 | });
53 | var cssFilter = $.filter('**/*.css', {
54 | restore: true
55 | });
56 | var assets;
57 |
58 | return gulp.src(path.join(conf.paths.tmp, '/serve/*.html'))
59 | .pipe($.inject(partialsInjectFile, partialsInjectOptions))
60 | .pipe(assets = $.useref.assets())
61 | .pipe($.rev())
62 | .pipe(jsFilter)
63 | .pipe(jsVendor)
64 | .pipe($.babel({
65 | presets: ['es2015', 'stage-0']
66 | }))
67 | .on('error', conf.errorHandler('babel'))
68 | .pipe(jsVendor.restore)
69 | //.pipe($.replace('assets/', 'app/assets/'))
70 | //.pipe($.sourcemaps.init())
71 | .pipe($.ngAnnotate())
72 | .pipe($.uglify({
73 | preserveComments: $.uglifySaveLicense
74 | })).on('error', conf.errorHandler('Uglify'))
75 | //.pipe($.sourcemaps.write('maps'))
76 | .pipe(jsFilter.restore)
77 | .pipe(cssFilter)
78 | .pipe($.replace('url(./fonts/', 'url(../fonts/')) // костылек для lumx
79 | //.pipe($.sourcemaps.init())
80 | //для favicon production
81 | .pipe($.minifyCss({
82 | processImport: false
83 | }))
84 | //.pipe($.sourcemaps.write('maps'))
85 | .pipe(cssFilter.restore)
86 | .pipe(assets.restore())
87 | .pipe($.useref())
88 | .pipe($.revReplace())
89 | .pipe(htmlFilter)
90 | .pipe($.replace('', ''))
91 | .pipe($.minifyHtml({
92 | empty: true,
93 | spare: true,
94 | quotes: true,
95 | conditionals: true
96 | }))
97 | .pipe(htmlFilter.restore)
98 | .pipe(gulp.dest(path.join(conf.paths.dist, '/')))
99 | .pipe($.size({
100 | title: path.join(conf.paths.dist, '/'),
101 | showFiles: true
102 | }));
103 | });
104 |
105 | // Only applies for fonts from bower dependencies
106 | // Custom fonts are handled by the "other" task
107 | gulp.task('fonts', function () {
108 | return gulp.src($.mainBowerFiles())
109 | .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}'))
110 | .pipe($.flatten())
111 | .pipe(gulp.dest(path.join(conf.paths.dist, '/fonts/')));
112 | });
113 |
114 | gulp.task('other', function () {
115 | var fileFilter = $.filter(function (file) {
116 | return file.stat.isFile();
117 | });
118 |
119 | return gulp.src([
120 | path.join(conf.paths.src, '/**/*'),
121 | path.join('!' + conf.paths.src, '/**/*.{html,css,js,less,json}')
122 | ])
123 | .pipe(fileFilter)
124 | .pipe(gulp.dest(path.join(conf.paths.dist, '/')));
125 | });
126 |
127 | gulp.task('clean', function () {
128 | return $.del([path.join(conf.paths.dist, '/'), path.join(conf.paths.tmp, '/')]);
129 | });
130 |
131 | gulp.task('cname', function () {
132 | return gulp.src('./assets/*').pipe(gulp.dest(path.join(conf.paths.dist, '/')));
133 | });
134 |
135 | gulp.task('build', ['html', 'fonts', 'other', 'cname']);
136 |
--------------------------------------------------------------------------------
/src/app/index.config.js:
--------------------------------------------------------------------------------
1 | ((angular, smi2) => {
2 | 'use strict';
3 |
4 |
5 | /**
6 | * Providers configuration
7 | */
8 | angular
9 | .module(smi2.app.name)
10 | .config([
11 | '$locationProvider',
12 | '$httpProvider',
13 | '$sceProvider',
14 | '$urlRouterProvider',
15 | '$mdThemingProvider',
16 | 'ThemeServiceProvider',
17 | function ($locationProvider,
18 | $httpProvider,
19 | $sceProvider,
20 | $urlRouterProvider,
21 | $mdThemingProvider,
22 | ThemeService) {
23 |
24 | // Запуск HTML5 режима HISTORY API, без решетки
25 | $locationProvider.html5Mode(true).hashPrefix('!');
26 |
27 | // Проверка авторизации в httpInterceptor
28 | $httpProvider.interceptors.push('HttpInterceptor');
29 |
30 | // Разрешаю ng-bind-html
31 | $sceProvider.enabled(false);
32 |
33 | // Если state не найден - шлю 404
34 | $urlRouterProvider.otherwise(function ($injector) {
35 | var $state = $injector.get("$state");
36 | $state.transitionTo('404');
37 | });
38 |
39 | if (ThemeService.$get().isDark()) {
40 | $mdThemingProvider
41 | .theme('default')
42 | .dark()
43 | .primaryPalette('blue')
44 | .accentPalette('blue', {
45 | 'default': '500'
46 | });
47 | }
48 | }
49 | ])
50 | .config(['$translateProvider', function ($translateProvider) {
51 | $translateProvider.translations('en', {
52 | 'Подключение': 'Connection',
53 | 'Неверные данные': 'some data is wrong',
54 | 'Название': 'name',
55 | 'Хост:порт': 'host:port',
56 | 'Логин': 'login',
57 | 'Пароль': 'password',
58 | 'Удалить': 'Delete',
59 | 'Войти': 'Sign in',
60 | 'не найдено': 'not found',
61 | 'нет данных': 'no data',
62 | 'Назад': 'Back',
63 | 'Выход': 'Sing out',
64 | 'Шрифт': 'font',
65 | 'История запросов пуста': 'Requests\'s history is empty',
66 | 'Структура': 'Structure',
67 | 'Данные': 'Data',
68 | 'Информация': 'Information',
69 | 'Размер': 'Size',
70 | 'Размер, байт': 'Size in bytes',
71 | 'Первая запись': 'First record',
72 | 'Последняя запись': 'Last record',
73 | 'Тип': 'Type',
74 | 'Default тип': 'Default type',
75 | 'Значение': 'Default value',
76 | 'Рабочий стол': 'Dashboard',
77 | 'База': 'Database',
78 | 'Выполнить ⌘ + ⏎': 'Run ⌘ + ⏎',
79 | 'Таблица': 'Table',
80 | 'Таблица ': 'Table ',
81 | 'Хотите покинуть страницу?': 'Do you want to leave this page?',
82 | 'Ошибка': 'Error',
83 | 'Не введен SQL': 'SQL request is empty',
84 | 'Выполнить выделенное ⌘ + ⏎': 'Run selected ⌘ + ⏎',
85 | 'Выполнить все ⇧ + ⌘ + ⏎': 'Run all ⇧ + ⌘ + ⏎',
86 | 'Просмотр': 'Preview',
87 | 'Ошибка ': 'Error ',
88 | 'Сохранить':'Save',
89 | 'Лог запросов': 'Request\'s log',
90 | 'Тема интерфейса': 'Theme',
91 | 'Тема': 'Theme',
92 | 'Максимум строк': 'Maximum lines',
93 | 'Настройки редактора': 'Editor settings',
94 | 'Закрыть': 'Close',
95 | 'Размер шрифта': 'Font size',
96 | 'Формат результатов': 'Result\'s format',
97 | 'Тема редактора':'Editor theme',
98 | 'Сохранять сессию': 'Save session',
99 | 'На весь экран': 'Full screen',
100 | 'Словари': 'Dictionaries',
101 | 'CSV с заголовком': 'CSV with headers',
102 | 'CSV без заголовка': 'CSV without headers',
103 | 'Табулированный текст с заголовками': 'Tabulated text with headers',
104 | 'Табулированный текст без заголовков': 'Tabulated text without headers',
105 | 'время':'time',
106 | 'строк прочитано':'lines read',
107 | 'прочитано':'read',
108 | 'SQL изменен. Сохранить перед закрытием?': 'SQL was changed. Save it before exit?',
109 | 'Да':'Yes',
110 | 'Нет': 'No',
111 | 'Светлая тема': 'Light theme',
112 | 'Темная тема': 'Dark theme',
113 | 'с': 'from',
114 | 'по': 'to',
115 | 'Открыть таблицу': 'Open table'
116 | })
117 | .translations('ru', {
118 | })
119 | .registerAvailableLanguageKeys(['en', 'ru'], {
120 | 'ru_*': 'ru',
121 | 'ru-*': 'ru',
122 | '*': 'en',
123 | })
124 | .determinePreferredLanguage();
125 | // this line used for translation check
126 | //$translateProvider.useMissingTranslationHandlerLog();
127 |
128 | }]);
129 | })(angular, smi2);
130 |
--------------------------------------------------------------------------------
/src/app/login/login.html:
--------------------------------------------------------------------------------
1 |
109 |
--------------------------------------------------------------------------------
/src/app/table/table-container.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{vars.currentDatabase}}
4 |
5 | {{vars.currentTable}}
6 |
7 |
8 |
9 |
10 |
11 | {{'Структура' | translate}}
12 |
13 |
14 |
15 |
16 |
17 | |
18 | {{'Название' | translate}} |
19 | {{'Тип' | translate}} |
20 | {{'Default тип' | translate}} |
21 | {{'Значение' | translate}} |
22 |
23 |
24 |
25 |
26 | | {{$index + 1}} |
27 |
28 | {{field[key]}}
29 | |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {{'Данные' | translate}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{'Данные' | translate}}
53 |
{{'с' | translate}}
54 | {{vars.offset}}
55 | {{'по' | translate}}
56 | {{vars.offset + vars.limit}}
57 |
58 |
59 |
{{'нет данных' | translate}}
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | {{'Информация' | translate}}
70 |
71 |
72 |
73 |
74 |
75 | | 1 |
76 | {{'Размер' | translate}} |
77 |
78 | {{vars.statistics.size}}
79 | |
80 |
81 |
82 | | 2 |
83 | {{'Размер, байт' | translate}} |
84 |
85 | {{vars.statistics.sizeBytes}}
86 | |
87 |
88 |
89 | | 3 |
90 | {{'Первая запись' | translate}} |
91 |
92 | {{vars.statistics.minDate}}
93 | |
94 |
95 |
96 | | 4 |
97 | {{'Последняя запись' | translate}} |
98 |
99 | {{vars.statistics.maxDate}}
100 | |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/src/less/layout.less:
--------------------------------------------------------------------------------
1 | .breadcrumb {
2 | list-style: none;
3 | margin: 0;
4 | }
5 |
6 | .breadcrumb li {
7 | display: inline-block;
8 | }
9 |
10 | body {
11 | font-family: Helvetica, arial;
12 | }
13 |
14 | body .wrapper {
15 | display: -webkit-box;
16 | display: -ms-flexbox;
17 | display: flex;
18 | -webkit-box-orient: vertical;
19 | -webkit-box-direction: normal;
20 | -ms-flex-direction: column;
21 | flex-direction: column;
22 | min-height: 100vh;
23 | }
24 |
25 | .content {
26 | &.login{
27 | width: 100%;
28 | .main-view{
29 | justify-content: center;
30 | }
31 | }
32 | display: flex;
33 | height: 100vh;
34 | }
35 | .main-content.login .footer{
36 | position: fixed;
37 | bottom: 0;
38 | left: 0;
39 | right: 0;
40 | }
41 | @media only screen and (min-width: 0px) and (max-width: 600px) {
42 | .content {
43 | margin-top: 56px;
44 | }
45 | }
46 |
47 | .content .main-content {
48 | flex: 1;
49 | display: flex;
50 | flex-flow: column nowrap;
51 | overflow: auto;
52 | margin-top: 40px;
53 | position: relative;
54 | }
55 |
56 | .content .main-content .main-view {
57 | -webkit-box-flex: 1;
58 | -ms-flex: 1;
59 | flex: 1;
60 | display: -webkit-box;
61 | display: -ms-flexbox;
62 | display: flex;
63 | -webkit-box-pack: center;
64 | -ms-flex-pack: center;
65 | justify-content: center;
66 | -webkit-box-align: center;
67 | -ms-flex-align: center;
68 | align-items: center;
69 | }
70 |
71 | md-sidenav[md-component-id="tableSiedenav"] {
72 | width: 50% !important;
73 | max-width: 50% !important;
74 | height: 100%;
75 | overflow: hidden;
76 | .right-button{
77 | position: absolute;
78 | right: 20px;
79 | top: 20px;
80 | z-index: 1;
81 | }
82 |
83 | .md-closed.md-locked-open-add-active,
84 | .md-locked-open {
85 | width: 50% !important;
86 | height: 100%;
87 | max-width: 50% !important;
88 | }
89 |
90 | md-content {
91 | position: absolute;
92 | top: 0;
93 | bottom: 0;
94 | width: 100%;
95 | overflow: hidden;
96 | padding-top: 0;
97 | padding-bottom: 0;
98 |
99 | md-tab-content {
100 | max-height: 100%;
101 |
102 | > div {
103 | max-height: 100%;
104 | }
105 | }
106 |
107 | .mCustomScrollbar {
108 | // height: 100%;
109 | }
110 | }
111 | }
112 | @media only screen and (min-width: 0px) and (max-width: 600px) {
113 | .content .main-content .main-view {
114 | margin-left: 8px;
115 | margin-right: 8px;
116 | }
117 | }
118 |
119 | .content .main-content .main-view.ng-enter {
120 | -webkit-animation: bounceIn 0.8s;
121 | animation: bounceIn 0.8s;
122 | }
123 |
124 | .content .main-content .main-view .full-width {
125 | -ms-flex-item-align: start;
126 | align-self: flex-start;
127 | padding: 10px 30px 20px;
128 | width: 100%;
129 | }
130 |
131 | .footer .copyright {
132 | margin: auto;
133 | text-shadow: 1px 1px rgba(0, 0, 0, 0.2);
134 | }
135 |
136 | .footer .toolbar {
137 | background: #424242 !important;
138 | }
139 |
140 | .header-view {
141 | position: fixed;
142 | width: 100%;
143 | z-index: 2;
144 | }
145 | @media only screen and (min-width: 0px) and (max-width: 600px) {
146 | .header-view .toolbar {
147 | padding-left: 0;
148 | padding-right: 0;
149 | }
150 | }
151 |
152 | .header-view .toolbar .fs-title a {
153 | text-decoration: none;
154 | }
155 |
156 | body #loading-bar .bar {
157 | background: #2c3e50;
158 | height: 3px;
159 | }
160 |
161 | body #loading-bar .peg {
162 | box-shadow: 0 0 10px #2c3e50, 0 0 5px #2c3e50;
163 | }
164 |
165 | body #loading-bar-spinner .spinner-icon {
166 | border-top-color: #2c3e50;
167 | border-left-color: #2c3e50;
168 | }
169 |
170 | .sidebar-view {
171 | border-right: 1px solid rgba(0, 0, 0, 0.2);
172 | }
173 | @media only screen and (min-width: 0px) and (max-width: 600px) {
174 | .sidebar-view {
175 | position: fixed;
176 | height: 100%;
177 | width: 200px;
178 | box-shadow: 3px 0 6px rgba(0, 0, 0, 0.2);
179 | -webkit-transform: translateX(-200px);
180 | transform: translateX(-200px);
181 | -webkit-transition: box-shadow, -webkit-transform 0.6s cubic-bezier(0.23, 1, 0.32, 1);
182 | transition: box-shadow, transform 0.6s cubic-bezier(0.23, 1, 0.32, 1);
183 | z-index: 999;
184 | }
185 |
186 | .sidebar-view.show {
187 | -webkit-transform: none;
188 | transform: none;
189 | }
190 | }
191 | .sidebar-view .sidebar .sidebar-menu {
192 | list-style: none;
193 | }
194 |
195 | .sidebar-view .sidebar .sidebar-menu .menu-item {
196 | border-bottom: 1px solid rgba(0, 0, 0, 0.2);
197 | }
198 |
199 | .sidebar-view .sidebar .sidebar-menu .menu-item.active {
200 | border-radius: 5px;
201 | background-color: rgba(146, 205, 255, 0.42);
202 | }
203 |
204 | .sidebar-view .sidebar .sidebar-menu .menu-item.active i {
205 | color: #4caf50;
206 | }
207 |
208 | .sidebar-view .sidebar .sidebar-menu .menu-item.active:hover {
209 | background-color: rgba(146, 205, 255, 0.42);
210 | }
211 |
212 | .sidebar-view .sidebar .sidebar-menu .menu-item:hover {
213 | text-shadow: 1px 1px rgba(0, 0, 0, 0.2);
214 | background-color: #eee;
215 | }
216 |
217 | .sidebar-view .sidebar .sidebar-menu .menu-item .link {
218 | display: block;
219 | text-decoration: none;
220 | }
221 |
222 | .sidebar-view .sidebar .sidebar-menu .menu-item .link .text {
223 | line-height: 32px;
224 | font-size: 14pt;
225 | }
226 | @media only screen and (min-width: 0px) and (max-width: 600px) {
227 | .sidebar-view .sidebar-backdrop {
228 | position: absolute;
229 | top: 0;
230 | left: 200px;
231 | z-index: 990;
232 | background-color: rgba(0, 0, 0, 0.5);
233 | height: 100%;
234 | width: 100%;
235 | }
236 | }
237 |
238 | .menu-button {
239 | width: 180px;
240 | text-align: left;
241 | margin: 0;
242 | padding: 8px;
243 | }
244 |
245 | .menu-button__title {
246 | font-size: 16px;
247 | line-height: 18px;
248 | }
249 |
250 | .menu-button__subtitle {
251 | display: block;
252 | font-size: 10px;
253 | line-height: 12px;
254 | margin-bottom: 8px;
255 | }
256 |
257 | .header {
258 | height: 40px;
259 | line-height: 40px;
260 |
261 | md-card {
262 | height: 40px;
263 | line-height: 40px;
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/src/app/services/api.js:
--------------------------------------------------------------------------------
1 | ((angular, smi2) => {
2 | 'use strict';
3 |
4 | angular.module(smi2.app.name).service('API', API);
5 | API.$inject = ['$http', '$q', 'localStorageService', '$sanitize', 'ThemeService'];
6 |
7 | /**
8 | * @ngdoc service
9 | * @name smi2.service:API
10 | * @description API manager
11 | */
12 | function API($http, $q, localStorageService, $sanitize, ThemeService) {
13 |
14 | const CURRENT_BASE_KEY = 'currentBaseConfig';
15 | const DEFAULT_PORT = 8123;
16 |
17 | let database = null;
18 | let connection = {};
19 |
20 | // Первичная загрузка данных из LS
21 | let data = localStorageService.get(CURRENT_BASE_KEY);
22 | if (data && data.host) {
23 | connection = data;
24 | }
25 |
26 | /**
27 | * @ngdoc method
28 | * @methodOf smi2.service:API
29 | * @name setConnection
30 | * @description Сохранение данных подключения в БД
31 | * @param {mixed} db Данные подключения
32 | */
33 | this.setConnection = (db) => {
34 | localStorageService.set(CURRENT_BASE_KEY, db);
35 | connection = angular.copy(db);
36 |
37 | // Check port
38 | if (!/.*\:\d+$/.test(connection.host)) {
39 | connection.host += ':' + DEFAULT_PORT;
40 | }
41 | };
42 |
43 | /**
44 | * @ngdoc method
45 | * @methodOf smi2.service:API
46 | * @name clear
47 | * @description Сброс авторизации
48 | */
49 | this.clear = () => {
50 | database = null;
51 | connection = {};
52 | localStorageService.set(CURRENT_BASE_KEY, {});
53 | };
54 |
55 | /**
56 | * @ngdoc method
57 | * @methodOf smi2.service:API
58 | * @name query
59 | * @description Выполнение запроса к БД
60 | * @param {string} sql Текст запроса
61 | * @return {promise} Promise
62 | */
63 | this.query = (sql, format, withDatabase, extend_settings) => {
64 | let defer = $q.defer();
65 | let query = '';
66 |
67 | if (format !== false) {
68 | format = (format || ' FoRmAt JSON');
69 | if (format == 'null') {
70 | format = '';
71 | }
72 | query = sql + ' ' + format;
73 | } else {
74 | query = sql;
75 | }
76 | var httpProto = '';
77 | if (!(connection.host.indexOf('://') > 0 || connection.host.indexOf('/') == 0)) {
78 | httpProto = 'http://';
79 | }
80 | let url = httpProto + connection.host +
81 | '/?query=' + encodeURIComponent(query);
82 | if (connection.login) {
83 | url += '&user=' + connection.login;
84 | }
85 | if (connection.password) {
86 | url += '&password=' + connection.password;
87 | }
88 | if (connection.host.indexOf('/') != 0) {
89 | url += '&add_http_cors_header=1';
90 | }
91 | if (withDatabase) {
92 | url += '&database=' + database;
93 | }
94 | if (extend_settings) {
95 | url += '&' + extend_settings;
96 | }
97 | // console.info(query);// Не удалять не только для DEBUG.
98 | // Бебебе удалил
99 |
100 | let req = {
101 | method: (format ? 'GET' : 'POST'), // if not set format use POST
102 | url: url,
103 | transformResponse: (data, header, status) => {
104 | try {
105 | return angular.fromJson(data);
106 | } catch (err) {
107 | return (data ? data : "\nStatus:" + status + "\nHeaders:" + angular.toJson(header()));
108 | }
109 | }
110 | };
111 |
112 | $http(req).then((response) => {
113 | defer.resolve(response.data);
114 | }, (response) => {
115 | defer.reject(response.data);
116 | });
117 |
118 | return defer.promise;
119 | };
120 |
121 | /**
122 | * @ngdoc method
123 | * @methodOf smi2.service:API
124 | * @name getConnectionInfo
125 | * @description Получение данных подключения
126 | * @return {mixed} Объект с данными подключения
127 | */
128 | this.getConnectionInfo = () => connection;
129 |
130 | /**
131 | * @ngdoc method
132 | * @methodOf smi2.service:API
133 | * @name setDatabase
134 | * @description Установка дефолтной БД
135 | * @param {mixed} db Данные БД
136 | */
137 | this.setDatabase = (db) => (database = db);
138 |
139 | /**
140 | * @ngdoc method
141 | * @methodOf smi2.service:API
142 | * @name getDatabase
143 | * @description Get database info
144 | */
145 | this.getDatabase = () => database;
146 |
147 | /**
148 | *
149 | * @param data
150 | * @returns {string}
151 | */
152 | this.dataToCreateTable = (data) => {
153 | let q = "\n" + 'CREATE TABLE x (' + "\n";
154 | let keys = [];
155 | data.meta.forEach((cell) => keys.push("\t" + cell.name + " " + cell.type));
156 |
157 | return q + keys.join(",\n") + "\n ) ENGINE = Log \n;;\n";
158 | };
159 |
160 | /**
161 | * @ngdoc method
162 | * @methodOf smi2.service:API
163 | * @name dataToHtml
164 | * @description Преобразование JSON данных запроса в таблицу
165 | * @param {mixed} data Объект, который содержит ответ БД
166 | * @return {string} Строка HTML
167 | */
168 | this.dataToHtml = (data) => {
169 |
170 | let html = ``;
171 | let keys = [];
172 | data.meta.forEach((cell) => {
173 | html += `| ${$sanitize(cell.name)}
174 |
175 | | `;
176 | keys.push(cell.name);
177 | });
178 | data.data.forEach((row) => {
179 | html += '
';
180 | keys.forEach((key) => {
181 | html += '| ' + $sanitize(row[key]) + ' | ';
182 | });
183 | html += '
';
184 | });
185 | html += '
';
186 | return html;
187 | };
188 |
189 |
190 | this.dataToUIGrid = (data) => {
191 |
192 | let columnDefs = [];
193 | data.meta.forEach((cell) => {
194 | columnDefs.push(
195 | // pinnedLeft:true , width: 250, enablePinning:false ,pinnedRight:true
196 | {field: cell.name, minWidth: 100, enableColumnResizing: true, headerTooltip: cell.type}
197 | );
198 | });
199 |
200 | return {
201 | enableSorting: true,
202 | enableFiltering: true,
203 | enableColumnResizing: true,
204 | columnDefs: columnDefs,
205 | enableGridMenu: true,
206 | enableSelectAll: true,
207 | showGridFooter: true,
208 | showColumnFooter: true,
209 | data: data.data
210 | };
211 | };
212 | }
213 | })(angular, smi2);
214 |
--------------------------------------------------------------------------------
/docs/fonts/ui-grid.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/less/core.less:
--------------------------------------------------------------------------------
1 | .width-100 {
2 | width: 100%;
3 | }
4 |
5 | .width-50 {
6 | width: 50%;
7 | }
8 |
9 | .pull-right {
10 | float: right;
11 | }
12 |
13 | .pull-left {
14 | float: left;
15 | }
16 |
17 | .text-center {
18 | text-align: center;
19 | }
20 |
21 | .m {
22 | margin: 8px !important;
23 | }
24 |
25 | .mt {
26 | margin-top: 8px !important;
27 | }
28 |
29 | .mr {
30 | margin-right: 8px !important;
31 | }
32 |
33 | .mb {
34 | margin-bottom: 8px !important;
35 | }
36 |
37 | .ml {
38 | margin-left: 8px !important;
39 | }
40 |
41 | .mh {
42 | margin-right: 8px !important;
43 | margin-left: 8px !important;
44 | }
45 |
46 | .mv {
47 | margin-top: 8px !important;
48 | margin-bottom: 8px !important;
49 | }
50 |
51 | .m- {
52 | margin: 4px !important;
53 | }
54 |
55 | .mt- {
56 | margin-top: 4px !important;
57 | }
58 |
59 | .mr- {
60 | margin-right: 4px !important;
61 | }
62 |
63 | .mb- {
64 | margin-bottom: 4px !important;
65 | }
66 |
67 | .ml- {
68 | margin-left: 4px !important;
69 | }
70 |
71 | .mh- {
72 | margin-right: 4px !important;
73 | margin-left: 4px !important;
74 | }
75 |
76 | .mv- {
77 | margin-top: 4px !important;
78 | margin-bottom: 4px !important;
79 | }
80 |
81 | .m\+ {
82 | margin: 16px !important;
83 | }
84 |
85 | .mt\+ {
86 | margin-top: 16px !important;
87 | }
88 |
89 | .mr\+ {
90 | margin-right: 16px !important;
91 | }
92 |
93 | .mb\+ {
94 | margin-bottom: 16px !important;
95 | }
96 |
97 | .ml\+ {
98 | margin-left: 16px !important;
99 | }
100 |
101 | .mh\+ {
102 | margin-right: 16px !important;
103 | margin-left: 16px !important;
104 | }
105 |
106 | .mv\+ {
107 | margin-top: 16px !important;
108 | margin-bottom: 16px !important;
109 | }
110 |
111 | .m\+\+ {
112 | margin: 24px !important;
113 | }
114 |
115 | .mt\+\+ {
116 | margin-top: 24px !important;
117 | }
118 |
119 | .mr\+\+ {
120 | margin-right: 24px !important;
121 | }
122 |
123 | .mb\+\+ {
124 | margin-bottom: 24px !important;
125 | }
126 |
127 | .ml\+\+ {
128 | margin-left: 24px !important;
129 | }
130 |
131 | .mh\+\+ {
132 | margin-right: 24px !important;
133 | margin-left: 24px !important;
134 | }
135 |
136 | .mv\+\+ {
137 | margin-top: 24px !important;
138 | margin-bottom: 24px !important;
139 | }
140 |
141 | .m\+\+\+ {
142 | margin: 32px !important;
143 | }
144 |
145 | .mt\+\+\+ {
146 | margin-top: 32px !important;
147 | }
148 |
149 | .mr\+\+\+ {
150 | margin-right: 32px !important;
151 | }
152 |
153 | .mb\+\+\+ {
154 | margin-bottom: 32px !important;
155 | }
156 |
157 | .ml\+\+\+ {
158 | margin-left: 32px !important;
159 | }
160 |
161 | .mh\+\+\+ {
162 | margin-right: 32px !important;
163 | margin-left: 32px !important;
164 | }
165 |
166 | .mv\+\+\+ {
167 | margin-top: 32px !important;
168 | margin-bottom: 32px !important;
169 | }
170 |
171 | .m0 {
172 | margin: 0 !important;
173 | }
174 |
175 | .mt0 {
176 | margin-top: 0 !important;
177 | }
178 |
179 | .mr0 {
180 | margin-right: 0 !important;
181 | }
182 |
183 | .mb0 {
184 | margin-bottom: 0 !important;
185 | }
186 |
187 | .ml0 {
188 | margin-left: 0 !important;
189 | }
190 |
191 | .mh0 {
192 | margin-right: 0 !important;
193 | margin-left: 0 !important;
194 | }
195 |
196 | .mv0 {
197 | margin-top: 0 !important;
198 | margin-bottom: 0 !important;
199 | }
200 |
201 | .p {
202 | padding: 8px !important;
203 | }
204 |
205 | .pt {
206 | padding-top: 8px !important;
207 | }
208 |
209 | .pr {
210 | padding-right: 8px !important;
211 | }
212 |
213 | .pb {
214 | padding-bottom: 8px !important;
215 | }
216 |
217 | .pl {
218 | padding-left: 8px !important;
219 | }
220 |
221 | .ph {
222 | padding-right: 8px !important;
223 | padding-left: 8px !important;
224 | }
225 |
226 | .pv {
227 | padding-top: 8px !important;
228 | padding-bottom: 8px !important;
229 | }
230 |
231 | .p- {
232 | padding: 4px !important;
233 | }
234 |
235 | .pt- {
236 | padding-top: 4px !important;
237 | }
238 |
239 | .pr- {
240 | padding-right: 4px !important;
241 | }
242 |
243 | .pb- {
244 | padding-bottom: 4px !important;
245 | }
246 |
247 | .pl- {
248 | padding-left: 4px !important;
249 | }
250 |
251 | .ph- {
252 | padding-right: 4px !important;
253 | padding-left: 4px !important;
254 | }
255 |
256 | .pv- {
257 | padding-top: 4px !important;
258 | padding-bottom: 4px !important;
259 | }
260 |
261 | .p\+ {
262 | padding: 16px !important;
263 | }
264 |
265 | .pt\+ {
266 | padding-top: 16px !important;
267 | }
268 |
269 | .pr\+ {
270 | padding-right: 16px !important;
271 | }
272 |
273 | .pb\+ {
274 | padding-bottom: 16px !important;
275 | }
276 |
277 | .pl\+ {
278 | padding-left: 16px !important;
279 | }
280 |
281 | .ph\+ {
282 | padding-right: 16px !important;
283 | padding-left: 16px !important;
284 | }
285 |
286 | .pv\+ {
287 | padding-top: 16px !important;
288 | padding-bottom: 16px !important;
289 | }
290 |
291 | .p\+\+ {
292 | padding: 24px !important;
293 | }
294 |
295 | .pt\+\+ {
296 | padding-top: 24px !important;
297 | }
298 |
299 | .pr\+\+ {
300 | padding-right: 24px !important;
301 | }
302 |
303 | .pb\+\+ {
304 | padding-bottom: 24px !important;
305 | }
306 |
307 | .pl\+\+ {
308 | padding-left: 24px !important;
309 | }
310 |
311 | .ph\+\+ {
312 | padding-right: 24px !important;
313 | padding-left: 24px !important;
314 | }
315 |
316 | .pv\+\+ {
317 | padding-top: 24px !important;
318 | padding-bottom: 24px !important;
319 | }
320 |
321 | .p\+\+\+ {
322 | padding: 32px !important;
323 | }
324 |
325 | .pt\+\+\+ {
326 | padding-top: 32px !important;
327 | }
328 |
329 | .pr\+\+\+ {
330 | padding-right: 32px !important;
331 | }
332 |
333 | .pb\+\+\+ {
334 | padding-bottom: 32px !important;
335 | }
336 |
337 | .pl\+\+\+ {
338 | padding-left: 32px !important;
339 | }
340 |
341 | .ph\+\+\+ {
342 | padding-right: 32px !important;
343 | padding-left: 32px !important;
344 | }
345 |
346 | .pv\+\+\+ {
347 | padding-top: 32px !important;
348 | padding-bottom: 32px !important;
349 | }
350 |
351 | .p0 {
352 | padding: 0 !important;
353 | }
354 |
355 | .pt0 {
356 | padding-top: 0 !important;
357 | }
358 |
359 | .pr0 {
360 | padding-right: 0 !important;
361 | }
362 |
363 | .pb0 {
364 | padding-bottom: 0 !important;
365 | }
366 |
367 | .pl0 {
368 | padding-left: 0 !important;
369 | }
370 |
371 | .ph0 {
372 | padding-right: 0 !important;
373 | padding-left: 0 !important;
374 | }
375 |
376 | .pv0 {
377 | padding-top: 0 !important;
378 | padding-bottom: 0 !important;
379 | }
380 | .h100 {
381 | height: 100%
382 | }
383 |
384 | .clearfix::after {
385 | clear: both;
386 | content: "";
387 | display: table;
388 | }
389 |
390 | .float-right {
391 | float: right !important;
392 | }
393 |
394 | .float-left {
395 | float: left !important;
396 | }
397 |
398 | .float-none {
399 | float: none !important;
400 | }
401 |
402 | .text-left {
403 | text-align: left !important;
404 | }
405 |
406 | .text-center {
407 | text-align: center !important;
408 | }
409 |
410 | .text-right {
411 | text-align: right !important;
412 | }
413 |
414 | .display-block {
415 | display: block;
416 | }
417 |
418 | .dissolve-animation.ng-hide-remove,
419 | .dissolve-animation.ng-hide-add {
420 | -webkit-transition: 0.5s linear all;
421 | transition: 0.5s linear all;
422 | }
423 |
424 | .dissolve-animation.ng-hide-remove.ng-hide-remove-active,
425 | .dissolve-animation.ng-hide-add {
426 | opacity: 1;
427 | }
428 |
429 | .dissolve-animation.ng-hide-add.ng-hide-add-active,
430 | .dissolve-animation.ng-hide-remove {
431 | opacity: 0;
432 | }
433 |
434 | .icon-breath-animation {
435 | -webkit-animation: icon-breath 1.5s ease-in-out infinite;
436 | animation: icon-breath 1.5s ease-in-out infinite;
437 | }
438 |
439 | .icon-rotate-animation {
440 | -webkit-animation: icon-rotate 0.8s linear infinite;
441 | animation: icon-rotate 0.8s linear infinite;
442 | margin-right: 5px;
443 | }
444 | .no-txt-transform {
445 | text-transform: none;
446 | }
447 |
448 | @-webkit-keyframes icon-breath {
449 | from {
450 | box-shadow: 0 0 2px 5px rgba(255, 138, 138, 0.48);
451 | opacity: 0.5;
452 | }
453 | to {
454 | box-shadow: none;
455 | opacity: 1;
456 | }
457 | }
458 |
459 | @keyframes icon-breath {
460 | from {
461 | box-shadow: 0 0 2px 5px rgba(255, 138, 138, 0.48);
462 | opacity: 0.5;
463 | }
464 | to {
465 | box-shadow: none;
466 | opacity: 1;
467 | }
468 | }
469 |
470 | @-webkit-keyframes icon-rotate {
471 | from {
472 | -webkit-transform: rotate(0deg);
473 | transform: rotate(0deg);
474 | }
475 | to {
476 | -webkit-transform: rotate(360deg);
477 | transform: rotate(360deg);
478 | }
479 | }
480 |
481 | @keyframes icon-rotate {
482 | from {
483 | -webkit-transform: rotate(0deg);
484 | transform: rotate(0deg);
485 | }
486 | to {
487 | -webkit-transform: rotate(360deg);
488 | transform: rotate(360deg);
489 | }
490 | }
491 |
492 | @media only screen and (min-width: 0px) and (max-width: 600px) {
493 | .hide-sm {
494 | display: none;
495 | }
496 | }
497 |
498 | @media only screen and (min-width: 600px) {
499 | .hide-gt-sm {
500 | display: none;
501 | }
502 | }
503 |
504 | @media only screen and (min-width: 600px) and (max-width: 960px) {
505 | .hide-md {
506 | display: none;
507 | }
508 | }
509 |
510 | @media only screen and (min-width: 960px) {
511 | .hide-gt-md {
512 | display: none;
513 | }
514 | }
515 |
516 | @media only screen and (min-width: 960px) and (max-width: 1200px) {
517 | .hide-lg {
518 | display: none;
519 | }
520 | }
521 |
522 | @media only screen and (min-width: 1200px) {
523 | .hide-gt-lg {
524 | display: none;
525 | }
526 | }
527 |
528 | @media only screen and (min-width: 0px) and (max-width: 600px) {
529 | .lx-date-picker__day {
530 | line-height: 16px;
531 | }
532 |
533 | .lx-date-picker__day a {
534 | width: 16px;
535 | height: 16px;
536 | line-height: 16px;
537 | }
538 | }
539 |
--------------------------------------------------------------------------------
/docs/styles/app-9c669350e7.css:
--------------------------------------------------------------------------------
1 | .mh,.mr{margin-right:8px!important}.mh,.ml{margin-left:8px!important}.mt,.mv{margin-top:8px!important}.mb,.mv{margin-bottom:8px!important}.ph,.pr{padding-right:8px!important}.ph,.pl{padding-left:8px!important}.pt,.pv{padding-top:8px!important}.pb,.pv{padding-bottom:8px!important}.login-view .login-checking,body .wrapper{-webkit-box-orient:vertical;-webkit-box-direction:normal}.login-view .card__header,.sidebar-view .sidebar .sidebar-menu .menu-item{border-bottom:1px solid rgba(0,0,0,.2)}.header-view .toolbar .fs-title a,a{text-decoration:none}.breadcrumb,.sidebar-view .sidebar .metismenu ul,.sidebar-view .sidebar .sidebar-menu{list-style:none}.width-100{width:100%}.width-50{width:50%}.pull-right{float:right}.pull-left{float:left}.m{margin:8px!important}.mh-,.mr-{margin-right:4px!important}.mh-,.ml-{margin-left:4px!important}.mt-,.mv-{margin-top:4px!important}.mb-,.mv-{margin-bottom:4px!important}.m-{margin:4px!important}.mh\+,.mr\+{margin-right:16px!important}.mh\+,.ml\+{margin-left:16px!important}.mt\+,.mv\+{margin-top:16px!important}.mb\+,.mv\+{margin-bottom:16px!important}.m\+{margin:16px!important}.mh\+\+,.mr\+\+{margin-right:24px!important}.mh\+\+,.ml\+\+{margin-left:24px!important}.mt\+\+,.mv\+\+{margin-top:24px!important}.mb\+\+,.mv\+\+{margin-bottom:24px!important}.m\+\+{margin:24px!important}.mh\+\+\+,.mr\+\+\+{margin-right:32px!important}.mh\+\+\+,.ml\+\+\+{margin-left:32px!important}.mt\+\+\+,.mv\+\+\+{margin-top:32px!important}.mb\+\+\+,.mv\+\+\+{margin-bottom:32px!important}.m\+\+\+{margin:32px!important}.mh0,.mr0{margin-right:0!important}.mh0,.ml0{margin-left:0!important}.mt0,.mv0{margin-top:0!important}.mb0,.mv0{margin-bottom:0!important}.m0{margin:0!important}.p{padding:8px!important}.ph-,.pr-{padding-right:4px!important}.ph-,.pl-{padding-left:4px!important}.pt-,.pv-{padding-top:4px!important}.pb-,.pv-{padding-bottom:4px!important}.p-{padding:4px!important}.ph\+,.pr\+{padding-right:16px!important}.ph\+,.pl\+{padding-left:16px!important}.pt\+,.pv\+{padding-top:16px!important}.pb\+,.pv\+{padding-bottom:16px!important}.p\+{padding:16px!important}.ph\+\+,.pr\+\+{padding-right:24px!important}.ph\+\+,.pl\+\+{padding-left:24px!important}.pt\+\+,.pv\+\+{padding-top:24px!important}.pb\+\+,.pv\+\+{padding-bottom:24px!important}.p\+\+{padding:24px!important}.ph\+\+\+,.pr\+\+\+{padding-right:32px!important}.ph\+\+\+,.pl\+\+\+{padding-left:32px!important}.pt\+\+\+,.pv\+\+\+{padding-top:32px!important}.pb\+\+\+,.pv\+\+\+{padding-bottom:32px!important}.p\+\+\+{padding:32px!important}.ph0,.pr0{padding-right:0!important}.ph0,.pl0{padding-left:0!important}.pt0,.pv0{padding-top:0!important}.pb0,.pv0{padding-bottom:0!important}.p0{padding:0!important}.h100{height:100%}.clearfix::after{clear:both;content:"";display:table}.float-right{float:right!important}.float-left{float:left!important}.float-none{float:none!important}.text-left{text-align:left!important}.text-center{text-align:center!important}.text-right{text-align:right!important}.display-block{display:block}.dissolve-animation.ng-hide-add,.dissolve-animation.ng-hide-remove{-webkit-transition:.5s linear all;transition:.5s linear all}.dissolve-animation.ng-hide-add,.dissolve-animation.ng-hide-remove.ng-hide-remove-active{opacity:1}.dissolve-animation.ng-hide-add.ng-hide-add-active,.dissolve-animation.ng-hide-remove{opacity:0}.icon-breath-animation{-webkit-animation:icon-breath 1.5s ease-in-out infinite;animation:icon-breath 1.5s ease-in-out infinite}.icon-rotate-animation{-webkit-animation:icon-rotate .8s linear infinite;animation:icon-rotate .8s linear infinite;margin-right:5px}.no-txt-transform{text-transform:none}@-webkit-keyframes icon-breath{from{box-shadow:0 0 2px 5px rgba(255,138,138,.48);opacity:.5}to{box-shadow:none;opacity:1}}@keyframes icon-breath{from{box-shadow:0 0 2px 5px rgba(255,138,138,.48);opacity:.5}to{box-shadow:none;opacity:1}}@-webkit-keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media only screen and (min-width:600px){.hide-gt-sm{display:none}}@media only screen and (min-width:600px) and (max-width:960px){.hide-md{display:none}}@media only screen and (min-width:960px){.hide-gt-md{display:none}}@media only screen and (min-width:960px) and (max-width:1200px){.hide-lg{display:none}}@media only screen and (min-width:1200px){.hide-gt-lg{display:none}}@media only screen and (min-width:0px) and (max-width:600px){.hide-sm{display:none}.lx-date-picker__day{line-height:16px}.lx-date-picker__day a{width:16px;height:16px;line-height:16px}}.breadcrumb{margin:0}.breadcrumb li{display:inline-block}body{font-family:Helvetica,arial}body .wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:100vh}.content,.content .main-content{display:-webkit-box;display:-ms-flexbox}.content{display:flex;height:100vh}.content.login{width:100%}.content.login .main-view{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.main-content.login .footer{position:fixed;bottom:0;left:0;right:0}@media only screen and (min-width:0px) and (max-width:600px){.content{margin-top:56px}.content .main-content .main-view{margin-left:8px;margin-right:8px}}.content .main-content{-webkit-box-flex:1;-ms-flex:1;flex:1;display:flex;-ms-flex-flow:column nowrap;flex-flow:column nowrap;overflow:auto;margin-top:40px;position:relative}.content .main-content .main-view{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}md-sidenav[md-component-id=tableSiedenav]{width:50%!important;max-width:50%!important;height:100%;overflow:hidden}md-sidenav[md-component-id=tableSiedenav] .right-button{position:absolute;right:20px;top:20px;z-index:1}md-sidenav[md-component-id=tableSiedenav] .md-closed.md-locked-open-add-active,md-sidenav[md-component-id=tableSiedenav] .md-locked-open{width:50%!important;height:100%;max-width:50%!important}md-sidenav[md-component-id=tableSiedenav] md-content{position:absolute;top:0;bottom:0;width:100%;overflow:hidden;padding-top:0;padding-bottom:0}md-sidenav[md-component-id=tableSiedenav] md-content md-tab-content,md-sidenav[md-component-id=tableSiedenav] md-content md-tab-content>div{max-height:100%}.content .main-content .main-view.ng-enter{-webkit-animation:bounceIn .8s;animation:bounceIn .8s}.content .main-content .main-view .full-width{-ms-flex-item-align:start;align-self:flex-start;padding:10px 30px 20px;width:100%}.footer .copyright{margin:auto;text-shadow:1px 1px rgba(0,0,0,.2)}.footer .toolbar{background:#424242!important}.header-view{position:fixed;width:100%;z-index:2}body #loading-bar .bar{background:#2c3e50;height:3px}body #loading-bar .peg{box-shadow:0 0 10px #2c3e50,0 0 5px #2c3e50}body #loading-bar-spinner .spinner-icon{border-top-color:#2c3e50;border-left-color:#2c3e50}.sidebar-view .sidebar .sidebar-menu .menu-item.active{border-radius:5px;background-color:rgba(146,205,255,.42)}.sidebar-view .sidebar .sidebar-menu .menu-item.active i{color:#4caf50}.sidebar-view .sidebar .sidebar-menu .menu-item.active:hover{background-color:rgba(146,205,255,.42)}.sidebar-view .sidebar .sidebar-menu .menu-item:hover{text-shadow:1px 1px rgba(0,0,0,.2);background-color:#eee}.sidebar-view .sidebar .sidebar-menu .menu-item .link{display:block;text-decoration:none}.sidebar-view .sidebar .sidebar-menu .menu-item .link .text{line-height:32px;font-size:14pt}@media only screen and (min-width:0px) and (max-width:600px){.header-view .toolbar{padding-left:0;padding-right:0}.sidebar-view{position:fixed;height:100%;width:200px;box-shadow:3px 0 6px rgba(0,0,0,.2);-webkit-transform:translateX(-200px);transform:translateX(-200px);-webkit-transition:box-shadow,-webkit-transform .6s cubic-bezier(.23,1,.32,1);transition:box-shadow,-webkit-transform .6s cubic-bezier(.23,1,.32,1);transition:box-shadow,transform .6s cubic-bezier(.23,1,.32,1);transition:box-shadow,transform .6s cubic-bezier(.23,1,.32,1),-webkit-transform .6s cubic-bezier(.23,1,.32,1);z-index:999}.sidebar-view.show{-webkit-transform:none;transform:none}.sidebar-view .sidebar-backdrop{position:absolute;top:0;left:200px;z-index:990;background-color:rgba(0,0,0,.5);height:100%;width:100%}}.menu-button{width:180px;text-align:left;margin:0;padding:8px}.menu-button__title{font-size:16px;line-height:18px}.menu-button__subtitle{display:block;font-size:10px;line-height:12px;margin-bottom:8px}.header,.header md-card{height:40px;line-height:40px}.login-view{position:relative}.login-view .login-checking{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;z-index:10;background:rgba(0,0,0,.6);text-align:center;position:absolute;width:100%;height:100%}.login-view .login-checking .loading-text{-webkit-animation:pulse .8s linear infinite;animation:pulse .8s linear infinite}.login-view .login-message{padding:8px}.login-view .login-message.error{background-color:#f44336}.login-view .login-message.success{background-color:#4caf50}md-input-container{margin:4px 0}a{color:inherit}md-toast.md-dark-theme .md-toast-content{background-color:#666}md-card{border-radius:0}.sidebar-view{border-right:1px solid rgba(0,0,0,.2);margin-right:14px;margin-top:40px}.sidebar-view.login{z-index:3}.sidebar-view .sidebar{overflow:hidden;height:100%;padding-left:5px}.sidebar-view .sidebar .mCSB_container_wrapper{margin-right:5px!important}.sidebar-view .sidebar .mCSB_container_wrapper .mCSB_container{padding-right:0!important}.sidebar-view .sidebar .metismenu{-webkit-margin-before:0;-webkit-padding-start:0}.sidebar-view .sidebar .metismenu ul li{position:relative;padding-right:5px}.sidebar-view .sidebar .metismenu ul li ul.sub-menu{-webkit-padding-start:0;margin-left:20px}.sidebar-view .sidebar .metismenu ul li button{width:100%;text-overflow:ellipsis;margin:0}.sidebar-view .sidebar .metismenu ul li button i{margin-right:5px}.logo{width:24px;height:24px;margin-left:10px;float:left;padding-top:7px}.sql-table th{background-color:rgba(200,200,200,.2)}.sql-table td,.sql-table th{white-space:nowrap;text-align:left;padding:5px}.sql-table .sql-table__subheader{font-size:80%;opacity:.7}pre{-moz-tab-size:2;-o-tab-size:2;tab-size:2}.fullscreen{width:100%;height:100%}.fullscreen.default{background-color:#fafafa}.fullscreen.dark{background-color:#303030}:-webkit-full-screen{background-color:inherit}.sql-tabs md-tabs-wrapper{padding-right:48px}.sql-tabs md-next-button{right:48px}
--------------------------------------------------------------------------------
/docs/styles/app-7900bfb0cd.css:
--------------------------------------------------------------------------------
1 | .mh,.mr{margin-right:8px!important}.mh,.ml{margin-left:8px!important}.mt,.mv{margin-top:8px!important}.mb,.mv{margin-bottom:8px!important}.ph,.pr{padding-right:8px!important}.ph,.pl{padding-left:8px!important}.pt,.pv{padding-top:8px!important}.pb,.pv{padding-bottom:8px!important}.content .main-content,.login-view .login-checking,body .wrapper{-webkit-box-orient:vertical;-webkit-box-direction:normal}.login-view .card__header,.sidebar-view .sidebar .sidebar-menu .menu-item{border-bottom:1px solid rgba(0,0,0,.2)}.header-view .toolbar .fs-title a,a{text-decoration:none}.breadcrumb,.sidebar-view .sidebar .metismenu ul,.sidebar-view .sidebar .sidebar-menu{list-style:none}.width-100{width:100%}.width-50{width:50%}.pull-right{float:right}.pull-left{float:left}.m{margin:8px!important}.mh-,.mr-{margin-right:4px!important}.mh-,.ml-{margin-left:4px!important}.mt-,.mv-{margin-top:4px!important}.mb-,.mv-{margin-bottom:4px!important}.m-{margin:4px!important}.mh\+,.mr\+{margin-right:16px!important}.mh\+,.ml\+{margin-left:16px!important}.mt\+,.mv\+{margin-top:16px!important}.mb\+,.mv\+{margin-bottom:16px!important}.m\+{margin:16px!important}.mh\+\+,.mr\+\+{margin-right:24px!important}.mh\+\+,.ml\+\+{margin-left:24px!important}.mt\+\+,.mv\+\+{margin-top:24px!important}.mb\+\+,.mv\+\+{margin-bottom:24px!important}.m\+\+{margin:24px!important}.mh\+\+\+,.mr\+\+\+{margin-right:32px!important}.mh\+\+\+,.ml\+\+\+{margin-left:32px!important}.mt\+\+\+,.mv\+\+\+{margin-top:32px!important}.mb\+\+\+,.mv\+\+\+{margin-bottom:32px!important}.m\+\+\+{margin:32px!important}.mh0,.mr0{margin-right:0!important}.mh0,.ml0{margin-left:0!important}.mt0,.mv0{margin-top:0!important}.mb0,.mv0{margin-bottom:0!important}.m0{margin:0!important}.p{padding:8px!important}.ph-,.pr-{padding-right:4px!important}.ph-,.pl-{padding-left:4px!important}.pt-,.pv-{padding-top:4px!important}.pb-,.pv-{padding-bottom:4px!important}.p-{padding:4px!important}.ph\+,.pr\+{padding-right:16px!important}.ph\+,.pl\+{padding-left:16px!important}.pt\+,.pv\+{padding-top:16px!important}.pb\+,.pv\+{padding-bottom:16px!important}.p\+{padding:16px!important}.ph\+\+,.pr\+\+{padding-right:24px!important}.ph\+\+,.pl\+\+{padding-left:24px!important}.pt\+\+,.pv\+\+{padding-top:24px!important}.pb\+\+,.pv\+\+{padding-bottom:24px!important}.p\+\+{padding:24px!important}.ph\+\+\+,.pr\+\+\+{padding-right:32px!important}.ph\+\+\+,.pl\+\+\+{padding-left:32px!important}.pt\+\+\+,.pv\+\+\+{padding-top:32px!important}.pb\+\+\+,.pv\+\+\+{padding-bottom:32px!important}.p\+\+\+{padding:32px!important}.ph0,.pr0{padding-right:0!important}.ph0,.pl0{padding-left:0!important}.pt0,.pv0{padding-top:0!important}.pb0,.pv0{padding-bottom:0!important}.p0{padding:0!important}.h100{height:100%}.clearfix::after{clear:both;content:"";display:table}.float-right{float:right!important}.float-left{float:left!important}.float-none{float:none!important}.text-left{text-align:left!important}.text-center{text-align:center!important}.text-right{text-align:right!important}.display-block{display:block}.dissolve-animation.ng-hide-add,.dissolve-animation.ng-hide-remove{-webkit-transition:.5s linear all;transition:.5s linear all}.dissolve-animation.ng-hide-add,.dissolve-animation.ng-hide-remove.ng-hide-remove-active{opacity:1}.dissolve-animation.ng-hide-add.ng-hide-add-active,.dissolve-animation.ng-hide-remove{opacity:0}.icon-breath-animation{-webkit-animation:icon-breath 1.5s ease-in-out infinite;animation:icon-breath 1.5s ease-in-out infinite}.icon-rotate-animation{-webkit-animation:icon-rotate .8s linear infinite;animation:icon-rotate .8s linear infinite;margin-right:5px}.no-txt-transform{text-transform:none}@-webkit-keyframes icon-breath{from{box-shadow:0 0 2px 5px rgba(255,138,138,.48);opacity:.5}to{box-shadow:none;opacity:1}}@keyframes icon-breath{from{box-shadow:0 0 2px 5px rgba(255,138,138,.48);opacity:.5}to{box-shadow:none;opacity:1}}@-webkit-keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes icon-rotate{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@media only screen and (min-width:600px){.hide-gt-sm{display:none}}@media only screen and (min-width:600px) and (max-width:960px){.hide-md{display:none}}@media only screen and (min-width:960px){.hide-gt-md{display:none}}@media only screen and (min-width:960px) and (max-width:1200px){.hide-lg{display:none}}@media only screen and (min-width:1200px){.hide-gt-lg{display:none}}@media only screen and (min-width:0px) and (max-width:600px){.hide-sm{display:none}.lx-date-picker__day{line-height:16px}.lx-date-picker__day a{width:16px;height:16px;line-height:16px}}.breadcrumb{margin:0}.breadcrumb li{display:inline-block}body{font-family:Helvetica,arial}body .wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-height:100vh}.content,.content .main-content{display:-webkit-box;display:-ms-flexbox}.content{display:flex;height:100vh}.content.login{width:100%}.content.login .main-view{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.main-content.login .footer{position:fixed;bottom:0;left:0;right:0}@media only screen and (min-width:0px) and (max-width:600px){.content{margin-top:56px}.content .main-content .main-view{margin-left:8px;margin-right:8px}}.content .main-content{-webkit-box-flex:1;-ms-flex:1;flex:1;display:flex;-ms-flex-flow:column nowrap;flex-flow:column nowrap;overflow:auto;margin-top:40px;position:relative}.content .main-content .main-view{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center}md-sidenav[md-component-id=tableSiedenav]{width:50%!important;max-width:50%!important;height:100%;overflow:hidden}md-sidenav[md-component-id=tableSiedenav] .right-button{position:absolute;right:20px;top:20px;z-index:1}md-sidenav[md-component-id=tableSiedenav] .md-closed.md-locked-open-add-active,md-sidenav[md-component-id=tableSiedenav] .md-locked-open{width:50%!important;height:100%;max-width:50%!important}md-sidenav[md-component-id=tableSiedenav] md-content{position:absolute;top:0;bottom:0;width:100%;overflow:hidden;padding-top:0;padding-bottom:0}md-sidenav[md-component-id=tableSiedenav] md-content md-tab-content,md-sidenav[md-component-id=tableSiedenav] md-content md-tab-content>div{max-height:100%}.content .main-content .main-view.ng-enter{-webkit-animation:bounceIn .8s;animation:bounceIn .8s}.content .main-content .main-view .full-width{-ms-flex-item-align:start;align-self:flex-start;padding:10px 30px 20px;width:100%}.footer .copyright{margin:auto;text-shadow:1px 1px rgba(0,0,0,.2)}.footer .toolbar{background:#424242!important}.header-view{position:fixed;width:100%;z-index:2}body #loading-bar .bar{background:#2c3e50;height:3px}body #loading-bar .peg{box-shadow:0 0 10px #2c3e50,0 0 5px #2c3e50}body #loading-bar-spinner .spinner-icon{border-top-color:#2c3e50;border-left-color:#2c3e50}.sidebar-view .sidebar .sidebar-menu .menu-item.active{border-radius:5px;background-color:rgba(146,205,255,.42)}.sidebar-view .sidebar .sidebar-menu .menu-item.active i{color:#4caf50}.sidebar-view .sidebar .sidebar-menu .menu-item.active:hover{background-color:rgba(146,205,255,.42)}.sidebar-view .sidebar .sidebar-menu .menu-item:hover{text-shadow:1px 1px rgba(0,0,0,.2);background-color:#eee}.sidebar-view .sidebar .sidebar-menu .menu-item .link{display:block;text-decoration:none}.sidebar-view .sidebar .sidebar-menu .menu-item .link .text{line-height:32px;font-size:14pt}@media only screen and (min-width:0px) and (max-width:600px){.header-view .toolbar{padding-left:0;padding-right:0}.sidebar-view{position:fixed;height:100%;width:200px;box-shadow:3px 0 6px rgba(0,0,0,.2);-webkit-transform:translateX(-200px);transform:translateX(-200px);-webkit-transition:box-shadow,-webkit-transform .6s cubic-bezier(.23,1,.32,1);transition:box-shadow,-webkit-transform .6s cubic-bezier(.23,1,.32,1);transition:box-shadow,transform .6s cubic-bezier(.23,1,.32,1);transition:box-shadow,transform .6s cubic-bezier(.23,1,.32,1),-webkit-transform .6s cubic-bezier(.23,1,.32,1);z-index:999}.sidebar-view.show{-webkit-transform:none;transform:none}.sidebar-view .sidebar-backdrop{position:absolute;top:0;left:200px;z-index:990;background-color:rgba(0,0,0,.5);height:100%;width:100%}}.menu-button{width:180px;text-align:left;margin:0;padding:8px}.menu-button__title{font-size:16px;line-height:18px}.menu-button__subtitle{display:block;font-size:10px;line-height:12px;margin-bottom:8px}.header,.header md-card{height:40px;line-height:40px}.login-view{position:relative}.login-view .login-checking{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;z-index:10;background:rgba(0,0,0,.6);text-align:center;position:absolute;width:100%;height:100%}.login-view .login-checking .loading-text{-webkit-animation:pulse .8s linear infinite;animation:pulse .8s linear infinite}.login-view .login-message{padding:8px}.login-view .login-message.error{background-color:#f44336}.login-view .login-message.success{background-color:#4caf50}md-input-container{margin:4px 0}a{color:inherit}md-toast.md-dark-theme .md-toast-content{background-color:#666}md-card{border-radius:0}.sidebar-view{border-right:1px solid rgba(0,0,0,.2);margin-right:14px;margin-top:40px}.sidebar-view.login{z-index:3}.sidebar-view .sidebar{overflow:hidden;height:100%;padding-left:5px}.sidebar-view .sidebar .mCSB_container_wrapper{margin-right:5px!important}.sidebar-view .sidebar .mCSB_container_wrapper .mCSB_container{padding-right:0!important}.sidebar-view .sidebar .metismenu{-webkit-margin-before:0;-webkit-padding-start:0}.sidebar-view .sidebar .metismenu ul li{position:relative;padding-right:5px}.sidebar-view .sidebar .metismenu ul li ul.sub-menu{-webkit-padding-start:0;margin-left:20px}.sidebar-view .sidebar .metismenu ul li button{width:100%;text-overflow:ellipsis;margin:0}.sidebar-view .sidebar .metismenu ul li button i{margin-right:5px}.logo{width:24px;height:24px;margin-left:10px;float:left;padding-top:7px}.sql-table th{background-color:rgba(200,200,200,.2)}.sql-table td,.sql-table th{white-space:nowrap;text-align:left;padding:5px}.sql-table .sql-table__subheader{font-size:80%;opacity:.7}pre{-moz-tab-size:2;-o-tab-size:2;tab-size:2}.fullscreen{width:100%;height:100%}.fullscreen.default{background-color:#fafafa}.fullscreen.dark{background-color:#303030}:-webkit-full-screen{background-color:inherit}.sql-tabs md-tabs-wrapper{padding-right:48px}.sql-tabs md-next-button{right:48px}
--------------------------------------------------------------------------------
/src/app/sql/ace-mode-clickhouse.js:
--------------------------------------------------------------------------------
1 | var define = window.define || window.ace.define;
2 |
3 | define("ace/mode/clickhouse_highlight_rules", ["$rootScope","require", "exports", "module", "ace/lib/oop", "ace/mode/text_highlight_rules"], function(require, exports) {
4 | "use strict";
5 |
6 | var oop = require("../lib/oop");
7 | var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;
8 |
9 |
10 | var ClickhouseHighlightRules = function() {
11 | var keywords = (
12 | "SELECT|INSERT|UPDATE|DELETE|FROM|WHERE|AND|OR|LIMIT|OFFSET|HAVING|AS|" +
13 | "WHEN|ELSE|END|TYPE|LEFT|RIGHT|JOIN|ON|OUTER|DESC|ASC|UNION|CREATE|TABLE|PRIMARY|KEY|" +
14 | "FOREIGN|NOT|REFERENCES|DEFAULT|NULL|INNER|CROSS|NATURAL|DATABASE|DROP|GRANT|" +
15 | "ANY|ATTACH|DETACH|DESCRIBE|OPTIMIZE|PREWHERE|TOTALS|DATABASES|PROCESSLIST|SHOW|IF"
16 | );
17 | var identifier = "[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*";
18 | var keywordsDouble="IF\\W+NOT\\W+EXISTS|IF\\W+EXISTS|FORMAT\\W+Vertical|FORMAT\\W+JSONCompact|FORMAT\\W+JSONEachRow|FORMAT\\W+TSKV|FORMAT\\W+TabSeparatedWithNames|FORMAT\\W+TabSeparatedWithNamesAndTypes|FORMAT\\W+TabSeparatedRaw|FORMAT\\W+BlockTabSeparated|FORMAT\\W+CSVWithNames|FORMAT\\W+CSV|FORMAT\\W+JSON|FORMAT\\W+TabSeparated";
19 |
20 | var builtinConstants = (
21 | "true|false"
22 | );
23 |
24 | var builtinFunctions = (
25 | "avg|count|first|last|max|min|sum|ucase|lcase|mid|len|round|rank|now|" +
26 | "coalesce|ifnull|isnull|nvl|countIf|timeSlot|yesterday|today|now|toRelativeSecondNum|" + "toRelativeMinuteNum|toRelativeHourNum|toRelativeDayNum|toRelativeWeekNum|toRelativeMonthNum|" +
27 | "toRelativeYearNum|toTime|toStartOfHour|toStartOfFiveMinute|toStartOfMinute|toStartOfYear|" +
28 | "toStartOfQuarter|toStartOfMonth|toMonday|toSecond|toMinute|toHour|toDayOfWeek|toDayOfMonth|" +
29 | "toMonth|toYear|toFixedString|toStringCutToZero|reinterpretAsString|reinterpretAsDate|" +
30 | "reinterpretAsDateTime|reinterpretAsFloat32|reinterpretAsFloat64|reinterpretAsInt8|" +
31 | "reinterpretAsInt16|reinterpretAsInt32|reinterpretAsInt64|reinterpretAsUInt8|" +
32 | "reinterpretAsUInt16|reinterpretAsUInt32|reinterpretAsUInt64|toUInt8|toUInt16|toUInt32|" +
33 | "toUInt64|toInt8|toInt16|toInt32|toInt64|toFloat32|toFloat64|toDate|toDateTime|toString|" +
34 | "bitAnd|bitOr|bitXor|bitNot|bitShiftLeft|bitShiftRight|abs|negate|modulo|intDivOrZero|" +
35 | "intDiv|divide|multiply|minus|plus|empty|notEmpty|length|lengthUTF8|lower|upper|lowerUTF8|" +
36 | "upperUTF8|reverse|reverseUTF8|concat|substring|substringUTF8|appendTrailingCharIfAbsent|" +
37 | "position|positionUTF8|match|extract|extractAll|like|notLike|replaceOne|replaceAll|" +
38 | "replaceRegexpOne|range|arrayElement|has|indexOf|countEqual|arrayEnumerate|arrayEnumerateUniq|" +
39 | "arrayJoin|arrayMap|arrayFilter|arrayExists|arrayCount|arrayAll|arrayFirst|arraySum|splitByChar|" +
40 | "splitByString|alphaTokens|domainWithoutWWW|topLevelDomain|firstSignificantSubdomain|" +
41 | "cutToFirstSignificantSubdomain|queryString|URLPathHierarchy|URLHierarchy|extractURLParameterNames|" +
42 | "extractURLParameters|extractURLParameter|queryStringAndFragment|cutWWW|cutQueryString|" +
43 | "cutFragment|cutQueryStringAndFragment|cutURLParameter|IPv4NumToString|IPv4StringToNum|" +
44 | "IPv4NumToStringClassC|IPv6NumToString|IPv6StringToNum|rand|rand64|halfMD5|MD5|sipHash64|" +
45 | "sipHash128|cityHash64|intHash32|intHash64|SHA1|SHA224|SHA256|URLHash|hex|unhex|bitmaskToList|" +
46 | "bitmaskToArray|floor|ceil|round|roundToExp2|roundDuration|roundAge|regionToCountry|" +
47 | "regionToContinent|regionToPopulation|regionIn|regionHierarchy|regionToName|OSToRoot|OSIn|" +
48 | "OSHierarchy|SEToRoot|SEIn|SEHierarchy|dictGetUInt8|dictGetUInt16|dictGetUInt32|" +
49 | "dictGetUInt64|dictGetInt8|dictGetInt16|dictGetInt32|dictGetInt64|dictGetFloat32|" +
50 | "dictGetFloat64|dictGetDate|dictGetDateTime|dictGetString|dictGetHierarchy|dictHas|dictIsIn|" +
51 | "uniq|argMin|argMax|uniqCombined|uniqHLL12|uniqExact|groupArray|groupUniqArray|quantile|" +
52 | "quantileDeterministic|quantileTiming|quantileTimingWeighted|quantileExact|" +
53 | "quantileExactWeighted|quantileTDigest|median|quantiles|varSamp|varPop|stddevSamp|stddevPop|" +
54 | "covarSamp|covarPop|corr|sequenceMatch|sequenceCount|uniqUpTo|countIf|avgIf|" +
55 | "quantilesTimingIf|argMinIf|uniqArray|sumArray|quantilesTimingArrayIf|uniqArrayIf|medianIf|" +
56 | "quantilesIf|varSampIf|varPopIf|stddevSampIf|stddevPopIf|covarSampIf|covarPopIf|corrIf|" +
57 | "uniqArrayIf|sumArrayIf"
58 | );
59 |
60 | var dataTypes = (
61 | "int|numeric|decimal|date|varchar|char|bigint|float|double|bit|binary|text|set|timestamp|" +
62 | "money|real|number|integer|" +
63 | "uint8|uint16|uint32|uint64|int8|int16|int32|int64|float32|float64|datetime|enum8|enum16|" +
64 | "array|tuple|string"
65 | );
66 |
67 | var keywordMapper = this.createKeywordMapper({
68 | "support.function": builtinFunctions,
69 | "keyword": keywords,
70 | "constant.language": builtinConstants,
71 | "storage.type": dataTypes,
72 | "markup.bold":window.global_keywords_tables,
73 | "markup.heading":window.global_keywords_fields
74 | }, "identifier", true);
75 |
76 |
77 | this.$rules = {
78 | "start": [{
79 | token: "comment",
80 | regex: "--.*$",
81 | caseInsensitive: true
82 | },{
83 | token: "keyword",
84 | regex: "GROUP\\W+BY|ORDER\\W+BY"
85 | },
86 | {
87 | token: "comment",
88 | start: "/\\*",
89 | end: "\\*/"
90 | }, {
91 | token: "string", // " string
92 | regex: '".*?"'
93 | },{
94 | token: "storage",
95 | regex: keywordsDouble
96 | }, {
97 | token: "string", // ' string
98 | regex: "'.*?'"
99 | }, {
100 | token: "constant.numeric", // float
101 | regex: "[+-]?\\d+(?:(?:\\.\\d*)?(?:[eE][+-]?\\d+)?)?\\b"
102 | }, {
103 | token: keywordMapper,
104 | regex: "[a-zA-Z_$][a-zA-Z0-9_$]*\\b"
105 | },
106 | {
107 | token: "constant.character.escape",
108 | regex: /;{2}/
109 | },
110 | {
111 | token: "punctuation",
112 | regex: /[?:,;.]/,
113 | },
114 | {
115 | token: "keyword.operator",
116 | regex: "\\+|\\-|\\/|\\/\\/|%|<@>|@>|<@|&|\\^|~|<|>|<=|=>|==|!=|<>|="
117 | }, {
118 | token: "paren.lparen",
119 | regex: "[\\(]"
120 | }, {
121 | token: "paren.rparen",
122 | regex: "[\\)]"
123 | }, {
124 | token: "text",
125 | regex: "\\s+"
126 | }
127 |
128 | ]
129 | };
130 | this.normalizeRules();
131 |
132 | var completions = [];
133 | var addCompletions = function(arr, meta) {
134 | arr.forEach(function(v) {
135 | completions.push({
136 | name: v,
137 | value: v,
138 | score: 0,
139 | meta: meta
140 | });
141 | });
142 | };
143 |
144 | addCompletions(builtinFunctions.split('|'), 'function');
145 | addCompletions(keywords.split('|'), 'keyword');
146 | addCompletions("GROUP BY|ORDER BY|FORMAT JSON|FORMAT JSONCompact|FORMAT JSONEachRow|FORMAT TSKV|FORMAT TabSeparated|FORMAT TabSeparatedWithNames|FORMAT TabSeparatedWithNamesAndTypes|FORMAT TabSeparatedRaw|FORMAT BlockTabSeparated|FORMAT CSV|FORMAT CSVWithNames".split('|'), 'keyword');
147 | addCompletions(dataTypes.split('|'), 'type');
148 | addCompletions(window.global_keywords_tables.split('|'), 'storage');
149 | addCompletions(window.global_keywords_fields.split('|'), 'storage');
150 | //this allows for custom 'meta' and proper case of completions
151 | this.completions = completions;
152 |
153 | };
154 |
155 | oop.inherits(ClickhouseHighlightRules, TextHighlightRules);
156 | exports.ClickhouseHighlightRules = ClickhouseHighlightRules;
157 | });
158 |
159 | define("ace/mode/clickhouse", ["require", "exports", "module", "ace/lib/oop", "ace/mode/text","ace/token_iterator", "ace/mode/clickhouse_highlight_rules", "ace/range"], function(require, exports) {
160 | "use strict";
161 |
162 | var oop = require("../lib/oop");
163 | var TextMode = require("./text").Mode;
164 | var ClickhouseHighlightRules = require("./clickhouse_highlight_rules").ClickhouseHighlightRules;
165 |
166 | var Mode = function() {
167 |
168 | this.HighlightRules = ClickhouseHighlightRules;
169 | };
170 | oop.inherits(Mode, TextMode);
171 |
172 | (function() {
173 |
174 | this.lineCommentStart = "--";
175 | this.getCompletions = function(state, session) {
176 | return session.$mode.$highlightRules.completions;
177 | };
178 |
179 | this.$id = "ace/mode/clickhouse";
180 | // ---------------------------------------------------------------------------
181 | this.findTokens = function(sql,type,needfirst) {
182 | sql=sql.replace(/^(\r\n|\n|\r)/gm,"").replace(/(\r\n|\n|\r)$/gm,"");
183 | var TokenIterator = require("ace/token_iterator").TokenIterator;
184 | var EditSession = require("ace/edit_session").EditSession;
185 |
186 | var session = new EditSession(sql,this);
187 | var iterator = new TokenIterator(session, 0, 0);
188 | var token = iterator.getCurrentToken();
189 | var matches=[];
190 | var startRow= 0, startCol=0;
191 |
192 | while (token) {
193 | var t = token;
194 | t['row'] = iterator.getCurrentTokenRow();
195 | t['col'] = iterator.getCurrentTokenColumn();
196 | if (t.type == type ) {
197 | matches.push(t);
198 | if (needfirst)
199 | {
200 | t.value=t.value.toLowerCase();
201 | return t;
202 | }
203 | }
204 | token = iterator.stepForward();
205 | }//w
206 | return matches;
207 | };
208 | this.splitByTokens = function(sql,type,value) {
209 | sql=sql.replace(/^(\r\n|\n|\r)/gm,"").replace(/(\r\n|\n|\r)$/gm,"");
210 |
211 | var TokenIterator = require("ace/token_iterator").TokenIterator;
212 | var EditSession = require("ace/edit_session").EditSession;
213 | var Range = require("ace/range").Range;
214 |
215 | var session = new EditSession(sql,this);
216 | var iterator = new TokenIterator(session, 0, 0);
217 | var token = iterator.getCurrentToken();
218 | var matches=[];
219 | var startRow= 0, startCol=0;
220 |
221 | while (token) {
222 | var t=token;
223 | t['row']=iterator.getCurrentTokenRow();
224 | t['col']=iterator.getCurrentTokenColumn();
225 | if ( t.type == type && t.value == value)
226 | {
227 | //var session = new EditSession(sql,this);
228 | //session.clearSelection();
229 | var range1 = new Range(startRow, startCol, t.row, t.col+value.length);
230 | var text=session.getTextRange(range1);
231 | startRow= t.row;
232 | startCol= t.col+value.length;
233 | text=text.trim().replace(new RegExp("^"+value+"|"+value+'$','g'),"").trim().replace(/^(\r\n|\n|\r)/gm,"").replace(/(\r\n|\n|\r)$/gm,"");
234 | if (text.length>2)
235 | {
236 | matches.push({sql:text,range:range1});
237 | }
238 | }
239 | token = iterator.stepForward();
240 | }
241 | var range1 = new Range(startRow, startCol,Number.MAX_VALUE,Number.MAX_VALUE);
242 | var text=session.getTextRange(range1);
243 | text=text.trim().replace("^("+value+")","").replace(value+"$","").trim().replace(/^(\r\n|\n|\r)/gm,"").replace(/(\r\n|\n|\r)$/gm,"");
244 | text=text.replace(new RegExp("^"+value+"|"+value+'$','g'),"").trim().replace(/^(\r\n|\n|\r)/gm,"").replace(/(\r\n|\n|\r)$/gm,"");
245 | if (text.length>2)
246 | {
247 |
248 | matches.push({sql:text,range:range1});
249 | }
250 | return matches;
251 | };
252 |
253 | }).call(Mode.prototype);
254 |
255 | exports.Mode = Mode;
256 |
257 | });
258 |
--------------------------------------------------------------------------------
/src/app/sql/sql.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{'Настройки редактора' | translate}}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{tab.name + (tab.changed ? ' *' : '')}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
31 |
{{tab.buttonTitle}}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | {{'На весь экран' | translate}}
40 |
41 |
42 |
43 | {{'Сохранить' | translate}}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{item.name}}
53 | {{'История зарпросов' | translate}}
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {{'Словари' | translate}}
65 |
66 |
67 |
68 |
69 | {{dictionary.title}}
70 |
71 |
72 |
73 |
74 |
75 |
76 | {{'Лог запросов' | translate}}
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | {{::result.time}}
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
108 | {{'CSV с заголовком' | translate}}
109 |
110 |
111 |
112 |
117 | {{'CSV без заголовка' | translate}}
118 |
119 |
120 |
121 |
128 | {{'Табулированный текст с заголовками' | translate}}
129 |
130 |
131 |
132 |
139 | {{'Табулированный текст без заголовков' | translate}}
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 | {{'время' | translate}}: {{item.statistics.elapsed | number:3}},
148 | {{'строк прочитано' | translate}}: {{item.statistics.rows_read | number}},
149 | {{'прочитано' | translate}}: {{item.statistics.bytes_read | filesize}}
150 |
151 |
152 |
153 |
154 |
155 |
{{'нет данных' | translate}}
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 | {{'Настройки редактора' | translate}}
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 | {{format.name}}
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 | {{theme}}
198 |
199 |
200 |
201 |
202 |
203 |
204 | {{theme.title}}
205 |
206 |
207 |
208 |
209 | {{'Сохранять сессию' | translate}}
210 |
211 |
212 |
213 |
214 | {{'Закрыть' | translate}}
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 | {{'Сохранить' | translate}}
225 |
226 | {{'Лог запросов' | translate}}
227 |
228 |
229 |
232 |
233 | {{'История запросов пуста' | translate}}
234 |
235 |
236 |
--------------------------------------------------------------------------------
/src/app/sql/sql.js:
--------------------------------------------------------------------------------
1 | // @todo : костылик , не получилось сделать http://stackoverflow.com/questions/22166784/dynamically-update-syntax-highlighting-mode-rules-for-the-ace-editor
2 | window.global_keywords_fields = "";
3 | window.global_keywords_tables = "";
4 |
5 | ((angular, smi2) => {
6 | 'use strict';
7 |
8 | angular.module(smi2.app.name).controller('SqlController', SqlController);
9 | SqlController.$inject = [
10 | '$scope',
11 | '$rootScope',
12 | '$window',
13 | 'localStorageService',
14 | 'API',
15 | '$mdSidenav',
16 | '$mdDialog',
17 | '$mdToast',
18 | 'ThemeService',
19 | '$timeout',
20 | '$filter'
21 | ];
22 |
23 | /**
24 | * @ngdoc controller
25 | * @name smi2.controller:SqlController
26 | * @description SQL controller data
27 | */
28 | function SqlController($scope,
29 | $rootScope,
30 | $window,
31 | localStorageService,
32 | API,
33 | $mdSidenav,
34 | $mdDialog,
35 | $mdToast,
36 | ThemeService,
37 | $timeout,
38 | $filter) {
39 |
40 | const SQL_HISTORY_KEY = 'sqlHistory2';
41 | const SQL_LOG_KEY = 'sqlLog';
42 | const SQL_SAVE_TABS_KEY = 'saveTabs';
43 | const SQL_SESSION_KEY = 'sessionData';
44 | const SQL_LOG_LENGTH = 30;
45 |
46 | $scope.vars = {
47 | sqlHistory: localStorageService.get(SQL_HISTORY_KEY) || [],
48 | dictionaries: [],
49 | tabs: [],
50 | saveTabs: localStorageService.get(SQL_SAVE_TABS_KEY) || false,
51 | uiTheme: ThemeService.themeObject,
52 | uiThemes: ThemeService.list,
53 | currentTab: {},
54 | selectedTab: 0,
55 | sqlLog: localStorageService.get(SQL_LOG_KEY) || [],
56 | formats: [{
57 | name: $filter('translate')('Таблица'),
58 | sql: ' format JSON',
59 | render: 'html'
60 | }, {
61 | name: 'JSON compact',
62 | sql: ' format JSONCompact'
63 | }
64 | ],
65 | db: null,
66 | limitRows: localStorageService.get('editorLimitRows') || 500,
67 | fontSize: localStorageService.get('editorFontSize') || 16,
68 | theme: localStorageService.get('editorTheme') || 'cobalt'
69 | };
70 | $scope.vars.format = $scope.vars.formats[0];
71 | $scope.vars.themes = [
72 | 'ambiance',
73 | 'eclipse', 'mono_industrial', 'tomorrow_night_blue',
74 | 'chaos', 'github', 'monokai', 'tomorrow_night_bright',
75 | 'chrome', 'idle_fingers', 'pastel_on_dark', 'tomorrow_night_eighties',
76 | 'clouds', 'iplastic', 'solarized_dark', 'tomorrow_night',
77 | 'clouds_midnight', 'katzenmilch', 'solarized_light', 'twilight',
78 | 'cobalt', 'kr_theme', 'sqlserver', 'vibrant_ink',
79 | 'crimson_editor', 'kuroir', 'terminal', 'xcode',
80 | 'dawn', 'merbivore', 'textmate',
81 | 'dreamweaver', 'merbivore_soft', 'tomorrow'
82 | ];
83 | $rootScope.breadcrumbs = [{
84 | text: 'SQL',
85 | link: 'sql'
86 | }];
87 |
88 | /**
89 | * Prevent data loss on close page
90 | */
91 | $window.onbeforeunload = (event) => {
92 | if ($scope.vars.currentTab.sql !== '' && location.hostname != 'localhost') {
93 | let message = $filter('translate')('Хотите покинуть страницу?');
94 | if (typeof event == 'undefined') {
95 | event = window.event;
96 | }
97 | if (event) {
98 | event.returnValue = message;
99 | }
100 | return message;
101 | }
102 | };
103 |
104 | /**
105 | * Prevent data loss on state change
106 | */
107 | const clearRouterListener = $scope.$on('$stateChangeStart', (event) => {
108 | let message = $filter('translate')('Хотите покинуть страницу?');
109 | if (!event.defaultPrevented && $scope.vars.currentTab !== '' && !confirm(message)) {
110 | event.preventDefault();
111 | }
112 | });
113 |
114 | /**
115 | * Save tabs session
116 | */
117 | const saveSession = () => {
118 | if ($scope.vars.saveTabs) {
119 | const tabs = $scope.vars.tabs.map((tab) => {
120 | return {
121 | name: tab.name,
122 | sql: tab.sql,
123 | buttonTitle: tab.buttonTitle,
124 | format: tab.format,
125 | results: [],
126 | editor: null,
127 | selectedResultTab: 0
128 | };
129 | });
130 | localStorageService.set(SQL_SESSION_KEY, tabs);
131 | }
132 | }
133 |
134 | /**
135 | * @ngdoc method
136 | * @methodOf smi2.controller:SqlController
137 | * @name executeQuery
138 | * @description Execute single SQL query
139 | * @param {} query
140 | */
141 | $scope.executeQuery = (query, queue, resultContainer) => {
142 |
143 | // содержит дополнительные GET параметры для выполнения запрос
144 | let extendSettings = '';
145 |
146 | $scope.vars.currentTab.loading = true;
147 |
148 | // если указан limitRows
149 | if ($scope.vars.limitRows) {
150 | extendSettings += 'max_result_rows=' + $scope.vars.limitRows + '&result_overflow_mode=throw';
151 | }
152 |
153 | API.query(query.sql, query.format, true, extendSettings).then((data) => {
154 |
155 | let r = data;
156 |
157 | if (typeof data !== 'object') {
158 | data = {
159 | data: r,
160 | meta: null,
161 | rows: null,
162 | statistics: null
163 | };
164 | }
165 | data.error = false;
166 | data.query = query;
167 | let resultData = $scope.renderResult(data);
168 | resultContainer.data.push(resultData);
169 |
170 | // Рекурсивный вызов executeQuery если в очереди
171 | // еще остались элементы
172 | if ((query.index + 1) < queue.length) {
173 | $scope.executeQuery(queue[query.index + 1], queue, resultContainer);
174 | }
175 | else {
176 | // отрисовка
177 | $scope.renderFinalResult(resultContainer);
178 | }
179 |
180 | }, (response) => {
181 | $mdToast.show(
182 | $mdToast
183 | .simple()
184 | .content($filter('translate')('Ошибка'))
185 | .theme(ThemeService.theme)
186 | .position('bottom right')
187 | );
188 |
189 | let result = {
190 | meta: null,
191 | rows: null,
192 | query: query,
193 | statistics: null
194 | };
195 | if (response && response.data) {
196 | result.error = angular.toJson(response.data).replace(/\\n/gi, '
').replace(/^"/, '').replace(/"$/, '');
197 | } else {
198 | result.error = response;
199 | }
200 | resultContainer.data.push($scope.renderResult(result));
201 |
202 | $scope.renderFinalResult(resultContainer);
203 | });
204 | };
205 |
206 | /**
207 | * Show results
208 | * @param data
209 | * @returns {*}
210 | */
211 | $scope.renderResult = (data) => {
212 | if (typeof data.error == 'string') {
213 | data.result = '' + data.error + '
';
214 | }
215 | else if (typeof data.data !== 'object') {
216 | if (typeof data.data !== 'string') {
217 | data.result = '' + angular.toJson(data.data, true) + '
';
218 | }
219 | else {
220 | data.result = '' + data.data + '
';
221 | }
222 | }
223 | else {
224 | data.result = API.dataToHtml(data);
225 | data.createtable = API.dataToCreateTable(data);
226 | }
227 | return data;
228 | };
229 |
230 | /**
231 | * Operations after render
232 | * @param result
233 | */
234 | $scope.renderFinalResult = (result) => {
235 | $scope.vars.currentTab.loading = false;
236 | if (result.data.find((item) => (
237 | item.query &&
238 | item.query.keyword &&
239 | ['DROP', 'CREATE', 'ALTER'].indexOf(
240 | item.query.keyword.toUpperCase()
241 | ) != -1
242 | ))) {
243 | $scope.selectDatabase($scope.vars.db);
244 | }
245 | };
246 |
247 | /**
248 | * Execute SQL statement
249 | * @param type
250 | */
251 | $scope.execute = (type, tab) => {
252 |
253 | let sql = tab.sql;
254 | let numquery = 0;
255 | const editor = tab.editor;
256 | let queue = [];
257 | let selectSql = editor.getSelectedText();
258 | let result = {
259 | data: [],
260 | time: $filter('date')(new Date(), 'HH:mm:ss'),
261 | pinned: false
262 | };
263 |
264 | // получаем выделенный текст, и если выделено - ранаем выделение
265 | if (!(selectSql === '' || selectSql === null)) {
266 | sql = selectSql;
267 | }
268 |
269 | // Выход если пустой sql
270 | if (sql === '' || sql === null) {
271 | $mdToast.show(
272 | $mdToast
273 | .simple()
274 | .content($filter('translate')('Не введен SQL'))
275 | .theme(ThemeService.theme)
276 | .position('bottom right')
277 | );
278 |
279 | return;
280 | }
281 |
282 | // Clear not-pinned tabs
283 | tab.results = tab.results.reduce((arr, item) => {
284 | if (item.pinned) {
285 | arr.push(item);
286 | }
287 | return arr;
288 | }, []);
289 | tab.results.unshift(result);
290 |
291 | // Save to SQL log
292 | if ($scope.vars.sqlLog.indexOf(sql) == -1) {
293 | $scope.vars.sqlLog.unshift(sql);
294 | if ($scope.vars.sqlLog.length > SQL_LOG_LENGTH) {
295 | $scope.vars.sqlLog.splice(0, SQL_LOG_LENGTH);
296 | }
297 | localStorageService.set(SQL_LOG_KEY, $scope.vars.sqlLog);
298 | }
299 |
300 | // Save tabs session
301 | saveSession();
302 |
303 | // Split SQL into subqueries
304 | editor
305 | .session
306 | .$mode
307 | .splitByTokens(sql, 'constant.character.escape', ';;')
308 | .forEach((item) => {
309 |
310 | const subSql = item.sql;
311 |
312 | // Ignore short queries
313 | if (subSql.length < 5) {
314 | return;
315 | }
316 |
317 | // Если комманда исполнить текущий и НЕ выделен текст -> пропускаем все пока не найдем подходящий
318 | if (type == 'current' && !selectSql) {
319 | let cursor = editor.selection.getCursor();
320 |
321 | if (!cursor || !angular.isDefined(cursor) ||
322 | item.range.compare(cursor.row, cursor.column) !== 0) {
323 | return;
324 | }
325 | }
326 |
327 | let _format = null;
328 | let _format_seted = false;
329 | let storage = false;
330 | let _keyword = null;
331 | let set_format = editor.session.$mode.findTokens(subSql, "storage", true);
332 | let keyword = editor.session.$mode.findTokens(subSql, "keyword", true);
333 |
334 | if (set_format.hasOwnProperty('value')) {
335 | _format = false;
336 | storage = set_format.value;
337 | }
338 | else {
339 | _format = ' FORMAT JSON ';
340 | _format_seted = true;
341 | }
342 | if (keyword.hasOwnProperty('value')) {
343 | _keyword = keyword.value;
344 | }
345 | if (_keyword !== 'select') {
346 | _format = false;
347 | _format_seted = false;
348 | }
349 |
350 | // Queue creation
351 | queue.push({
352 | sql: subSql,
353 | index: numquery,
354 | format: _format,
355 | setedformat: _format_seted,
356 | keyword: _keyword,
357 | storage: storage
358 | });
359 |
360 | numquery++;
361 | });
362 |
363 | if (queue.length) {
364 | $scope.executeQuery(queue[0], queue, result);
365 | }
366 |
367 | };
368 |
369 | /**
370 | * Set theme for all editors
371 | * @param theme
372 | */
373 | $scope.setTheme = (theme) => {
374 | $scope.vars.theme = theme;
375 | $scope.vars.tabs.forEach((tab) => tab.editor.setTheme('ace/theme/' + theme));
376 | localStorageService.set('editorTheme', theme);
377 | };
378 |
379 | /**
380 | * Set database name
381 | * @param db
382 | */
383 | $scope.selectDatabase = (db) => {
384 |
385 | if (!db) {
386 | return;
387 | }
388 |
389 | $scope.vars.db = db;
390 | API.setDatabase(db);
391 |
392 | API.query("SELECT table,name,type FROM system.columns WHERE database=\'" + db + "\'", null)
393 | .then((data) => {
394 |
395 | let fields = [],
396 | ufields = {};
397 | let tables = [],
398 | utables = {};
399 | let keys = [];
400 | data.meta.forEach((cell) => {
401 | keys.push(cell.name);
402 | });
403 | data.data.forEach((row) => {
404 | keys.forEach((key) => {
405 |
406 | if (key == 'table') {
407 | if (!utables.hasOwnProperty(row[key])) {
408 | utables[row[key]] = 1;
409 | tables.push(row[key]);
410 | }
411 | }
412 | if (key == 'name') {
413 | if (!ufields.hasOwnProperty(row[key])) {
414 | ufields[row[key]] = 1;
415 | fields.push(row[key]);
416 | }
417 | }
418 | });
419 | });
420 |
421 | window.global_keywords_fields = fields.join('|') + '|';
422 | window.global_keywords_tables = tables.join('|') + '|' + db;
423 |
424 | // reload highlights
425 | $scope.vars.tabs.forEach((tab) => {
426 | if (tab.editor) {
427 | tab.editor.session.setMode({
428 | path: "ace/mode/clickhouse",
429 | v: Date.now()
430 | });
431 | tab.editor.session.bgTokenizer.start(0);
432 | }
433 | });
434 | });
435 | };
436 |
437 | /**
438 | * Add word from dict to SQL etitor
439 | */
440 | $scope.addDictionariesWord = (word) => {
441 | const editor = $scope.vars.currentTab.editor;
442 | const position = editor.getCursorPosition();
443 | position.column += word.length;
444 | editor.clearSelection();
445 | editor.insert(word);
446 | $scope.vars.currentTab.sql = editor.getValue();
447 | $timeout(() => {
448 | editor.focus();
449 | editor.moveCursorToPosition(position);
450 | });
451 | };
452 |
453 | /**
454 | * Load dicts for ACE autocomplete
455 | */
456 | $scope.loadDictionaries = () => {
457 | $scope.vars.dictionaries = [];
458 | API.query("select name,key,attribute.names,attribute.types from system.dictionaries ARRAY JOIN attribute ORDER BY name,attribute.names", null).then((data) => {
459 | data.data.forEach((item) => {
460 | // dictGetUInt64('ads.x', 'site_id', toUInt64(xxxx)) AS site_id,
461 | let dic = 'dictGet' + item["attribute.types"] + '(\'' + item.name + '\',\'' + item["attribute.names"] + '\',to' + item.key + '( ID ) ) AS ' + item.name.replace(/\./, '_') + '_' + item["attribute.names"] + ',';
462 |
463 | $scope.vars.dictionaries.push({
464 | dic: dic,
465 | title: item.name + '.' + item["attribute.names"] + ' as ' + item["attribute.types"]
466 | });
467 | });
468 | });
469 | };
470 |
471 | /**
472 | * ACE editor init on creation
473 | * @param editor
474 | */
475 | $scope.aceLoaded = (editor) => {
476 | let tab = $scope.vars.tabs.find(tab => !tab.editor) || $scope.vars.currentTab;
477 | tab.editor = editor;
478 | editor.$blockScrolling = Infinity;
479 |
480 | // Load settings from LocalStorage
481 | editor.setOptions({
482 | fontSize: $scope.vars.fontSize + 'px',
483 | enableBasicAutocompletion : true,
484 | enableSnippet:true,
485 | enableLiveAutocompletion:false
486 | });
487 | editor.setTheme('ace/theme/' + $scope.vars.theme);
488 |
489 | // reload keywords & highlights
490 | editor.session.setMode({
491 | path: "ace/mode/clickhouse",
492 | v: Date.now()
493 | });
494 | editor.session.bgTokenizer.start(0);
495 |
496 | // @todo:Кастомный cmd+enter чтобы ранать Все/или выделенное
497 | editor.commands.addCommand({
498 | name: 'runCurrentCommand',
499 | bindKey: {
500 | win: 'Ctrl-Enter',
501 | mac: 'Command-Enter'
502 | },
503 | exec: () => {
504 | $scope.execute('current', tab);
505 | }
506 | });
507 |
508 | editor.commands.addCommand({
509 | name: 'runAllCommand',
510 | bindKey: {
511 | win: 'Shift-Ctrl-Enter',
512 | mac: 'Shift-Command-Enter'
513 | },
514 | exec: () => {
515 | $scope.execute('all', tab);
516 | }
517 | });
518 |
519 | editor.clearSelection();
520 | editor.focus();
521 | editor.selection.moveTo(0, 0);
522 |
523 | // Повесить эвент и переиминовывать кнопку -"Выполнить"
524 | editor.on('changeSelection', () => {
525 | $timeout(() => {
526 | tab.buttonTitle = editor.getSelectedText() !== '' ? $filter('translate')('Выполнить выделенное ⌘ + ⏎') : $filter('translate')('Выполнить все ⇧ + ⌘ + ⏎');
527 | if (tab.originalSql) {
528 | tab.changed = (tab.originalSql != tab.sql);
529 | }
530 | });
531 | });
532 |
533 | $scope.loadDictionaries();
534 | };
535 |
536 | /**
537 | * Watch for menu database changes
538 | */
539 | $rootScope.$watch('currentDatabase', $scope.selectDatabase);
540 |
541 | /**
542 | * Watch and save settings in LocalStorage
543 | */
544 | $scope.$watch('vars.limitRows', (curr) => localStorageService.set('editorLimitRows', curr));
545 |
546 | /**
547 | * Watch and save settings in LocalStorage
548 | */
549 | $scope.$watch('vars.fontSize', (fontSize) => {
550 | if (fontSize) {
551 | $scope.vars.tabs.forEach((tab) => tab.editor && tab.editor.setOptions({
552 | fontSize: fontSize + 'px'
553 | }));
554 | localStorageService.set('editorFontSize', fontSize);
555 | }
556 | });
557 |
558 | /**
559 | * Save SQL to history
560 | * @param tab
561 | * @param ev
562 | */
563 | $scope.save = (tab, ev) => $mdDialog.show(
564 | $mdDialog.prompt()
565 | .title($filter('translate')('Сохранить SQL как'))
566 | .placeholder($filter('translate')('название'))
567 | .initialValue(tab.name)
568 | .targetEvent(ev)
569 | .ok($filter('translate')('Сохранить'))
570 | .cancel($filter('translate')('Отмена'))
571 | ).then((name)=> {
572 | const index = $scope.vars.sqlHistory.findIndex((item) => (item.name == tab.name));
573 | if (index != -1) {
574 | $scope.vars.sqlHistory[index].sql = tab.sql;
575 | $scope.vars.sqlHistory[index].name = name;
576 | } else {
577 | $scope.vars.sqlHistory.push({
578 | sql: tab.sql,
579 | name
580 | });
581 | }
582 | tab.originalSql = tab.sql;
583 | tab.name = name;
584 | localStorageService.set(SQL_HISTORY_KEY, $scope.vars.sqlHistory);
585 | saveSession();
586 | });
587 |
588 | /**
589 | * Load SQL from history
590 | * @param history
591 | */
592 | $scope.load = (history) => {
593 | $scope.vars.currentTab.sql = history.sql;
594 | $scope.vars.currentTab.originalSql = history.sql;
595 | $scope.vars.currentTab.name = history.name;
596 | };
597 |
598 | /**
599 | * Create export data
600 | * @param result
601 | * @returns {number[]}
602 | */
603 | $scope.getExportData = result => (
604 | result.map(item => Object.keys(item).map(key => (
605 | angular.isArray(item[key]) ? item[key].join(', ') : item[key]
606 | )))
607 | );
608 |
609 | $scope.getExportHeaders = result => result.map(item => item.name);
610 |
611 | /**
612 | * Inserting new SQL tab
613 | */
614 | $scope.addTab = () => {
615 | $scope.vars.currentTab = {
616 | name: 'new SQL',
617 | sql: '',
618 | buttonTitle: $filter('translate')('Выполнить ⌘ + ⏎'),
619 | format: {},
620 | editor: null,
621 | results: [],
622 | selectedResultTab: 0
623 | };
624 | $scope.vars.tabs.push($scope.vars.currentTab);
625 | saveSession();
626 | };
627 |
628 | /**
629 | * Remove SQL tab
630 | * @param tab
631 | * @param event
632 | */
633 | $scope.removeTab = (tab, event) => {
634 | event.stopPropagation();
635 |
636 | const clear = () => {
637 | $scope.vars.tabs.splice($scope.vars.tabs.indexOf(tab), 1);
638 | if ($scope.vars.tabs.length == $scope.vars.selectedTab) {
639 | $scope.vars.selectedTab--;
640 | }
641 | };
642 |
643 | if (tab.changed) {
644 | $mdDialog.show(
645 | $mdDialog.confirm()
646 | .title($filter('translate')('SQL изменен. Сохранить перед закрытием?'))
647 | .targetEvent(event)
648 | .ok($filter('translate')('Да'))
649 | .cancel($filter('translate')('Нет'))
650 | ).then(()=> {
651 | const index = $scope.vars.sqlHistory.findIndex((item) => (item.name == tab.name));
652 | if (index != -1) {
653 | $scope.vars.sqlHistory[index].sql = tab.sql;
654 | $scope.vars.sqlHistory[index].name = tab.name;
655 | }
656 | localStorageService.set(SQL_HISTORY_KEY, $scope.vars.sqlHistory);
657 | clear();
658 | }, clear);
659 | } else {
660 | clear();
661 | }
662 | };
663 |
664 | /**
665 | * Remove result Tab
666 | * @param tab
667 | * @param result
668 | * @param $event
669 | */
670 | $scope.removeResult = (tab, result, event) => {
671 | event.stopPropagation();
672 | tab.results.splice(tab.results.indexOf(result), 1);
673 | };
674 |
675 | /**
676 | * Toggle settings panel
677 | */
678 | $scope.toggleSidenav = (id) => {
679 | $mdSidenav(id).toggle();
680 | };
681 |
682 | /**
683 | * Remove history item
684 | * @param item
685 | * @param event
686 | */
687 | $scope.removeHistory = (item, event) => {
688 | event.preventDefault();
689 | const index = $scope.vars.sqlHistory.indexOf(item);
690 | if (index != -1) {
691 | $mdDialog.show(
692 | $mdDialog.confirm()
693 | .title(`Удалить ${item.name}?`)
694 | .targetEvent(event)
695 | .ok('Да')
696 | .cancel('Нет')
697 | ).then(()=> {
698 | $scope.vars.sqlHistory.splice(index, 1);
699 | localStorageService.set(SQL_HISTORY_KEY, $scope.vars.sqlHistory);
700 | });
701 | }
702 | };
703 |
704 | /**
705 | * Save session checkbox state in LS
706 | */
707 | $scope.$watch('vars.saveTabs', (value, old) => {
708 | localStorageService.set(SQL_SAVE_TABS_KEY, value);
709 | if (old === false && value === true) {
710 | saveSession();
711 | }
712 | });
713 |
714 | /**
715 | * Change UI themetam_tam641
716 | * @param theme
717 | */
718 | $scope.setUiTheme = (theme) => ThemeService.set(theme.name);
719 |
720 | /**
721 | * Set SQL in editor
722 | * @param sql
723 | */
724 | $scope.setSql = (sql) => {
725 | $scope.vars.currentTab.sql = sql;
726 | $scope.toggleSidenav('log');
727 | $timeout(() => $scope.vars.currentTab.editor.focus(), 500);
728 | };
729 |
730 | /**
731 | * Rename tab
732 | * @param tab
733 | * @param event
734 | */
735 | $scope.changeTabName = (tab, event) => {
736 | event.stopPropagation();
737 | $mdDialog.show(
738 | $mdDialog.prompt()
739 | .title($filter('translate')('Название вкладки'))
740 | .placeholder($filter('translate')('название'))
741 | .initialValue(tab.name)
742 | .targetEvent(event)
743 | .ok($filter('translate')('Применить'))
744 | .cancel($filter('translate')('Отмена'))
745 | ).then((name)=> {
746 | const index = $scope.vars.sqlHistory.findIndex((item) => (item.name == tab.name));
747 | if (index != -1) {
748 | tab.originalSql = tab.sql;
749 | $scope.vars.sqlHistory[index].name = name;
750 | localStorageService.set(SQL_HISTORY_KEY, $scope.vars.sqlHistory);
751 | }
752 | tab.name = name;
753 | saveSession();
754 | });
755 | };
756 |
757 | /**
758 | * Init :)
759 | */
760 | if ($scope.vars.saveTabs) {
761 | $scope.vars.tabs = localStorageService.get(SQL_SESSION_KEY);
762 | if ($scope.vars.tabs.length > 0) {
763 | $timeout(() => ($scope.vars.currentTab = $scope.vars.tabs[0]), 500);
764 | } else {
765 | $scope.addTab();
766 | }
767 | } else {
768 | localStorageService.set(SQL_SESSION_KEY, []);
769 | $scope.addTab();
770 | }
771 | if ($rootScope.currentDatabase) {
772 | $scope.selectDatabase($rootScope.currentDatabase);
773 | };
774 |
775 | /**
776 | * Controller destructor
777 | */
778 | $scope.$on('$destroy', () => {
779 | API.setDatabase($rootScope.currentDatabase);
780 | clearRouterListener();
781 | $window.onbeforeunload = null;
782 | });
783 | }
784 | })(angular, smi2);
785 |
--------------------------------------------------------------------------------