discription
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

999 lines
25 KiB

3 years ago
<template>
<div>
3 years ago
<div class="row">
3 years ago
<div class="col-12 d-flex justify-content-center text-center">
3 years ago
<!-- <h4 class="text-info">Search</h4> -->
3 years ago
</div>
3 years ago
</div>
3 years ago
3 years ago
<div class="row">
<div class="col-6">
3 years ago
<div class="input-group input-group-sm sm-6">
3 years ago
<div class="input-group-prepend">
<div
class="form-control custom-control custom-switch custom-control-inline"
>
<input
type="checkbox"
class="custom-control-input"
id="log_logerror"
v-model="logcheck.error"
/>
<label class="custom-control-label" for="log_logerror"
>Error</label
>
3 years ago
</div>
3 years ago
<div
class="form-control custom-control custom-switch custom-control-inline"
3 years ago
>
3 years ago
<input
type="checkbox"
class="custom-control-input"
id="log_output"
v-model="logcheck.output"
data-size="large"
data-toggle="toggle"
/>
<label class="custom-control-label" for="log_output"
>Output</label
>
</div>
3 years ago
</div>
3 years ago
</div>
</div>
<!-- col -->
3 years ago
3 years ago
<div class="col-6">
<div class="input-group input-group-sm sm-6"></div>
3 years ago
</div>
3 years ago
<!-- col -->
3 years ago
</div>
3 years ago
<!-- row -->
3 years ago
3 years ago
<div class="row">
<div class="col-6">
3 years ago
<div class="input-group-append">
<button
class="btn btn-outline-primary"
round
type="button"
@click="Hotlog"
>
Hot_log ALL
</button>
</div>
<div class="input-group-append">
<button
class="btn btn-outline-primary"
round
type="button"
@click="mongorawf"
>
Hot_log MONGO RAW
</button>
<div class="input-group-append">
<button
class="btn btn-outline-primary"
round
type="button"
@click="allrawf"
3 years ago
>
3 years ago
Hot_log RAW ALL
</button>
3 years ago
</div>
3 years ago
</div>
3 years ago
</div>
3 years ago
<!-- col -->
3 years ago
3 years ago
<div class="col-5">
<div class="input-group input-group-sm sm-5 justify-content-end ">
<div class="input-group-prepend">
<div class="input-group-append">
<button
class="btn btn-outline-warning"
round
type="button"
@click="Clearlog"
3 years ago
>
3 years ago
Clear
</button>
</div>
3 years ago
<div class="input-group-append">
<button
class="btn btn-outline-warning"
round
type="button"
@click="Hidelog"
>
Hide
</button>
</div>
<div class="input-group-append">
<button
class="btn btn-outline-warning"
round
type="button"
@click="Refresh"
>
Force Refresh
</button>
</div>
3 years ago
</div>
</div>
</div>
<!-- col -->
3 years ago
3 years ago
<div class="col-1"></div>
<!-- col -->
</div>
<!-- row -->
<div class="row rowlog overflow-auto">
<!-- col -->
3 years ago
<div v-show="onEvent" class="logs">
<h2>ON-event logs for all apps (Simplified - ALL)</h2>
3 years ago
<div class="input-group-append">
<button
class="btn btn-outline-primary"
round
type="button"
@click="scroll(1)"
>
Scroll to Bottom
</button>
</div>
<div id="applogs" class="table2">
<vuetable
3 years ago
ref="apptable"
3 years ago
:api-mode="false"
:fields="fields"
:css="css.table"
>
</vuetable>
</div>
3 years ago
</div>
3 years ago
<div v-show="mongoraw" class="logs">
<h2>ON-event logs for MongoDB only (RAW)</h2>
3 years ago
<div class="input-group-append">
<button
class="btn btn-outline-primary"
round
type="button"
@click="scroll(2)"
>
Scroll to Bottom
</button>
</div>
<div id="mongologs" class="table2">
<vuetable
3 years ago
ref="mongotable"
3 years ago
:api-mode="false"
:fields="fields2"
:css="css.table"
>
</vuetable>
</div>
</div>
3 years ago
<div v-show="raw" class="logs">
<h2>ON-event logs (RAW - ALL)</h2>
3 years ago
<!-- <div class="input-group-append">
3 years ago
<button
class="btn btn-outline-primary"
round
type="button"
@click="scroll(3)"
>
Scroll to Bottom
</button>
3 years ago
</div> -->
<div id="rawlogs">
3 years ago
<vuetable
3 years ago
ref="rawtable"
3 years ago
:api-mode="false"
:fields="fields3"
:css="css.table"
table-height="400px"
3 years ago
>
</vuetable>
</div>
</div>
3 years ago
</div>
<!-- row -->
3 years ago
</div>
</template>
<script>
3 years ago
import { mapState, mapGetters, mapActions, dispatch } from "vuex";
import Vue from "vue";
import store from "@/store/index";
import JSZip from "jszip";
import DatePicker from "vue2-datepicker";
import "vue2-datepicker/index.css";
3 years ago
import { DateTime } from "luxon";
3 years ago
import FileSaver from "file-saver";
3 years ago
import Vuetable from "vuetable-2";
import axios from "axios";
3 years ago
import CssConfig from "vuetable-2/src/components/VuetableCssConfig.js";
3 years ago
3 years ago
export default {
3 years ago
components: {
3 years ago
Vuetable,
3 years ago
DatePicker,
},
data() {
3 years ago
return {
3 years ago
countIndex: 1,
hover: false,
3 years ago
css: CssConfig,
fields: [
{
name: "message",
title: "Log",
3 years ago
width: "20%",
visible: true,
3 years ago
dataClass: "center aligned",
},
{
name: "timestamp",
title: "Time",
width: "20%",
visible: true,
},
{
name: "type",
titleClass: "center aligned",
width: "10%",
},
{
name: "process_id",
visible: true,
},
{
name: "app_name",
visible: true,
title: '<span class="orange"></span>App Name',
},
// 'timestamp',
// 'type',
// 'process_id',
// 'app_name',
],
fields2: [
{
name: "t",
3 years ago
title: "Timestamp",
visible: true,
3 years ago
width: "30%",
},
3 years ago
{
name: "s",
3 years ago
title: "Severity",
visible: true,
3 years ago
width: "5%",
},
{
name: "attr",
title: "Attributes",
width: "40%",
},
{
name: "c",
3 years ago
title: "Components",
titleClass: "center aligned",
3 years ago
width: "10%",
},
{
name: "id",
3 years ago
visible: false,
},
{
name: "ctx",
visible: true,
3 years ago
title: "Context",
3 years ago
width: "5%",
},
{
name: "msg",
3 years ago
title: "Message",
3 years ago
width: "10%",
},
3 years ago
// 'timestamp',
// 'type',
// 'process_id',
// 'app_name',
],
3 years ago
fields3: [
{
name: "container_id",
title: "Container_id",
width: "13%",
3 years ago
},
{
name: "log",
title: "Log",
width: "40%",
3 years ago
},
3 years ago
3 years ago
{
name: "container_name",
title: "Container Name",
width: "17%",
3 years ago
},
{
name: "source",
title: "Source",
width: "8%",
3 years ago
},
{
3 years ago
name: "time",
title: "Time",
width: "22%",
3 years ago
},
],
3 years ago
log_path: [],
log_path_lenght: [],
log_path_lenghtstatus: [],
loglenghttotal: 32,
3 years ago
onEvent: false,
allHistory: false,
3 years ago
search: {
datestart: "",
dateend: "",
log: "",
output: "",
3 years ago
error: "",
},
logcheck: {
3 years ago
log: true,
error: true,
3 years ago
output: true,
3 years ago
},
selected: [],
3 years ago
showlloedit: true,
issocket: "close",
socketdata: "",
logintoken: "",
code: [],
logdata: [],
mongodata: [],
3 years ago
mongoerror: [],
mongoout: [],
3 years ago
localData: [],
3 years ago
localDataError: [],
localDataOut: [],
3 years ago
rawdata: [],
3 years ago
rawout: [],
rawerror: [],
3 years ago
mongoraw: false,
allraw: false,
raw: false,
3 years ago
};
3 years ago
},
methods: {
3 years ago
// ανανεώνει τα λογκς των πινάκων
3 years ago
Refresh() {
this.checklogs();
this.$refs.apptable.reload();
},
3 years ago
// ελέγχει τα κριτήρια για το τι λογκς να εμφανίσει
3 years ago
checklogs() {
if (this.logcheck.error && !this.logcheck.output) {
// console.log("Inside check");
this.$refs.apptable.setData(this.localDataError);
this.$refs.mongotable.setData(this.mongoerror);
this.$refs.rawtable.setData(this.rawerror);
} else if (this.logcheck.error && this.logcheck.output) {
// console.log("Inside check default");
this.$refs.apptable.setData(this.localData);
this.$refs.mongotable.setData(this.mongodata);
this.$refs.rawtable.setData(this.rawdata);
} else if (!this.logcheck.error && this.logcheck.output) {
this.$refs.apptable.setData(this.localDataOut);
this.$refs.mongotable.setData(this.mongoout);
this.$refs.rawtable.setData(this.rawout);
}
},
3 years ago
// για το bottom scrolling στους πίνακες
3 years ago
scroll(param) {
if (param == 1) {
var container = this.$el.querySelector("#applogs");
container.scrollTop = container.scrollHeight;
3 years ago
} else if (param == 2) {
3 years ago
var container = this.$el.querySelector("#mongologs");
container.scrollTop = container.scrollHeight;
}
3 years ago
// } else if (param == 3) {
// console.log("moving scroll bar of raw logs");
// const container = this.$refs.rawtable;
// container.scrollIntoView({ behavior: "smooth" });
// }
3 years ago
},
3 years ago
// παίρνει τα στοιχεία του user βάσει του token
async getuser(token) {
var data = await this.checktoken(token);
console.log("User: " + data.user + " Token: " + data.token);
this.$socket.client.emit("onevent", data.user);
},
3 years ago
// έλεγχος αν η δομή είναι κατάλληλη για json μετατροπή
3 years ago
IsJsonString(str) {
3 years ago
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
},
3 years ago
// έλεγχος του token
async checktoken(value) {
try {
var token = value;
var pipelines = {
3 years ago
source: token,
};
var params = {
3 years ago
run: pipelines,
};
var options = {
headers: {
3 years ago
"content-type": "application/json",
// rejectUnauthorized: false,
Authorization: `Bearer ${token}`,
},
};
3 years ago
var res = await axios.post(
"https://api.swarmlab.io/istokenvalidsso",
params,
options
);
return res.data;
} catch (e) {
if (
e.message == "Request failed with status code 401" ||
/401/i.test(e.message)
) {
window.location.href = "https://api-login.swarmlab.io:8089";
}
console.error(e);
}
},
3 years ago
//
// εφμάνιση πίνακα απλοποιημένος λογκς
3 years ago
Hotlog() {
3 years ago
// var logintoken = store.getters["pipelineLLO/gettoken"];
3 years ago
//call to start on event logs
this.onEvent = true;
3 years ago
},
3 years ago
// εμφάνιση πίνακα λογκς της μονγκο
3 years ago
mongorawf() {
this.mongoraw = true;
},
3 years ago
// εμφάνιση όλων των λογκς σε raw
3 years ago
allrawf() {
this.raw = true;
},
3 years ago
// απόκρυψη των πινάκων
3 years ago
Hidelog() {
3 years ago
this.onEvent = false;
3 years ago
this.mongoraw = false;
this.raw = false;
3 years ago
},
3 years ago
// καθαρισμός των λογκς
3 years ago
Clearlog() {
// this.onEvent = false;
// this.mongoraw = false;
// this.raw = false;
this.localData = [];
this.localDataError = [];
this.localDataOut = [];
this.rawdata = [];
this.rawout = [];
this.rawerror = [];
this.mongodata = [];
this.mongoout = [];
this.mongoerror = [];
this.Refresh();
},
3 years ago
//
3 years ago
logview(item) {
3 years ago
//console.log('path ' + JSON.stringify(item))
3 years ago
if (this.logcheck.log) {
if (item.endsWith("-log")) {
3 years ago
return true;
}
}
3 years ago
if (this.logcheck.error) {
if (item.endsWith("-error")) {
3 years ago
return true;
}
}
3 years ago
if (this.logcheck.output) {
if (item.endsWith("-output")) {
3 years ago
return true;
}
}
3 years ago
},
async socketopen() {
3 years ago
console.log("inside socketopen()");
3 years ago
this.$socket.client.open();
},
async socketauthenticate() {
3 years ago
var tokentmp = this.logintoken;
3 years ago
// pernaw hardcoded to token
3 years ago
console.log("Inside socket authenticate");
3 years ago
this.$socket.client.emit("authenticate", tokentmp);
3 years ago
},
async socketreconnect() {
3 years ago
console.log("inside socketreconnect() --- sending to socketopen()");
3 years ago
var log = await this.socketopen();
this.socketauthenticate();
},
/**
*
* == socketclose()
*
* [source,javascript]
* ----
* this.$socket.client.close();
* ----
*
*/
3 years ago
3 years ago
async socketclose() {
this.$socket.client.close();
},
3 years ago
},
3 years ago
computed: {},
3 years ago
beforeMount() {
this.socketauthenticate();
console.log("send1");
3 years ago
},
3 years ago
3 years ago
/**
3 years ago
*
* == Socket subscribe
*
* [source,javascript]
* ----
3 years ago
* ...
* })
* sdfsf
den to perni sdfsf
* ----
3 years ago
* <1> EventBus is used for parent/child component communication.
*
*/
3 years ago
mounted() {
3 years ago
// from AdhocView
3 years ago
this.$root.$on("SERVER_socket_connect", (v) => {
this.$nextTick(function() {
3 years ago
console.log("socket recconect inside runllo");
3 years ago
this.socketreconnect();
});
});
},
/**
*
* == Destroy EventBus
*
* See
* https://www.digitalocean.com/community/tutorials/vuejs-component-lifecycle[Vue.js Lifecycle Hooks^].
*
* *beforeDestroy*
*
* - beforeDestroy is fired right before teardown. Your component will still be fully present and functional.
*
* [source,javascript]
* ----
* ----
* <1> EventBus is used for parent/child component communication.
*
*/
beforeDestroy() {
this.$root.$off("SERVER_socket_socket");
},
/**
*
* == Open a socket
*
* See
* https://www.digitalocean.com/community/tutorials/vuejs-component-lifecycle[ Vue.js Lifecycle Hooks^]
*
* *Created*
*
* - You are able to access reactive data and events that are active with the created hook. Templates and Virtual DOM have not yet been mounted or rendered:
*
* [source,javascript]
* ----
* this.socketopen()
* ----
*
*/
created() {
3 years ago
//var logintoken = new URL(location.href).searchParams.get("token");
3 years ago
// ΕΔΩ ΠΡΠΕΕΙ ΝΑ ΑΛΛΑΧΤΕΙ ΤΟ URL ΩΣΤΕ ΝΑ ΚΑΝΕΙ AUTHENTICATION
3 years ago
var logintoken = new URL(
3 years ago
"https://api-client.swarmlab.io:8088/?token=b0dddc676d2491d8e98632d8afc636b6a28f1234"
3 years ago
).searchParams.get("token");
this.logintoken = logintoken;
// === We get the user + check for the token if exists
this.checktoken(this.logintoken);
//console.log(" from created ", data);
3 years ago
var log = store.dispatch("pipelineLLO/settoken", {
3 years ago
token: logintoken,
3 years ago
});
3 years ago
this.socketopen();
3 years ago
// let p = axios.get("http://localhost:3000/test");
// console.log(p);
3 years ago
},
/**
*
* == Socket events
*
* [source,javascript]
* ----
* this.$socket.client.emit('authenticate', 'logintoken');
* ----
*
*/
sockets: {
connect() {
3 years ago
var logintoken = store.getters["pipelineLLO/gettoken"];
this.$socket.client.emit("authenticate", logintoken);
3 years ago
this.$socket.client.emit("socket_id_get", "socketdatasend");
//var username = 'username'
//var roomname = 'roomname'
//this.$socket.client.emit('setUsername',{user:username});
//this.$socket.client.emit('createRoom', {user:username,room:roomname});
//this.$socket.client.emit('joinRooom',{room:roomname});
//var msg = {user:username,room:roomname,msg:'hiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii'}
//this.$socket.client.emit('sendMessage',msg);
console.log("socket connected " + "socketdatasend");
this.issocket = "open";
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "on");
this.getuser(logintoken);
3 years ago
//asd
3 years ago
},
/**
*
* === onError
*
*/
error(error) {
console.log("socket error " + JSON.stringify(error));
this.issocket = "close";
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
},
/**
*
* === connect_error
*
*/
connect_error(error) {
console.log("socket connect_error " + JSON.stringify(error));
this.issocket = "close";
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
this.socketreconnect();
},
/**
*
* === connect_error
*
*/
disconnect(reason) {
console.log("socket disconnect " + JSON.stringify(reason));
this.issocket = "close";
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
this.socketreconnect();
},
/**
*
* === Socket connect_timeout
*
*/
connect_timeout(reason) {
console.log("socket timeout " + JSON.stringify(reason));
this.issocket = "close";
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
this.socketreconnect();
},
/**
*
* === Socket reconnect
*
*/
reconnect(attemptNumber) {
console.log(
"socket reconnect attemptNumber " + JSON.stringify(attemptNumber)
);
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
this.socketreconnect();
},
/**
*
* === connect_attempt
*
*/
reconnect_attempt(attemptNumber) {
console.log("socket reconnect_attempt " + JSON.stringify(attemptNumber));
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
this.socketreconnect();
},
/**
*
* === Socket reconnecting
*
*/
reconnecting(attemptNumber) {
console.log("socket reconnecting " + JSON.stringify(attemptNumber));
this.$root.$emit("SERVER_socket_status", "off");
this.socketreconnect();
},
/**
*
* === reconnect_error
*
*/
reconnect_error(error) {
console.log("socket reconnect_error " + JSON.stringify(error));
this.issocket = "close";
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
this.socketreconnect();
},
/**
*
* === unauthorized
*
*/
unauthorized(val) {
console.log("socket unauthorized " + JSON.stringify(val));
this.issocket = "close";
// send to AdhocView
this.$root.$emit("SERVER_socket_status", "off");
},
/**
*
* === connected
*
*/
socket_id_emit(val) {
console.log("socket id from server " + JSON.stringify(val));
console.log("socket id from serveri saved " + JSON.stringify(socketsave));
this.issocket = "open";
},
/**
*
* === Socket onMessage
*
*/
/*
*/
3 years ago
async message(val) {
console.log(" socket message " + JSON.stringify(val));
},
3 years ago
// Lefos --- socket event to add the data to the right tables
3 years ago
async logsend(val) {
3 years ago
// ==== LEFOS
3 years ago
//Check which data array to put as data to the tables
this.checklogs();
3 years ago
var log = store.dispatch("pipelineLLO/addlog", {
log: val,
});
3 years ago
val.container_id = '<div class="conid">' + val.container_id + "</div>";
// console.log("raw before.. " + val.log);
3 years ago
3 years ago
// A bit of edit to fit perfectly into the table
val.log = val.log.replace(/,/g, ", ");
val.log = val.log.replace(/":/g, '": ');
// console.log("raw after .. " + val.log);
3 years ago
// Change color of source for ALL RAW table to be able to see the error logs easy
if (val.source == "stdout") {
val.source = '<div class="outtype" >' + val.source + "</div>";
3 years ago
this.rawout.push(val);
} else {
val.source = '<div class="errtype" >' + val.source + "</div>";
3 years ago
this.rawerror.push(val);
}
3 years ago
this.countIndex++;
this.rawdata.push(val);
// Check if the log is by a node_service and change color of the type field
3 years ago
if (val.tag.includes("node") && this.IsJsonString(val.log)) {
var test = JSON.parse(val.log);
3 years ago
test.app_name = val.container_name;
if (test.type == "out") {
test.type = '<div class="outtype" >' + test.type + "</div>";
3 years ago
this.localDataOut.push(test);
} else if (test.type == "err") {
test.type = '<div class="errtype" >' + test.type + "</div>";
3 years ago
this.localDataError.push(test);
}
this.localData.push(test);
} //check if the log is by the mongodb and change color
3 years ago
else if (val.tag.includes("node") && !this.IsJsonString(val.log)) {
var tmp2;
var type;
if (val.source.includes("stdout")) {
// console.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
//type = '<div class="outtype" >' + val.source + "</div>";
tmp2 = {
message: val.log,
timestamp: val.time,
type: '<div class="outtype" >' + "out" + "</div>",
process_id: "-",
app_name: val.container_name,
};
this.localDataOut.push(tmp2);
this.localData.push(tmp2);
} else if (val.source.includes("stderr")) {
//type = '<div class="outtype" >' + val.source + "</div>";
tmp2 = {
message: val.log,
timestamp: val.time,
type: '<div class="errtype" >' + "err" + "</div>",
process_id: "-",
app_name: val.container_name,
};
this.localDataError.push(tmp2);
this.localData.push(tmp2);
}
} else if (val.tag.includes("mongodb") && this.IsJsonString(val.log)) {
// ========== push data for raw table ============
var tmp = JSON.parse(val.log);
3 years ago
if (tmp.s == "I") {
tmp.s = '<div class="outtype" >' + tmp.s + "</div>";
3 years ago
this.mongoout.push(tmp);
} else {
tmp.s = '<div class="errtype" >' + tmp.s + "</div>";
3 years ago
this.mongoerror.push(tmp);
}
tmp.t = '<div class="mongod">' + JSON.stringify(tmp.t) + "</div>";
this.mongodata.push(tmp);
// ===========
//msg
var tmplog = JSON.parse(val.log);
var msg2 = tmplog.msg;
//timestam
var time = tmplog.t;
time = time.$date;
var tmp = {
message: msg2,
timestamp: time,
type: "<div class= 'outtype'>out</div>",
process_id: "Unknown",
3 years ago
app_name: val.container_name,
};
3 years ago
this.localDataOut.push(tmp);
this.localData.push(tmp);
// console.log("HEY " + JSON.stringify(test));
3 years ago
} else if (val.tag.includes("redis")) {
var tmplog = val;
//timestamp
var time = tmplog.time;
var tmp = {
message: tmplog.log,
timestamp: time,
type: "<div class= 'outtype'>out</div>",
process_id: "Unknown",
3 years ago
app_name: val.container_name,
};
3 years ago
this.localDataOut.push(tmp);
this.localData.push(tmp);
}
3 years ago
},
3 years ago
},
3 years ago
};
</script>
<style>
3 years ago
.table2 {
overflow: auto;
3 years ago
/* overflow-anchor: none; */
3 years ago
max-height: 400px;
min-width: 100%;
}
.outtype {
color: rgb(0, 0, 0) !important;
background-color: rgba(13, 233, 13, 0.411) !important;
padding: 5px;
}
.errtype {
color: white;
background-color: rgb(197, 29, 29);
padding: 5px;
}
3 years ago
h2 {
background-color: dimgray;
padding: 10px;
color: white;
margin-top: 20px;
border-radius: 5px;
width: 100%;
3 years ago
}
3 years ago
.CodeMirror {
3 years ago
font-family: monospace;
3 years ago
}
.rowlog {
display: flex; /* equal height of the children */
3 years ago
min-height: 600px;
3 years ago
box-shadow: 3px 3px 10px 10px rgba(0, 0, 0, 0.281);
margin-bottom: 40px;
margin-top: 20px;
padding: 5px;
}
.logs {
width: 100%;
3 years ago
}
3 years ago
button {
padding: 10px 10px 10px 10px;
margin: 10px 10px 10px 0px;
}
.conid {
overflow: hidden;
text-overflow: ellipsis;
padding: 5px;
width: 120px;
3 years ago
height: 100px;
display: inline-block;
}
.conid:hover {
transition: 0.7s;
3 years ago
width: 580px;
background-color: rgb(49, 49, 49);
color: #fff;
position: relative;
border-radius: 6px;
padding: 5px;
}
.mongod {
overflow: hidden;
text-overflow: ellipsis;
padding: 5px;
3 years ago
width: 110px;
height: 140px;
/* position: relative; */
display: inline-block;
}
.mongod:hover {
transition: 0.7s;
3 years ago
width: 330px;
background-color: rgb(63, 61, 61);
color: #fff;
border-radius: 6px;
padding: 5px;
}
3 years ago
</style>