본문으로 바로가기

728x90

TCP Socket Router 예제를 통한 Electron Application 작성방법에 대한 연재가 완료되었습니다.

 

Electron + Vue.js 애플리케이션 만들기

1. Electron 프로젝트 생성하기(Create Electron Application Project)

2. Electron IPC 통신(Electron Architecture, IPC Main/IPC Renderer)

3. TCP Router 기능 구현(Implements TCP Router Communication)

4. 화면 UI 구성(Main Window UI)

5. 환경설정 구현하기(Preferences Window)

6. 메뉴 사용하기(Application Menu & Context Menu)

7. 시스템 트레이 사용 및 창 최소화(System Tray & Minimize to Tray)

8. Bootstrap Vue와 Font Awesome 사용하기(Using Bootstrap Vue & Font Awesome)

9. Dark Layout과 Frameless Window(Dark Layout & Frameless Window)

10. 빌드 및 배포, 자동 업데이트(Build, Auto Updater)

 

이번 포스트에서는 Bootstrap을 설치 및 Awesome Font를 설치하여 화면 UI를 개선하는 방법을 설명합니다.

 

Boostrap Vue 설치 및 설정하기

Bootstrap Vue 설치

# With npm
npm install vue bootstrap-vue bootstrap

# With yarn
yarn add vue bootstrap-vue bootstrap

Bootstrap Vue 설정하기

/src/renderer/main.js 에 Bootstrap Vue 초기화 코드를 삽입합니다. 본 예시에서는 css 대신에 scss를 사용하기 위해 css 는 주석처리하였습니다.

// Bootstrap Vue
import { BootstrapVue, BootstrapVueIcons } from 'bootstrap-vue'
// import 'bootstrap/dist/css/bootstrap.css'
// import 'bootstrap-vue/dist/bootstrap-vue.css'
Vue.use(BootstrapVue)
Vue.use(BootstrapVueIcons)
import '@babel/polyfill'

/sec/renderer/App.vue 에 scss를 정의합니다.

<style lang="scss">
@import 'node_modules/bootstrap/scss/bootstrap';
@import 'node_modules/bootstrap-vue/src/index.scss';
</style>

.electron-vue/webpack.renderer.config.js 파일에 whiteListedModules에 bootstrap-vue를 추가합니다.

// let whiteListedModules = ['vue']
let whiteListedModules = ['vue', 'bootstrap-vue']

polyfill 설치 및 설정하기

오래된 브라우저의 호환성을 위하여 polyfill을 설치합니다.

npm install @babel/polyfill

/src/renderer/main.js에 polyfill 설정을 추가합니다.

import '@babel/polyfill'

화면 코드 수정하기

메인 UI 코드

bootstrap vue의 b-card를 사용하여 적절하게 화면을 구성합니다.

<template>
  <b-card no-body class="h-100 main-wrapper" @click="clearActiveItem">
    <b-card-body>
    <div class="row h-100">
      <div class="col-md-6">
        <b-card no-body class="h-100">
          <b-card-header>TCP Router Connections</b-card-header>
          <b-card-body>
            <div class="items overflow-y">
              <div class="item" v-for="(item, key) in tcpRouterState"
                :key="key"
                :class="{'selected': activeItem[key]}"
                @click.prevent.stop="setActiveItem(key)"
              >
                <div class="name">{{ item.listen }}, {{item.host}}:{{item.port}}</div>
                <div class="value">Connections : {{ item.count }}</div>
                <div class="value">Read : {{ item.bytes.clientReadBytes }} bytes</div>
                <div class="value">Write : {{ item.bytes.clientWriteBytes }} bytes</div>
              </div>
            </div>
          </b-card-body>
        </b-card>
      </div>

      <div class="col-md-6" v-if="selectedItem && selectedState">
        <b-card no-body class="h-100">
          <b-card-header>Connection Details</b-card-header>
          <b-card-body>
            <div class="details overflow-y">
              <div class="name">{{ selectedState.listen }}, {{selectedState.host}}:{{selectedState.port}}</div>
              <div class="value">Connections : {{ selectedState.count }}</div>
              <div class="value">Client Read : {{ selectedState.bytes.clientReadBytes }} bytes</div>
              <div class="value">Client Write : {{ selectedState.bytes.clientWriteBytes }} bytes</div>
              <div class="value">Server Read : {{ selectedState.bytes.serverReadBytes }} bytes</div>
              <div class="value">Server Write : {{ selectedState.bytes.serverWriteBytes }} bytes</div>

              <div class="value" v-for="(detail, index) in selectedItem" :key="index">
                <div class="name">{{ detail.address }} : {{ detail.port }}</div>
                <div class="value">Client Read : {{ detail.clientReadBytes }} bytes / Client Write : {{ detail.clientWriteBytes }} bytes</div>
                <div class="value">Server Read : {{ detail.serverReadBytes }} bytes / Server Write : {{ detail.serverWriteBytes }} bytes</div>
              </div>
            </div>
          </b-card-body>
        </b-card>
      </div>

      <div class="col-md-6" v-show="!selectedItem">
        <b-card no-body class="h-100">
          <b-card-header>Event Log</b-card-header>
          <b-card-body>
            <div class="logs overflow-y" ref="logs">
              <div class="log" v-for="(log, index) in logs" :key="index">
                <span class="event-date">{{ log.date }}</span> : <span>{{ log.text }}</span>
              </div>
            </div>
          </b-card-body>
        </b-card>
      </div>
    </div>
    </b-card-body>
    <b-card-footer>
    <div class="row">
      <div class="col-md-6 text-left">
        <button class="alt" @click="handleStartup"><b-icon icon="play" font-scale="1.5"></b-icon> Start</button>
        <button @click="handleShutdown"><b-icon icon="stop" font-scale="1.5"></b-icon> Shutdown</button>
      </div>
      <div class="col-md-6 text-right">
        <button class="alt" @click="handleClearLog"><b-icon icon="trash" font-scale="1.5"></b-icon> Clear Log</button>
        <button @click="handlePreferences"><b-icon icon="gear" font-scale="1.5"></b-icon> Preferences</button>
      </div>
    </div>
    </b-card-footer>
  </b-card>
</template>

<script>
......(중간생략)......
</script>

......(이하생략)......

메인 화면 보기

메인 화면

환경설정 UI 코드

bootstrap vue의 b-card를 사용하여 적절하게 화면을 구성합니다.

<template>
  <b-card no-body class="h-100 prefs-wrapper">
    <b-tabs card>
      <b-tab active>
        <template v-slot:title>
          <b-icon icon='play' font-scale='1.5'></b-icon> Common
        </template>
        <b-card-body>
          <b-form-group label="Startup Options:">
            <b-form-checkbox name="autostartup" id="autostartup" v-model="preferences.common.autostartup">Auto start program when operating system starts</b-form-checkbox>
            <b-form-checkbox name="autoservice" id="autoservice" v-model="preferences.common.autoservice">Service auto start at program start</b-form-checkbox>
            <b-form-checkbox name="minimizestart" id="minimizestart" v-model="preferences.common.minimizestart">Start with minimize at program start</b-form-checkbox>
          </b-form-group>
          <b-form-group label="Window minimize & close:">
            <b-form-checkbox name="minimizeToTray" id="minimizeToTray" v-model="preferences.common.minimizeToTray">Hide in system tray when minimizing window</b-form-checkbox>
            <b-form-checkbox name="closeToTray" id="closeToTray" v-model="preferences.common.closeToTray">Hide in system tray when closing window</b-form-checkbox>
          </b-form-group>
        </b-card-body>
      </b-tab>
      <b-tab title="TCP Router">
        <template v-slot:title>
          <b-icon icon='cloud' font-scale='1.5'></b-icon> TCP Router
        </template>
        <b-card-group>
          <b-card no-body style="max-width: 250px;">
            <b-card-text class="items overflow-y">
              <div class="item" v-for="(item, index) in preferences.routers"
                :key="index"
                :class="{'selected': selectedIndex == index}"
                @click.prevent.stop="setselectedIndex(index)"
              >
                <div class="name">{{ item.listen }}, {{item.host}}:{{item.port}}</div>
              </div>
            </b-card-text>
          </b-card>
          <b-card no-body>
            <b-card-body>
              <b-row class="my-1">
                <b-col sm="3">Listen</b-col>
                <b-col sm="9"><b-form-input type="number" name="listen" v-model="selectedItem.listen" /></b-col>
              </b-row>
              <b-row class="my-1">
                <b-col sm="3">Host</b-col>
                <b-col sm="9"><b-form-input type="text" name="host" v-model="selectedItem.host" /></b-col>
              </b-row>
              <b-row class="my-1">
                <b-col sm="3">Port</b-col>
                <b-col sm="9"><b-form-input type="number" name="port" v-model="selectedItem.port" /></b-col>
              </b-row>
            </b-card-body>
            <b-card-footer class="text-center">
              <b-row>
                <b-col sm="5" class="text-left">
                  <button class="sml alt" @click="handleNew">New Router</button>
                </b-col>
                <b-col sm="7" class="text-right">
                  <button class="sml alt" @click="handleAdd" v-if="selectedIndex >= 0">Modify</button>
                  <button class="sml alt" @click="handleAdd" v-else>Add</button>
                  <button class="sml alt" @click="handleDelete" v-if="selectedIndex >= 0">Delete</button>
                  <button class="sml" @click="handleReset">Reset</button>
                </b-col>
              </b-row>
            </b-card-footer>
          </b-card>
        </b-card-group>
      </b-tab>
    </b-tabs>
    <b-card-footer class="text-right">
      <button @click="handleSave"><b-icon icon="check-circle" font-scale="1.5"></b-icon> Save</button>
      <button class="alt" @click="handleClose"><b-icon icon="x" font-scale="1.5"></b-icon> Close</button>
    </b-card-footer>
  </b-card>
</template>

<script>
......(중간생략)......
</script>

......(이하생략)......

메인 화면 보기

일반 설정
라우팅 설정

 

Font Awesome 설치하기

아래의 명령을 수행하여 Font Awesome 모듈을 설치합니다.

npm i --save @fortawesome/fontawesome-svg-core
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-brands-svg-icons
npm i --save @fortawesome/vue-fontawesome

/src/rederer/main.js 에 Font Awesome 초기화코드를 추가합니다.

import { library } from '@fortawesome/fontawesome-svg-core'
import { far } from '@fortawesome/free-regular-svg-icons'
import { fas } from '@fortawesome/free-solid-svg-icons'
import { fab } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

library.add(far)
library.add(fas)
library.add(fab)

Vue.component('font-awesome-icon', FontAwesomeIcon)

화면 코드 수정하기

메인 UI 코드메인 화면 보기

<template>
  <b-card no-body class="h-100 main-wrapper" @click="clearActiveItem">
    <b-card-body>
    <div class="row h-100">
      <div class="col-md-6">
        <b-card no-body class="h-100">
          <b-card-header>TCP Router Connections</b-card-header>
          <b-card-body>
            <div class="items overflow-y">
              <div class="item" v-for="(item, key) in tcpRouterState"
                :key="key"
                :class="{'selected': activeItem[key]}"
                @click.prevent.stop="setActiveItem(key)"
              >
                <div class="name"><font-awesome-icon :icon="['fas', 'bezier-curve']" /> {{ item.listen }}, <font-awesome-icon :icon="['fas', 'cloud']" /> {{item.host}}:{{item.port}}</div>
                <div class="value"><font-awesome-icon :icon="['fas', 'link']" /> Connections : {{ item.count }}</div>
                <div class="value"><font-awesome-icon :icon="['fas', 'download']" /> Read : {{ item.bytes.clientReadBytes }} bytes</div>
                <div class="value"><font-awesome-icon :icon="['fas', 'upload']" /> Write : {{ item.bytes.clientWriteBytes }} bytes</div>
              </div>
            </div>
          </b-card-body>
        </b-card>
      </div>

      <div class="col-md-6" v-if="selectedItem && selectedState">
        <b-card no-body class="h-100">
          <b-card-header>Connection Details</b-card-header>
          <b-card-body>
            <div class="details overflow-y">
              <div class="name"><font-awesome-icon :icon="['fas', 'bezier-curve']" /> {{ selectedState.listen }}, <font-awesome-icon :icon="['fas', 'cloud']" /> {{selectedState.host}}:{{selectedState.port}}</div>
              <div class="value"><font-awesome-icon :icon="['fas', 'link']" /> Connections : {{ selectedState.count }}</div>
              <div class="value"><font-awesome-icon :icon="['fas', 'download']" /> Client Read : {{ selectedState.bytes.clientReadBytes }} bytes</div>
              <div class="value"><font-awesome-icon :icon="['fas', 'upload']" /> Client Write : {{ selectedState.bytes.clientWriteBytes }} bytes</div>
              <div class="value"><font-awesome-icon :icon="['fas', 'cloud-download-alt']" /> Server Read : {{ selectedState.bytes.serverReadBytes }} bytes</div>
              <div class="value"><font-awesome-icon :icon="['fas', 'cloud-upload-alt']" /> Server Write : {{ selectedState.bytes.serverWriteBytes }} bytes</div>

              <div class="value" v-for="(detail, index) in selectedItem" :key="index">
                <div class="name"><font-awesome-icon :icon="['fas', 'link']" /> {{ detail.address }} : {{ detail.port }}</div>
                <div class="value"><font-awesome-icon :icon="['fas', 'download']" /> Client Read : {{ detail.clientReadBytes }} bytes / <font-awesome-icon :icon="['fas', 'upload']" /> Client Write : {{ detail.clientWriteBytes }} bytes</div>
                <div class="value"><font-awesome-icon :icon="['fas', 'cloud-download-alt']" /> Server Read : {{ detail.serverReadBytes }} bytes / <font-awesome-icon :icon="['fas', 'cloud-upload-alt']" /> Server Write : {{ detail.serverWriteBytes }} bytes</div>
              </div>
            </div>
          </b-card-body>
        </b-card>
      </div>

      <div class="col-md-6" v-show="!selectedItem">
        <b-card no-body class="h-100">
          <b-card-header>Event Log</b-card-header>
          <b-card-body>
            <div class="logs overflow-y" ref="logs">
              <div class="log" v-for="(log, index) in logs" :key="index">
                <span class="event-date">{{ log.date }}</span> : <span>{{ log.text }}</span>
              </div>
            </div>
          </b-card-body>
        </b-card>
      </div>
    </div>
    </b-card-body>
    <b-card-footer>
    <div class="row">
      <div class="col-md-5 text-left">
        <button class="alt" @click="handleStartup"><b-icon icon="play" font-scale="1.5"></b-icon> Start</button>
        <button @click="handleShutdown"><b-icon icon="stop" font-scale="1.5"></b-icon> Shutdown</button>
      </div>
      <div class="col-md-7 text-right">
        <button class="alt" @click="handleFontawesome"><font-awesome-icon :icon="['fab', 'font-awesome']" size="lg" /></button>
        <button class="alt" @click="handleClearLog"><b-icon icon="trash" font-scale="1.5"></b-icon> Clear Log</button>
        <button @click="handlePreferences"><b-icon icon="gear" font-scale="1.5"></b-icon> Preferences</button>
      </div>
    </div>
    </b-card-footer>
  </b-card>
</template>

<script>
......(중간생략)......
export default {
......(중간생략)......
}
</script>

......(이하생략)......

환경설정 UI 코드메인 화면 보기

Bootstrap Vue + Font Awesome

 

참고 자료

 

소스코드

본 포스트 관련 소스코드는 여기에서 다운로드 가능합니다.

 

728x90