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.
 
 
 
 
 
 

1449 lines
39 KiB

<template>
<card class="card-user" style="max-height:100%">
<div class="author">
<img class="avatar border-white" src="@/assets/img/academy.png" alt="..."
v-if="datadir"
>
<img class="avatar border-white" src="@/assets/img/library.png" alt="..."
v-else
>
</div>
<v-wait for="myRunInstancetutor1">
<template slot="waiting">
<div>
<img src="@/assets/loading.gif" />
Enter Lab_room...
</div>
</template>
</v-wait>
<b-container fluid class="bv-example-row"
v-show="datadir"
>
<!-- timeline -->
<b-container class="container" >
<div class="row justify-content-center myscrool">
<div class="col-xl-10 col-12">
<div class="timeline timeline-line-solid">
<span class="timeline-label">
<span class="label">{{datatitle}}</span>
</span>
<!-- timeline -->
<div class="timeline-item"
v-for="(course, i) in coursesview"
>
<div
v-if="!course.exercise_file"
>
<div class="timeline-point timeline-point"></div>
<div class="timeline-event shadow p-3 mb-5 bg-white rounded">
<div class="widget has-shadow" >
<div class="widget-header d-flex align-items-center"
:class="{ active: i === activeItem}"
>
<div class="user-image">
<img class="rounded-circle" src="@/assets/img/penguinfledgling.png" alt="...">
</div>
<div class="d-flex flex-column mr-auto">
<div class="title">
<span class="username">{{ course.title }} </span>
</div>
</div>
<!--
<div class="widget-options">
<div class="dropdown">
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="dropdown-toggle">
<i class="la la-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a href="#" class="dropdown-item">
<i class="la la-edit"></i>Edit Post
</a>
<a href="#" class="dropdown-item">
<i class="la la-trash"></i>Delete Post
</a>
</div>
</div>
</div>
-->
</div>
<div class="widget-body">
<p>
{{ course.subtitle}}
</p>
<small>{{ course.desc}}</small>
</div>
<div class="widget-footer d-flex align-items-center">
<div class="col-12 no-padding d-flex justify-content-end">
<div class="meta">
<ul class="list-group list-group-horizontal">
<li
v-if="course.doc_file"
>
<a href="#"
@click="viewllo(course,'asciidoc',i)"
><small > </small> <span title="doc" class="rounded-circle bg-dark text-white ti-book"></span></a>
</li>
<li
v-if="course.code_file"
>
<a href="#"
@click="viewllo(course,'asciinema',i)"
><small > </small><span title="asciinema" class="rounded-circle text-success ti-shortcode"></span></a>
</li>
<li
v-if="course.video_file"
>
<a href="#"
@click="viewllo(course,'video',i)"
><small > </small><span title="video" class="text-info ti-video-camera"></span></a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- timeline right -->
</div>
</div>
<div class="col-xl-10 col-12">
<div class="timeline timeline-line-solid">
<span class="timeline-label">
<span class="label">Exercises</span>
</span>
<!-- timeline -->
<div class="timeline-item"
v-for="(course, i) in coursesview"
>
<div
v-if="course.exercise_file"
>
<div class="timeline-point timeline-point"></div>
<div class="timeline-event shadow p-3 mb-5 bg-white rounded">
<div class="widget has-shadow" >
<div class="widget-header d-flex align-items-center"
:class="{ active: i === activeItem}"
>
<div class="user-image">
<img class="rounded-circle" src="@/assets/img/penguinfledgling.png" alt="...">
</div>
<div class="d-flex flex-column mr-auto">
<div class="title">
<span class="username">{{ course.title }} </span>
</div>
</div>
<!--
<div class="widget-options">
<div class="dropdown">
<button type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="dropdown-toggle">
<i class="la la-ellipsis-h"></i>
</button>
<div class="dropdown-menu">
<a href="#" class="dropdown-item">
<i class="la la-edit"></i>Edit Post
</a>
<a href="#" class="dropdown-item">
<i class="la la-trash"></i>Delete Post
</a>
</div>
</div>
</div>
-->
</div>
<div class="widget-body">
<p>
{{ course.subtitle}}
</p>
<small>{{ course.desc}}</small>
</div>
<div class="widget-footer d-flex align-items-center">
<div class="col-12 no-padding d-flex justify-content-end">
<div class="meta">
<ul class="list-group list-group-horizontal">
<li
v-if="course.exercise_file"
>
<a href="#"
@click="viewllo(course,'exercise',i)"
><small > </small> <span title="Challenge" class="rounded-circle bg-dark text-white ti-book"></span></a>
</li>
<li
v-if="course.exercise_answerfile"
>
<a href="#"
@click="viewllo(course,'exerciseanswer',i)"
><small > </small><span title="Answer" class="text-info ti-book"></span></a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- timeline right -->
<span class="timeline-label">
<span class="label">{{datatitle}}</span>
</span>
</div>
</div>
</div>
</b-container>
<!--
<div class="list-group myscrool"
>
<a href="#" class="list-group-item list-group-item-action flex-column align-items-start"
v-for="(course, i) in courses"
@click="viewllo(course,i)"
:class="{ active: i === activeItem}"
>
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{course.title}}</h5>
<small
:class="checkClass(course)"
> {{ course.type }}
<small class="ti-shortcode"
v-if="course.type=='asciinema'"
> </small>
<small class="ti-video-camera"
v-if="course.type=='video'"
> </small>
<small class="ti-book"
v-if="course.type=='asciidoc'"
> </small>
<small class="ti-help-alt text-exercise"
v-if="course.type=='exercise'"
> </small>
</small>
</div>
<small>{{ course.desc}}</small>
</a>
-->
</div>
</b-container>
<br>
<b-container fluid class="bv-example-row">
<div class="progress"
v-show="datadir"
>
<div class="progress-bar progress-bar-striped bg-secondary" role="progressbar" :style="{ width: progress + '%' }" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100">{{ progress }}</div>
</div>
<div class="input-group input-group-sm sm-3">
<input type="text"
class="form-control"
aria-label="Small" aria-describedby="inputGroup-sizing-sm"
placeholder="Search by Name"
v-model="searchFor"
@keyup.enter="setFilter"
>
<div class="input-group-prepend">
<select
class="custom-select custom-select-sm" v-model="searchcourselang">
<option value="0" selected>Lang...</option>
<option value="1">English</option>
<option value="2">Ελληνικά</option>
</select>
</div>
<div class="input-group-prepend">
<select
class="custom-select custom-select-sm" v-model="searchcourselevel">
<option value="0" selected>Level...</option>
<option value="1">Elementary</option>
<option value="2">Intermediate</option>
<option value="3">Advanced</option>
<option value="4">Expert</option>
<option value="5">Guru</option>
</select>
</div>
<div class="input-group-append">
<button
class="btn btn-outline-primary"
round
type="button"
@click="setFilter">
Go</button>
</div>
<div class="input-group-append">
<button class="btn btn-outline-secondary"
round
type="button"
@click="resetFilter">
Reset</button>
</div>
</div>
<div class="white h-100 flex-fixed-width-item"
<vuetable
ref="vuetable"
:key="vuetablekey"
:api-url='apiurl'
:api-mode="true"
:http-options="httpOptions"
:fields="fields"
:item-actions="itemActions"
:sort-order="sortOrder"
:show-sort-icons="true"
:multi-sort="multiSort"
:per-page="perpage"
pagination-path="links.pagination"
:pagination-component="paginationComponent"
:append-params="moreParams"
wrapper-class="vuetable-wrapper"
loading-class="loading"
detail-row-id="id"
@vuetable:pagination-data="onPaginationData"
@vuetable:load-success="loadsuccess"
@vuetable:load-error="onLoadError"
:css="css.table"
>
<div slot="actions" slot-scope="props">
<button
v-if="actionrowindex == props.rowIndex"
class="ti-more-alt btn btn-secondary btn-sm"
title="View Course Details"
round
@click="onAction('run-more', props.rowData, props.rowIndex)">
</button>
<button
v-else
class="ti-more btn btn-outline-secondary btn-sm"
title="View Course Details"
round
@click="onAction('run-more', props.rowData, props.rowIndex)">
</button>
<button
class="ti-info btn btn-outline-secondary btn-sm"
title="View Course Info"
round
@click="onAction('run-info', props.rowData, props.rowIndex)">
</button>
</div>
</vuetable>
<div class="vuetable-pagination ui basic segment grid">
<vuetable-pagination-info
ref="paginationInfo"
:css="css.paginationInfo"
>
</vuetable-pagination-info>
<vuetable-pagination
:css="css.pagination"
ref="pagination"
@vuetable-pagination:change-page="onChangePage"
>
</vuetable-pagination>
</div>
</div>
</b-container>
</card>
</template>
<script>
import store from '@/store/index'
import {mapState, mapGetters, mapActions,dispatch} from 'vuex'
import Vue from 'vue'
import {Vuetable, VuetablePaginationDropdown} from 'vuetable-2'
import VuetablePaginationInfo from 'vuetable-2/src/components/VuetablePaginationInfo'
import VuetablePagination from 'vuetable-2/src/components/VuetablePagination'
import CssConfig from 'vuetable-2/src/components/VuetableCssConfig.js'
import card from '@/components/Card.vue'
import {ApiConfig} from "@/config/index";
export default {
components: {
card,
Vuetable,
VuetablePagination,
VuetablePaginationInfo,
VuetablePaginationDropdown
},
props: {
},
data() {
return{
coursesview:{},
actionrowindex:'',
activeItem: null,
playbookInfo: {},
token: '',
playbook: {
'title':'',
'name':'',
'description':''
},
datadir:'',
datatitle:'',
progress:0,
searchcourselevel:0,
searchcourselang:0,
courses:[],
courseshow:false,
container:{
name:'',
view:0
},
pipeline:{},
selected: 'hybrid',
options: [
{ text: 'Packages', value: 'packages' },
{ text: 'Images', value: 'images' },
{ text: 'Scripts', value: 'scripts' }
],
showModal: false,
visibility: [],
active:false,
vuetablekey:0,
fielddata:{},
fields: [
{
name: 'title',
title: '<span class="orange"></span>Name',
sortField: 'title',
visible:true,
dataClass: 'text-left text-wrap text-break break-word',
width: '20%'
},
{
name: 'subtitle',
title: '<span class="orange"></span>Course',
//sortField: 'subtitle',
visible:true,
dataClass: 'left aligned w-25',
width: '25%'
},
{
name: '_id',
title: '<span class="orange"></span>mongo',
visible:false
},
{
name: 'desc',
title: '<span class="orange"></span>Description',
//sortField: 'desc',
titleClass: 'center aligned',
visible:true,
width: '30%'
},
{
name: 'lang',
title: '<span class="orange"></span>Lang',
sortField: 'lang',
titleClass: 'center aligned',
dataClass: 'text-center',
visible:true,
width: '10%'
},
{
name: '__slot:actions', // <----
title: '<span class="d-flex justify-content-center">Actions</span>',
titleClass: 'center aligned',
dataClass: 'text-center',
width: '15%'
}
],
apiurl:ApiConfig.url_80+"/get_llo",
//apiurl:ApiConfig.swarmlab_url_80+"/swarmlabhybridservices",
css: CssConfig,
perpage: 5,
searchFor: '',
sortOrder: [{
field: 'title',
direction: 'asc'
}],
multiSort: true,
paginationComponent: 'vuetable-pagination',
itemActions: [
{ name: 'view-item', label: '', icon: 'glyphicon glyphicon-zoom-in', class: 'btn btn-info', extra: {'title': 'View', 'data-toggle':"tooltip", 'data-placement': "left"} },
{ name: 'edit-item', label: '', icon: 'glyphicon glyphicon-pencil', class: 'btn btn-warning', extra: {title: 'Edit', 'data-toggle':"tooltip", 'data-placement': "top"} },
{ name: 'delete-item', label: '', icon: 'glyphicon glyphicon-remove', class: 'btn btn-danger', extra: {title: 'Delete', 'data-toggle':"tooltip", 'data-placement': "right" } }
],
moreParams: {
'filter': '',
'level': '',
'type': 'scripts',
'tutor': 'yes'
},
}
},
mounted() {
this.$root.$on('hybrid_progress_refresh', () => {
Vue.nextTick( () => this.getprogress(this.datadir))
})
this.$root.$on('hybrid_refresh_dockerservices', () => {
//Vue.nextTick( () => this.$refs.vuetable.refresh())
})
//refresh from socket mytable
this.$root.$on('hybrid_refresh_table', (v) => {
this.$nextTick(function () {
Vue.nextTick( () => this.$refs.vuetable.refresh())
this.viewhybridoptions = false
})
})
},
created() {
var url_string = window.location.href
var url = new URL(url_string);
this.token = url.searchParams.get("token");
console.log("token "+ this.token);
},
beforeDestroy () {
this.$root.$off('hybrid_progress_refresh')
this.$root.$off('hybrid_refresh_dockerservices')
this.$root.$off('hybrid_refresh_table')
},
computed: {
httpOptions() {
var token = this.token
var p="headers: {Authorization: token}}"; //table props -> :http-options="httpOptions"
return {headers: {Authorization: 'Bearer ' + token}} //table props -> :http-options="httpOptions"
},
},
methods: {
async getprogress(course){
console.log('course '+course)
var value1 = {}
value1.course = this.datadir
var llo = await store.dispatch('pipelineLLO/getprogress', value1)
var sprogress = llo.data.data.progress.progress
var synolochapter = this.courses.length
//synolochapter--
var synolo = Math.floor((sprogress/synolochapter)*100)
this.progress = synolo
},
checkClass(course){
if(course.type == 'video'){
return 'text-primary'
} else if(course.type == 'asciidoc'){
return 'text-success'
} else if(course.type == 'asciinema'){
return 'text-info'
} else if(course.type == 'exercise'){
return 'text-warning'
}else{
return 'text-secondary'
}
},
onError (type,description) {
var winfo=description
var info='<h5>Bootstrap '+type+'</h5>'
this.$swal({
type: type,
html: info+winfo,
showCloseButton: true,
showLoaderOnConfirm: false,
allowOutsideClick: false,
cancelButtonText: 'No, cancel!',
showCancelButton: false,
showLoaderOnConfirm: false,
reverseButtons: true,
focusCancel: true,
confirmButtonText: 'Ok!'
})
},
setFilter () {
this.moreParams = {
'filter': this.searchFor,
'level': this.searchcourselevel,
'lang': this.searchcourselang,
'type': this.selected
}
Vue.nextTick( () => this.$refs.vuetable.refresh())
},
resetFilter () {
this.moreParams = {}
this.searchFor = ''
Vue.nextTick( () => this.$refs.vuetable.refresh())
},
onPaginationData (paginationData) {
this.$refs.pagination.setPaginationData(paginationData)
this.$refs.paginationInfo.setPaginationData(paginationData)
},
onChangePage (page) {
this.$refs.vuetable.changePage(page)
},
editRow(rowData) {
alert("You clicked edit on"+ JSON.stringify(rowData));
},
async viewllo (course,coursetype,i) {
this.courseshow = true
if(coursetype == 'video' ){
console.log(JSON.stringify(course))
var value = {}
value.type = coursetype
value.course = course.video_file
value.llo = this.datadir
this.$root.$emit('hybrid_llo_viecourse',value)
}else if(coursetype == 'asciidoc' ){
var value1 = {}
value1.dir = this.datadir
value1.file = course.doc_file
//console.log('asciidoc '+JSON.stringify(value1))
var llo = await store.dispatch('pipelineLLO/get_llo_course_ascii', value1)
console.log('asciidoc '+JSON.stringify(llo))
//this.courses = JSON.parse(llo.data)
var value = {}
value.type = coursetype
value.code = llo.data.data
value.course = course.doc_file
value.llo = this.datadir
this.$root.$emit('hybrid_llo_viecourse',value)
}else if(coursetype == 'asciinema' ){
console.log(JSON.stringify(course))
var value = {}
value.type = coursetype
value.course = course.code_file
value.llo = this.datadir
this.$root.$emit('hybrid_llo_viecourse',value)
}else if(coursetype == 'exercise' ){
var value1 = {}
value1.dir = this.datadir
value1.file = course.exercise_file
//console.log('asciidoc '+JSON.stringify(value1))
var llo = await store.dispatch('pipelineLLO/get_llo_course_ascii', value1)
console.log('asciidoc '+JSON.stringify(llo))
//this.courses = JSON.parse(llo.data)
var value = {}
value.type = 'asciidoc'
value.code = llo.data.data
value.course = course.exercise_file
value.llo = this.datadir
this.$root.$emit('hybrid_llo_viecourse',value)
}else if(coursetype == 'exerciseanswer' ){
var value1 = {}
value1.dir = this.datadir
value1.file = course.exercise_answerfile
//console.log('asciidoc '+JSON.stringify(value1))
var llo = await store.dispatch('pipelineLLO/get_llo_course_ascii', value1)
console.log('asciidoc '+JSON.stringify(llo))
//this.courses = JSON.parse(llo.data)
var value = {}
value.type = 'asciidoc'
value.code = llo.data.data
value.course = course.exercise_answerfile
value.llo = this.datadir
this.$root.$emit('hybrid_llo_viecourse',value)
}
this.activeItem = i;
this.getprogress(this.datadir)
},
async onAction (action, data, index) {
//console.log('index '+this.actionrowindex)
if(action == 'run-more' ){
this.actionrowindex = index
this.datadir = data.dir
this.datatitle = data.title
var llo = await store.dispatch('pipelineLLO/get_llo_course', data.dir)
this.courses = llo.data
var countchapter = Math.max.apply(Math, this.courses.map(function(o){return o.chapter;}))
console.log('countchapter '+ JSON.stringify(countchapter))
var coursesnew1 = []
for(var i = 1; i <= countchapter; i++) {
console.log('count '+ JSON.stringify(i))
var coursesnew = {}
var coursetmp = this.courses.filter(x => x.chapter === i);
var countcourse1 = coursetmp.length
/*
coursesnew.video_file = coursetmp[ii].file
coursesnew.video_service = coursetmp[ii].service
coursesnew.doc_file = coursetmp[ii].file
coursesnew.code_file = coursetmp[ii].file
coursesnew.exercise_file = coursetmp[ii].file
coursesnew.exercise_difficulty = coursetmp[ii].difficulty
coursesnew.chapter = i
coursesnew.title = coursetmp[ii].title
coursesnew.subtitle = coursetmp[ii].subtitle
coursesnew.desc = coursetmp[ii].desc
*/
for(var ii = 0; ii < countcourse1; ii++) {
//console.log('for '+ JSON.stringify(coursetmp))
if( coursetmp[ii].type == 'video'){
coursesnew.video_file = coursetmp[ii].file
coursesnew.video_service = coursetmp[ii].service
}else if( coursetmp[ii].type == 'asciidoc'){
coursesnew.doc_file = coursetmp[ii].file
}else if( coursetmp[ii].type == 'asciinema'){
coursesnew.code_file = coursetmp[ii].file
}else if( coursetmp[ii].type == 'exercise'){
coursesnew.exercise_file = coursetmp[ii].file
coursesnew.exercise_answerfile = coursetmp[ii].answerfile
coursesnew.exercise_difficulty = coursetmp[ii].difficulty
}
if(coursesnew.chapter == '' || coursesnew.chapter == undefined){
coursesnew.chapter = i
}
if(coursesnew.title == '' || coursesnew.title == undefined){
coursesnew.title = coursetmp[ii].title
}
if(coursesnew.subtitle == '' || coursesnew.subtitle == undefined){
coursesnew.subtitle = coursetmp[ii].subtitle
}
if(coursesnew.desc == '' || coursesnew.desc == undefined){
coursesnew.desc = coursetmp[ii].desc
}
if(coursesnew.course == '' || coursesnew.course == undefined){
coursesnew.course = this.datatitle
}
}
coursesnew1.push(coursesnew)
}
this.coursesview = coursesnew1
console.log('new '+JSON.stringify(coursesnew1))
//this.courses = JSON.parse(llo.data)
}else if(action == 'run-info' ){
console.log(JSON.stringify(data))
// {"title":"linux","subtitle":"Basics","level":"Intermediate","desc":"description","dir":"hybrid-linux"}
var info = `
<table class="table table-hover table-sm text-left">
<tbody>
<tr>
<th ><b>Title</b></th>
<td>${data.title}</td>
</tr>
<tr>
<td><b>Subtitle</b></td>
<td>${data.subtitle}</td>
</tr>
<tr>
<th ><b>Level</b></th>
<td>${data.level}</td>
</tr>
<tr>
<td ><b>Description</b></td>
<td>${data.desc}</td>
</tr>
</tbody>
</table>`
this.$swal({
type: 'Info',
title: 'Course Info!',
icon:'info',
html: info,
showCloseButton: true,
showLoaderOnConfirm: false,
allowOutsideClick: false,
cancelButtonText: 'No, cancel!',
showCancelButton: false,
showLoaderOnConfirm: false,
reverseButtons: true,
focusCancel: true,
confirmButtonText: 'Ok!'
})
}
this.getprogress(this.datadir)
},
refreshVuetable() {
this.$nextTick(()=>{
this.vuetablekey += 1
})
},
playbookinfoShow(value) {
return this.visibility[value]=true
},
playbookinfo(value) {
return this.playbookInfo=value
},
rowClicked(row, event) {
return {
html: true,
title: () => { return 'Hello <b>Popover:</b> ' + (++this.counter) },
content: () => { return 'The date is:<br><em>' + new Date() + '</em>' }
}
},
loadsuccess(response) {
var data = response.data.data
this.fielddata=data
var n = data.length
n=n-1
},
onLoadError(payload) {
/*
//error2 "invalid_token" join-service.vue:684
//error2 "The access token provided has expired" join-service.vue:685
//error2 "Unauthorized" join-service.vue:686
//error2 401
console.log('error2 '+JSON.stringify(payload.response.data.error))
console.log('error2 '+JSON.stringify(payload.response.data.error_description))
console.log('error2 '+JSON.stringify(payload.response.statusText))
console.log('error2 '+JSON.stringify(payload.response.status))
*/
if(payload.response.status == '401'){
window.location.href = 'https://api-login.swarmlab.io:8089';
Vue.nextTick( () => window.location.href = 'https://api-login.swarmlab.io:8089')
}
}
},
actions: {
}
};
</script>
<style>
.flex-fixed-width-item {
flex: 0 0 100px;
}
.modalinfo {
z-index: 10000000 !important;
position:fixed;
}
/* a container with flex-direction column */
.vue-notifyjs.notifications{
.alert{
z-index: 100;
}
.list-move {
transition: transform 0.3s, opacity 0.4s;
}
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active {
transition: transform 0.2s ease-in, opacity 0.4s ease-in;
}
.list-leave-active {
transition: transform 1s ease-out, opacity 0.4s ease-out;
}
.list-enter {
opacity: 0;
transform: scale(1.1);
}
.list-leave-to {
opacity: 0;
transform: scale(1.2, 0.7);
}
}
.myscrool {
height: 500px;
max-height: 500px;
overflow-y: auto;
//margin-bottom: 10px;
-webkit-overflow-scrolling: touch;
}
pre {
//background-color: rgb(255, 247, 229);
background-color: #eff0f1;
border: 1px solid blue;
//white-space: pre-line;
}
.timeline {
width: 100%;
position: relative;
padding: 1px 0;
list-style: none;
font-weight: 500
}
.timeline .timeline-item {
padding-left: 0;
padding-right: 30px
}
.timeline .timeline-item.timeline-item-right,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left) {
padding-left: 30px;
padding-right: 0
}
.timeline .timeline-item .timeline-event {
width: 100%
}
.timeline:before {
border-right-style: solid
}
.timeline:before,
.timeline:after {
content: " ";
display: block
}
.timeline:after {
clear: both
}
.timeline:before {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 50%;
height: 100%!important;
margin-left: 2px;
border-right-width: 4px;
border-right-style: solid;
border-right-color: rgba(52, 40, 104, .1)
}
.timeline .timeline-label .label {
background-color: #e76c90;
border-radius: 35px;
color: #fff;
display: inline;
font-size: .85rem;
font-weight: 600;
line-height: 1;
padding: .65rem 1.4rem;
text-align: center;
vertical-align: baseline;
white-space: nowrap
}
.timeline.timeline-line-solid:before {
border-right-style: solid
}
.timeline.timeline-line-dotted:before {
border-right-style: dotted
}
.timeline.timeline-line-dashed:before {
border-right-style: dashed
}
.timeline .timeline-item {
position: relative;
float: left;
clear: left;
width: 50%;
margin-bottom: 20px
}
.timeline .timeline-item:before,
.timeline .timeline-item:after {
content: "";
display: table
}
.timeline .timeline-item:after {
clear: both
}
.timeline .timeline-item:last-child {
margin-bottom: 0!important
}
.timeline .timeline-item.timeline-item-right>.timeline-event,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left)>.timeline-event {
float: right!important
}
.timeline .timeline-item.timeline-item-right>.timeline-event:before,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left)>.timeline-event:before,
.timeline .timeline-item.timeline-item-right>.timeline-event:after,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left)>.timeline-event:after {
right: auto!important;
border-left-width: 0!important
}
.timeline .timeline-item.timeline-item-right>.timeline-event:before,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left)>.timeline-event:before {
left: -15px!important;
border-right-width: 15px!important
}
.timeline .timeline-item.timeline-item-right>.timeline-event:after,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left)>.timeline-event:after {
left: -14px!important;
border-right-width: 14px!important
}
.timeline .timeline-item>.timeline-event:before {
top: 10px;
right: -15px;
border-top: 15px solid transparent;
border-left-width: 15px;
border-left-style: solid;
border-right-width: 0;
border-right-style: solid;
border-bottom: 15px solid transparent
}
.timeline .timeline-item>.timeline-event:after {
top: 11px;
right: -14px;
border-top: 14px solid transparent;
border-left-width: 14px;
border-left-style: solid;
border-right-width: 0;
border-right-style: solid;
border-bottom: 14px solid transparent
}
.timeline .timeline-item>.timeline-point {
top: 25px
}
.timeline .timeline-item:nth-of-type(2) {
margin-top: 40px
}
.timeline .timeline-item.timeline-item-left,
.timeline .timeline-item.timeline-item-right {
clear: both!important
}
.timeline .timeline-item.timeline-item-right,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left) {
float: right;
clear: right
}
.timeline .timeline-item.timeline-item-right>.timeline-point,
.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left)>.timeline-point {
left: -14px
}
.timeline .timeline-item>.timeline-event {
position: relative;
float: left
}
.timeline .timeline-item>.timeline-event:before {
border-left-color: rgba(52, 40, 104, .04);
border-right-color: rgba(52, 40, 104, .04)
}
.timeline .timeline-item>.timeline-event:after {
border-left-color: rgba(52, 40, 104, .04);
border-right-color: rgba(52, 40, 104, .04)
}
.timeline .timeline-item>.timeline-event:before,
.timeline .timeline-item>.timeline-event:after {
content: "";
display: inline-block;
position: absolute
}
.timeline .timeline-item>.timeline-point {
color: #5d5386;
background: #5d5386;
right: -14px;
width: 12px;
height: 12px;
margin-top: -6px;
margin-left: 8px;
margin-right: 8px;
position: absolute;
z-index: 100;
border-width: 3px;
border-style: solid;
border-radius: 100%;
line-height: 20px;
text-align: center;
box-shadow: 0 0 0 5px #f2f3f8
}
.timeline .timeline-label {
position: relative;
float: left;
clear: left;
width: 50%;
margin-bottom: 20px;
top: 1px;
width: 100%;
margin-left: auto;
margin-right: auto;
padding: 0;
text-align: center
}
.timeline .timeline-label:before,
.timeline .timeline-label:after {
content: "";
display: table
}
.timeline .timeline-label:after {
clear: both
}
.timeline .timeline-label:last-child {
margin-bottom: 0!important
}
.timeline .timeline-label + .timeline-item {
margin-top: 0
}
.timeline .timeline-label + .timeline-item + .timeline-item {
margin-top: 40px
}
.timeline .time-right {
color: rgba(52, 40, 104, .5);
position: absolute;
bottom: 0;
right: 0;
font-size: .85rem;
font-weight: 500
}
.timeline .time-left {
color: rgba(52, 40, 104, .5);
position: absolute;
bottom: 0;
left: 0;
font-size: .85rem;
font-weight: 500
}
.timeline .widget-header .user-image {
display: inline-block;
vertical-align: middle;
margin-right: 1.07rem
}
.timeline .widget-header .user-image img {
width: 50px
}
.timeline .widget-header .title {
color: #2c304d;
font-size: 1rem
}
.timeline .widget-header .username {
color: #e76c90
}
.timeline .widget-header .time {
font-size: .85rem
}
.timeline .widget-body {
padding: 1rem 1.4rem
}
.timeline .widget-footer {
border-top: 1px solid #eee;
margin: 0 1.4rem;
padding: 1.07rem 0
}
.timeline .users-like {
padding: 0
}
.timeline .users-like a {
margin: 0 -1.6rem 0 0;
transition: all 0.4s ease
}
.timeline .users-like a:hover {
margin-right: -.3rem
}
.timeline .users-like img {
width: 40px;
border: .25rem solid #fff
}
.timeline .users-like a.view-more {
background: #5d5386;
color: #fff;
width: 40px;
height: 40px;
border: .25rem solid #fff;
border-radius: 50%;
vertical-align: middle;
font-size: .85rem;
text-align: center;
line-height: 30px;
margin-right: 0
}
.timeline .widget-footer .meta li {
display: inline-block;
margin-right: .5rem
}
.timeline .widget-footer .meta li:last-child {
margin-right: 0
}
.timeline .widget-footer .meta li a {
color: rgba(52, 40, 104, .3)
}
.timeline .widget-footer .meta li a:hover {
color: rgba(52, 40, 104, .9)
}
.timeline .widget-footer .meta li i {
font-size: 1.8rem;
vertical-align: middle;
margin-right: .3rem
}
.timeline .widget-footer .meta li .numb {
vertical-align: middle
}
@media screen and (max-width:768px) {
.timeline.timeline {
width: 100%;
max-width: 100%
}
.timeline.timeline .timeline-item {
padding-left: 72px;
padding-right: 0
}
.timeline.timeline .timeline-item.timeline-item-right,
.timeline.timeline .timeline-item:nth-of-type(even):not(.timeline-item-left) {
padding-left: 72px;
padding-right: 0
}
.timeline.timeline .timeline-item .timeline-event {
width: 100%
}
.timeline.timeline:before {
left: 42px;
width: 0;
margin-left: -1px
}
.timeline.timeline .timeline-item {
width: 100%;
margin-bottom: 20px
}
.timeline.timeline .timeline-item:nth-of-type(even) {
margin-top: 0
}
.timeline.timeline .timeline-item>.timeline-event {
float: right!important
}
.timeline.timeline .timeline-item>.timeline-event:before,
.timeline.timeline .timeline-item>.timeline-event:after {
right: auto!important;
border-left-width: 0!important
}
.timeline.timeline .timeline-item>.timeline-event:before {
left: -15px!important;
border-right-width: 15px!important
}
.timeline.timeline .timeline-item>.timeline-event:after {
left: -14px!important;
border-right-width: 14px!important
}
.timeline.timeline .timeline-item>.timeline-point {
transform: translateX(-50%);
left: 42px!important;
margin-left: 0
}
.timeline.timeline .timeline-label {
transform: translateX(-50%);
margin: 0 0 20px 42px
}
.timeline.timeline .timeline-label + .timeline-item + .timeline-item {
margin-top: 0
}
.timeline .time-right {
left: 0
}
}
.rounded-widget .widget {
border-radius: 4px
}
.rounded-widget .widget-image {
border-radius: 4px
}
.rounded-widget .widget-header {
border-radius: 4px 4px 0 0
}
.rounded-widget .widget-footer {
border-radius: 0 0 4px 4px
}
.widget {
background: #fff;
border-radius: 0;
border: none;
margin-bottom: 30px
}
.widget-image {
background: #fff;
border-radius: 0;
border: none;
margin-bottom: 30px;
position: relative
}
.widget-header {
background: #fff;
padding: .85rem 1.4rem;
position: relative;
width: 100%
}
.widget-header.bordered {
border-bottom: .07rem solid #eee
}
.widget-header.no-actions {
padding: 1.49rem
}
.widget-options .dropdown-toggle {
color: #98a8b4;
background: none;
border: none;
padding: 0;
font-size: 1.7rem;
}
.widget-header h1,
.widget-header h2,
.widget-header h3,
.widget-header h4,
.widget-header h5,
.widget-header h6 {
color: #2c304d;
margin-bottom: 0
}
.section-title h1,
.section-title h2,
.section-title h3,
.section-title h4,
.section-title h5,
.section-title h6 {
color: #2c304d;
font-weight: 600;
margin-bottom: 0
}
.widget-header h2 {
color: #2c304d;
font-size: 1.2rem;
display: table-cell;
vertical-align: middle;
margin-right: auto
}
.widget-header h2 i {
color: #aea9c3;
font-size: 1.8rem;
padding-right: .5rem;
vertical-align: middle
}
.widget-body {
padding: 1.4rem
}
.widget-image-footer {
background: #fff;
position: absolute;
bottom: 0;
width: 100%;
padding: 1.2rem 0;
z-index: 1
}
.widget-footer {
background: #fff;
padding: 1rem 1.07rem;
position: relative
}
.widget-footer.big {
padding: 2.1rem 1.07rem
}
.no-border {
border: 0 none!important;
}
.no-bg {
background: none!important;
}
</style>