Notifications board

This commit is contained in:
Fabio 2020-05-15 17:52:59 +02:00
parent aea94f0325
commit 55b1991869
15 changed files with 363 additions and 26 deletions

109
package-lock.json generated
View File

@ -2487,6 +2487,11 @@
"integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
"dev": true "dev": true
}, },
"buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
},
"buffer-xor": { "buffer-xor": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
@ -9063,6 +9068,11 @@
} }
} }
}, },
"packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"pako": { "pako": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
@ -9291,11 +9301,73 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
}, },
"pg": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.2.0.tgz",
"integrity": "sha512-EQeWKZv7qBTQZQa7EraR61AOi0bpizvlZLvrPdgAGaraX4YI+y40iQnL39XjPMXVnHOOG3jV6kAGtc0WSJn/+A==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.2.2",
"pg-pool": "^3.2.0",
"pg-protocol": "^1.2.3",
"pg-types": "^2.1.0",
"pgpass": "1.x",
"semver": "4.3.2"
},
"dependencies": {
"pg-connection-string": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.2.tgz",
"integrity": "sha512-+hel4DGuSZCjCZwglAuyi+XlodHnKmrbyTw0hVWlmGN2o4AfJDkDo5obAFzblS5M5PFBMx0uDt5Y1QjlNC+tqg=="
},
"semver": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz",
"integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c="
}
}
},
"pg-connection-string": { "pg-connection-string": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.0.tgz", "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.2.0.tgz",
"integrity": "sha512-xB/+wxcpFipUZOQcSzcgkjcNOosGhEoPSjz06jC89lv1dj7mc9bZv6wLVy8M2fVjP0a/xN0N988YDq1L0FhK3A==" "integrity": "sha512-xB/+wxcpFipUZOQcSzcgkjcNOosGhEoPSjz06jC89lv1dj7mc9bZv6wLVy8M2fVjP0a/xN0N988YDq1L0FhK3A=="
}, },
"pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-pool": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.0.tgz",
"integrity": "sha512-7BLwDNDEfPFjE9vmZLcJPLFwuDAVGZ5lIZo2MeQfwYG7EPGfdNVis/dz6obI/yKqvQIx2sf6QBKXMLB+y/ftgA=="
},
"pg-protocol": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.2.3.tgz",
"integrity": "sha512-erHFURS0mPmTbq18cn/zNL3Y4IzNCrU4sgCim0qy7zAPe3Vc0rvK5cImJR6lDvIaz3fJU2R1R9FNOlnUtyF10Q=="
},
"pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"requires": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
}
},
"pgpass": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz",
"integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=",
"requires": {
"split": "^1.0.0"
}
},
"pify": { "pify": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
@ -9597,6 +9669,29 @@
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==",
"dev": true "dev": true
}, },
"postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
},
"postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU="
},
"postgres-date": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.5.tgz",
"integrity": "sha512-pdau6GRPERdAYUQwkBnGKxEfPyhVZXG/JiS44iZWiNdSOWE09N2lUgN6yshuq6fVSon4Pm0VMXd1srUUkLe9iA=="
},
"postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"requires": {
"xtend": "^4.0.0"
}
},
"prelude-ls": { "prelude-ls": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -11286,6 +11381,14 @@
"resolved": "https://registry.npmjs.org/spectre.css/-/spectre.css-0.5.8.tgz", "resolved": "https://registry.npmjs.org/spectre.css/-/spectre.css-0.5.8.tgz",
"integrity": "sha512-3N4WocWY+Dl6b3e5v3nsZYyp+VSDcBfGDzyyHw/H78ie9BoAhHkxmrhLxo9y8RadxYzVrPjfPdlev3hXEUzR2w==" "integrity": "sha512-3N4WocWY+Dl6b3e5v3nsZYyp+VSDcBfGDzyyHw/H78ie9BoAhHkxmrhLxo9y8RadxYzVrPjfPdlev3hXEUzR2w=="
}, },
"split": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
"integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
"requires": {
"through": "2"
}
},
"split-string": { "split-string": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@ -11765,8 +11868,7 @@
"through": { "through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
"dev": true
}, },
"through2": { "through2": {
"version": "2.0.5", "version": "2.0.5",
@ -13416,8 +13518,7 @@
"xtend": { "xtend": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
"dev": true
}, },
"y18n": { "y18n": {
"version": "4.0.0", "version": "4.0.0",

View File

@ -32,6 +32,7 @@
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"mssql": "^6.2.0", "mssql": "^6.2.0",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"pg": "^8.2.0",
"source-map-support": "^0.5.16", "source-map-support": "^0.5.16",
"spectre.css": "^0.5.8", "spectre.css": "^0.5.8",
"vue-i18n": "^8.17.4", "vue-i18n": "^8.17.4",

View File

@ -0,0 +1,3 @@
export function uidGen () {
return Math.random().toString(36).substr(2, 9).toUpperCase();
};

View File

@ -12,6 +12,7 @@
</div> </div>
</div> </div>
<TheFooter /> <TheFooter />
<TheNotificationsBoard />
<ModalNewConnection v-if="isNewConnModal" /> <ModalNewConnection v-if="isNewConnModal" />
</div> </div>
</template> </template>
@ -20,6 +21,7 @@
import { mapActions, mapGetters } from 'vuex'; import { mapActions, mapGetters } from 'vuex';
import TheSettingBar from '@/components/TheSettingBar'; import TheSettingBar from '@/components/TheSettingBar';
import TheFooter from '@/components/TheFooter'; import TheFooter from '@/components/TheFooter';
import TheNotificationsBoard from '@/components/TheNotificationsBoard';
import TheAppWelcome from '@/components/TheAppWelcome'; import TheAppWelcome from '@/components/TheAppWelcome';
import DatabaseWorkspace from '@/components/DatabaseWorkspace'; import DatabaseWorkspace from '@/components/DatabaseWorkspace';
import ModalNewConnection from '@/components/ModalNewConnection'; import ModalNewConnection from '@/components/ModalNewConnection';
@ -29,6 +31,7 @@ export default {
components: { components: {
TheSettingBar, TheSettingBar,
TheFooter, TheFooter,
TheNotificationsBoard,
TheAppWelcome, TheAppWelcome,
DatabaseWorkspace, DatabaseWorkspace,
ModalNewConnection ModalNewConnection

View File

@ -0,0 +1,73 @@
<template>
<div class="toast mt-2" :class="notificationStatus.className">
<span class="p-vcentered text-left">
<i class="material-icons mr-1">{{ notificationStatus.iconName }}</i>
<span class="notification-message">{{ message }}</span>
</span>
<button class="btn btn-clear" @click="hideToast" />
</div>
</template>
<script>
export default { // TODO: open notifications button
name: 'BaseNotification',
props: {
message: {
type: String,
default: ''
},
status: {
type: String,
default: ''
}
},
computed: {
notificationStatus () {
let className = '';
let iconName = '';
switch (this.status) {
case 'success':
className = 'toast-success';
iconName = 'done';
break;
case 'error':
className = 'toast-error';
iconName = 'error';
break;
case 'warning':
className = 'toast-warning';
iconName = 'warning';
break;
case 'primary':
className = 'toast-primary';
iconName = 'info_outline';
break;
}
return { className, iconName };
}
},
methods: {
hideToast () {
this.$emit('close');
}
}
};
</script>
<style scoped>
.toast{
display: flex;
justify-content: space-between;
user-select: text;
word-break: break-all;
}
.notification-message{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
width: 25rem;
user-select: none;
}
</style>

View File

@ -64,6 +64,7 @@ export default {
methods: { methods: {
hideToast () { hideToast () {
this.isVisible = false; this.isVisible = false;
this.$emit('close');
} }
} }
}; };

View File

@ -1,9 +1,13 @@
<template> <template>
<div class="workspace-explorebar column"> <div class="workspace-explorebar column">
<div class="workspace-explorebar-title">
{{ connection.user }}@{{ connection.host }}:{{ connection.port }}
</div>
<button <button
v-if="!isConnected" v-if="!isConnected"
class="btn btn-primary mt-4" class="btn btn-success mt-4"
@click="$emit('connect')" :class="{'loading': isConnecting}"
@click="startConnection"
> >
Connect Connect
</button> </button>
@ -11,11 +15,35 @@
</template> </template>
<script> <script>
import { mapActions } from 'vuex';
import Connection from '@/ipc-api/Connection';
export default { export default {
name: 'DatabaseExploreBar', name: 'DatabaseExploreBar',
props: { props: {
uid: String, connection: Object
isConnected: Boolean },
data () {
return {
isConnected: false,
isConnecting: false
};
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async startConnection () {
this.isConnecting = true;
try {
this.structure = await Connection.connect(this.connection);
this.isConnected = true;
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isConnecting = false;
}
} }
}; };
</script> </script>
@ -27,10 +55,24 @@ export default {
flex-direction: column; flex-direction: column;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
text-align: left;
background: $bg-color-gray; background: $bg-color-gray;
margin-bottom: $footer-height; margin-bottom: $footer-height;
box-shadow: 0 0 1px 0px #000; box-shadow: 0 0 1px 0px #000;
z-index: 8; z-index: 8;
flex: initial; flex: initial;
position: relative;
padding-top: 1.4rem;
.workspace-explorebar-title{
top: 0;
left: 0;
right: 0;
padding: .3rem;
position: absolute;
font-size: .6rem;
font-weight: 700;
text-transform: uppercase;
}
} }
</style> </style>

View File

@ -1,18 +1,14 @@
<template> <template>
<div v-show="selectedConnection === connection.uid" class="workspace column columns"> <div v-show="selectedConnection === connection.uid" class="workspace column columns">
<DatabaseExploreBar <DatabaseExploreBar :connection="connection" />
:uid="connection.uid"
:is-connected="isConnected"
@connect="startConnection"
/>
<div class="workspace-tabs column"> <div class="workspace-tabs column">
<p>{{ connection.uid }}</p> <p>{{ connection }}</p>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters, mapActions } from 'vuex';
import Connection from '@/ipc-api/Connection'; import Connection from '@/ipc-api/Connection';
import DatabaseExploreBar from '@/components/DatabaseExploreBar'; import DatabaseExploreBar from '@/components/DatabaseExploreBar';
@ -26,7 +22,6 @@ export default {
}, },
data () { data () {
return { return {
isConnected: false,
structure: null structure: null
}; };
}, },
@ -37,14 +32,19 @@ export default {
}, },
async created () { async created () {
this.isConnected = await Connection.checkConnection(this.connection.uid); this.isConnected = await Connection.checkConnection(this.connection.uid);
if (this.isConnected) if (this.isConnected) {
try {
this.structure = await Connection.connect(this.connection);// TODO: use refresh this.structure = await Connection.connect(this.connection);// TODO: use refresh
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
}
}, },
methods: { methods: {
async startConnection () { ...mapActions({
this.structure = await Connection.connect(this.connection); addNotification: 'notifications/addNotification'
this.isConnected = true; })
}
} }
}; };
</script> </script>

View File

@ -17,7 +17,11 @@
<label class="form-label">Client:</label> <label class="form-label">Client:</label>
</div> </div>
<div class="col-9 col-sm-12"> <div class="col-9 col-sm-12">
<select v-model="connection.client" class="form-select"> <select
v-model="connection.client"
class="form-select"
@change="setDefaults"
>
<option value="mysql"> <option value="mysql">
MySQL/MariaDB MySQL/MariaDB
</option> </option>
@ -129,6 +133,7 @@
<script> <script>
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import Connection from '@/ipc-api/Connection'; import Connection from '@/ipc-api/Connection';
import { uidGen } from 'common/libs/utilities';
import ModalAskCredentials from '@/components/ModalAskCredentials'; import ModalAskCredentials from '@/components/ModalAskCredentials';
import BaseToast from '@/components/BaseToast'; import BaseToast from '@/components/BaseToast';
@ -153,7 +158,7 @@ export default {
user: 'root', user: 'root',
password: '', password: '',
ask: false, ask: false,
uid: Math.random().toString(36).substr(2, 9).toUpperCase() uid: uidGen()
}, },
toast: { toast: {
status: '', status: '',
@ -168,6 +173,22 @@ export default {
closeModal: 'connections/hideNewConnModal', closeModal: 'connections/hideNewConnModal',
addConnection: 'connections/addConnection' addConnection: 'connections/addConnection'
}), }),
setDefaults () {
switch (this.connection.client) {
case 'mysql':
this.connection.port = '3306';
break;
case 'mssql':
this.connection.port = '1433';
break;
case 'pg':
this.connection.port = '5432';
break;
case 'oracledb':
this.connection.port = '1521';
break;
}
},
async startTest () { async startTest () {
this.isTesting = true; this.isTesting = true;
this.toast = { this.toast = {

View File

@ -0,0 +1,48 @@
<template>
<div id="notifications-board">
<transition-group name="slide-fade">
<BaseNotification
v-for="notification in latestNotifications"
:key="notification.uid"
:message="notification.message"
:title="notification.message"
:status="notification.status"
@close="removeNotification(notification.uid)"
/>
</transition-group>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
import BaseNotification from '@/components/BaseNotification';
export default {
name: 'TheNotificationsBoard',
components: {
BaseNotification
},
computed: {
...mapGetters({
notifications: 'notifications/getNotifications'
}),
latestNotifications () {
return this.notifications.slice(0, 10);
}
},
methods: {
...mapActions({
removeNotification: 'notifications/removeNotification'
})
}
};
</script>
<style lang="scss">
#notifications-board{
position: absolute;
z-index: 9;
right: 1rem;
bottom: $footer-height+1rem;
}
</style>

View File

@ -0,0 +1,10 @@
.slide-fade-enter-active {
transition: all .3s ease;
}
.slide-fade-leave-active {
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
.slide-fade-enter, .slide-fade-leave-to {
transform: translateX(10px);
opacity: 0;
}

View File

@ -1,10 +1,11 @@
/*Colors*/ /*Colors*/
$body-bg: #1d1d1d; $body-bg: #1d1d1d;
$body-font-color: #fff; $body-font-color: #fff;
$primary-color: #e36929;
$bg-color: #1d1d1d; $bg-color: #1d1d1d;
$bg-color-light: #3f3f3f; $bg-color-light: #3f3f3f;
$bg-color-gray: #272727; $bg-color-gray: #272727;
$primary-color: #e36929;
$error-color: #de3b28;
/*Sizes*/ /*Sizes*/
$settingbar-width: 3rem; $settingbar-width: 3rem;

View File

@ -1,4 +1,5 @@
@import "variables"; @import "variables";
@import "transitions";
@import "mdi-additions"; @import "mdi-additions";
@import "~spectre.css/src/spectre"; @import "~spectre.css/src/spectre";

View File

@ -6,6 +6,7 @@ import VuexPersist from 'vuex-persist';
import application from './modules/application.store'; import application from './modules/application.store';
import connections from './modules/connections.store'; import connections from './modules/connections.store';
import notifications from './modules/notifications.store';
const vuexLocalStorage = new VuexPersist({ const vuexLocalStorage = new VuexPersist({
key: 'application', // The key to store the state on in the storage provider. key: 'application', // The key to store the state on in the storage provider.
@ -21,7 +22,8 @@ export default new Vuex.Store({
strict: true, strict: true,
modules: { modules: {
application, application,
connections connections,
notifications
}, },
plugins: [vuexLocalStorage.plugin] plugins: [vuexLocalStorage.plugin]
}); });

View File

@ -0,0 +1,30 @@
'use strict';
import { uidGen } from 'common/libs/utilities';
export default {
namespaced: true,
strict: true,
state: {
notifications: []
},
getters: {
getNotifications: state => state.notifications
},
mutations: {
ADD_NOTIFICATION (state, payload) {
state.notifications.unshift(payload);
},
REMOVE_NOTIFICATION (state, uid) {
state.notifications = state.notifications.filter(item => item.uid !== uid);
}
},
actions: {
addNotification ({ commit }, payload) {
payload.uid = uidGen();
commit('ADD_NOTIFICATION', payload);
},
removeNotification ({ commit }, uid) {
commit('REMOVE_NOTIFICATION', uid);
}
}
};