Skip to content

Commit 54aec4a

Browse files
committed
V 1.1.4
- Add shutdown denoman - Introducing Terminal - Minor fix to search box - Minor fix to expanded Tr (v-if instead of v-show) - Styling scrollbar
1 parent 91c64bd commit 54aec4a

File tree

11 files changed

+5212
-4907
lines changed

11 files changed

+5212
-4907
lines changed

mod.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts";
77

88
import { actions, getDependsServices, getServices } from "./src/services.ts";
99
import { getPerfmon, getSystem } from "./src/system.ts";
10+
import { executeCommand } from "./src/command.ts";
1011
import * as cache from "./src/memcache.ts";
1112
import {
1213
DependenciesModel,
@@ -20,6 +21,14 @@ await initServer(await getWwwRoot());
2021

2122
const router = new Router();
2223

24+
router.get("/api/exit", (ctx) => {
25+
ctx.response.body = true;
26+
console.log("Exiting...");
27+
setTimeout(() => {
28+
Deno.exit(0);
29+
}, 200);
30+
});
31+
2332
router.get("/api/:apiName", async (ctx) => {
2433
const payload: WinRMPayload = {
2534
username: ctx.request.url.searchParams.get("username")!,
@@ -101,12 +110,21 @@ router.post("/api/service", async (ctx) => {
101110
ctx.response.body = service;
102111
});
103112

104-
router.put("/api/service", (ctx) => {
105-
ctx.response.body = "Received a PUT HTTP method";
106-
});
107-
108-
router.delete("/api/service", (ctx) => {
109-
ctx.response.body = "Received a DELETE HTTP method";
113+
router.post("/api/command", async (ctx) => {
114+
const payload: WinRMPayload = {
115+
username: ctx.request.url.searchParams.get("username")!,
116+
password: ctx.request.url.searchParams.get("password")!,
117+
protocol: (ctx.request.url.searchParams.get("protocol")!),
118+
hostname: ctx.request.url.searchParams.get("hostname")!,
119+
port: Number(ctx.request.url.searchParams.get("port")!),
120+
};
121+
const req = await ctx.request.body.json();
122+
const res = await executeCommand(
123+
payload,
124+
req.command,
125+
req.isPowerShell,
126+
);
127+
ctx.response.body = res;
110128
});
111129

112130
const app = new Application();

q-manui/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "q-manui",
3-
"version": "1.1.3",
4-
"description": "DenoMan 1.1.3",
3+
"version": "1.1.4",
4+
"description": "DenoMan 1.1.4",
55
"productName": "DenoMan",
66
"author": "Sameh Fakoua <s.fakoua@gmail.com>",
77
"private": true,
@@ -50,4 +50,4 @@
5050
"npm": ">= 6.13.4",
5151
"yarn": ">= 1.21.1"
5252
}
53-
}
53+
}

q-manui/src/components/ServerComponent.vue

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<q-tab name="dashboard" icon="dashboard" />
66
<q-tab name="performance" icon="insights" />
77
<q-tab name="services" icon="miscellaneous_services" />
8+
<q-tab name="terminal" icon="terminal" />
89
</q-tabs>
910
</div>
1011
<div class="s-right col-grow">
@@ -54,6 +55,9 @@
5455
</template>
5556
</q-splitter>
5657
</q-tab-panel>
58+
<q-tab-panel name="terminal">
59+
<terminal-component :host="host" />
60+
</q-tab-panel>
5761
</q-tab-panels>
5862
</div>
5963
</div>
@@ -167,6 +171,7 @@ import ServiceDetailsComponent from '../components/service-window/ServiceDetails
167171
import ServiceWindowComponent from '../components/service-window/ServiceWindowComponent.vue';
168172
import PerfmonComponent from '../components/performance/PerfmonComponent.vue';
169173
import DashboardComponent from '../components/dashboard/DashboardComponent.vue';
174+
import TerminalComponent from '../components/terminal/TerminalComponent.vue';
170175
171176
import { ServiceModel, WinRMPayload } from './models';
172177
@@ -178,6 +183,7 @@ export default defineComponent({
178183
ServiceWindowComponent,
179184
DashboardComponent,
180185
PerfmonComponent,
186+
TerminalComponent,
181187
},
182188
props: {
183189
host: {

q-manui/src/components/models.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,14 @@ export type PerfmonModel = {
9191
disks: DiskModel[];
9292
networks: NetworkModel;
9393
};
94+
95+
export type ShellResponse = {
96+
stdout: string,
97+
stderr: string,
98+
exitCode: number,
99+
state?: string,
100+
error?: {
101+
reason?: string,
102+
message?: string
103+
}
104+
};

q-manui/src/components/service-api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
DependenciesModel,
55
PerfmonModel,
66
ServiceModel,
7+
ShellResponse,
78
SystemModel,
89
WinRMPayload,
910
} from './models';
@@ -68,3 +69,13 @@ export async function getPerfmon(params: WinRMPayload): Promise<PerfmonModel> {
6869
const res = await api.get('http://localhost:8001/api/perfmon', { params });
6970
return res.data as PerfmonModel;
7071
}
72+
73+
export async function execCommand(params: WinRMPayload, command: string, isPowerShell: boolean): Promise<ShellResponse> {
74+
const res = await api.post('http://localhost:8001/api/command', { command, isPowerShell }, { params });
75+
return res.data as ShellResponse;
76+
}
77+
78+
export function exitApp() {
79+
api.get('http://localhost:8001/api/exit').catch(() => {
80+
});
81+
}

q-manui/src/components/service-window/ServicesListComponent.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@
113113
color="grey-14"
114114
text-color="white"
115115
label="Ctrl+K"
116-
v-if="!filterFocused"
116+
v-if="!filterFocused && !filter"
117117
/>
118-
<q-icon name="search" v-if="filterFocused" />
118+
<q-icon name="search" v-if="filterFocused && !filter" />
119119
</template>
120120
</q-input>
121121
</template>
@@ -263,7 +263,7 @@
263263
{{ col.value }}
264264
</q-td>
265265
</q-tr>
266-
<q-tr v-show="props.expand" :props="props">
266+
<q-tr v-if="props.expand" v-show="props.expand" :props="props">
267267
<q-td colspan="100%" style="padding-left: 6px; background-color: white">
268268
<div class="text-left">
269269
<dependencies-row-component
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<template>
2+
<div class="fit row wrap justify-start items-start content-start">
3+
<div class="col-grow">
4+
<div>Terminal</div>
5+
<div>
6+
<q-input
7+
ref="commandInput"
8+
outlined
9+
v-model="command"
10+
type="textarea"
11+
:disable="executing"
12+
:loading="executing"
13+
:error="errorIndicator"
14+
>
15+
<q-inner-loading :showing="executing">
16+
<q-spinner-gears size="50px" color="primary" />
17+
</q-inner-loading>
18+
</q-input>
19+
</div>
20+
<div style="margin-top: 4px">
21+
<q-btn
22+
label="Execute as PowerShell"
23+
color="black"
24+
unelevated
25+
:disable="executing"
26+
@click="
27+
() => {
28+
executeCommand(true);
29+
}
30+
"
31+
style="margin-right: 4px"
32+
/>
33+
<q-btn
34+
color="primary"
35+
label="Execute as Command Line"
36+
unelevated
37+
:disable="executing"
38+
@click="
39+
() => {
40+
executeCommand(false);
41+
}
42+
"
43+
/>
44+
</div>
45+
<div>
46+
<div class="term">
47+
{{ terminalResult }}
48+
</div>
49+
</div>
50+
</div>
51+
</div>
52+
</template>
53+
54+
<style lang="scss" scoped>
55+
.term {
56+
background-color: #000;
57+
color: #fff;
58+
height: 460px;
59+
overflow: auto;
60+
padding: 10px;
61+
font-family: monospace;
62+
font-size: 12px;
63+
white-space: pre-wrap;
64+
word-wrap: break-word;
65+
border-radius: 5px;
66+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
67+
margin-top: 20px;
68+
margin-bottom: 10px;
69+
}
70+
</style>
71+
72+
<script lang="ts">
73+
import { PropType, computed, defineComponent, ref } from 'vue';
74+
import * as serviceApi from '../service-api';
75+
import { ShellResponse, WinRMPayload } from '../models';
76+
import { QInput } from 'quasar';
77+
78+
export default defineComponent({
79+
name: 'TerminalComponent',
80+
81+
props: {
82+
host: {
83+
type: Object as PropType<WinRMPayload>,
84+
required: true,
85+
},
86+
},
87+
88+
methods: {
89+
async executeCommand(isPowerShell: boolean) {
90+
this.executing = true;
91+
this.response = await serviceApi.execCommand(
92+
this.host,
93+
this.command,
94+
isPowerShell,
95+
);
96+
97+
this.errorIndicator = this.response.exitCode !== 0;
98+
window.setTimeout(() => {
99+
this.errorIndicator = false;
100+
}, 5000);
101+
if (this.response.exitCode === 0) {
102+
this.command = '';
103+
}
104+
this.executing = false;
105+
window.setTimeout(() => {
106+
(this.commandInput! as QInput).focus();
107+
}, 100);
108+
},
109+
},
110+
111+
setup() {
112+
const command = ref('');
113+
const response = ref<ShellResponse>({} as ShellResponse);
114+
const executing = ref(false);
115+
const errorIndicator = ref(false);
116+
const commandInput = ref(null);
117+
const terminalResult = computed(() => {
118+
if (response.value.exitCode === 0) {
119+
return response.value.stdout;
120+
} else {
121+
return response.value.stderr;
122+
}
123+
});
124+
return {
125+
command,
126+
commandInput,
127+
response,
128+
executing,
129+
errorIndicator,
130+
terminalResult,
131+
};
132+
},
133+
});
134+
</script>

q-manui/src/css/app.css

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,26 @@
1-
/* app global css */
1+
/* Works on Chrome, Edge, and Safari */
2+
*::-webkit-scrollbar {
3+
height: 12px;
4+
width: 14px;
5+
background: transparent;
6+
z-index: 12;
7+
overflow: visible;
8+
}
9+
10+
*::-webkit-scrollbar-thumb {
11+
width: 10px;
12+
background-color: #00b4ff;
13+
border-radius: 10px;
14+
z-index: 12;
15+
border: 4px solid rgba(0, 0, 0, 0);
16+
background-clip: padding-box;
17+
-webkit-transition: background-color 0.28s ease-in-out;
18+
transition: background-color 0.28s ease-in-out;
19+
margin: 4px;
20+
min-height: 32px;
21+
min-width: 32px;
22+
}
23+
24+
::-webkit-scrollbar-thumb:hover {
25+
background: #00b4ff;
26+
}

q-manui/src/layouts/MainLayout.vue

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,70 @@
11
<template>
2-
<q-layout view="lHh Lpr lFf">
2+
<q-layout v-if="!shutdown" view="lHh Lpr lFf">
33
<q-header elevated>
44
<q-toolbar>
55
<q-avatar>
66
<img src="/icons/denoman.svg" alt="DenoMan" />
77
</q-avatar>
88
<q-toolbar-title> DenoMan </q-toolbar-title>
99

10-
<div>v {{ version }}</div>
10+
<div>
11+
v {{ version }}
12+
<q-btn
13+
flat
14+
round
15+
dense
16+
icon="power_settings_new"
17+
@click="confirm = true"
18+
/>
19+
</div>
1120
</q-toolbar>
1221
</q-header>
1322
<q-page-container>
1423
<router-view />
1524
</q-page-container>
1625
</q-layout>
26+
<q-dialog v-model="confirm" persistent>
27+
<q-card>
28+
<q-card-section class="row items-center">
29+
<q-avatar
30+
icon="power_settings_new"
31+
color="deep-orange"
32+
text-color="white"
33+
/>
34+
<span class="q-ml-sm">Are you sure you want to exit DenoMan?</span>
35+
</q-card-section>
36+
37+
<q-card-actions align="right">
38+
<q-btn flat label="Cancel" color="primary" v-close-popup />
39+
<q-btn flat label="Exit DenoMan" color="deep-orange" @click="exitApp" />
40+
</q-card-actions>
41+
</q-card>
42+
</q-dialog>
1743
</template>
1844

1945
<script lang="ts">
20-
import { defineComponent } from 'vue';
46+
import { defineComponent, ref } from 'vue';
47+
import * as serviceApi from '../components/service-api';
48+
2149
import { version } from '../../package.json';
2250
2351
export default defineComponent({
2452
name: 'MainLayout',
2553
54+
methods: {
55+
async exitApp() {
56+
this.confirm = false;
57+
serviceApi.exitApp();
58+
this.shutdown = true;
59+
},
60+
},
2661
setup() {
62+
const confirm = ref(false);
63+
const shutdown = ref(false);
2764
return {
65+
shutdown,
2866
version,
67+
confirm,
2968
};
3069
},
3170
});

0 commit comments

Comments
 (0)