first
This commit is contained in:
103
inspond-nuxt-safekiso/pages/admin/dashboard/index.vue
Normal file
103
inspond-nuxt-safekiso/pages/admin/dashboard/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div>
|
||||
<div class="bg-gray-50 pt-12 sm:pt-16">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<h2
|
||||
class="text-3xl font-extrabold text-gray-900 sm:text-4xl"
|
||||
>
|
||||
어드민 대시보드
|
||||
</h2>
|
||||
<p class="mt-3 text-xl text-gray-500 sm:mt-4">
|
||||
서비스 현황 개괄이 표시될 페이지 입니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-10 pb-12 bg-white sm:pb-16">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 h-1/2 bg-gray-50" />
|
||||
<div
|
||||
class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
||||
>
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<dl
|
||||
class="rounded-lg bg-white shadow-lg sm:grid sm:grid-cols-3"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col border-b border-gray-100 p-6 text-center sm:border-0 sm:border-r"
|
||||
>
|
||||
<dt
|
||||
class="order-2 mt-2 text-lg leading-6 font-medium text-gray-500"
|
||||
>
|
||||
word watched
|
||||
</dt>
|
||||
<dd
|
||||
class="order-1 text-5xl font-extrabold text-indigo-600"
|
||||
>
|
||||
{{ formattedCount }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col border-t border-b border-gray-100 p-6 text-center sm:border-0 sm:border-l sm:border-r"
|
||||
>
|
||||
<dt
|
||||
class="order-2 mt-2 text-lg leading-6 font-medium text-gray-500"
|
||||
>
|
||||
hit count
|
||||
</dt>
|
||||
<dd
|
||||
class="order-1 text-5xl font-extrabold text-indigo-600"
|
||||
>
|
||||
{{ formattedHitCount }}
|
||||
</dd>
|
||||
</div>
|
||||
<div
|
||||
class="flex flex-col border-t border-gray-100 p-6 text-center sm:border-0 sm:border-l"
|
||||
>
|
||||
<dt
|
||||
class="order-2 mt-2 text-lg leading-6 font-medium text-gray-500"
|
||||
>
|
||||
avg response time
|
||||
</dt>
|
||||
<dd
|
||||
class="order-1 text-5xl font-extrabold text-indigo-600"
|
||||
>
|
||||
{{ elapsedAvg.toFixed(2) }}ms
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const wordCount = ref(0);
|
||||
const hitCount = ref(0);
|
||||
const elapsedAvg = ref(0);
|
||||
|
||||
const formattedCount = computed(() => {
|
||||
return _utils.formatNumberWithComma(wordCount.value);
|
||||
});
|
||||
|
||||
const formattedHitCount = computed(() => {
|
||||
return _utils.formatNumberWithComma(hitCount.value);
|
||||
});
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'dashboard', {});
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
wordCount.value = responseJson['data']['wordCount'];
|
||||
hitCount.value = responseJson['data']['hitCount'];
|
||||
elapsedAvg.value = responseJson['data']['elapsedAvg'] * 1000;
|
||||
}
|
||||
</script>
|
||||
218
inspond-nuxt-safekiso/pages/admin/filter/index.vue
Normal file
218
inspond-nuxt-safekiso/pages/admin/filter/index.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<!--
|
||||
This example requires Tailwind CSS v2.0+
|
||||
|
||||
This example requires some changes to your config:
|
||||
|
||||
```
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
// ...
|
||||
plugins: [
|
||||
// ...
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
```
|
||||
-->
|
||||
<template>
|
||||
<div class="m-8">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doFilter"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터 테스트
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
필터에 문장을 넣어 테스트 합니다.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="doSyncFromFile"
|
||||
>
|
||||
DB 초기화
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="$router.push('/admin/filter/word/list')"
|
||||
>
|
||||
표현 관리
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
검사하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 sm:mt-5 space-y-6 sm:space-y-5">
|
||||
<div>
|
||||
<fieldset class="mt-4">
|
||||
<legend class="sr-only">Filter Mode</legend>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="notificationMethod in notificationMethods"
|
||||
:key="notificationMethod.id"
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
:id="notificationMethod.id"
|
||||
v-model="filterMode"
|
||||
:value="notificationMethod.id"
|
||||
name="notification-method"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
:for="notificationMethod.id"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{{ notificationMethod.title }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="filterSource"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
검사할 문장 입력
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<textarea
|
||||
id="filterSource"
|
||||
v-model="filterSource"
|
||||
name="filterSource"
|
||||
rows="3"
|
||||
class="max-w-lg shadow-sm block w-full focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md"
|
||||
/>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
필터 테스트를 위한 문장을 입력하고 아래
|
||||
검사하기 버튼을 누르세요. <br />
|
||||
(브라우저에서 측정한 실행시간 :
|
||||
{{ elispe.toFixed(2) }} 밀리초. 네트워크
|
||||
전송 지연 포함.)
|
||||
</p>
|
||||
<pre>{{ resultJson }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const notificationMethods = [
|
||||
{
|
||||
id: 'quick',
|
||||
title: 'quick. 첫번째 매칭이 발견되면 더 이상 검사하지 않고 바로 결과를 리턴',
|
||||
},
|
||||
{ id: 'normal', title: 'normal. 전체를 검사하여 모든 매칭을 리턴' },
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'filter. 전체를 검사하며 모든 매칭을 마스크로 치환한 결과까지 리턴',
|
||||
},
|
||||
];
|
||||
|
||||
const filterMode = ref('filter');
|
||||
|
||||
const filterSource = ref('테스트 할 문장을 이곳에 넣어 주세요.');
|
||||
|
||||
const resultJson = ref({});
|
||||
|
||||
const elispe = ref(0);
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
async function doFilter() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
inPregressFlag.value = true;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
const responseJson = await _crossCtl.doFilter('', {
|
||||
text: filterSource.value,
|
||||
mode: filterMode.value,
|
||||
});
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
inPregressFlag.value = false;
|
||||
|
||||
elispe.value = endTime - startTime;
|
||||
|
||||
// console.log('responseJson=', responseJson);
|
||||
const result = responseJson;
|
||||
resultJson.value = result;
|
||||
|
||||
if (responseJson['Status']['Code'] == 2000) {
|
||||
// alert(result.length + ' match found');
|
||||
} else {
|
||||
alert(responseJson['Status']['Message']);
|
||||
}
|
||||
}
|
||||
|
||||
async function doSyncFromFile() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
window.confirm(
|
||||
'이 기능은 DB에 저장된 모든 필터 단어를 삭제하고 원본 소스 파일의 내용으로 초기화 합니다. 실행하시겠습니까? (최소한 수십초가 소요되며 완료되면 ok라는 메세지가 출력됩니다.)'
|
||||
)
|
||||
) {
|
||||
inPregressFlag.value = true;
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/update',
|
||||
'filter',
|
||||
{}
|
||||
);
|
||||
inPregressFlag.value = false;
|
||||
|
||||
// console.log('huk filter update result = ', responseJson);
|
||||
|
||||
alert(
|
||||
responseJson['responseMessage'] +
|
||||
' with ' +
|
||||
_utils.formatNumberInBytesStyle(
|
||||
parseInt(responseJson['wordCount']),
|
||||
2
|
||||
) +
|
||||
' words'
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
342
inspond-nuxt-safekiso/pages/admin/filter/word/[hero]/edit.vue
Normal file
342
inspond-nuxt-safekiso/pages/admin/filter/word/[hero]/edit.vue
Normal file
@@ -0,0 +1,342 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="divide-y divide-gray-200">
|
||||
<form class="lg:col-span-9">
|
||||
<!-- Profile section -->
|
||||
<div class="py-6 px-4 sm:p-6 lg:pb-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터 단어 정보 확인, 변경
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
필터 단어의 세부 내용을 확인하고 수정합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="gotoAdminKeyLog()"
|
||||
>
|
||||
로그
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 sm:col-span-6">
|
||||
<label
|
||||
for="by"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>등록자</label
|
||||
>
|
||||
<input
|
||||
id="by"
|
||||
v-model="by"
|
||||
disabled
|
||||
type="text"
|
||||
name="by"
|
||||
autocomplete="by"
|
||||
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-sky-500 focus:border-sky-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 sm:col-span-6"></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-col lg:flex-row">
|
||||
<div class="flex-grow space-y-6">
|
||||
<div>
|
||||
<label
|
||||
for="raw"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
모욕적인 표현
|
||||
</label>
|
||||
<div class="mt-1 rounded-md shadow-sm flex">
|
||||
<input
|
||||
id="raw"
|
||||
v-model="raw"
|
||||
disabled
|
||||
type="text"
|
||||
name="raw"
|
||||
autocomplete="raw"
|
||||
class="focus:ring-sky-500 focus:border-sky-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p
|
||||
class="text-sm text-gray-500"
|
||||
>
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대
|
||||
필터만 동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="10"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="50"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="100"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="memo"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
메모
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<textarea
|
||||
id="memo"
|
||||
v-model="memo"
|
||||
name="memo"
|
||||
rows="3"
|
||||
class="shadow-sm focus:ring-sky-500 focus:border-sky-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Privacy section -->
|
||||
<div class="pt-0">
|
||||
<div class="mt-4 py-4 px-4 flex justify-between sm:px-6">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-red-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
@click="doDelete"
|
||||
>
|
||||
{{ status == 0 ? '삭제' : '복구' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-5 bg-sky-700 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-sky-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500"
|
||||
@click="doUpdateInfo"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const hero = route.params.hero;
|
||||
|
||||
function gotoAdminKeyLog() {
|
||||
navigateTo('/admin/word/' + hero + '/log');
|
||||
}
|
||||
|
||||
const by = ref('');
|
||||
const raw = ref('');
|
||||
const level = ref('');
|
||||
const memo = ref('');
|
||||
const status = ref(0);
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'word', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
console.log(responseJson['data']);
|
||||
raw.value = responseJson['data'][0]['raw'];
|
||||
level.value = responseJson['data'][0]['level'];
|
||||
memo.value = responseJson['data'][0]['memo'];
|
||||
by.value = responseJson['data'][0]['by'];
|
||||
status.value = responseJson['data'][0]['status'];
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
|
||||
async function doUpdateInfo() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
inPregressFlag.value = true;
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'word', {
|
||||
hero: hero,
|
||||
raw: raw.value,
|
||||
level: level.value,
|
||||
memo: memo.value,
|
||||
status: status.value,
|
||||
});
|
||||
inPregressFlag.value = false;
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
|
||||
async function doCancel() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
async function doDelete() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
window.confirm(
|
||||
status.value == 0
|
||||
? '이 표현을 삭제하시겠습니까?'
|
||||
: '이 표현을 복구하시겠습니까?'
|
||||
)
|
||||
) {
|
||||
inPregressFlag.value = true;
|
||||
if (status.value == 0) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/delete',
|
||||
'word',
|
||||
{
|
||||
hero: hero,
|
||||
raw: raw.value,
|
||||
}
|
||||
);
|
||||
inPregressFlag.value = false;
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 4;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
status.value = 0;
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/update',
|
||||
'word',
|
||||
{
|
||||
hero: hero,
|
||||
raw: raw.value,
|
||||
level: level.value,
|
||||
memo: memo.value,
|
||||
status: 0,
|
||||
revive: true,
|
||||
}
|
||||
);
|
||||
inPregressFlag.value = false;
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 0;
|
||||
alert('ok');
|
||||
|
||||
// router.back();
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
328
inspond-nuxt-safekiso/pages/admin/filter/word/editbak.vue
Normal file
328
inspond-nuxt-safekiso/pages/admin/filter/word/editbak.vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="divide-y divide-gray-200">
|
||||
<form class="lg:col-span-9">
|
||||
<!-- Profile section -->
|
||||
<div class="py-6 px-4 sm:p-6 lg:pb-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터 단어 정보 확인, 변경
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
필터 단어의 세부 내용을 확인하고 수정합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 grid grid-cols-12 gap-6">
|
||||
<div class="col-span-12 sm:col-span-6">
|
||||
<label
|
||||
for="by"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>등록자</label
|
||||
>
|
||||
<input
|
||||
id="by"
|
||||
v-model="by"
|
||||
disabled
|
||||
type="text"
|
||||
name="by"
|
||||
autocomplete="by"
|
||||
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-sky-500 focus:border-sky-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 sm:col-span-6"></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-col lg:flex-row">
|
||||
<div class="flex-grow space-y-6">
|
||||
<div>
|
||||
<label
|
||||
for="raw"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
모욕적인 표현
|
||||
</label>
|
||||
<div class="mt-1 rounded-md shadow-sm flex">
|
||||
<input
|
||||
id="raw"
|
||||
v-model="raw"
|
||||
disabled
|
||||
type="text"
|
||||
name="raw"
|
||||
autocomplete="raw"
|
||||
class="focus:ring-sky-500 focus:border-sky-500 flex-grow block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p
|
||||
class="text-sm text-gray-500"
|
||||
>
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대
|
||||
필터만 동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="10"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="50"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="100"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="memo"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
메모
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<textarea
|
||||
id="memo"
|
||||
v-model="memo"
|
||||
name="memo"
|
||||
rows="3"
|
||||
class="shadow-sm focus:ring-sky-500 focus:border-sky-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Privacy section -->
|
||||
<div class="pt-0">
|
||||
<div class="mt-4 py-4 px-4 flex justify-between sm:px-6">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-red-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
@click="doDelete"
|
||||
>
|
||||
{{ status == 0 ? '삭제' : '복구' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white border border-gray-300 rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-5 bg-sky-700 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-sky-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500"
|
||||
@click="doUpdateInfo"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const hero = route.params.target;
|
||||
const by = ref('');
|
||||
const raw = ref('');
|
||||
const level = ref('');
|
||||
const memo = ref('');
|
||||
const status = ref(0);
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'word', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
console.log(responseJson['data']);
|
||||
raw.value = responseJson['data'][0]['raw'];
|
||||
level.value = responseJson['data'][0]['level'];
|
||||
memo.value = responseJson['data'][0]['memo'];
|
||||
by.value = responseJson['data'][0]['by'];
|
||||
status.value = responseJson['data'][0]['status'];
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
|
||||
async function doUpdateInfo() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
inPregressFlag.value = true;
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'word', {
|
||||
hero: hero,
|
||||
raw: raw.value,
|
||||
level: level.value,
|
||||
memo: memo.value,
|
||||
status: status.value,
|
||||
});
|
||||
inPregressFlag.value = false;
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
|
||||
async function doCancel() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
async function doDelete() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
window.confirm(
|
||||
status.value == 0
|
||||
? '이 표현을 삭제하시겠습니까?'
|
||||
: '이 표현을 복구하시겠습니까?'
|
||||
)
|
||||
) {
|
||||
inPregressFlag.value = true;
|
||||
if (status.value == 0) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/delete',
|
||||
'word',
|
||||
{
|
||||
hero: hero,
|
||||
raw: raw.value,
|
||||
}
|
||||
);
|
||||
inPregressFlag.value = false;
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 4;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
status.value = 0;
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/update',
|
||||
'word',
|
||||
{
|
||||
hero: hero,
|
||||
raw: raw.value,
|
||||
level: level.value,
|
||||
memo: memo.value,
|
||||
status: 0,
|
||||
revive: true,
|
||||
}
|
||||
);
|
||||
inPregressFlag.value = false;
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 0;
|
||||
alert('ok');
|
||||
|
||||
// router.back();
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
467
inspond-nuxt-safekiso/pages/admin/filter/word/list.vue
Normal file
467
inspond-nuxt-safekiso/pages/admin/filter/word/list.vue
Normal file
@@ -0,0 +1,467 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<br />
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
{{ pageTitle }}
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
전체 등록 필터 단어 리스트를 확인할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
v-for="(headingAction, index) in headingActions"
|
||||
:key="headingAction"
|
||||
type="button"
|
||||
:class="index > 0 ? 'ml-3' : ''"
|
||||
class="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-3 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
@click="doHeadingAction(headingAction)"
|
||||
>
|
||||
{{ headingAction }}
|
||||
</button>
|
||||
<div class="ml-3">
|
||||
<select
|
||||
id="targetLevel"
|
||||
v-model="targetLevel"
|
||||
name="targetLevel"
|
||||
class="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
|
||||
@change="onChangeLevel($event)"
|
||||
>
|
||||
<option>all</option>
|
||||
<option>high</option>
|
||||
<option>mid</option>
|
||||
<option>low</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<label for="mobile-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<label for="desktop-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<div class="flex rounded-md shadow-sm">
|
||||
<div class="relative flex-grow focus-within:z-10">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
||||
>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="22"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<input
|
||||
id="mobile-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="mobile-search-candidate"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 pr-10 sm:hidden border-gray-300"
|
||||
placeholder=""
|
||||
@keydown.enter.prevent="onEnterHandler()"
|
||||
/>
|
||||
<input
|
||||
id="desktop-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="desktop-search-candidate"
|
||||
class="hidden focus:ring-indigo-500 focus:border-indigo-500 w-full rounded-none rounded-l-md pl-10 pr-10 sm:block sm:text-sm border-gray-300"
|
||||
placeholder=""
|
||||
@keydown.enter.prevent="onEnterHandler()"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="absolute inset-y-0 right-0 pr-3 lr-3 flex items-center"
|
||||
>
|
||||
<a href="javascript:void(0)" @click="clearSearch()">
|
||||
<XCircleIcon
|
||||
class="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
@click="doAction('search', searchKeyword)"
|
||||
>
|
||||
<span class="ml-2">검색</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3"></div>
|
||||
<div v-if="searchKeyword != ''">
|
||||
검색 대상 단어 : {{ searchKeyword }}
|
||||
<div class="text-red-600 text-sm">
|
||||
{{
|
||||
searchKeyword.trim() != searchKeyword
|
||||
? '주의! 검색어 앞이나 뒤에 공백이 포함되어 있습니다!'
|
||||
: ''
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3"></div>
|
||||
<div v-if="searchKeyword != ''">정확한 검색 결과</div>
|
||||
<BaseList1
|
||||
v-if="searchKeyword != ''"
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="exactMatchs"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<div class="mt-3"></div>
|
||||
<div v-if="searchKeyword != ''">유사한 검색 결과</div>
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
MagnifyingGlassCircleIcon,
|
||||
XCircleIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
const listMode = ref(route.query.mode ? route.query.mode : '');
|
||||
|
||||
const pageTitle = ref(
|
||||
listMode.value == 'trashcan'
|
||||
? '필터 단어 - 삭제 단어 리스트'
|
||||
: '필터 단어 - 리스트'
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const headingActions = ['표현 추가', '리스트 모드'];
|
||||
|
||||
let listTarget = 'admin:word:all';
|
||||
let hero = '';
|
||||
|
||||
const searchKeyword = ref('');
|
||||
|
||||
const targetLevel = ref('all');
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '일련번호',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'serial',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
|
||||
{
|
||||
title: '구분',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'tag',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '단어',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'raw',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '레벨',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'level',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: '상태',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'status',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
{
|
||||
title: '수정일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = ['상세보기'];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'raw',
|
||||
'level',
|
||||
'comment',
|
||||
'status',
|
||||
'updated',
|
||||
'created',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const exactMatchs = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
|
||||
/*
|
||||
if (_crossCtl.volatilePool['/admin/filter/word/list:hero'] != undefined) {
|
||||
console.log('got cache');
|
||||
hero = _crossCtl.volatilePool['/admin/filter/word/list:hero'];
|
||||
searchKeyword.value = hero;
|
||||
listTarget = 'admin:word:like';
|
||||
_crossCtl.volatilePool['/admin/filter/word/list:hero'] = undefined;
|
||||
} else {
|
||||
console.log('no cache');
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
_crossCtl.volatilePool['/admin/filter/word/list:currentPageNumber'] !=
|
||||
undefined
|
||||
) {
|
||||
console.log('got page cache');
|
||||
currentPageNumber.value =
|
||||
_crossCtl.volatilePool['/admin/filter/word/list:currentPageNumber'];
|
||||
|
||||
_crossCtl.volatilePool['/admin/filter/word/list:currentPageNumber'] =
|
||||
undefined;
|
||||
} else {
|
||||
console.log('no page cache');
|
||||
}
|
||||
*/
|
||||
|
||||
async function doHeadingAction(tag) {
|
||||
console.log('on doHeadingAction(), tag=', tag);
|
||||
|
||||
switch (tag) {
|
||||
case '표현 추가':
|
||||
navigateTo('/admin/filter/word/new');
|
||||
break;
|
||||
case '리스트 모드':
|
||||
console.log('listMode.value=', '[' + listMode.value + ']');
|
||||
if (listMode.value == 'trashcan') {
|
||||
console.log('huk 1');
|
||||
pageTitle.value = '필터 단어 - 리스트';
|
||||
await navigateTo('/admin/filter/word/list', { replace: true });
|
||||
listMode.value = '';
|
||||
} else {
|
||||
console.log('huk 2');
|
||||
pageTitle.value = '필터 단어 - 삭제 리스트';
|
||||
await navigateTo('/admin/filter/word/list?mode=trashcan', {
|
||||
replace: true,
|
||||
});
|
||||
listMode.value = 'trashcan';
|
||||
}
|
||||
pageMove(1);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
alert('unhandled heading action. tag = ' + tag);
|
||||
}
|
||||
|
||||
// alert('headingAction : ' + tag);
|
||||
}
|
||||
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log("columnFilter(), key = ", key, ", val = ", val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
// return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'raw') {
|
||||
return '[' + val + ']';
|
||||
} else if (key == 'status') {
|
||||
let statusTag = '정상';
|
||||
switch (val) {
|
||||
case 0:
|
||||
statusTag = '정상등록';
|
||||
break;
|
||||
case 4:
|
||||
statusTag = '삭제됨';
|
||||
break;
|
||||
default:
|
||||
statusTag = val;
|
||||
}
|
||||
return statusTag;
|
||||
} else if (key == 'level') {
|
||||
let levelTag = 'mid';
|
||||
switch (val) {
|
||||
case 10:
|
||||
levelTag = 'high';
|
||||
break;
|
||||
case 50:
|
||||
levelTag = 'mid';
|
||||
break;
|
||||
case 100:
|
||||
levelTag = 'low';
|
||||
break;
|
||||
|
||||
default:
|
||||
levelTag = val;
|
||||
}
|
||||
return levelTag;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
|
||||
if (tag == '상세보기') {
|
||||
navigateTo('/admin/filter/word/' + target + '/edit');
|
||||
} else if (tag == 'search') {
|
||||
console.log('search for ', target);
|
||||
if (target == '') {
|
||||
listTarget = 'admin:word:all';
|
||||
} else {
|
||||
listTarget = 'admin:word:like';
|
||||
hero = target;
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (process.client) {
|
||||
inPregressFlag.value = true;
|
||||
const tmpListTarget =
|
||||
listTarget + (listMode.value == 'trashcan' ? ':deleted' : '');
|
||||
console.log('tmpListTarget=', tmpListTarget);
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
tmpListTarget,
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: hero,
|
||||
targetLevel: targetLevel.value,
|
||||
}
|
||||
);
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
|
||||
inPregressFlag.value = false;
|
||||
|
||||
if (responseJson['responseCode'] == 200) {
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
if (responseJson['metaData'] != null) {
|
||||
exactMatchs.value = responseJson['metaData']['exactMatchs'];
|
||||
}
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onChangeLevel(e) {
|
||||
console.log('targetLevel.value=', targetLevel.value);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onEnterHandler() {
|
||||
currentPageNumber.value = 1;
|
||||
doAction('search', searchKeyword.value);
|
||||
}
|
||||
|
||||
function clearSearch() {
|
||||
exactMatchs.value = [];
|
||||
currentPageNumber.value = 1;
|
||||
searchKeyword.value = '';
|
||||
doAction('search', searchKeyword.value);
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
257
inspond-nuxt-safekiso/pages/admin/filter/word/new.vue
Normal file
257
inspond-nuxt-safekiso/pages/admin/filter/word/new.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<!--
|
||||
This example requires Tailwind CSS v2.0+
|
||||
|
||||
This example requires some changes to your config:
|
||||
|
||||
```
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
// ...
|
||||
plugins: [
|
||||
// ...
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
```
|
||||
-->
|
||||
<template>
|
||||
<div class="m-8">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doCreate"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div
|
||||
class="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5"
|
||||
>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
새 욕설·비속어 등록
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
필터 대상이 될 욕설·비속어을 추가로 등록 합니다.
|
||||
현재는 개발 테스트용으로 단어 하나 추가 후
|
||||
완료까지 시간이 오래 걸립니다. 단어 추가 후 바로
|
||||
수정된 내용을 연속으로 추가할 수 있습니다.
|
||||
작업을 마치고 나면 이전 화면으로 돌아가는 버튼을
|
||||
누르세요.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="raw"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
표현
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="raw"
|
||||
v-model="raw"
|
||||
type="text"
|
||||
name="raw"
|
||||
autocomplete="raw"
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="memo"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
메모
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<textarea
|
||||
id="memo"
|
||||
v-model="memo"
|
||||
name="memo"
|
||||
rows="3"
|
||||
class="shadow-sm focus:ring-sky-500 focus:border-sky-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p class="text-sm text-gray-500">
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대 필터만
|
||||
동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="10"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="50"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="100"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
이전 화면으로 이동
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
만들기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const raw = ref('');
|
||||
const memo = ref('');
|
||||
const level = ref(10);
|
||||
|
||||
async function doCancel() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
router.back();
|
||||
}
|
||||
|
||||
watch(level, (newValue, oldValue) => {
|
||||
console.log('level 의 변이가 감지되었을 때 ', {
|
||||
newValue,
|
||||
oldValue,
|
||||
});
|
||||
});
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
async function doCreate() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (raw.value == '') {
|
||||
alert('빈칸은 입력하실 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
inPregressFlag.value = true;
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/insert', 'word', {
|
||||
raw: raw.value,
|
||||
memo: memo.value,
|
||||
level: level.value,
|
||||
});
|
||||
inPregressFlag.value = false;
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
} else {
|
||||
if (responseJson['responseMessage'] == 'ER_DUP_ENTRY') {
|
||||
alert(
|
||||
'오류 : 이미 같은 단어가 등록되어 있습니다. 삭제된 상태의 단어를 다시 등록하시려는 경우에는 복구 기능을 이용해 주세요. '
|
||||
);
|
||||
} else {
|
||||
alert('오류 : ' + responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
318
inspond-nuxt-safekiso/pages/admin/key/[hero]/delet/[_term].vue
Normal file
318
inspond-nuxt-safekiso/pages/admin/key/[hero]/delet/[_term].vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용량 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 사용량 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<label
|
||||
for="location"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>API Key</label
|
||||
>
|
||||
<select
|
||||
id="targetKey"
|
||||
v-model="targetKey"
|
||||
name="targetKey"
|
||||
class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="item in keys"
|
||||
:key="item.key"
|
||||
:selected="item.current"
|
||||
:value="item.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import HeroVue from '~~/base/pages/support/inquiry/view/[hero].vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'total',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit_ratio',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit_ratio',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'size',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'size_avg',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size_avg',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'ip',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_ip',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'referrer',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_referrer',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
const { $dayjs } = useNuxtApp();
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'admin:statistics',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: targetKey.value,
|
||||
term: _term,
|
||||
termPrefix: '2022',
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const targetKey = ref('');
|
||||
|
||||
const keys = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const _term = route.params._term;
|
||||
|
||||
console.log('hero = ', hero);
|
||||
console.log('_term = ', _term);
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'admin:key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 0) {
|
||||
_crossCtl.openModal(
|
||||
'error',
|
||||
'잘못된 파라메타',
|
||||
'키 정보를 읽어올 수 없습니다.\n확인 버튼을 누르면 메인 화면으로 돌아갑니다.',
|
||||
['확인'],
|
||||
(btnIdx) => {
|
||||
navigateTo('/');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const tmpKeys = [];
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
if (i == 0) {
|
||||
targetKey.value = responseJson['data'][i]['api_key'];
|
||||
}
|
||||
|
||||
tmpKeys.push({
|
||||
current: i == 0,
|
||||
name: responseJson['data'][i]['name'],
|
||||
key: responseJson['data'][i]['api_key'],
|
||||
});
|
||||
}
|
||||
keys.value = tmpKeys;
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetKey.value=', targetKey.value);
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
439
inspond-nuxt-safekiso/pages/admin/key/[hero]/delet/index.vue
Normal file
439
inspond-nuxt-safekiso/pages/admin/key/[hero]/delet/index.vue
Normal file
@@ -0,0 +1,439 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용량 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 사용량 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<select
|
||||
id="targetTerm"
|
||||
v-model="targetTerm"
|
||||
name="targetTerm"
|
||||
class="mt-0 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="term in terms"
|
||||
:key="term.key"
|
||||
:selected="term.current"
|
||||
:value="term.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ term.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'month'"
|
||||
v-model="targetDateMonth"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:year-picker="true"
|
||||
:format="formatForMonth"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'day'"
|
||||
v-model="targetDateDay"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:month-picker="true"
|
||||
:format="formatForDay"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'hour'"
|
||||
v-model="targetDateHour"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:format="formatForHour"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateDay = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateHour = ref(
|
||||
$dayjs(new Date().toISOString()).format('DD/MM/YYYY')
|
||||
);
|
||||
|
||||
// console.log('huk = ', targetDate.value);
|
||||
const formatForMonth = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${year}`;
|
||||
};
|
||||
|
||||
const formatForDay = (date) => {
|
||||
console.log('huk format date = ', date);
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${date.year}${(date.month + 1 < 10 ? '0' : '') + (date.month + 1)}`;
|
||||
};
|
||||
|
||||
const formatForHour = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
const hour = date.getHours();
|
||||
|
||||
return `${year}${(month < 10 ? '0' : '') + month}${
|
||||
(day < 10 ? '0' : '') + day
|
||||
}`;
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'total',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit_ratio',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit_ratio',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'size',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'size_avg',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size_avg',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'ip',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_ip',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'referrer',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_referrer',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'admin:statistics',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: targetKey.value,
|
||||
term: targetTerm.value,
|
||||
// termPrefix: _utils.getDateTimeTag(targetTerm.value.substring(0, 1)),
|
||||
// termPrefix: _utils.getDateTimeTag('y'),
|
||||
termPrefix: targetDate.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const targetTerm = ref('year');
|
||||
const targetKey = ref('');
|
||||
|
||||
const tmpTerms = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
terms.value = tmpTerms;
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'admin:key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 0) {
|
||||
_crossCtl.openModal(
|
||||
'error',
|
||||
'잘못된 파라메타',
|
||||
'키 정보를 읽어올 수 없습니다.\n확인 버튼을 누르면 메인 화면으로 돌아갑니다.',
|
||||
['확인'],
|
||||
(btnIdx) => {
|
||||
navigateTo('/');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
targetKey.value = responseJson['data'][0]['api_key'];
|
||||
}
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
|
||||
let result = date;
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
result = `${date.year}${
|
||||
(date.month + 1 < 10 ? '0' : '') + (date.month + 1)
|
||||
}`;
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
result = $dayjs(date).format('YYYYMMDD');
|
||||
break;
|
||||
}
|
||||
|
||||
targetDate.value = result;
|
||||
|
||||
console.log('huk result = ', result);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMM'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMMDD'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
terms.value = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
327
inspond-nuxt-safekiso/pages/admin/key/[hero]/edit.vue
Normal file
327
inspond-nuxt-safekiso/pages/admin/key/[hero]/edit.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<div class="m-8">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doUpdate"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div
|
||||
class="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5"
|
||||
>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 키 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API키의 설정을 변경하거나 삭제할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="mr-2 inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="
|
||||
navigateTo(
|
||||
'/admin/statistics/byterm/word?key=' +
|
||||
apiKey
|
||||
)
|
||||
"
|
||||
>
|
||||
새 단어 통계
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="mr-2 inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="
|
||||
navigateTo(
|
||||
'/admin/statistics/byterm/usage?key=' +
|
||||
apiKey
|
||||
)
|
||||
"
|
||||
>
|
||||
새 사용 통계
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="gotoAdminKeyLog()"
|
||||
>
|
||||
로그
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="gotoAdminStatistics()"
|
||||
>
|
||||
사용량 통계
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="gotoAdminWordStatistics()"
|
||||
>
|
||||
단어 통계
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
이름
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
name="name"
|
||||
autocomplete="name"
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="api-key"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
API 키
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="api-key"
|
||||
v-model="apiKey"
|
||||
type="text"
|
||||
name="api-key"
|
||||
autocomplete="api-key"
|
||||
disabled
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p class="text-sm text-gray-500">
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대 필터만
|
||||
동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="high"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="mid"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="low"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-red-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
@click="doDelete"
|
||||
>
|
||||
{{ status == 0 ? '삭제' : '복구' }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
이전화면
|
||||
</button>
|
||||
<button
|
||||
v-if="status == 0"
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const hero = route.params.hero;
|
||||
|
||||
function gotoAdminKeyLog() {
|
||||
navigateTo('/admin/key/' + hero + '/log');
|
||||
}
|
||||
|
||||
function gotoAdminStatistics() {
|
||||
navigateTo('/admin/key/' + hero + '/statistics');
|
||||
}
|
||||
|
||||
function gotoAdminWordStatistics() {
|
||||
navigateTo('/admin/word/' + hero + '/statistics');
|
||||
}
|
||||
|
||||
console.log('hero=', hero);
|
||||
|
||||
const apiKey = ref('');
|
||||
const name = ref('');
|
||||
const level = ref('');
|
||||
const status = ref(0);
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'admin:key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
const keyInfo = responseJson['data'][0];
|
||||
|
||||
apiKey.value = keyInfo['api_key'];
|
||||
name.value = keyInfo['name'];
|
||||
level.value = keyInfo['level'];
|
||||
status.value = keyInfo['status'];
|
||||
|
||||
async function doUpdate() {
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'admin:key', {
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
status: status.value,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
|
||||
async function doDelete() {
|
||||
if (status.value == 0) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/delete',
|
||||
'admin:key',
|
||||
{
|
||||
hero: hero,
|
||||
}
|
||||
);
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 4;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/update',
|
||||
'admin:key',
|
||||
{
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
revive: true,
|
||||
status: 0,
|
||||
}
|
||||
);
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 0;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function doCancel() {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
262
inspond-nuxt-safekiso/pages/admin/key/[hero]/log.vue
Normal file
262
inspond-nuxt-safekiso/pages/admin/key/[hero]/log.vue
Normal file
@@ -0,0 +1,262 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<br />
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API KEY 로그 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API KEY의 생성, 변경, 삭제 기록을 확인할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<label for="mobile-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<label for="desktop-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<div class="flex rounded-md shadow-sm">
|
||||
<div class="relative flex-grow focus-within:z-10">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
||||
>
|
||||
<MagnifyingGlassCircleIcon
|
||||
class="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
id="mobile-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="mobile-search-candidate"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:hidden border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
<input
|
||||
id="desktop-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="desktop-search-candidate"
|
||||
class="hidden focus:ring-indigo-500 focus:border-indigo-500 w-full rounded-none rounded-l-md pl-10 sm:block sm:text-sm border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
@click="doAction('search', searchKeyword)"
|
||||
>
|
||||
<span class="ml-2">검색</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
MagnifyingGlassCircleIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import consolaGlobalInstance from 'consola';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
console.log('route.params=', route.params);
|
||||
|
||||
const targetName = route.params.hero;
|
||||
const hero = route.params.hero;
|
||||
|
||||
console.log('targetName=', targetName);
|
||||
console.log('hero=', hero);
|
||||
|
||||
const listTarget = 'admin:log:key';
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '누가',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'name',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: '언제',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'created',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '무엇을',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'target_key',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: '어떻게',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'tag',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'raw',
|
||||
'level',
|
||||
'comment',
|
||||
'status',
|
||||
'updated',
|
||||
'created',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log("columnFilter(), key = ", key, ", val = ", val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'uid') {
|
||||
return '' + targetName + '';
|
||||
} else if (key == 'status') {
|
||||
let statusTag = '정상';
|
||||
switch (val) {
|
||||
case 0:
|
||||
statusTag = '정상등록';
|
||||
break;
|
||||
case 4:
|
||||
statusTag = '삭제됨';
|
||||
break;
|
||||
default:
|
||||
statusTag = val;
|
||||
}
|
||||
return statusTag;
|
||||
} else if (key == 'level') {
|
||||
let levelTag = 'mid';
|
||||
switch (val) {
|
||||
case 10:
|
||||
levelTag = 'high';
|
||||
break;
|
||||
case 50:
|
||||
levelTag = 'mid';
|
||||
break;
|
||||
case 100:
|
||||
levelTag = 'low';
|
||||
break;
|
||||
|
||||
default:
|
||||
levelTag = val;
|
||||
}
|
||||
return levelTag;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
|
||||
if (tag == '상세보기') {
|
||||
navigateTo('/admin/user/' + hero + '/history/detail/' + target);
|
||||
}
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm('local/list', listTarget, {
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
console.log('listTarget=', listTarget);
|
||||
console.log('hero=', hero);
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
439
inspond-nuxt-safekiso/pages/admin/key/[hero]/statistics.vue
Normal file
439
inspond-nuxt-safekiso/pages/admin/key/[hero]/statistics.vue
Normal file
@@ -0,0 +1,439 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용량 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 사용량 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<select
|
||||
id="targetTerm"
|
||||
v-model="targetTerm"
|
||||
name="targetTerm"
|
||||
class="mt-0 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="term in terms"
|
||||
:key="term.key"
|
||||
:selected="term.current"
|
||||
:value="term.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ term.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'month'"
|
||||
v-model="targetDateMonth"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:year-picker="true"
|
||||
:format="formatForMonth"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'day'"
|
||||
v-model="targetDateDay"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:month-picker="true"
|
||||
:format="formatForDay"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'hour'"
|
||||
v-model="targetDateHour"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:format="formatForHour"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateDay = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateHour = ref(
|
||||
$dayjs(new Date().toISOString()).format('MM/DD/YYYY')
|
||||
);
|
||||
|
||||
// console.log('huk = ', targetDate.value);
|
||||
const formatForMonth = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${year}`;
|
||||
};
|
||||
|
||||
const formatForDay = (date) => {
|
||||
console.log('huk format date = ', date);
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${date.year}${(date.month + 1 < 10 ? '0' : '') + (date.month + 1)}`;
|
||||
};
|
||||
|
||||
const formatForHour = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
const hour = date.getHours();
|
||||
|
||||
return `${year}${(month < 10 ? '0' : '') + month}${
|
||||
(day < 10 ? '0' : '') + day
|
||||
}`;
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'total',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit_ratio',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit_ratio',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'size',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'size_avg',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size_avg',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'ip',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_ip',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'referrer',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_referrer',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'admin:statistics',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: targetKey.value,
|
||||
term: targetTerm.value,
|
||||
// termPrefix: _utils.getDateTimeTag(targetTerm.value.substring(0, 1)),
|
||||
// termPrefix: _utils.getDateTimeTag('y'),
|
||||
termPrefix: targetDate.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const targetTerm = ref('year');
|
||||
const targetKey = ref('');
|
||||
|
||||
const tmpTerms = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
terms.value = tmpTerms;
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'admin:key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 0) {
|
||||
_crossCtl.openModal(
|
||||
'error',
|
||||
'잘못된 파라메타',
|
||||
'키 정보를 읽어올 수 없습니다.\n확인 버튼을 누르면 메인 화면으로 돌아갑니다.',
|
||||
['확인'],
|
||||
(btnIdx) => {
|
||||
navigateTo('/');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
targetKey.value = responseJson['data'][0]['api_key'];
|
||||
}
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
|
||||
let result = date;
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
result = `${date.year}${
|
||||
(date.month + 1 < 10 ? '0' : '') + (date.month + 1)
|
||||
}`;
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
result = $dayjs(date).format('YYYYMMDD');
|
||||
break;
|
||||
}
|
||||
|
||||
targetDate.value = result;
|
||||
|
||||
console.log('huk result = ', result);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMM'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMMDD'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
terms.value = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
36
inspond-nuxt-safekiso/pages/admin/key/backStatistics.vue
Normal file
36
inspond-nuxt-safekiso/pages/admin/key/backStatistics.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="bg-white">
|
||||
<div class="max-w-7xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<h2
|
||||
class="text-base font-semibold text-indigo-600 tracking-wide uppercase"
|
||||
>
|
||||
어드민 / API 키
|
||||
</h2>
|
||||
<p
|
||||
class="mt-1 text-4xl font-extrabold text-gray-900 sm:text-5xl sm:tracking-tight lg:text-6xl"
|
||||
>
|
||||
특정 키의 사용량 정보 보기
|
||||
</p>
|
||||
<p class="max-w-xl mt-5 mx-auto text-xl text-gray-500">
|
||||
현재 선택된 키의 상세 사용량 정보를 볼 수 있습니다.
|
||||
</p>
|
||||
|
||||
<br />
|
||||
갈 수 있는 페이지 :
|
||||
<a href="javascript:void(0)" @click="$router.back()">
|
||||
이전 페이지
|
||||
</a>
|
||||
,
|
||||
|
||||
<a href="javascript:void(0)" @click="$router.push('/')"> 홈 </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
</script>
|
||||
180
inspond-nuxt-safekiso/pages/admin/key/deleted.vue
Normal file
180
inspond-nuxt-safekiso/pages/admin/key/deleted.vue
Normal file
@@ -0,0 +1,180 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<br />
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
삭제된 키 리스트
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
삭제된 내 API 키를 보고 복구할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="$router.push('/admin/key/list')"
|
||||
>
|
||||
전체 키 리스트
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const targetUID = ref(route.query.uid ? route.query.uid : 'all');
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '이름',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'name',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [
|
||||
{ class: 'sr-only', title: '단계' },
|
||||
{ class: 'sr-only sm:hidden', title: '상태' },
|
||||
],
|
||||
dds: [
|
||||
{ class: 'mt-1 truncate text-gray-700', key: 'level' },
|
||||
{
|
||||
class: 'mt-1 truncate text-gray-500 sm:hidden',
|
||||
key: 'status',
|
||||
},
|
||||
],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: '단계',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'level',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '상태',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'status',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: '생성일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'created',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = ['상세보기'];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = ['serial', 'api_key', 'name', 'level', 'status', 'created'];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
const { $dayjs } = useNuxtApp();
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
// return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
return $dayjs(val).format('YY/MM/DD');
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function maekNewKey() {
|
||||
return navigateTo({
|
||||
path: '/key/new',
|
||||
query: {},
|
||||
});
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
/*
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
*/
|
||||
navigateTo('/admin/key/' + target + '/edit');
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (process.client) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'admin:key:deleted',
|
||||
{
|
||||
hero: targetUID.value,
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
</script>
|
||||
261
inspond-nuxt-safekiso/pages/admin/key/edit.vue
Normal file
261
inspond-nuxt-safekiso/pages/admin/key/edit.vue
Normal file
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<div class="m-8">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doUpdate"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div
|
||||
class="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5"
|
||||
>
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
API 키 상세 보기
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
API키의 설정을 변경하거나 삭제할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
이름
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
name="name"
|
||||
autocomplete="name"
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="api-key"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
API 키
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="api-key"
|
||||
v-model="apiKey"
|
||||
type="text"
|
||||
name="api-key"
|
||||
autocomplete="api-key"
|
||||
disabled
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p class="text-sm text-gray-500">
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대 필터만
|
||||
동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="high"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="mid"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="low"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-red-600 border border-transparent rounded-md shadow-sm py-2 px-4 inline-flex justify-center text-sm font-medium text-white hover:bg-red-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
@click="doDelete"
|
||||
>
|
||||
{{ status == 0 ? '삭제' : '복구' }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
이전화면
|
||||
</button>
|
||||
<button
|
||||
v-if="status == 0"
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const hero = route.params.target;
|
||||
|
||||
console.log('hero=', hero);
|
||||
|
||||
const apiKey = ref('');
|
||||
const name = ref('');
|
||||
const level = ref('');
|
||||
const status = ref(0);
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'admin:key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
const keyInfo = responseJson['data'][0];
|
||||
|
||||
apiKey.value = keyInfo['api_key'];
|
||||
name.value = keyInfo['name'];
|
||||
level.value = keyInfo['level'];
|
||||
status.value = keyInfo['status'];
|
||||
|
||||
async function doUpdate() {
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'admin:key', {
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
status: status.value,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
|
||||
async function doDelete() {
|
||||
if (status.value == 0) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/delete',
|
||||
'admin:key',
|
||||
{
|
||||
hero: hero,
|
||||
}
|
||||
);
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 4;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/update',
|
||||
'admin:key',
|
||||
{
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
status: 0,
|
||||
}
|
||||
);
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 0;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function doCancel() {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
192
inspond-nuxt-safekiso/pages/admin/key/list.vue
Normal file
192
inspond-nuxt-safekiso/pages/admin/key/list.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<br />
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">키 리스트</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
전체 사용자의 API 키를 보고 관리할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex mr-3 items-center justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="$router.push('/admin/key/deleted')"
|
||||
>
|
||||
삭제 키 리스트
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const targetUID = ref(route.query.uid ? route.query.uid : 'all');
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '소유자',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'by',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
|
||||
{
|
||||
title: '이름',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'name',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [
|
||||
{ class: 'sr-only', title: '단계' },
|
||||
{ class: 'sr-only sm:hidden', title: '상태' },
|
||||
],
|
||||
dds: [
|
||||
{ class: 'mt-1 truncate text-gray-700', key: 'level' },
|
||||
{
|
||||
class: 'mt-1 truncate text-gray-500 sm:hidden',
|
||||
key: 'status',
|
||||
},
|
||||
],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: '단계',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'level',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '상태',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'status',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: '생성일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'created',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = ['상세보기'];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'by',
|
||||
'api_key',
|
||||
'name',
|
||||
'level',
|
||||
'status',
|
||||
'created',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
if (key == 'updated' || key == 'created') {
|
||||
// return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
return $dayjs(val).format('YY/MM/DD');
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
/*
|
||||
router.push({
|
||||
name: 'admin-key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
*/
|
||||
navigateTo('/admin/key/' + target + '/edit');
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (process.client) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'admin:key:active',
|
||||
{
|
||||
hero: targetUID.value,
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
</script>
|
||||
487
inspond-nuxt-safekiso/pages/admin/lab/board.vue
Normal file
487
inspond-nuxt-safekiso/pages/admin/lab/board.vue
Normal file
@@ -0,0 +1,487 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="space-y-6 lg:col-start-1 lg:col-span-2">
|
||||
<!-- Description list-->
|
||||
<section aria-labelledby="applicant-indivation-title">
|
||||
<div class="bg-white shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h2
|
||||
id="applicant-indivation-title"
|
||||
class="text-lg leading-6 font-medium text-gray-900"
|
||||
>
|
||||
게시판에 글을 작성하는 화면에서 API 사용 예
|
||||
</h2>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
문제가 있는 표현을 고지하고 수정하기 전에는 저장을
|
||||
하지 못하게 합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
<div>
|
||||
<div
|
||||
class="shadow sm:rounded-md sm:overflow-hidden"
|
||||
>
|
||||
<div
|
||||
class="px-4 py-5 bg-white space-y-6 sm:p-6"
|
||||
>
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<div class="col-span-3 sm:col-span-3">
|
||||
<label
|
||||
for="company-website"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
제목
|
||||
</label>
|
||||
<div
|
||||
class="mt-1 flex rounded-md shadow-sm"
|
||||
>
|
||||
<input
|
||||
id="company-website"
|
||||
v-model="boardTitle"
|
||||
type="text"
|
||||
name="company-website"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="about"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
내용
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<textarea
|
||||
id="about"
|
||||
v-model="boardText"
|
||||
name="about"
|
||||
rows="7"
|
||||
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
모욕적인 표현이 포함된 경우 저장을
|
||||
하실 수 없습니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="px-4 py-3 bg-gray-50 text-right sm:px-6"
|
||||
>
|
||||
<SwitchGroup
|
||||
as="div"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Switch
|
||||
v-model="boardErrorEnabled"
|
||||
:class="[
|
||||
boardErrorEnabled
|
||||
? 'bg-indigo-600'
|
||||
: 'bg-gray-200',
|
||||
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
:class="[
|
||||
boardErrorEnabled
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0',
|
||||
'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
|
||||
]"
|
||||
/>
|
||||
</Switch>
|
||||
<SwitchLabel as="span" class="ml-3">
|
||||
<span
|
||||
class="text-sm font-medium text-gray-900"
|
||||
>API 오류 시뮬레이션
|
||||
</span>
|
||||
</SwitchLabel>
|
||||
</SwitchGroup>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="handleBoard"
|
||||
>
|
||||
전송
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Comments-->
|
||||
<section aria-labelledby="notes-title">
|
||||
<div class="bg-white shadow sm:rounded-lg sm:overflow-hidden">
|
||||
<div class="divide-y divide-gray-200">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h2
|
||||
id="notes-title"
|
||||
class="text-lg font-medium text-gray-900"
|
||||
>
|
||||
댓글 작성 화면에서의 API 사용 예
|
||||
</h2>
|
||||
</div>
|
||||
<div class="px-4 py-6 sm:px-6">
|
||||
<ul role="list" class="space-y-8">
|
||||
<li
|
||||
v-for="comment in comments"
|
||||
:key="comment.id"
|
||||
>
|
||||
<div class="flex space-x-3">
|
||||
<div class="flex-shrink-0">
|
||||
<img
|
||||
class="h-10 w-10 rounded-full"
|
||||
:src="`https://images.unsplash.com/photo-${comment.imageId}?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=divat&fit=facearea&facepad=2&w=256&h=256&q=80`"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm">
|
||||
<a
|
||||
href="#"
|
||||
class="font-medium text-gray-900"
|
||||
>{{ comment.name }}</a
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="mt-1 text-sm text-gray-700"
|
||||
>
|
||||
<p>
|
||||
{{ comment.body }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-2 text-sm space-x-2">
|
||||
<span
|
||||
class="text-gray-500 font-medium"
|
||||
>{{ comment.date }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 px-4 py-6 sm:px-6">
|
||||
<div class="flex space-x-3">
|
||||
<div class="flex-shrink-0">
|
||||
<img
|
||||
class="h-10 w-10 rounded-full"
|
||||
:src="user.imageUrl"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div action="#">
|
||||
<div>
|
||||
<label for="comment" class="sr-only"
|
||||
>About</label
|
||||
>
|
||||
<textarea
|
||||
id="comment"
|
||||
v-model="commentMessage"
|
||||
name="comment"
|
||||
rows="3"
|
||||
class="shadow-sm block w-full focus:ring-blue-500 focus:border-blue-500 sm:text-sm border border-gray-300 rounded-md"
|
||||
placeholder="댓글 본문을 입력해 주세요."
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 flex items-center justify-between"
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="group inline-flex items-start text-sm space-x-2 text-gray-500 hover:text-gray-900"
|
||||
>
|
||||
<QuestionMarkCircleIcon
|
||||
class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>
|
||||
모욕적인 표현이 포함된 경우
|
||||
게시할 수 없습니다.
|
||||
</span>
|
||||
</a>
|
||||
<SwitchGroup
|
||||
as="div"
|
||||
class="flex items-center"
|
||||
>
|
||||
<Switch
|
||||
v-model="commentErrorEnabled"
|
||||
:class="[
|
||||
commentErrorEnabled
|
||||
? 'bg-indigo-600'
|
||||
: 'bg-gray-200',
|
||||
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
:class="[
|
||||
commentErrorEnabled
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0',
|
||||
'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
|
||||
]"
|
||||
/>
|
||||
</Switch>
|
||||
<SwitchLabel as="span" class="ml-3">
|
||||
<span
|
||||
class="text-sm font-medium text-gray-900"
|
||||
>API 오류 시뮬레이션
|
||||
</span>
|
||||
</SwitchLabel>
|
||||
</SwitchGroup>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
@click="handleComment"
|
||||
>
|
||||
전송
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<!-- Global notification live region, render this permanently at the end of the document -->
|
||||
<div
|
||||
aria-live="assertive"
|
||||
class="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start"
|
||||
>
|
||||
<div
|
||||
class="w-full flex flex-col items-center space-y-4 sm:items-end"
|
||||
>
|
||||
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
|
||||
<transition
|
||||
enter-active-class="transform ease-out duration-300 transition"
|
||||
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
|
||||
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
|
||||
leave-active-class="transition ease-in duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="notiShow"
|
||||
class="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden"
|
||||
>
|
||||
<div class="p-4">
|
||||
<div class="flex items-start">
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
v-if="isGoodFlag"
|
||||
class="h-6 w-6 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ExclamationCircleIcon
|
||||
v-else
|
||||
class="h-6 w-6 text-red-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-3 w-0 flex-1 pt-0.5">
|
||||
<p
|
||||
class="text-sm font-medium text-gray-900"
|
||||
>
|
||||
{{ notiTitle }}
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{{ notiMessage }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-4 flex-shrink-0 flex">
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="notiShow = false"
|
||||
>
|
||||
<span class="sr-only">Close</span>
|
||||
<XIcon
|
||||
class="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { XMarkIcon, QuestionMarkCircleIcon } from '@heroicons/vue/24/solid';
|
||||
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ExclamationCircleIcon,
|
||||
} from '@heroicons/vue/24/outline';
|
||||
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
|
||||
const user = {
|
||||
name: 'Whitney Francis',
|
||||
email: 'whitney@example.com',
|
||||
imageUrl:
|
||||
'https://images.unsplash.com/photo-1517365830460-955ce3ccd263?ixlib=rb-=eyJhcHBfaWQiOjEyMDd9&auto=divat&fit=facearea&facepad=8&w=256&h=256&q=80',
|
||||
};
|
||||
|
||||
let commentSerial = 0;
|
||||
|
||||
const boardErrorEnabled = ref(false);
|
||||
const commentErrorEnabled = ref(false);
|
||||
|
||||
const comments = ref([
|
||||
{
|
||||
id: ++commentSerial,
|
||||
name: '김재순',
|
||||
date: '하루 전',
|
||||
imageId: '1506794778202-cad84cf45f1d',
|
||||
body: '짜장면이 맛이 있느냐 짬뽕이 맛이 있느냐 논쟁 같은 느낌인데요? ',
|
||||
},
|
||||
]);
|
||||
const notiShow = ref(false);
|
||||
const isGoodFlag = ref(false);
|
||||
|
||||
const notiTitle = ref('');
|
||||
const notiMessage = ref('');
|
||||
|
||||
const notiTimer = null;
|
||||
|
||||
function showNotifications(isGood, title, message) {
|
||||
isGoodFlag.value = isGood;
|
||||
notiTitle.value = title;
|
||||
notiMessage.value = message;
|
||||
notiShow.value = true;
|
||||
if (notiTimer != null) {
|
||||
clearTimeout(notiTimer);
|
||||
notiTimer = null;
|
||||
}
|
||||
notiTimer = setTimeout(() => {
|
||||
notiShow.value = false;
|
||||
}, 8000);
|
||||
}
|
||||
|
||||
const boardTitle = ref('');
|
||||
const boardText = ref('');
|
||||
const commentMessage = ref('');
|
||||
|
||||
const missingParts = 'e3';
|
||||
|
||||
async function handleFilter(source, at, errorSimulFlag, cb) {
|
||||
const responseJson = await _crossCtl.doFilter(
|
||||
'' + (errorSimulFlag == false ? '' : missingParts),
|
||||
{
|
||||
text: source,
|
||||
mode: 'quick',
|
||||
}
|
||||
);
|
||||
|
||||
// console.log('responseJson=', responseJson);
|
||||
|
||||
if (responseJson['Status']['Code'] == 2000) {
|
||||
// Detected
|
||||
if (responseJson['Detected'].length == 0) {
|
||||
cb(null);
|
||||
} else {
|
||||
showNotifications(
|
||||
false,
|
||||
'잘못된 표현',
|
||||
at +
|
||||
'에 이 표현은 쓸 수 없습니다. : ' +
|
||||
responseJson['Detected'][0][1]
|
||||
);
|
||||
cb(false);
|
||||
}
|
||||
} else {
|
||||
showNotifications(
|
||||
false,
|
||||
'오류',
|
||||
'API 호출 오류 : ' + responseJson['Status']['Message']
|
||||
);
|
||||
cb(true);
|
||||
}
|
||||
}
|
||||
|
||||
function handleBoard() {
|
||||
// console.log(boardTitle.value);
|
||||
// console.log(boardText.value);
|
||||
|
||||
if (boardTitle.value.trim() == '') {
|
||||
showNotifications(false, '오류', '제목을 입력해 주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (boardText.value.trim() == '') {
|
||||
showNotifications(false, '오류', '본문을 입력해 주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
handleFilter(
|
||||
boardTitle.value,
|
||||
'게시판 제목',
|
||||
boardErrorEnabled.value,
|
||||
function (error) {
|
||||
if (error == null) {
|
||||
handleFilter(
|
||||
boardText.value,
|
||||
'게시판 본문',
|
||||
boardErrorEnabled.value,
|
||||
function (error) {
|
||||
if (error == null) {
|
||||
showNotifications(
|
||||
true,
|
||||
'게시 가능',
|
||||
'입력하신 제목과 내용에 문제가 없습니다.'
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function handleComment() {
|
||||
if (commentMessage.value == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
handleFilter(
|
||||
commentMessage.value,
|
||||
'댓글',
|
||||
commentErrorEnabled.value,
|
||||
function (error) {
|
||||
console.log('error=', error);
|
||||
if (error == null) {
|
||||
comments.value.push({
|
||||
id: ++commentSerial,
|
||||
name: '당신',
|
||||
date: '조금 전',
|
||||
imageId: '1517365830460-955ce3ccd263',
|
||||
body: commentMessage.value,
|
||||
});
|
||||
|
||||
commentMessage.value = '';
|
||||
} else if (error == true) {
|
||||
} else {
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
77
inspond-nuxt-safekiso/pages/admin/lab/chat.vue
Normal file
77
inspond-nuxt-safekiso/pages/admin/lab/chat.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<!--
|
||||
This example requires Tailwind CSS v2.0+
|
||||
|
||||
This example requires some changes to your config:
|
||||
|
||||
```
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
// ...
|
||||
plugins: [
|
||||
// ...
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
```
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
<div class="mt-5 md:mt-0 md:col-span-2">
|
||||
<form action="#" method="POST">
|
||||
<div class="shadow sm:rounded-md sm:overflow-hidden">
|
||||
<div class="px-4 py-5 bg-white space-y-6 sm:p-6">
|
||||
<div class="grid grid-cols-3 gap-6">
|
||||
<div class="col-span-3 sm:col-span-3">
|
||||
<label
|
||||
for="company-website"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
제목
|
||||
</label>
|
||||
<div class="mt-1 flex rounded-md shadow-sm">
|
||||
<input
|
||||
id="company-website"
|
||||
type="text"
|
||||
name="company-website"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-r-md sm:text-sm border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
for="about"
|
||||
class="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
내용
|
||||
</label>
|
||||
<div class="mt-1">
|
||||
<textarea
|
||||
id="about"
|
||||
name="about"
|
||||
rows="7"
|
||||
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border border-gray-300 rounded-md"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
모욕적인 표현이 포함된 경우 저장을 하실 수
|
||||
없습니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-3 bg-gray-50 text-right sm:px-6">
|
||||
<button
|
||||
type="submit"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
187
inspond-nuxt-safekiso/pages/admin/lab/chatting.vue
Normal file
187
inspond-nuxt-safekiso/pages/admin/lab/chatting.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="">
|
||||
<div class="container mx-auto">
|
||||
<div class="min-w-full border rounded lg:grid lg:grid-cols-3">
|
||||
<div class="hidden lg:col-span-3 lg:block">
|
||||
<div class="flex flex-col min-h-screen w-full">
|
||||
<div
|
||||
class="relative flex items-center p-3 border-b border-gray-300"
|
||||
>
|
||||
<img
|
||||
class="object-cover w-10 h-10 rounded-full"
|
||||
src="https://cdn.pixabay.com/photo/2018/01/15/07/51/woman-3083383__340.jpg"
|
||||
alt="username"
|
||||
/>
|
||||
<span class="block ml-2 font-bold text-gray-600">
|
||||
채팅 상황에서의 API 사용 예{{ ' ' }}
|
||||
|
||||
<p class="text-xs">
|
||||
{{
|
||||
messageList[messageList.length - 1][
|
||||
'elapsedServer'
|
||||
] != ''
|
||||
? messageList[
|
||||
messageList.length - 1
|
||||
]['elapsedServer'] +
|
||||
' / ' +
|
||||
messageList[
|
||||
messageList.length - 1
|
||||
]['elapsed']
|
||||
: ''
|
||||
}}
|
||||
</p>
|
||||
</span>
|
||||
<span
|
||||
class="absolute w-3 h-3 bg-green-600 rounded-full left-10 top-3"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-grow bg-gray-50 justify-center">
|
||||
<div
|
||||
class="relative w-full p-6 overflow-y-auto max-h-fit"
|
||||
>
|
||||
<ul class="space-y-2">
|
||||
<li
|
||||
v-for="message in messageList"
|
||||
:key="message['serial']"
|
||||
class="flex"
|
||||
:class="
|
||||
message['left'] == true
|
||||
? 'justify-start'
|
||||
: 'justify-end'
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="relative max-w-xl px-4 py-2 text-gray-700 rounded shadow"
|
||||
>
|
||||
<span class="block">{{
|
||||
message['text']
|
||||
}}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between w-full p-3 border-t border-gray-300"
|
||||
>
|
||||
<input
|
||||
v-model="justEnteredText"
|
||||
type="text"
|
||||
placeholder="Message"
|
||||
class="block w-full py-2 pl-4 mx-3 bg-gray-100 rounded-full outline-none focus:text-gray-700"
|
||||
name="message"
|
||||
required
|
||||
@keyup.enter="checkChatInput"
|
||||
/>
|
||||
<button type="button" @click="checkChatInput">
|
||||
<svg
|
||||
class="w-5 h-5 text-gray-500 origin-center transform rotate-90"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
>
|
||||
<path
|
||||
d="M10.894 2.553a1 1 0 00-1.788 0l-7 14a1 1 0 001.169 1.409l5-1.429A1 1 0 009 15.571V11a1 1 0 112 0v4.571a1 1 0 00.725.962l5 1.428a1 1 0 001.17-1.408l-7-14z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-end w-full p-3 border-t border-gray-300"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-user',
|
||||
});
|
||||
|
||||
let tmpIdx = 0;
|
||||
|
||||
const messageList = ref([
|
||||
{
|
||||
serial: tmpIdx++,
|
||||
left: true,
|
||||
text: '안녕하세요?',
|
||||
elapsedServer: '',
|
||||
elapsed: '',
|
||||
},
|
||||
{
|
||||
serial: tmpIdx++,
|
||||
left: false,
|
||||
text: '테스트 하러 들어 왔습니다.',
|
||||
elapsedServer: '',
|
||||
elapsed: '',
|
||||
},
|
||||
{
|
||||
serial: tmpIdx++,
|
||||
left: true,
|
||||
text: '테스트 하면 이 문장이죠.',
|
||||
elapsedServer: '',
|
||||
elapsed: '',
|
||||
},
|
||||
{
|
||||
serial: tmpIdx++,
|
||||
left: true,
|
||||
text: 'Lorem ipsum dolor sit, amet consectetur adipisicing elit.',
|
||||
elapsedServer: '',
|
||||
elapsed: '',
|
||||
},
|
||||
]);
|
||||
|
||||
const justEnteredText = ref('');
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
const elispe = ref(0);
|
||||
|
||||
async function checkChatInput() {
|
||||
console.log('huk');
|
||||
|
||||
if (justEnteredText.value == '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (inPregressFlag.value == true) {
|
||||
return;
|
||||
}
|
||||
inPregressFlag.value = true;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
const responseJson = await _crossCtl.doFilter('', {
|
||||
text: justEnteredText.value,
|
||||
mode: 'filter',
|
||||
});
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
inPregressFlag.value = false;
|
||||
|
||||
elispe.value = endTime - startTime;
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
|
||||
if (responseJson['Status']['Code'] == 2000) {
|
||||
messageList.value.push({
|
||||
serial: tmpIdx++,
|
||||
left: false,
|
||||
text: responseJson['Filtered'],
|
||||
elapsedServer: responseJson['Elapsed'].replace('0 s, ', ''),
|
||||
elapsed: elispe.value.toFixed(2) + 'ms',
|
||||
});
|
||||
|
||||
justEnteredText.value = '';
|
||||
} else {
|
||||
alert(responseJson['Status']['Message']);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
45
inspond-nuxt-safekiso/pages/admin/lab/index.vue
Normal file
45
inspond-nuxt-safekiso/pages/admin/lab/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 키 테스트 기능
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키가 실제 서비스 예에서 사용되는 것을 볼 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"></div>
|
||||
</div>
|
||||
|
||||
<nav class="mt-5 space-y-1" aria-label="Sidebar">
|
||||
<a
|
||||
v-for="item in navigation"
|
||||
:key="item.name"
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
|
||||
'flex items-center px-3 py-2 text-sm font-medium rounded-md',
|
||||
]"
|
||||
:aria-current="'page'"
|
||||
@click="$router.push(item.href)"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const navigation = [
|
||||
{ name: 'API 키 테스트 게시판', href: '/admin/lab/board' },
|
||||
{ name: 'API 키 테스트 웹 채팅', href: '/admin/lab/chatting' },
|
||||
];
|
||||
</script>
|
||||
79
inspond-nuxt-safekiso/pages/admin/lab/statistics.vue
Normal file
79
inspond-nuxt-safekiso/pages/admin/lab/statistics.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
통계 테스트 페이지
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
통계 개발과 기능 테스트를 위해 만들어진 임시 페이지입니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"></div>
|
||||
</div>
|
||||
|
||||
<nav class="mt-5 space-y-1" aria-label="Sidebar">
|
||||
<a
|
||||
v-for="item in navigation"
|
||||
:key="item.name"
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
|
||||
'flex items-center px-3 py-2 text-sm font-medium rounded-md',
|
||||
]"
|
||||
:aria-current="'page'"
|
||||
@click="doAction(item.actionTag)"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const navigation = [
|
||||
{ name: 'hello?', actionTag: 'hello' },
|
||||
{ name: '2022년 통계 가공', actionTag: '2022' },
|
||||
{ name: '2022년 9월 통계 가공', actionTag: '202209' },
|
||||
{ name: '2022년 9월 27일 통계 가공', actionTag: '20220927' },
|
||||
];
|
||||
|
||||
async function doAction(tag) {
|
||||
let responseJson = null;
|
||||
switch (tag) {
|
||||
case '2022':
|
||||
responseJson = await _crossCtl.doComm('local/lab', 'makestat', {
|
||||
termTag: 'year',
|
||||
dateTag: '2022',
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
break;
|
||||
case '202209':
|
||||
responseJson = await _crossCtl.doComm('local/lab', 'makestat', {
|
||||
termTag: 'month',
|
||||
dateTag: '202209',
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
break;
|
||||
case '20220927':
|
||||
responseJson = await _crossCtl.doComm('local/lab', 'makestat', {
|
||||
termTag: 'day',
|
||||
dateTag: '20220927',
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
break;
|
||||
|
||||
case 'hello':
|
||||
responseJson = await _crossCtl.doComm('local/lab', 'hello', {});
|
||||
console.log('responseJson=', responseJson);
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
328
inspond-nuxt-safekiso/pages/admin/statistics/byterm/[hero].vue
Normal file
328
inspond-nuxt-safekiso/pages/admin/statistics/byterm/[hero].vue
Normal file
@@ -0,0 +1,328 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용 통계 {{ hero == 'usage' ? '(사용량)' : '(단어)' }}
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
사용량 통계를 보여 줍니다. 구분 항목으로 시간별, 날짜별,
|
||||
월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<select
|
||||
id="targetUnit"
|
||||
v-model="targetUnit"
|
||||
name="targetUnit"
|
||||
class="mt-0 block pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="unit in units"
|
||||
:key="unit.key"
|
||||
:selected="unit.current"
|
||||
:value="unit.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ unit.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<Datepicker
|
||||
v-model="date"
|
||||
class="w-64 mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
range
|
||||
multi-calendars
|
||||
multi-calendars-solo
|
||||
:format="inputFormat"
|
||||
:preview-format="previewFormat"
|
||||
@update:modelValue="handleDate"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<StatisticsTable1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<button
|
||||
type="button"
|
||||
class="relative inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none"
|
||||
@click="doDownload()"
|
||||
>
|
||||
<span> 엑셀파일로 다운로드 </span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const route = useRoute();
|
||||
const hero = ref(route.params.hero);
|
||||
|
||||
const targetUID = ref(route.query.uid ? route.query.uid : 'all');
|
||||
const targetKey = ref(route.query.key ? route.query.key : 'all');
|
||||
const targetUnit = ref(route.query.unit ? route.query.unit : 'day');
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateDay = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateHour = ref($dayjs(new Date().toISOString()));
|
||||
|
||||
const units = ref([
|
||||
{ current: targetUnit.value == 'year', key: 'year' },
|
||||
{ current: targetUnit.value == 'month', key: 'month' },
|
||||
{ current: targetUnit.value == 'day', key: 'day' },
|
||||
{ current: targetUnit.value == 'hour', key: 'hour' },
|
||||
]);
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetUnit.value=', targetUnit.value);
|
||||
|
||||
navigateTo(
|
||||
'/admin/statistics/byterm/' +
|
||||
hero.value +
|
||||
'?unit=' +
|
||||
targetUnit.value +
|
||||
'&uid=' +
|
||||
targetUID.value +
|
||||
'&key=' +
|
||||
targetKey.value
|
||||
);
|
||||
refresh();
|
||||
|
||||
units.value = [
|
||||
{ current: targetUnit.value == 'year', key: 'year' },
|
||||
{ current: targetUnit.value == 'month', key: 'month' },
|
||||
{ current: targetUnit.value == 'day', key: 'day' },
|
||||
{ current: targetUnit.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
const date = ref([]);
|
||||
|
||||
const inputFormat = (date) => {
|
||||
// console.log('huk date=', date);
|
||||
if (date.length == 1) {
|
||||
const day = date[0].getDate();
|
||||
const month = date[0].getMonth() + 1;
|
||||
const year = date[0].getFullYear();
|
||||
|
||||
return `${day}/${month}/${year}`;
|
||||
} else if (date.length == 2) {
|
||||
const day1 = date[0].getDate();
|
||||
const month1 = date[0].getMonth() + 1;
|
||||
const year1 = date[0].getFullYear();
|
||||
const day2 = date[1].getDate();
|
||||
const month2 = date[1].getMonth() + 1;
|
||||
const year2 = date[1].getFullYear();
|
||||
|
||||
return `${year1}-${month1}-${day1} ~ ${year2}-${month2}-${day2}`;
|
||||
}
|
||||
};
|
||||
|
||||
const previewFormat = (date) => {
|
||||
// console.log('huk date=', date);
|
||||
if (date.length == 1) {
|
||||
const day = date[0].getDate();
|
||||
const month = date[0].getMonth() + 1;
|
||||
const year = date[0].getFullYear();
|
||||
|
||||
return `${day}/${month}/${year}`;
|
||||
} else if (date.length == 2) {
|
||||
const day1 = date[0].getDate();
|
||||
const month1 = date[0].getMonth() + 1;
|
||||
const year1 = date[0].getFullYear();
|
||||
const day2 = date[1].getDate();
|
||||
const month2 = date[1].getMonth() + 1;
|
||||
const year2 = date[1].getFullYear();
|
||||
|
||||
return `${year1}-${month1}-${day1} ~ ${year2}-${month2}-${day2}`;
|
||||
}
|
||||
};
|
||||
|
||||
const endDate = new Date();
|
||||
const startDate = new Date(new Date().setDate(endDate.getDate() - 7));
|
||||
|
||||
date.value = [startDate, endDate];
|
||||
|
||||
const startDateTag = ref($dayjs(startDate.toISOString()).format('YYYYMMDDHH'));
|
||||
const endDateTag = ref($dayjs(endDate.toISOString()).format('YYYYMMDDHH'));
|
||||
|
||||
function doDownload() {
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
let urlBase = '';
|
||||
|
||||
const currentHost = window.location.host.toLowerCase();
|
||||
const currentProtocol = window.location.protocol;
|
||||
const currentDomain = _utils.getDomain(window.location.href);
|
||||
|
||||
const apiBaseUrl = _crossCtl.config['API_BASE_URL'];
|
||||
|
||||
console.log('currentHost=', currentHost);
|
||||
console.log('currentProtocol=', currentProtocol);
|
||||
console.log('currentDomain=', currentDomain);
|
||||
|
||||
console.log('apiBaseUrl=', apiBaseUrl);
|
||||
|
||||
if (apiBaseUrl.indexOf(currentHost) == -1) {
|
||||
urlBase = apiBaseUrl;
|
||||
} else {
|
||||
urlBase = '/api/';
|
||||
}
|
||||
|
||||
console.log('urlBase=', urlBase);
|
||||
|
||||
anchor.href =
|
||||
urlBase +
|
||||
'local/download/report_' +
|
||||
hero.value +
|
||||
'_' +
|
||||
startDateTag.value +
|
||||
'_' +
|
||||
endDateTag.value +
|
||||
'.xlsx?tag=' +
|
||||
hero.value +
|
||||
'&startDateTag=' +
|
||||
startDateTag.value +
|
||||
'&endDateTag=' +
|
||||
endDateTag.value +
|
||||
'&unit=' +
|
||||
targetUnit.value +
|
||||
'&uid=' +
|
||||
targetUID.value +
|
||||
'&key=' +
|
||||
targetKey.value;
|
||||
|
||||
anchor.target = '_blank';
|
||||
|
||||
anchor.click();
|
||||
}
|
||||
|
||||
const listHeadings =
|
||||
hero.value == 'usage'
|
||||
? [
|
||||
{
|
||||
title: 'date',
|
||||
key: 'date_tag',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
key: 'total',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
key: 'hit',
|
||||
},
|
||||
{
|
||||
title: 'size',
|
||||
key: 'size',
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'word',
|
||||
key: 'word',
|
||||
widthRatio: '100',
|
||||
},
|
||||
{
|
||||
title: 'count',
|
||||
key: 'count_sum',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/select',
|
||||
hero.value == 'usage'
|
||||
? 'admin:statistics:usage'
|
||||
: 'admin:statistics:word',
|
||||
{
|
||||
unit: targetUnit.value,
|
||||
uid: targetUID.value,
|
||||
key: targetKey.value,
|
||||
startDateTag: startDateTag.value,
|
||||
endDateTag: endDateTag.value,
|
||||
}
|
||||
);
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
startDateTag.value = $dayjs(date[0].toISOString()).format('YYYYMMDDHH');
|
||||
endDateTag.value = $dayjs(date[1].toISOString()).format('YYYYMMDDHH');
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
194
inspond-nuxt-safekiso/pages/admin/statistics/index.vue
Normal file
194
inspond-nuxt-safekiso/pages/admin/statistics/index.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<template>
|
||||
<div class="m-8">
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
전체 사용량 통계
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
전체 서버의 사용량 통계를 확인 합니다.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="
|
||||
navigateTo('/admin/statistics/byterm/usage')
|
||||
"
|
||||
>
|
||||
상세 사용량 통계
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="navigateTo('/admin/statistics/byterm/word')"
|
||||
>
|
||||
상세 단어 통계
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section aria-labelledby="applicant-information-title">
|
||||
<div class="bg-white mt-3 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h2
|
||||
id="applicant-information-title"
|
||||
class="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
최근 24시간 사용량
|
||||
</h2>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 px-4 py-5 sm:px-6">
|
||||
<LineChart
|
||||
:chart-data="lineChartData"
|
||||
:chart-options="lineChartOptions"
|
||||
/>
|
||||
</div>
|
||||
<!--
|
||||
<div>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="block bg-gray-50 px-4 py-4 text-center text-sm font-medium text-gray-500 hover:text-gray-700 sm:rounded-b-lg"
|
||||
@click="refresh()"
|
||||
>새로 고침</a
|
||||
>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section aria-labelledby="applicant-information-title">
|
||||
<div class="bg-white mt-3 shadow sm:rounded-lg">
|
||||
<div class="px-4 py-5 sm:px-6">
|
||||
<h2
|
||||
id="applicant-information-title"
|
||||
class="text-lg font-medium leading-6 text-gray-900"
|
||||
>
|
||||
최근 24시간 키별 점유율
|
||||
</h2>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 px-4 py-5 sm:px-6">
|
||||
<PieChart
|
||||
:chart-data="pieChartData"
|
||||
:chart-options="pieChartOptions"
|
||||
/>
|
||||
</div>
|
||||
<!--
|
||||
<div>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="block bg-gray-50 px-4 py-4 text-center text-sm font-medium text-gray-500 hover:text-gray-700 sm:rounded-b-lg"
|
||||
@click="refresh()"
|
||||
>새로 고침</a
|
||||
>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
const inPregressFlag = ref(true);
|
||||
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/select',
|
||||
'admin:dashboard',
|
||||
{}
|
||||
);
|
||||
|
||||
let rawData1 = [];
|
||||
let rawData2 = [];
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
inPregressFlag.value = false;
|
||||
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
rawData1 = responseJson['result']['adminDashData1'];
|
||||
rawData2 = responseJson['result']['adminDashData2'];
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
|
||||
const lineChartData = {
|
||||
labels: rawData1.map((item) => item['date_tag']),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Total',
|
||||
backgroundColor: '#00D8FF',
|
||||
data: rawData1.map((item) => item['total']),
|
||||
},
|
||||
{
|
||||
label: 'Hit',
|
||||
backgroundColor: '#f87979',
|
||||
data: rawData1.map((item) => item['hit']),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const lineChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
const pieChartData = {
|
||||
labels: rawData2.map((item) => item['key_name']),
|
||||
datasets: [
|
||||
{
|
||||
// backgroundColor: ['#41B883', '#E46651', '#00D8FF', '#DD1B16'],
|
||||
data: rawData2.map((item) => item['total']),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const pieChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
const chartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data One',
|
||||
backgroundColor: '#f87979',
|
||||
data: [40, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
</script>
|
||||
121
inspond-nuxt-safekiso/pages/admin/statistics/index_bak.vue
Normal file
121
inspond-nuxt-safekiso/pages/admin/statistics/index_bak.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="mt-5 sm:px-3 lg:px-5">전체 통계 페이지 대시보드</div>
|
||||
|
||||
<LineChart
|
||||
:chart-data="lineChartData"
|
||||
:chart-options="lineChartOptions"
|
||||
/>
|
||||
|
||||
<PieChart :chart-data="pieChartData" :chart-options="pieChartOptions" />
|
||||
|
||||
<div class="mt-5 sm:px-3 lg:px-5">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="navigateTo('/admin/statistics/byterm/usage')"
|
||||
>기간별 사용량 통계 화면으로 →</a
|
||||
>
|
||||
</div>
|
||||
<div class="mt-5 sm:px-3 lg:px-5">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="navigateTo('/admin/statistics/byterm/word')"
|
||||
>기간별 단어 통계 화면으로 →</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
/*
|
||||
const route = useRoute();
|
||||
let hero: string | string[] = '';
|
||||
hero = route.params['hero'];
|
||||
*/
|
||||
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/select',
|
||||
'admin:dashboard',
|
||||
{}
|
||||
);
|
||||
|
||||
let rawData1 = [];
|
||||
let rawData2 = [];
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
rawData1 = responseJson['result']['adminDashData1'];
|
||||
rawData2 = responseJson['result']['adminDashData2'];
|
||||
}
|
||||
|
||||
const lineChartData = {
|
||||
labels: rawData1.map((item) => item['date_tag']),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Total',
|
||||
backgroundColor: '#00D8FF',
|
||||
data: rawData1.map((item) => item['total']),
|
||||
},
|
||||
{
|
||||
label: 'Hit',
|
||||
backgroundColor: '#f87979',
|
||||
data: rawData1.map((item) => item['hit']),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const lineChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
const pieChartData = {
|
||||
labels: rawData2.map((item) => item['key_name']),
|
||||
datasets: [
|
||||
{
|
||||
// backgroundColor: ['#41B883', '#E46651', '#00D8FF', '#DD1B16'],
|
||||
data: rawData2.map((item) => item['total']),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const pieChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
const chartData = {
|
||||
labels: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Data One',
|
||||
backgroundColor: '#f87979',
|
||||
data: [40, 20, 12, 39, 10, 40, 39, 80, 40, 20, 12, 11],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
</script>
|
||||
272
inspond-nuxt-safekiso/pages/admin/word/[hero]/log.vue
Normal file
272
inspond-nuxt-safekiso/pages/admin/word/[hero]/log.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<br />
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터 단어 로그 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
필터 단어의 생성, 변경, 삭제 기록을 확인할 수 있습니다.
|
||||
기록이 없다면 시스템 입력된 단어입니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<label for="mobile-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<label for="desktop-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<div class="flex rounded-md shadow-sm">
|
||||
<div class="relative flex-grow focus-within:z-10">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
||||
>
|
||||
<MagnifyingGlassCircleIcon
|
||||
class="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
id="mobile-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="mobile-search-candidate"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:hidden border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
<input
|
||||
id="desktop-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="desktop-search-candidate"
|
||||
class="hidden focus:ring-indigo-500 focus:border-indigo-500 w-full rounded-none rounded-l-md pl-10 sm:block sm:text-sm border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
@click="doAction('search', searchKeyword)"
|
||||
>
|
||||
<span class="ml-2">검색</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
MagnifyingGlassCircleIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import consolaGlobalInstance from 'consola';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
console.log('route.params=', route.params);
|
||||
|
||||
const targetName = route.params.hero;
|
||||
const hero = route.params.hero;
|
||||
|
||||
console.log('targetName=', targetName);
|
||||
console.log('hero=', hero);
|
||||
|
||||
const listTarget = 'admin:log:word';
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '누가',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'name',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: '언제',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'created',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '어떻게',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'description',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: '파라메타',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'infos',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'raw',
|
||||
'level',
|
||||
'comment',
|
||||
'status',
|
||||
'updated',
|
||||
'created',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log("columnFilter(), key = ", key, ", val = ", val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'infos') {
|
||||
const tmpInfosJson = JSON.parse(val);
|
||||
delete tmpInfosJson.hero;
|
||||
delete tmpInfosJson.raw;
|
||||
delete tmpInfosJson.target;
|
||||
delete tmpInfosJson.status;
|
||||
delete tmpInfosJson.revive;
|
||||
|
||||
const strInfos = JSON.stringify(tmpInfosJson);
|
||||
|
||||
return '' + strInfos.substring(1, strInfos.length - 1) + '';
|
||||
} else if (key == 'status') {
|
||||
let statusTag = '정상';
|
||||
switch (val) {
|
||||
case 0:
|
||||
statusTag = '정상등록';
|
||||
break;
|
||||
case 4:
|
||||
statusTag = '삭제됨';
|
||||
break;
|
||||
default:
|
||||
statusTag = val;
|
||||
}
|
||||
return statusTag;
|
||||
} else if (key == 'level') {
|
||||
let levelTag = 'mid';
|
||||
switch (val) {
|
||||
case 10:
|
||||
levelTag = 'high';
|
||||
break;
|
||||
case 50:
|
||||
levelTag = 'mid';
|
||||
break;
|
||||
case 100:
|
||||
levelTag = 'low';
|
||||
break;
|
||||
|
||||
default:
|
||||
levelTag = val;
|
||||
}
|
||||
return levelTag;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
|
||||
if (tag == '상세보기') {
|
||||
navigateTo('/admin/user/' + hero + '/history/detail/' + target);
|
||||
}
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm('local/list', listTarget, {
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
console.log('listTarget=', listTarget);
|
||||
console.log('hero=', hero);
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
366
inspond-nuxt-safekiso/pages/admin/word/[hero]/statistics.vue
Normal file
366
inspond-nuxt-safekiso/pages/admin/word/[hero]/statistics.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터된 단어 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 딘아 필터 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<select
|
||||
id="targetTerm"
|
||||
v-model="targetTerm"
|
||||
name="targetTerm"
|
||||
class="mt-0 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="term in terms"
|
||||
:key="term.key"
|
||||
:selected="term.current"
|
||||
:value="term.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ term.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'year'"
|
||||
v-model="targetDateYear"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:year-picker="true"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'month'"
|
||||
v-model="targetDateMonth"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:month-picker="true"
|
||||
:format="formatForMonth"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'day'"
|
||||
v-model="targetDateDay"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:format="formatForDay"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
const router = useRouter();
|
||||
const { $dayjs } = useNuxtApp();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateYear = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateDay = ref(
|
||||
$dayjs(new Date().toISOString()).format('MM/DD/YYYY')
|
||||
);
|
||||
|
||||
// console.log('huk = ', targetDate.value);
|
||||
const format = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${year}${month}${day}`;
|
||||
};
|
||||
|
||||
const formatForDay = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
const hour = date.getHours();
|
||||
|
||||
return `${year}${(month < 10 ? '0' : '') + month}${
|
||||
(day < 10 ? '0' : '') + day
|
||||
}`;
|
||||
};
|
||||
|
||||
const formatForMonth = (date) => {
|
||||
return `${date.year}${(date.month + 1 < 10 ? '0' : '') + (date.month + 1)}`;
|
||||
};
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'word',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'word',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'count',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'admin:statistics:word',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: targetKey.value,
|
||||
term: targetTerm.value,
|
||||
// termPrefix: _utils.getDateTimeTag(targetTerm.value.substring(0, 1)),
|
||||
termPrefix: targetDate.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const targetTerm = ref('year');
|
||||
const targetKey = ref('');
|
||||
|
||||
const tmpTerms = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
];
|
||||
|
||||
terms.value = tmpTerms;
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'admin:key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 0) {
|
||||
_crossCtl.openModal(
|
||||
'error',
|
||||
'잘못된 파라메타',
|
||||
'키 정보를 읽어올 수 없습니다.\n확인 버튼을 누르면 메인 화면으로 돌아갑니다.',
|
||||
['확인'],
|
||||
(btnIdx) => {
|
||||
navigateTo('/');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
targetKey.value = responseJson['data'][0]['api_key'];
|
||||
}
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
|
||||
let result = date;
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
result = `${date.year}${
|
||||
(date.month + 1 < 10 ? '0' : '') + (date.month + 1)
|
||||
}`;
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
result = $dayjs(date).format('YYYYMMDD');
|
||||
break;
|
||||
}
|
||||
|
||||
targetDate.value = result;
|
||||
|
||||
console.log('huk result = ', result);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMM'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMMDD'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
terms.value = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
1258
inspond-nuxt-safekiso/pages/docs/privacy.vue
Normal file
1258
inspond-nuxt-safekiso/pages/docs/privacy.vue
Normal file
File diff suppressed because it is too large
Load Diff
2393
inspond-nuxt-safekiso/pages/docs/stipulation.vue
Normal file
2393
inspond-nuxt-safekiso/pages/docs/stipulation.vue
Normal file
File diff suppressed because it is too large
Load Diff
176
inspond-nuxt-safekiso/pages/filter/index.vue
Normal file
176
inspond-nuxt-safekiso/pages/filter/index.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<!--
|
||||
This example requires Tailwind CSS v2.0+
|
||||
|
||||
This example requires some changes to your config:
|
||||
|
||||
```
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
// ...
|
||||
plugins: [
|
||||
// ...
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
```
|
||||
-->
|
||||
<template>
|
||||
<div class="m-8">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doFilter"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터 테스트
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
입력된 문장을 api를 이용해 검사하고 그 결과를
|
||||
표시합니다.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
검사하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 sm:mt-5 space-y-6 sm:space-y-5">
|
||||
<div>
|
||||
<fieldset class="mt-4">
|
||||
<legend class="sr-only">Filter Mode</legend>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="notificationMethod in notificationMethods"
|
||||
:key="notificationMethod.id"
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
:id="notificationMethod.id"
|
||||
v-model="filterMode"
|
||||
:value="notificationMethod.id"
|
||||
name="notification-method"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
:for="notificationMethod.id"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{{ notificationMethod.title }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="filterText"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
검사할 문장 입력
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<textarea
|
||||
id="filterText"
|
||||
v-model="filterText"
|
||||
name="filterText"
|
||||
rows="3"
|
||||
class="max-w-lg shadow-sm block w-full focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md"
|
||||
/>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
필터 테스트를 위한 문장을 입력하고 아래
|
||||
검사하기 버튼을 누르세요. <br />
|
||||
(마지막 실행시간
|
||||
{{ elispe.toFixed(2) }} 밀리초)
|
||||
</p>
|
||||
<pre>{{ resultJson }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const notificationMethods = [
|
||||
{
|
||||
id: 'quick',
|
||||
title: 'mode = quick. 첫번째 매칭이 발견되면 더 이상 검사하지 않고 바로 결과를 리턴',
|
||||
},
|
||||
{ id: 'normal', title: 'mode = normal. 전체를 검사하여 모든 매칭을 리턴' },
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'mode = filter. 전체를 검사하며 모든 매칭을 마스크로 치환한 결과까지 리턴',
|
||||
},
|
||||
];
|
||||
|
||||
const filterText = ref('테스트 할 문장을 이곳에 넣어 주세요.');
|
||||
|
||||
const filterMode = ref('filter');
|
||||
|
||||
const filterCallback = ref('');
|
||||
|
||||
const resultJson = ref({});
|
||||
|
||||
const elispe = ref(0);
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
async function doFilter() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
inPregressFlag.value = true;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
const responseJson = await _crossCtl.doFilter('', {
|
||||
text: filterText.value,
|
||||
mode: filterMode.value,
|
||||
});
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
inPregressFlag.value = false;
|
||||
|
||||
elispe.value = endTime - startTime;
|
||||
|
||||
// console.log('responseJson=', responseJson);
|
||||
const result = responseJson;
|
||||
resultJson.value = result;
|
||||
|
||||
if (responseJson['Status']['Code'] == 2000) {
|
||||
// alert(result.length + ' match found');
|
||||
} else {
|
||||
alert(responseJson['Status']['Message']);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
543
inspond-nuxt-safekiso/pages/index.vue
Normal file
543
inspond-nuxt-safekiso/pages/index.vue
Normal file
@@ -0,0 +1,543 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="mx-auto max-w-3xl text-base leading-7 text-gray-700 px-4">
|
||||
<h1 class="mt-10 text-3xl font-bold tracking-tight text-gray-900">
|
||||
KISO 이용자 보호 시스템 API 서비스
|
||||
</h1>
|
||||
<p class="mt-2 text-lg leading-8 text-gray-600">
|
||||
KSS(KISO Safeguard System) API Service
|
||||
</p>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2 class="mt-16 text-2xl font-bold tracking-tight text-gray-900">
|
||||
KSS 소개
|
||||
</h2>
|
||||
<p class="mt-6">
|
||||
KISO 이용자보호시스템(KSS, KISO Safeguard System) API는 국내
|
||||
대표 포털 네이버와 카카오로부터 제공받은 욕설·비속어 DB를 활용해
|
||||
개발되었습니다.
|
||||
</p>
|
||||
<p class="mt-6">
|
||||
해당 API는 약 80만 건의 욕설·비속어 DB를 활용해 입력된 표현 중
|
||||
사전에 포함된 단어가 있는지 검사하고 그 결과를 필터링해 주는
|
||||
서비스를 제공합니다.
|
||||
</p>
|
||||
<p class="mt-6">
|
||||
본 서비스의 목적은 다양한 인터넷 서비스에서 사업자가 별도의
|
||||
욕설·비속어 DB구축 및 유지 보수의 부담이 없이 욕설·비속어 표현에
|
||||
대한 서비스 관리 및 운영에 도움을 드리는 것입니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2 class="mt-16 text-2xl font-bold tracking-tight text-gray-900">
|
||||
주요 특징
|
||||
</h2>
|
||||
|
||||
<ul role="list" class="mt-8 max-w-xl space-y-8 text-gray-600">
|
||||
<li class="flex gap-x-3">
|
||||
<CheckCircleIcon
|
||||
class="mt-1 h-5 w-5 flex-none text-indigo-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
><strong class="font-semibold text-gray-900"
|
||||
>표준화된 필터 데이터베이스</strong
|
||||
>
|
||||
<p>
|
||||
KSS에서 사용하는 필터 단어 데이터베이스는 네이버와
|
||||
카카오와 같은 국내 대형 포털로부터 제공받은 기반
|
||||
데이터를 정제하여 표준화 된 것입니다. 대부분의
|
||||
인터넷 커뮤니티 서비스에서 바로 사용하기에 적합
|
||||
합니다.
|
||||
</p>
|
||||
</span>
|
||||
</li>
|
||||
<li class="flex gap-x-3">
|
||||
<CheckCircleIcon
|
||||
class="mt-1 h-5 w-5 flex-none text-indigo-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
><strong class="font-semibold text-gray-900"
|
||||
>빠른 검색 속도</strong
|
||||
>
|
||||
<p>
|
||||
현재 검색 대상 단어 데이터베이스에는 80만개의 단어가
|
||||
등록되어 있으며 이는 시간이 흐름에 따라 계속
|
||||
늘어나게 됩니다. 하지만 고객사의 API 호출시 필터
|
||||
단어를 검색하는 시간은 데이터베이스 크기와 무관하게
|
||||
검사 대상인 텍스트의 사이즈만큼만 시간을 소모하도록
|
||||
만들어져 있어서 대부분의 인터넷 서비스에 사용 적합
|
||||
합니다.
|
||||
</p>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
<li class="flex gap-x-3">
|
||||
<CheckCircleIcon
|
||||
class="mt-1 h-5 w-5 flex-none text-indigo-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
><strong class="font-semibold text-gray-900"
|
||||
>안정적인 인프라</strong
|
||||
>
|
||||
<p>
|
||||
KSS는 우수한 성능과 안정성을 보유한 AWS 클라우드를
|
||||
사용하여 안정적인 서비스를 제공합니다. 서비스
|
||||
사용량이 많아서 충분한 처리 속도를 확보하기를 원하는
|
||||
회원사를 위한 부하 격리 기능도 준비되어 있습니다.
|
||||
</p>
|
||||
</span>
|
||||
</li>
|
||||
<li class="flex gap-x-3">
|
||||
<CheckCircleIcon
|
||||
class="mt-1 h-5 w-5 flex-none text-indigo-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
><strong class="font-semibold text-gray-900"
|
||||
>지속적인 업데이트</strong
|
||||
>
|
||||
<p>
|
||||
KSS는 포털 회원사의 지속적인 DB 제공으로 인터넷상
|
||||
빠르게 전파되는 욕설·비속어에 대응할 수 있습니다.
|
||||
DB를 직접 제공하지 않고 응용프로그램 인터페이스(API)
|
||||
방식으로 서비스하는 것은 업데이트를 하기
|
||||
위해서입니다.
|
||||
</p>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2 class="mt-16 text-2xl font-bold tracking-tight text-gray-900">
|
||||
필터 DB 생성 절차
|
||||
</h2>
|
||||
|
||||
<div class="mt-6 lg:border-b lg:border-t lg:border-gray-200">
|
||||
<nav class="mx-auto max-w-7xl px-2" aria-label="Progress">
|
||||
<ol
|
||||
role="list"
|
||||
class="overflow-hidden rounded-md lg:flex lg:rounded-none lg:border-l lg:border-r lg:border-gray-200"
|
||||
>
|
||||
<li
|
||||
v-for="(step, stepIdx) in steps_db"
|
||||
:key="step.id"
|
||||
class="relative overflow-hidden lg:flex-1"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
stepIdx === 0
|
||||
? 'rounded-t-md border-b-0'
|
||||
: '',
|
||||
stepIdx === steps.length - 1
|
||||
? 'rounded-b-md border-t-0'
|
||||
: '',
|
||||
'overflow-hidden border border-gray-200 lg:border-0',
|
||||
]"
|
||||
>
|
||||
<a :href="step.href" aria-current="step">
|
||||
<span
|
||||
class="absolute left-0 top-0 h-full w-1 bg-indigo-600 lg:bottom-0 lg:top-auto lg:h-1 lg:w-full"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
:class="[
|
||||
stepIdx !== 0 ? '' : '',
|
||||
'flex items-start px-5 py-5 text-sm font-medium',
|
||||
]"
|
||||
>
|
||||
<span class="flex-shrink-0">
|
||||
<span
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full border-2 border-indigo-600"
|
||||
>
|
||||
<span class="text-indigo-600">{{
|
||||
step.id
|
||||
}}</span>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="lg:text-center ml-2 mt-0.5 flex min-w-0 flex-col"
|
||||
>
|
||||
<span
|
||||
class="text-sm font-medium text-indigo-600"
|
||||
>{{ step.name }}</span
|
||||
>
|
||||
<span
|
||||
class="text-sm font-medium text-gray-500"
|
||||
>{{ step.description }}</span
|
||||
>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<template v-if="stepIdx !== 0">
|
||||
<!-- Separator -->
|
||||
<div
|
||||
class="absolute inset-0 left-0 top-0 hidden w-3 lg:block"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
class="h-full w-full text-gray-300"
|
||||
viewBox="0 0 12 82"
|
||||
fill="none"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<path
|
||||
d="M0.5 0V31L10.5 41L0.5 51V82"
|
||||
stroke="currentcolor"
|
||||
vector-effect="non-scaling-stroke"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div></div>
|
||||
|
||||
<p class="mt-6 text-sm">
|
||||
위 과정을 일정 기간마다 주기적으로 수행하여 항상 최신의 표준화된
|
||||
필터 DB를 서비스 합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2 class="mt-16 text-2xl font-bold tracking-tight text-gray-900">
|
||||
서비스 비용
|
||||
</h2>
|
||||
<p class="mt-6">
|
||||
KSS API 서비스는 회원사, 언론사, 공공기관에 무료로 이용
|
||||
가능하도록 배포하고 있습니다. KSS 만을 이용하는 경우 서비스
|
||||
이용료(월 6만원)가 발생 됩니다.
|
||||
</p>
|
||||
|
||||
<div class="bg-white py-1 rounded-2xl">
|
||||
<div class="mx-auto max-w-7xl px-2">
|
||||
<div class="mx-auto max-w-2xl lg:max-w-none">
|
||||
<dl
|
||||
class="mt-1 mb-1 grid grid-cols-1 gap-0.5 overflow-hidden rounded-2xl text-center sm:grid-cols-2 lg:grid-cols-4"
|
||||
>
|
||||
<div
|
||||
v-for="stat in stats"
|
||||
:key="stat.id"
|
||||
class="flex flex-col bg-gray-400/5 py-6"
|
||||
>
|
||||
<dt
|
||||
class="text-2sm font-semibold leading-6 text-gray-600"
|
||||
>
|
||||
{{ stat.name }}
|
||||
</dt>
|
||||
<dd
|
||||
class="order-first text-xl font-semibold tracking-tight text-gray-900"
|
||||
>
|
||||
{{ stat.value }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 text-sm">
|
||||
* 서비스 제공 내용과 요금은 향후 변동 가능하므로 공지 사항이나
|
||||
이메일 공지를 반드시 확인해 주세요.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2
|
||||
id="service-use-agreement"
|
||||
class="mt-16 text-2xl font-bold tracking-tight text-gray-900"
|
||||
>
|
||||
서비스 이용 절차
|
||||
</h2>
|
||||
<nav class="mt-6 ml-6" aria-label="Progress">
|
||||
<ol role="list" class="overflow-hidden">
|
||||
<li
|
||||
v-for="(step, stepIdx) in steps"
|
||||
:key="step.name"
|
||||
:class="[
|
||||
stepIdx !== steps.length - 1 ? 'pb-10' : '',
|
||||
'relative',
|
||||
]"
|
||||
>
|
||||
<template v-if="step.status === 'complete'">
|
||||
<div
|
||||
v-if="stepIdx !== steps.length - 1"
|
||||
class="absolute left-4 top-4 -ml-px mt-0.5 h-full w-0.5 bg-indigo-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<a
|
||||
:href="step.href"
|
||||
class="group relative flex items-start"
|
||||
>
|
||||
<span class="flex h-9 items-center">
|
||||
<span
|
||||
class="relative z-10 flex h-8 w-8 items-center justify-center rounded-full bg-indigo-600 group-hover:bg-indigo-800"
|
||||
>
|
||||
<CheckIcon
|
||||
class="h-5 w-5 text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span class="ml-4 flex min-w-0 flex-col">
|
||||
<span class="text-sm font-medium">{{
|
||||
step.name
|
||||
}}</span>
|
||||
<span class="text-sm text-gray-500">{{
|
||||
step.description
|
||||
}}</span>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else-if="step.status === 'current'">
|
||||
<div
|
||||
v-if="stepIdx !== steps.length - 1"
|
||||
class="absolute left-4 top-4 -ml-px mt-0.5 h-full w-0.5 bg-gray-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<a
|
||||
:href="step.href"
|
||||
class="group relative flex items-start"
|
||||
aria-current="step"
|
||||
>
|
||||
<span
|
||||
class="flex h-9 items-center"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span
|
||||
class="relative z-10 flex h-8 w-8 items-center justify-center rounded-full border-2 border-indigo-600 bg-white"
|
||||
>
|
||||
<span
|
||||
class="h-2.5 w-2.5 rounded-full bg-indigo-600"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span class="ml-4 flex min-w-0 flex-col">
|
||||
<span
|
||||
class="text-sm font-medium text-indigo-600"
|
||||
>{{ step.name }}</span
|
||||
>
|
||||
<span class="text-sm text-gray-500">{{
|
||||
step.description
|
||||
}}</span>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="stepIdx !== steps.length - 1"
|
||||
class="absolute left-4 top-4 -ml-px mt-0.5 h-full w-0.5 bg-gray-300"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<a
|
||||
:href="step.href"
|
||||
class="group relative flex items-start"
|
||||
>
|
||||
<span
|
||||
class="flex h-9 items-center"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span
|
||||
class="relative z-10 flex h-8 w-8 items-center justify-center rounded-full border-2 border-gray-300 bg-white group-hover:border-gray-400"
|
||||
>
|
||||
<span
|
||||
class="h-2.5 w-2.5 rounded-full bg-transparent group-hover:bg-gray-300"
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
<span class="ml-4 flex min-w-0 flex-col">
|
||||
<span
|
||||
class="text-sm font-medium text-gray-500"
|
||||
>{{ step.name }}</span
|
||||
>
|
||||
<span class="text-sm text-gray-500">{{
|
||||
step.description
|
||||
}}</span>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2 class="mt-16 text-2xl font-bold tracking-tight text-gray-900">
|
||||
사용문의
|
||||
</h2>
|
||||
<p class="mt-6">
|
||||
API 사용에 필요한 자세한 기술 문서는
|
||||
<a
|
||||
class="font-semibold text-indigo-600 hover:text-indigo-500"
|
||||
href="javascript:void()"
|
||||
@click="navigateTo('/doc/api_doc')"
|
||||
>API 연동 안내</a
|
||||
>
|
||||
에 수록되어 있습니다. 또한
|
||||
<a
|
||||
class="font-semibold text-indigo-600 hover:text-indigo-500"
|
||||
href="javascript:void()"
|
||||
@click="navigateTo('/doc/guide')"
|
||||
>서비스 도입 안내</a
|
||||
>,
|
||||
|
||||
<a
|
||||
class="font-semibold text-indigo-600 hover:text-indigo-500"
|
||||
href="javascript:void()"
|
||||
@click="navigateTo('/support/faq')"
|
||||
>FAQ
|
||||
</a>
|
||||
|
||||
을 통해 KSS API 서비스 개념과 사용방법에 대해 살펴보시기
|
||||
바랍니다.
|
||||
</p>
|
||||
|
||||
<p class="mt-6">
|
||||
API 연동 관련 문의는 이메일(netsafe@kiso.or.kr)로 보내주시기
|
||||
바랍니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2
|
||||
class="mt-16 text-2xl font-bold tracking-tight text-gray-900"
|
||||
></h2>
|
||||
<p class="mt-6"></p>
|
||||
</div>
|
||||
|
||||
<div class="py-1">
|
||||
<div class="mx-auto max-w-7xl px-6">
|
||||
<div class="mx-auto max-w-2xl">
|
||||
<div
|
||||
class="mx-auto mt-2 mb-2 grid grid-cols-4 items-start gap-x-8 gap-y-10"
|
||||
>
|
||||
<img
|
||||
class="col-span-2 max-h-12 w-full object-contain object-left"
|
||||
src="https://dev.safekiso.com/kiso_ci_1.png"
|
||||
alt="Transistor"
|
||||
width="158"
|
||||
height="48"
|
||||
/>
|
||||
<img
|
||||
class="col-span-2 max-h-12 w-full object-contain object-left"
|
||||
src="https://www.safekiso.com/logos/kss_certification_logo_box_english.png"
|
||||
alt="Reform"
|
||||
width="158"
|
||||
height="48"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 max-w-2xl">
|
||||
<h2
|
||||
class="mt-16 text-2xl font-bold tracking-tight text-gray-900"
|
||||
></h2>
|
||||
<p class="mt-6"></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/vue/20/solid';
|
||||
|
||||
import { CheckIcon } from '@heroicons/vue/20/solid';
|
||||
|
||||
const steps_db = [
|
||||
{
|
||||
id: '01',
|
||||
name: '네이버, 카카오',
|
||||
description: '욕설·비속어 DB 제공',
|
||||
href: '#',
|
||||
status: 'current',
|
||||
},
|
||||
{
|
||||
id: '02',
|
||||
name: 'KISO',
|
||||
description: '표준화, DB 구축',
|
||||
href: '#',
|
||||
status: 'current',
|
||||
},
|
||||
{
|
||||
id: '03',
|
||||
name: 'KSS API',
|
||||
description: 'API 서비스 제공',
|
||||
href: '#',
|
||||
status: 'current',
|
||||
},
|
||||
];
|
||||
|
||||
const steps = [
|
||||
{
|
||||
name: '서비스 사용 신청',
|
||||
description: '이 메일을 통한 서비스 등록 신청',
|
||||
href: '#',
|
||||
status: 'complete',
|
||||
},
|
||||
{
|
||||
name: '내부 검토 및 계정 등록',
|
||||
description: '고객사의 회원 자격 등을 검토 하여 계정을 등록 합니다.',
|
||||
href: '#',
|
||||
status: 'current',
|
||||
},
|
||||
{
|
||||
name: '회원 가입',
|
||||
description: '등록된 이메일 주소로 회원 가입을 진행 합니다.',
|
||||
href: '#',
|
||||
status: 'upcoming',
|
||||
},
|
||||
{
|
||||
name: 'API 키 생성',
|
||||
description: '웹 어드민 기능을 통해 필요한 API 키를 생성 합니다.',
|
||||
href: '#',
|
||||
status: 'upcoming',
|
||||
},
|
||||
{
|
||||
name: '서비스 연동',
|
||||
description:
|
||||
'연동 가이드 등을 참고로 발급된 API 키를 서비스와 연동 합니다.',
|
||||
href: '#',
|
||||
status: 'upcoming',
|
||||
},
|
||||
];
|
||||
|
||||
const includedFeatures = [
|
||||
'최신 통합 필터 DB 적용',
|
||||
'일 최대 8백만건 호출',
|
||||
'초당 최대 100회 호출',
|
||||
'웹 어드민 페이지',
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ id: 1, name: '일 최대 호출', value: '8,640,000' },
|
||||
{ id: 2, name: '1초 호출 제한', value: '100회' },
|
||||
{ id: 3, name: '관리 기능 제공', value: '웹 어드민' },
|
||||
{ id: 4, name: '낮은 도입 부담', value: '월 단위 결제' },
|
||||
];
|
||||
|
||||
const route = useRoute();
|
||||
console.log('route.query = ', route.query);
|
||||
console.log('route = ', route.fullPath);
|
||||
|
||||
onMounted(() => {
|
||||
console.log('myheader mounted');
|
||||
|
||||
if (route.fullPath.endsWith('#service-use-agreement')) {
|
||||
const container = document.getElementById('service-use-agreement');
|
||||
container.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
451
inspond-nuxt-safekiso/pages/key/[hero]/edit.vue
Normal file
451
inspond-nuxt-safekiso/pages/key/[hero]/edit.vue
Normal file
@@ -0,0 +1,451 @@
|
||||
<template>
|
||||
<div class="m-0">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doUpdate"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div
|
||||
class="divide-y divide-gray-200 pt-0 space-y-6 sm:pt-0 sm:space-y-5"
|
||||
>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 키 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API키의 설정을 변경하거나 삭제할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="gotoStatistics()"
|
||||
>
|
||||
사용 통계
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="gotoWordStatistics()"
|
||||
>
|
||||
단어 통계
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
이름
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
name="name"
|
||||
autocomplete="name"
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="api-key"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
API 키
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="api-key"
|
||||
v-model="apiKey"
|
||||
type="text"
|
||||
name="api-key"
|
||||
autocomplete="api-key"
|
||||
disabled
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p class="text-sm text-gray-500">
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대 필터만
|
||||
동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="high"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="mid"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
disabled
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="low"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
disabled
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doDelete"
|
||||
>
|
||||
{{ status == 0 ? '삭제' : '복구' }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
이전화면
|
||||
</button>
|
||||
<button
|
||||
v-if="status == 0"
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-200 space-x-8"></div>
|
||||
</form>
|
||||
|
||||
<form
|
||||
class="space-y-6 pt-8 sm:pt-10 divide-y divide-gray-200"
|
||||
@submit.prevent="doFilter"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터 테스트
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
입력된 문장을 api를 이용해 검사하고 그 결과를
|
||||
표시합니다.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
검사하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 sm:mt-5 space-y-6 sm:space-y-5">
|
||||
<div>
|
||||
<fieldset class="mt-4">
|
||||
<legend class="sr-only">Filter Mode</legend>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="notificationMethod in notificationMethods"
|
||||
:key="notificationMethod.id"
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
:id="notificationMethod.id"
|
||||
v-model="filterMode"
|
||||
:value="notificationMethod.id"
|
||||
name="notification-method"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
:for="notificationMethod.id"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{{ notificationMethod.title }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="filterText"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
검사할 문장 입력
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<textarea
|
||||
id="filterText"
|
||||
v-model="filterText"
|
||||
name="filterText"
|
||||
rows="3"
|
||||
class="max-w-lg shadow-sm block w-full focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md"
|
||||
/>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
필터 테스트를 위한 문장을 입력하고 아래
|
||||
검사하기 버튼을 누르세요. <br />
|
||||
(마지막 실행시간
|
||||
{{ elispe.toFixed(2) }} 밀리초)
|
||||
</p>
|
||||
<pre>{{ resultJson }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const hero = route.params.hero;
|
||||
|
||||
console.log('hero=', hero);
|
||||
|
||||
const apiKey = ref('');
|
||||
const name = ref('');
|
||||
const level = ref('');
|
||||
const status = ref(0);
|
||||
|
||||
const notificationMethods = [
|
||||
{
|
||||
id: 'quick',
|
||||
title: 'mode = quick. 첫번째 매칭이 발견되면 더 이상 검사하지 않고 바로 결과를 리턴',
|
||||
},
|
||||
{ id: 'normal', title: 'mode = normal. 전체를 검사하여 모든 매칭을 리턴' },
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'mode = filter. 전체를 검사하며 모든 매칭을 마스크로 치환한 결과까지 리턴',
|
||||
},
|
||||
];
|
||||
|
||||
const filterText = ref('테스트할 문장을 여기에 입력해 주세요.');
|
||||
|
||||
const filterMode = ref('filter');
|
||||
|
||||
const filterCallback = ref('');
|
||||
|
||||
const resultJson = ref({});
|
||||
|
||||
const elispe = ref(0);
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
async function doFilter() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
inPregressFlag.value = true;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
const responseJson = await _crossCtl.doFilterRaw(apiKey.value, {
|
||||
text: filterText.value,
|
||||
mode: filterMode.value,
|
||||
});
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
inPregressFlag.value = false;
|
||||
|
||||
elispe.value = endTime - startTime;
|
||||
|
||||
// console.log('responseJson=', responseJson);
|
||||
const result = responseJson;
|
||||
resultJson.value = result;
|
||||
|
||||
if (responseJson['Status']['Code'] == 2000) {
|
||||
// alert(result.length + ' match found');
|
||||
} else {
|
||||
alert(responseJson['Status']['Message']);
|
||||
}
|
||||
}
|
||||
|
||||
function gotoStatistics() {
|
||||
navigateTo('/key/' + hero + '/statistics');
|
||||
}
|
||||
|
||||
function gotoWordStatistics() {
|
||||
navigateTo('/word/' + hero + '/statistics');
|
||||
}
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 0) {
|
||||
_crossCtl.openModal(
|
||||
'error',
|
||||
'잘못된 파라메타',
|
||||
'키 정보를 읽어올 수 없습니다.\n확인 버튼을 누르면 메인 화면으로 돌아갑니다.',
|
||||
['확인'],
|
||||
(btnIdx) => {
|
||||
navigateTo('/');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const keyInfo = responseJson['data'][0];
|
||||
|
||||
apiKey.value = keyInfo['api_key'];
|
||||
name.value = keyInfo['name'];
|
||||
level.value = keyInfo['level'];
|
||||
status.value = keyInfo['status'];
|
||||
}
|
||||
|
||||
async function doUpdate() {
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'key', {
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
status: status.value,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
|
||||
async function doDelete() {
|
||||
if (status.value == 0) {
|
||||
const responseJson = await _crossCtl.doComm('local/delete', 'key', {
|
||||
hero: hero,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 4;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'key', {
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
revive: true,
|
||||
status: 0,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 0;
|
||||
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function doCancel() {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
265
inspond-nuxt-safekiso/pages/key/[hero]/log.vue
Normal file
265
inspond-nuxt-safekiso/pages/key/[hero]/log.vue
Normal file
@@ -0,0 +1,265 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<br />
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API KEY 로그 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API KEY의 생성, 변경, 삭제 기록을 확인할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<label for="mobile-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<label for="desktop-search-candidate" class="sr-only"
|
||||
>Search</label
|
||||
>
|
||||
<div class="flex rounded-md shadow-sm">
|
||||
<div class="relative flex-grow focus-within:z-10">
|
||||
<div
|
||||
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
|
||||
>
|
||||
<MagnifyingGlassCircleIcon
|
||||
class="h-5 w-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
id="mobile-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="mobile-search-candidate"
|
||||
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full rounded-none rounded-l-md pl-10 sm:hidden border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
<input
|
||||
id="desktop-search-candidate"
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
name="desktop-search-candidate"
|
||||
class="hidden focus:ring-indigo-500 focus:border-indigo-500 w-full rounded-none rounded-l-md pl-10 sm:block sm:text-sm border-gray-300"
|
||||
placeholder=""
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
@click="doAction('search', searchKeyword)"
|
||||
>
|
||||
<span class="ml-2">검색</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
MagnifyingGlassCircleIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import consolaGlobalInstance from 'consola';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-admin',
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
console.log('route.params=', route.params);
|
||||
|
||||
const targetName = route.params.hero;
|
||||
const hero = route.params.uid;
|
||||
|
||||
console.log('targetName=', targetName);
|
||||
console.log('hero=', hero);
|
||||
|
||||
let listTarget = 'log:user:active';
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '누가',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'uid',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: '언제',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'created',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '무엇을',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'tag',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
];
|
||||
const listActions = ['상세보기'];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'raw',
|
||||
'level',
|
||||
'comment',
|
||||
'status',
|
||||
'updated',
|
||||
'created',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log("columnFilter(), key = ", key, ", val = ", val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'uid') {
|
||||
return '' + targetName + '';
|
||||
} else if (key == 'status') {
|
||||
let statusTag = '정상';
|
||||
switch (val) {
|
||||
case 0:
|
||||
statusTag = '정상등록';
|
||||
break;
|
||||
case 4:
|
||||
statusTag = '삭제됨';
|
||||
break;
|
||||
default:
|
||||
statusTag = val;
|
||||
}
|
||||
return statusTag;
|
||||
} else if (key == 'level') {
|
||||
let levelTag = 'mid';
|
||||
switch (val) {
|
||||
case 10:
|
||||
levelTag = 'high';
|
||||
break;
|
||||
case 50:
|
||||
levelTag = 'mid';
|
||||
break;
|
||||
case 100:
|
||||
levelTag = 'low';
|
||||
break;
|
||||
|
||||
default:
|
||||
levelTag = val;
|
||||
}
|
||||
return levelTag;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
|
||||
if (tag == '상세보기') {
|
||||
navigateTo('/admin/user/' + hero + '/history/detail/' + target);
|
||||
} else if (tag == 'search') {
|
||||
console.log('search for ', target);
|
||||
if (target == '') {
|
||||
listTarget = 'log:user:active';
|
||||
} else {
|
||||
listTarget = 'log:user';
|
||||
// hero = target;
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm('list', listTarget, {
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
console.log('listTarget=', listTarget);
|
||||
console.log('hero=', hero);
|
||||
|
||||
console.log('responseJson=', responseJson);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
}
|
||||
|
||||
if (hero != undefined) {
|
||||
doAction('search', hero);
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
</script>
|
||||
433
inspond-nuxt-safekiso/pages/key/[hero]/statistics/index.vue
Normal file
433
inspond-nuxt-safekiso/pages/key/[hero]/statistics/index.vue
Normal file
@@ -0,0 +1,433 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용량 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 사용량 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<select
|
||||
id="targetTerm"
|
||||
v-model="targetTerm"
|
||||
name="targetTerm"
|
||||
class="mt-0 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="term in terms"
|
||||
:key="term.key"
|
||||
:selected="term.current"
|
||||
:value="term.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ term.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'month'"
|
||||
v-model="targetDateMonth"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:year-picker="true"
|
||||
:format="formatForMonth"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'day'"
|
||||
v-model="targetDateDay"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:month-picker="true"
|
||||
:format="formatForDay"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'hour'"
|
||||
v-model="targetDateHour"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:format="formatForHour"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateDay = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateHour = ref($dayjs(new Date().toISOString()));
|
||||
|
||||
// console.log('huk = ', targetDate.value);
|
||||
const formatForMonth = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${year}`;
|
||||
};
|
||||
|
||||
const formatForDay = (date) => {
|
||||
console.log('huk format date = ', date);
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${date.year}${(date.month + 1 < 10 ? '0' : '') + (date.month + 1)}`;
|
||||
};
|
||||
|
||||
const formatForHour = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
const hour = date.getHours();
|
||||
|
||||
return `${year}${(month < 10 ? '0' : '') + month}${
|
||||
(day < 10 ? '0' : '') + day
|
||||
}`;
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'total',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit_ratio',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit_ratio',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'size',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'size_avg',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size_avg',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'ip',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_ip',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'referrer',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_referrer',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm('local/list', 'statistics', {
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: targetKey.value,
|
||||
term: targetTerm.value,
|
||||
// termPrefix: _utils.getDateTimeTag(targetTerm.value.substring(0, 1)),
|
||||
// termPrefix: _utils.getDateTimeTag('y'),
|
||||
termPrefix: targetDate.value,
|
||||
});
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const targetTerm = ref('year');
|
||||
const targetKey = ref('');
|
||||
|
||||
const tmpTerms = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
terms.value = tmpTerms;
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 0) {
|
||||
_crossCtl.openModal(
|
||||
'error',
|
||||
'잘못된 파라메타',
|
||||
'키 정보를 읽어올 수 없습니다.\n확인 버튼을 누르면 메인 화면으로 돌아갑니다.',
|
||||
['확인'],
|
||||
(btnIdx) => {
|
||||
navigateTo('/');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
targetKey.value = responseJson['data'][0]['api_key'];
|
||||
}
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
|
||||
let result = date;
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
result = `${date.year}${
|
||||
(date.month + 1 < 10 ? '0' : '') + (date.month + 1)
|
||||
}`;
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
result = $dayjs(date).format('YYYYMMDD');
|
||||
break;
|
||||
}
|
||||
|
||||
targetDate.value = result;
|
||||
|
||||
console.log('huk result = ', result);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMM'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMMDD'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
terms.value = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
175
inspond-nuxt-safekiso/pages/key/deleted.vue
Normal file
175
inspond-nuxt-safekiso/pages/key/deleted.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
삭제된 키 리스트
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
삭제된 내 API 키를 보고 복구할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex mr-3 items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-400 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="$router.push('/key/list')"
|
||||
>
|
||||
키 리스트
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '이름',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'name',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [
|
||||
{ class: 'sr-only', title: '단계' },
|
||||
{ class: 'sr-only sm:hidden', title: '상태' },
|
||||
],
|
||||
dds: [
|
||||
{ class: 'mt-1 truncate text-gray-700', key: 'level' },
|
||||
{
|
||||
class: 'mt-1 truncate text-gray-500 sm:hidden',
|
||||
key: 'status',
|
||||
},
|
||||
],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: '단계',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'level',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '상태',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'status',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: '생성일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'created',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = ['상세보기'];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = ['serial', 'api_key', 'name', 'level', 'status', 'created'];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
const { $dayjs } = useNuxtApp();
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
// return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
return $dayjs(val).format('YY/MM/DD');
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function maekNewKey() {
|
||||
return navigateTo({
|
||||
path: '/key/new',
|
||||
query: {},
|
||||
});
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
/*
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
*/
|
||||
navigateTo('/key/' + target + '/edit');
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (process.client) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'key:deleted',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
</script>
|
||||
425
inspond-nuxt-safekiso/pages/key/edit.vue
Normal file
425
inspond-nuxt-safekiso/pages/key/edit.vue
Normal file
@@ -0,0 +1,425 @@
|
||||
<template>
|
||||
<div class="m-0">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doUpdate"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div
|
||||
class="divide-y divide-gray-200 pt-0 space-y-6 sm:pt-0 sm:space-y-5"
|
||||
>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 키 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API키의 설정을 변경하거나 삭제할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="gotoStatistics()"
|
||||
>
|
||||
통계 보기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="name"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
이름
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
name="name"
|
||||
autocomplete="name"
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="api-key"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
API 키
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="api-key"
|
||||
v-model="apiKey"
|
||||
type="text"
|
||||
name="api-key"
|
||||
autocomplete="api-key"
|
||||
disabled
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p class="text-sm text-gray-500">
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대 필터만
|
||||
동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="high"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="mid"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="low"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doDelete"
|
||||
>
|
||||
{{ status == 0 ? '삭제' : '복구' }}
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
이전화면
|
||||
</button>
|
||||
<button
|
||||
v-if="status == 0"
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divide-y divide-gray-200 space-x-8"></div>
|
||||
</form>
|
||||
|
||||
<form
|
||||
class="space-y-6 pt-8 sm:pt-10 divide-y divide-gray-200"
|
||||
@submit.prevent="doFilter"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div>
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터 테스트
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
입력된 문장을 api를 이용해 검사하고 그 결과를
|
||||
표시합니다.
|
||||
</p>
|
||||
</div>
|
||||
<img
|
||||
v-if="inPregressFlag"
|
||||
width="32"
|
||||
src="/loading-load-2.gif"
|
||||
/>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
검사하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 sm:mt-5 space-y-6 sm:space-y-5">
|
||||
<div>
|
||||
<fieldset class="mt-4">
|
||||
<legend class="sr-only">Filter Mode</legend>
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="notificationMethod in notificationMethods"
|
||||
:key="notificationMethod.id"
|
||||
class="flex items-center"
|
||||
>
|
||||
<input
|
||||
:id="notificationMethod.id"
|
||||
v-model="filterMode"
|
||||
:value="notificationMethod.id"
|
||||
name="notification-method"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
:for="notificationMethod.id"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
{{ notificationMethod.title }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="filterText"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
검사할 문장 입력
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<textarea
|
||||
id="filterText"
|
||||
v-model="filterText"
|
||||
name="filterText"
|
||||
rows="3"
|
||||
class="max-w-lg shadow-sm block w-full focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md"
|
||||
/>
|
||||
<p class="mt-2 text-sm text-gray-500">
|
||||
필터 테스트를 위한 문장을 입력하고 아래
|
||||
검사하기 버튼을 누르세요. <br />
|
||||
(마지막 실행시간
|
||||
{{ elispe.toFixed(2) }} 밀리초)
|
||||
</p>
|
||||
<pre>{{ resultJson }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const hero = route.params.target;
|
||||
|
||||
console.log('hero=', hero);
|
||||
|
||||
const apiKey = ref('');
|
||||
const name = ref('');
|
||||
const level = ref('');
|
||||
const status = ref(0);
|
||||
|
||||
const notificationMethods = [
|
||||
{
|
||||
id: 'quick',
|
||||
title: 'mode = quick. 첫번째 매칭이 발견되면 더 이상 검사하지 않고 바로 결과를 리턴',
|
||||
},
|
||||
{ id: 'normal', title: 'mode = normal. 전체를 검사하여 모든 매칭을 리턴' },
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'mode = filter. 전체를 검사하며 모든 매칭을 마스크로 치환한 결과까지 리턴',
|
||||
},
|
||||
];
|
||||
|
||||
const filterText = ref('테스트할 문장을 여기에 입력해 주세요.');
|
||||
|
||||
const filterMode = ref('filter');
|
||||
|
||||
const filterCallback = ref('');
|
||||
|
||||
const resultJson = ref({});
|
||||
|
||||
const elispe = ref(0);
|
||||
|
||||
const inPregressFlag = ref(false);
|
||||
|
||||
async function doFilter() {
|
||||
if (inPregressFlag.value == true) {
|
||||
alert('이전 동작이 아직 진행중입니다.');
|
||||
return;
|
||||
}
|
||||
inPregressFlag.value = true;
|
||||
|
||||
const startTime = performance.now();
|
||||
|
||||
const responseJson = await _crossCtl.doFilterRaw(apiKey.value, {
|
||||
text: filterText.value,
|
||||
mode: filterMode.value,
|
||||
});
|
||||
|
||||
const endTime = performance.now();
|
||||
|
||||
inPregressFlag.value = false;
|
||||
|
||||
elispe.value = endTime - startTime;
|
||||
|
||||
// console.log('responseJson=', responseJson);
|
||||
const result = responseJson;
|
||||
resultJson.value = result;
|
||||
|
||||
if (responseJson['Status']['Code'] == 2000) {
|
||||
// alert(result.length + ' match found');
|
||||
} else {
|
||||
alert(responseJson['Status']['Message']);
|
||||
}
|
||||
}
|
||||
|
||||
function gotoStatistics() {
|
||||
navigateTo('/key/' + hero + '/statistics/year');
|
||||
}
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
const keyInfo = responseJson['data'][0];
|
||||
|
||||
apiKey.value = keyInfo['api_key'];
|
||||
name.value = keyInfo['name'];
|
||||
level.value = keyInfo['level'];
|
||||
status.value = keyInfo['status'];
|
||||
|
||||
async function doUpdate() {
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'key', {
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
status: status.value,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
|
||||
async function doDelete() {
|
||||
if (status.value == 0) {
|
||||
const responseJson = await _crossCtl.doComm('local/delete', 'key', {
|
||||
hero: hero,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 4;
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
} else {
|
||||
const responseJson = await _crossCtl.doComm('local/update', 'key', {
|
||||
hero: hero,
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
status: 0,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
status.value = 0;
|
||||
|
||||
alert('ok');
|
||||
} else {
|
||||
alert(responseJson['responseMessage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function doCancel() {
|
||||
router.back();
|
||||
}
|
||||
</script>
|
||||
194
inspond-nuxt-safekiso/pages/key/list.vue
Normal file
194
inspond-nuxt-safekiso/pages/key/list.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">키 리스트</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
내 API 키를 보고 관리할 수 있습니다. ({{ recordsTotal }} /
|
||||
{{ limitCount }})
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex mr-3 items-center justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="$router.push('/key/deleted')"
|
||||
>
|
||||
삭제 키 리스트
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto"
|
||||
@click="maekNewKey"
|
||||
>
|
||||
새 키 만들기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '이름',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'name',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [
|
||||
{ class: 'sr-only', title: '단계' },
|
||||
{ class: 'sr-only sm:hidden', title: '상태' },
|
||||
],
|
||||
dds: [
|
||||
{ class: 'mt-1 truncate text-gray-700', key: 'level' },
|
||||
{
|
||||
class: 'mt-1 truncate text-gray-500 sm:hidden',
|
||||
key: 'status',
|
||||
},
|
||||
],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: '단계',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'level',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: '상태',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'status',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: '생성일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'created',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = ['상세보기'];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = ['serial', 'api_key', 'name', 'level', 'status', 'created'];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(10);
|
||||
const recordsTotal = ref(0);
|
||||
|
||||
const limitCount = ref(5);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
const { $dayjs } = useNuxtApp();
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
// return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
return $dayjs(val).format('YY/MM/DD');
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function maekNewKey() {
|
||||
if (recordsTotal.value >= limitCount.value) {
|
||||
alert(
|
||||
'키 생성 갯수 제한을 초과 할 수 없습니다. 관리자에게 문의하세요.'
|
||||
);
|
||||
} else {
|
||||
return navigateTo({
|
||||
path: '/key/new',
|
||||
query: {},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
/*
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
*/
|
||||
navigateTo('/key/' + target + '/edit');
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
if (process.client) {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'key:active',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
limitCount.value = responseJson['metaData']['limitCount'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
}
|
||||
refresh();
|
||||
</script>
|
||||
206
inspond-nuxt-safekiso/pages/key/new.vue
Normal file
206
inspond-nuxt-safekiso/pages/key/new.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<!--
|
||||
This example requires Tailwind CSS v2.0+
|
||||
|
||||
This example requires some changes to your config:
|
||||
|
||||
```
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
// ...
|
||||
plugins: [
|
||||
// ...
|
||||
require('@tailwindcss/forms'),
|
||||
],
|
||||
}
|
||||
```
|
||||
-->
|
||||
<template>
|
||||
<div class="m-8">
|
||||
<form
|
||||
class="space-y-8 divide-y divide-gray-200"
|
||||
@submit.prevent="doCreate"
|
||||
>
|
||||
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
|
||||
<div
|
||||
class="divide-y divide-gray-200 pt-8 space-y-6 sm:pt-10 sm:space-y-5"
|
||||
>
|
||||
<div>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
새 API 키 생성
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500">
|
||||
모욕적 언어 필터 서비스 이용에 필요한 새로운 API
|
||||
키를 만듭니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5"
|
||||
>
|
||||
<label
|
||||
for="first-name"
|
||||
class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2"
|
||||
>
|
||||
이름
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<input
|
||||
id="first-name"
|
||||
v-model="name"
|
||||
type="text"
|
||||
name="first-name"
|
||||
autocomplete="given-name"
|
||||
class="max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="space-y-6 sm:space-y-5 divide-y divide-gray-200"
|
||||
>
|
||||
<div class="pt-6 sm:pt-5">
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="label-notifications"
|
||||
>
|
||||
<div
|
||||
class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
id="label-notifications"
|
||||
class="text-base font-medium text-gray-900 sm:text-sm sm:text-gray-700"
|
||||
>
|
||||
필터 단계
|
||||
</div>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<p class="text-sm text-gray-500">
|
||||
필터 대상이 되는 단계를
|
||||
선택합니다. (현재는 최대 필터만
|
||||
동작)
|
||||
</p>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-high"
|
||||
v-model="level"
|
||||
value="high"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
class="focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-high"
|
||||
class="ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
최대 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-mid"
|
||||
v-model="level"
|
||||
value="mid"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
disabled
|
||||
class="opacity-75 focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-mid"
|
||||
class="opacity-75 ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
중간 필터
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input
|
||||
id="filter-low"
|
||||
v-model="level"
|
||||
value="low"
|
||||
name="filter-level"
|
||||
type="radio"
|
||||
disabled
|
||||
class="opacity-75 focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300"
|
||||
/>
|
||||
<label
|
||||
for="filter-low"
|
||||
class="opacity-75 ml-3 block text-sm font-medium text-gray-700"
|
||||
>
|
||||
가장 가벼운 필터
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
@click="doCancel"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
만들기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const name = ref('');
|
||||
const level = ref('high');
|
||||
|
||||
async function doCancel() {
|
||||
router.back();
|
||||
}
|
||||
|
||||
watch(level, (newValue, oldValue) => {
|
||||
console.log('level 의 변이가 감지되었을 때 ', {
|
||||
newValue,
|
||||
oldValue,
|
||||
});
|
||||
});
|
||||
|
||||
async function doCreate() {
|
||||
const responseJson = await _crossCtl.doComm('local/insert', 'key', {
|
||||
name: name.value,
|
||||
level: level.value,
|
||||
});
|
||||
console.log('responseJson=', responseJson);
|
||||
if (responseJson['responseMessage'] == 'ok') {
|
||||
alert('ok');
|
||||
router.back();
|
||||
} else {
|
||||
if (responseJson['responseMessage'] == 'exceed limit') {
|
||||
alert(
|
||||
'키 생성 갯수 제한을 초과 할 수 없습니다. 관리자에게 문의하세요.'
|
||||
);
|
||||
} else {
|
||||
responseJson['responseMessage'];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
47
inspond-nuxt-safekiso/pages/key/statistics/daily.vue
Normal file
47
inspond-nuxt-safekiso/pages/key/statistics/daily.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 키 사용 통계
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
내 API 키의 사용량을 확인할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"></div>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<nav class="space-y-1" aria-label="Sidebar">
|
||||
<a
|
||||
v-for="item in navigation"
|
||||
:key="item.name"
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
|
||||
'flex items-center px-3 py-2 text-sm font-medium rounded-md',
|
||||
]"
|
||||
:aria-current="'page'"
|
||||
@click="$router.push(item.href)"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const navigation = [
|
||||
{ name: '일별 통계', href: '/key/statistics/daily' },
|
||||
{ name: '월별 통계', href: '/key/statistics/monthly' },
|
||||
];
|
||||
</script>
|
||||
276
inspond-nuxt-safekiso/pages/key/statistics/detail.vue
Normal file
276
inspond-nuxt-safekiso/pages/key/statistics/detail.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용량 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 사용량 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>현재 통계 범위 : {{ targetTerm }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const router = useRouter();
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '키',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'api_key',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'total',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit_ratio',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit_ratio',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'size',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'size_avg',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size_avg',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'ip',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_ip',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'referrer',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_referrer',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'api_key',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
const { $dayjs } = useNuxtApp();
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else if (key == 'api_key') {
|
||||
if (keyNameMap[val] != undefined) {
|
||||
return keyNameMap[val];
|
||||
} else {
|
||||
return 'invailed_key';
|
||||
}
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'statistics:month',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: targetTerm.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const targetTerm = ref(_utils.getDateTimeTag('m'));
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const keyNameMap = {};
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/list', 'key', {
|
||||
start: 0,
|
||||
length: -1,
|
||||
});
|
||||
|
||||
const tmpTerms = [];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
keyNameMap[responseJson['data'][i]['api_key']] =
|
||||
responseJson['data'][i]['name'];
|
||||
}
|
||||
|
||||
console.log('tmpTerms=', tmpTerms);
|
||||
terms.value = tmpTerms;
|
||||
|
||||
function onChangeTerm(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
413
inspond-nuxt-safekiso/pages/key/statistics/index.vue
Normal file
413
inspond-nuxt-safekiso/pages/key/statistics/index.vue
Normal file
@@ -0,0 +1,413 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용량 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 사용량 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<select
|
||||
id="targetTerm"
|
||||
v-model="targetTerm"
|
||||
name="targetTerm"
|
||||
class="mt-0 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="term in terms"
|
||||
:key="term.key"
|
||||
:selected="term.current"
|
||||
:value="term.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ term.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'month'"
|
||||
v-model="targetDateMonth"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:year-picker="true"
|
||||
:format="formatForMonth"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'day'"
|
||||
v-model="targetDateDay"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:month-picker="true"
|
||||
:format="formatForDay"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'hour'"
|
||||
v-model="targetDateHour"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:format="formatForHour"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateDay = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateHour = ref($dayjs(new Date().toISOString()));
|
||||
|
||||
// console.log('huk = ', targetDate.value);
|
||||
const formatForMonth = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${year}`;
|
||||
};
|
||||
|
||||
const formatForDay = (date) => {
|
||||
console.log('huk format date = ', date);
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${date.year}${(date.month + 1 < 10 ? '0' : '') + (date.month + 1)}`;
|
||||
};
|
||||
|
||||
const formatForHour = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
const hour = date.getHours();
|
||||
|
||||
return `${year}${(month < 10 ? '0' : '') + month}${
|
||||
(day < 10 ? '0' : '') + day
|
||||
}`;
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'total',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit_ratio',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit_ratio',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'size',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'size_avg',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size_avg',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'ip',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_ip',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'referrer',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_referrer',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm('local/list', 'statistics', {
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
term: targetTerm.value,
|
||||
// termPrefix: _utils.getDateTimeTag(targetTerm.value.substring(0, 1)),
|
||||
// termPrefix: _utils.getDateTimeTag('y'),
|
||||
termPrefix: targetDate.value,
|
||||
});
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const targetTerm = ref('month');
|
||||
|
||||
const tmpTerms = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
terms.value = tmpTerms;
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
|
||||
let result = date;
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
result = `${date.year}${
|
||||
(date.month + 1 < 10 ? '0' : '') + (date.month + 1)
|
||||
}`;
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
result = $dayjs(date).format('YYYYMMDD');
|
||||
break;
|
||||
}
|
||||
|
||||
targetDate.value = result;
|
||||
|
||||
console.log('huk result = ', result);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMM'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMMDD'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
terms.value = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
245
inspond-nuxt-safekiso/pages/key/statistics/index_bak.vue
Normal file
245
inspond-nuxt-safekiso/pages/key/statistics/index_bak.vue
Normal file
@@ -0,0 +1,245 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 키 사용 통계
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
내 API 키의 사용량을 확인할 수 있습니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none"></div>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 bg-gray-100">
|
||||
<div class="pt-12 sm:pt-16 lg:pt-20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<h2
|
||||
class="text-3xl font-extrabold text-gray-900 sm:text-4xl lg:text-5xl"
|
||||
>
|
||||
이번달 사용량과 예상 비용
|
||||
</h2>
|
||||
<p class="mt-4 text-xl text-gray-600">
|
||||
이 페이지를 보고 있는 시점까지 가공된 로그를
|
||||
기준으로 사용량을 판정하여 비용을 예상해 보여
|
||||
드립니다.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 bg-white pb-16 sm:mt-12 sm:pb-20 lg:pb-28">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 h-1/2 bg-gray-100" />
|
||||
<div
|
||||
class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"
|
||||
>
|
||||
<div
|
||||
class="max-w-lg mx-auto rounded-lg shadow-lg overflow-hidden lg:max-w-none lg:flex"
|
||||
>
|
||||
<div class="flex-1 bg-white px-6 py-8 lg:p-12">
|
||||
<h3
|
||||
class="text-2xl font-extrabold text-gray-900 sm:text-3xl"
|
||||
>
|
||||
실시간 사용량
|
||||
</h3>
|
||||
|
||||
<ul
|
||||
role="list"
|
||||
class="mt-8 space-y-5 lg:space-y-0 lg:grid lg:grid-cols-2 lg:gap-x-8 lg:gap-y-5"
|
||||
>
|
||||
<li class="flex items-start lg:col-span-1">
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
class="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p class="ml-1 text-sm text-gray-700">
|
||||
총 호출 :
|
||||
{{
|
||||
_utils.formatNumberInBytesStyle(
|
||||
total,
|
||||
2
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="flex items-start lg:col-span-1">
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
class="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p class="ml-3 text-sm text-gray-700">
|
||||
검출 수 :
|
||||
{{
|
||||
_utils.formatNumberInBytesStyle(
|
||||
hit,
|
||||
2
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="flex items-start lg:col-span-1">
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
class="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p class="ml-1 text-sm text-gray-700">
|
||||
전송량 :
|
||||
{{ _utils.formatBytes(size, 2) }}
|
||||
</p>
|
||||
</li>
|
||||
|
||||
<li class="flex items-start lg:col-span-1">
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
class="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p class="ml-1 text-sm text-gray-700">
|
||||
검출율 :
|
||||
{{
|
||||
(hitRatio * 100).toFixed(2) +
|
||||
'%'
|
||||
}}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-8">
|
||||
<ul>
|
||||
<li
|
||||
class="flex items-start lg:col-span-1"
|
||||
>
|
||||
<div class="flex-shrink-0">
|
||||
<CheckCircleIcon
|
||||
class="h-5 w-5 text-green-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
class="ml-1 text-sm text-gray-700"
|
||||
>
|
||||
평균 전송량 :
|
||||
{{
|
||||
_utils.formatBytes(
|
||||
avgSize,
|
||||
2
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="py-8 px-6 text-center bg-gray-50 lg:flex-shrink-0 lg:flex lg:flex-col lg:justify-center lg:p-12"
|
||||
>
|
||||
<p
|
||||
class="text-lg leading-6 font-medium text-gray-900"
|
||||
>
|
||||
예상 요금
|
||||
</p>
|
||||
<div
|
||||
class="mt-4 flex items-center justify-center text-4xl font-extrabold text-gray-900"
|
||||
>
|
||||
<span>
|
||||
₩{{
|
||||
_utils.formatNumberWithComma(
|
||||
(total - 10 < 0
|
||||
? 0
|
||||
: total - 10) * 1
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
class="ml-3 text-xl font-medium text-gray-500"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<p class="mt-4 text-sm">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="font-medium text-gray-500 underline"
|
||||
@click="navigateTo('/doc/bill')"
|
||||
>
|
||||
요금 계산 기준 보기
|
||||
</a>
|
||||
</p>
|
||||
<div class="mt-6"></div>
|
||||
<div class="mt-4 text-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="space-y-1" aria-label="Sidebar">
|
||||
<a
|
||||
v-for="item in navigation"
|
||||
:key="item.name"
|
||||
href="javascript:void(0)"
|
||||
:class="[
|
||||
'text-gray-600 hover:bg-gray-100 hover:text-gray-900',
|
||||
'flex items-center px-3 py-2 text-sm font-medium rounded-md',
|
||||
]"
|
||||
:aria-current="'page'"
|
||||
@click="$router.push(item.href)"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CheckCircleIcon } from '@heroicons/vue/24/solid';
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const total = ref(0);
|
||||
const hit = ref(0);
|
||||
const size = ref(0);
|
||||
const hitRatio = ref(0);
|
||||
const avgSize = ref(0);
|
||||
|
||||
const includedFeatures = [
|
||||
'Private forum access',
|
||||
'Member resources',
|
||||
'Entry to annual conference',
|
||||
'Official member t-shirt',
|
||||
];
|
||||
const navigation = [{ name: '상세 통계 보기', href: '/key/statistics/detail' }];
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/list', 'bill:month', {
|
||||
start: 0,
|
||||
length: 1,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 1) {
|
||||
total.value = responseJson['data'][0]['total'];
|
||||
hit.value = responseJson['data'][0]['hit'];
|
||||
size.value = responseJson['data'][0]['size'];
|
||||
hitRatio.value =
|
||||
responseJson['data'][0]['hit'] / responseJson['data'][0]['total'];
|
||||
avgSize.value =
|
||||
responseJson['data'][0]['size'] / responseJson['data'][0]['total'];
|
||||
}
|
||||
|
||||
console.log('huk responseJson = ', responseJson);
|
||||
</script>
|
||||
38
inspond-nuxt-safekiso/pages/key/test.vue
Normal file
38
inspond-nuxt-safekiso/pages/key/test.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="bg-white">
|
||||
<div class="max-w-7xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<h2
|
||||
class="text-base font-semibold text-indigo-600 tracking-wide uppercase"
|
||||
>
|
||||
API 키
|
||||
</h2>
|
||||
<p
|
||||
class="mt-1 text-4xl font-extrabold text-gray-900 sm:text-5xl sm:tracking-tight lg:text-6xl"
|
||||
>
|
||||
키 필터 테스트
|
||||
</p>
|
||||
<p class="max-w-xl mt-5 mx-auto text-xl text-gray-500">
|
||||
선택된 키의 필터 기능을 테스트 합니다.
|
||||
<br />
|
||||
검사할 텍스트
|
||||
</p>
|
||||
|
||||
<br />
|
||||
갈 수 있는 페이지 :
|
||||
<a href="javascript:void(0)" @click="$router.push('/key/list')">
|
||||
API 키 리스트
|
||||
</a>
|
||||
,
|
||||
|
||||
<a href="javascript:void(0)" @click="$router.push('/')"> 홈 </a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
</script>
|
||||
415
inspond-nuxt-safekiso/pages/statistics/index.vue
Normal file
415
inspond-nuxt-safekiso/pages/statistics/index.vue
Normal file
@@ -0,0 +1,415 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
API 사용량 통계 상세 보기
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 사용량 통계를 보여 줍니다. 구분 항목으로 시간별, 날짜별,
|
||||
월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<select
|
||||
id="targetTerm"
|
||||
v-model="targetTerm"
|
||||
name="targetTerm"
|
||||
class="mt-0 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="term in terms"
|
||||
:key="term.key"
|
||||
:selected="term.current"
|
||||
:value="term.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ term.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'month'"
|
||||
v-model="targetDateMonth"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:year-picker="true"
|
||||
:format="formatForMonth"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'day'"
|
||||
v-model="targetDateDay"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:month-picker="true"
|
||||
:format="formatForDay"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'hour'"
|
||||
v-model="targetDateHour"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:format="formatForHour"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<BasePagination1
|
||||
:total-page-count="totalPageCount"
|
||||
:current-page-number="currentPageNumber"
|
||||
:page-size="pageSize"
|
||||
:records-total="recordsTotal"
|
||||
:page-move="pageMove"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<!--
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
const { $dayjs } = useNuxtApp();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateDay = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateHour = ref($dayjs(new Date().toISOString()));
|
||||
|
||||
// console.log('huk = ', targetDate.value);
|
||||
const formatForMonth = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${year}`;
|
||||
};
|
||||
|
||||
const formatForDay = (date) => {
|
||||
console.log('huk format date = ', date);
|
||||
// return `${year}${(month < 10 ? '0' : '') + month}${(day < 10 ? '0' : '') + day}`;
|
||||
return `${date.year}${(date.month + 1 < 10 ? '0' : '') + (date.month + 1)}`;
|
||||
};
|
||||
|
||||
const formatForHour = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
const hour = date.getHours();
|
||||
|
||||
return `${year}${(month < 10 ? '0' : '') + month}${
|
||||
(day < 10 ? '0' : '') + day
|
||||
}`;
|
||||
};
|
||||
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'total',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'total',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit_ratio',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'hit_ratio',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'size',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'size_avg',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'size_avg',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'ip',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_ip',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'referrer',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'uniq_referrer',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm('local/list', 'statistics:my', {
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
term: targetTerm.value,
|
||||
// termPrefix: _utils.getDateTimeTag(targetTerm.value.substring(0, 1)),
|
||||
// termPrefix: _utils.getDateTimeTag('y'),
|
||||
termPrefix: targetDate.value,
|
||||
});
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const targetTerm = ref('year');
|
||||
|
||||
const tmpTerms = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
terms.value = tmpTerms;
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
|
||||
let result = date;
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
result = `${date.year}${
|
||||
(date.month + 1 < 10 ? '0' : '') + (date.month + 1)
|
||||
}`;
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
result = $dayjs(date).format('YYYYMMDD');
|
||||
break;
|
||||
}
|
||||
|
||||
targetDate.value = result;
|
||||
|
||||
console.log('huk result = ', result);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMM'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'hour':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMMDD'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
terms.value = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
{ current: targetTerm.value == 'hour', key: 'hour' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
358
inspond-nuxt-safekiso/pages/word/[hero]/statistics/index.vue
Normal file
358
inspond-nuxt-safekiso/pages/word/[hero]/statistics/index.vue
Normal file
@@ -0,0 +1,358 @@
|
||||
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||
<template>
|
||||
<div class="pb-8 px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-xl font-semibold text-gray-900">
|
||||
필터된 단어 통계 상세 보기 (Top 10 only)
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700">
|
||||
API 키별 딘아 필터 통계를 보여 줍니다. 구분 항목으로 시간별,
|
||||
날짜별, 월별 구분이 가능합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<div>
|
||||
<select
|
||||
id="targetTerm"
|
||||
v-model="targetTerm"
|
||||
name="targetTerm"
|
||||
class="mt-0 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
v-for="term in terms"
|
||||
:key="term.key"
|
||||
:selected="term.current"
|
||||
:value="term.key"
|
||||
>
|
||||
<span class="truncate">
|
||||
{{ term.key }}
|
||||
</span>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'year'"
|
||||
v-model="targetDateYear"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:year-picker="true"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'month'"
|
||||
v-model="targetDateMonth"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:month-picker="true"
|
||||
:format="formatForMonth"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
|
||||
<Datepicker
|
||||
v-if="targetTerm == 'day'"
|
||||
v-model="targetDateDay"
|
||||
class="mt-4 sm:mt-0 sm:ml-2 sm:flex-none"
|
||||
locale="ko"
|
||||
:format="formatForDay"
|
||||
@update:modelValue="handleDate"
|
||||
></Datepicker>
|
||||
</div>
|
||||
|
||||
<BaseList1
|
||||
:headings="listHeadings"
|
||||
:actions="listActions"
|
||||
:keys="listKeys"
|
||||
:data="listData"
|
||||
:action-key="actionKey"
|
||||
:column-filter="columnFilter"
|
||||
:do-action="doAction"
|
||||
/>
|
||||
|
||||
<div class="p-0 rounded-bl-2xl rounded-br-2xl md:px-0">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
class="text-base font-medium text-indigo-700 hover:text-indigo-600"
|
||||
@click="$router.back()"
|
||||
>←이전 화면으로<span aria-hidden="true"> </span
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Datepicker from '@vuepic/vue-datepicker';
|
||||
import '@vuepic/vue-datepicker/dist/main.css';
|
||||
|
||||
const router = useRouter();
|
||||
const { $dayjs } = useNuxtApp();
|
||||
definePageMeta({
|
||||
middleware: 'check-auth-op',
|
||||
});
|
||||
|
||||
const targetDate = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateYear = ref($dayjs(new Date().toISOString()).format('YYYY'));
|
||||
const targetDateMonth = ref({
|
||||
month: new Date().getMonth(),
|
||||
year: new Date().getFullYear,
|
||||
});
|
||||
const targetDateDay = ref(
|
||||
$dayjs(new Date().toISOString()).format('MM/DD/YYYY')
|
||||
);
|
||||
|
||||
// console.log('huk = ', targetDate.value);
|
||||
const format = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${year}${month}${day}`;
|
||||
};
|
||||
|
||||
const formatForDay = (date) => {
|
||||
const day = date.getDate();
|
||||
const month = date.getMonth() + 1;
|
||||
const year = date.getFullYear();
|
||||
const hour = date.getHours();
|
||||
|
||||
return `${year}${(month < 10 ? '0' : '') + month}${
|
||||
(day < 10 ? '0' : '') + day
|
||||
}`;
|
||||
};
|
||||
|
||||
const formatForMonth = (date) => {
|
||||
return `${date.year}${(date.month + 1 < 10 ? '0' : '') + (date.month + 1)}`;
|
||||
};
|
||||
|
||||
const listHeadings = [
|
||||
{
|
||||
title: '구분',
|
||||
class: 'py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6',
|
||||
key: 'date_tag',
|
||||
hiddenInfo: {
|
||||
headClass: 'font-normal lg:hidden',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass:
|
||||
'w-full max-w-0 py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:w-auto sm:max-w-none sm:pl-6',
|
||||
},
|
||||
{
|
||||
title: 'word',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell',
|
||||
key: 'word',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 lg:table-cell',
|
||||
},
|
||||
{
|
||||
title: 'hit',
|
||||
class: 'hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell',
|
||||
key: 'count',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'hidden px-3 py-4 text-sm text-gray-500 sm:table-cell',
|
||||
},
|
||||
|
||||
{
|
||||
title: '갱신일',
|
||||
class: 'px-3 py-3.5 text-left text-sm font-semibold text-gray-900',
|
||||
key: 'updated',
|
||||
hiddenInfo: {
|
||||
headClass: '',
|
||||
dts: [],
|
||||
dds: [],
|
||||
},
|
||||
subClass: 'px-3 py-4 text-sm text-gray-500',
|
||||
},
|
||||
];
|
||||
const listActions = [];
|
||||
const actionKey = 'serial';
|
||||
const listKeys = [
|
||||
'serial',
|
||||
'date_tag',
|
||||
'total',
|
||||
'hit',
|
||||
'size',
|
||||
'uniq_ip',
|
||||
'uniq_referrer',
|
||||
'updated',
|
||||
];
|
||||
const listData = ref([]);
|
||||
|
||||
const totalPageCount = ref(0);
|
||||
const currentPageNumber = ref(1);
|
||||
const pageSize = ref(26);
|
||||
const recordsTotal = ref(0);
|
||||
// const order = [{ column: 'serial', dir: 'desc' }];
|
||||
// const columns = { serial: { data: 'serial' } };
|
||||
|
||||
function columnFilter(key, val) {
|
||||
// console.log('columnFilter(), key = ', key, ', val = ', val);
|
||||
|
||||
if (key == 'updated' || key == 'created') {
|
||||
return $dayjs(val).format('YY/MM/DD A h:mm:ss');
|
||||
// return $dayjs(val).format('YY/MM/DD');
|
||||
} else if (key == 'size') {
|
||||
return _utils.formatBytes(val, 2);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function doAction(tag, target) {
|
||||
console.log('on doAction(), tag=', tag, ', target=', target);
|
||||
router.push({
|
||||
name: 'key-edit',
|
||||
params: { target: target },
|
||||
});
|
||||
}
|
||||
|
||||
function pageMove(targetPageIdex) {
|
||||
console.log('on pageMove(), targetPageIdex=', targetPageIdex);
|
||||
currentPageNumber.value = targetPageIdex;
|
||||
refresh();
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
const responseJson = await _crossCtl.doComm(
|
||||
'local/list',
|
||||
'statistics:word',
|
||||
{
|
||||
start: (currentPageNumber.value - 1) * pageSize.value,
|
||||
length: pageSize.value,
|
||||
hero: targetKey.value,
|
||||
term: targetTerm.value,
|
||||
// termPrefix: _utils.getDateTimeTag(targetTerm.value.substring(0, 1)),
|
||||
termPrefix: targetDate.value,
|
||||
}
|
||||
);
|
||||
|
||||
currentPageNumber.value = responseJson['currentPageNumber'];
|
||||
totalPageCount.value = responseJson['totalPageCount'];
|
||||
pageSize.value = parseInt(responseJson['pageSize']);
|
||||
recordsTotal.value = responseJson['recordsTotal'];
|
||||
|
||||
for (let i = 0; i < responseJson['data'].length; i++) {
|
||||
responseJson['data'][i]['hit_ratio'] =
|
||||
(
|
||||
(responseJson['data'][i]['hit'] /
|
||||
responseJson['data'][i]['total']) *
|
||||
100
|
||||
).toFixed(2) + '%';
|
||||
responseJson['data'][i]['size_avg'] = _utils.formatBytes(
|
||||
responseJson['data'][i]['size'] / responseJson['data'][i]['total'],
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
listData.value = responseJson['data'];
|
||||
|
||||
console.log('listData.value=', listData.value);
|
||||
}
|
||||
|
||||
const terms = ref([]);
|
||||
|
||||
const route = useRoute();
|
||||
const hero = route.params.hero;
|
||||
|
||||
const targetTerm = ref('year');
|
||||
const targetKey = ref('');
|
||||
|
||||
const tmpTerms = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
];
|
||||
|
||||
terms.value = tmpTerms;
|
||||
|
||||
const responseJson = await _crossCtl.doComm('local/select', 'key', {
|
||||
hero: hero,
|
||||
});
|
||||
|
||||
if (responseJson['data'].length == 0) {
|
||||
_crossCtl.openModal(
|
||||
'error',
|
||||
'잘못된 파라메타',
|
||||
'키 정보를 읽어올 수 없습니다.\n확인 버튼을 누르면 메인 화면으로 돌아갑니다.',
|
||||
['확인'],
|
||||
(btnIdx) => {
|
||||
navigateTo('/');
|
||||
}
|
||||
);
|
||||
} else {
|
||||
targetKey.value = responseJson['data'][0]['api_key'];
|
||||
}
|
||||
|
||||
function handleDate(date) {
|
||||
console.log('huk date = ', date);
|
||||
|
||||
let result = date;
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
result = `${date}`;
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
result = `${date.year}${
|
||||
(date.month + 1 < 10 ? '0' : '') + (date.month + 1)
|
||||
}`;
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
result = $dayjs(date).format('YYYYMMDD');
|
||||
break;
|
||||
}
|
||||
|
||||
targetDate.value = result;
|
||||
|
||||
console.log('huk result = ', result);
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
console.log('targetTerm.value=', targetTerm.value);
|
||||
|
||||
switch (targetTerm.value) {
|
||||
case 'year':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format('YYYY');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMM'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'day':
|
||||
targetDate.value = $dayjs(new Date().toISOString()).format(
|
||||
'YYYYMMDD'
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
terms.value = [
|
||||
{ current: targetTerm.value == 'year', key: 'year' },
|
||||
{ current: targetTerm.value == 'month', key: 'month' },
|
||||
{ current: targetTerm.value == 'day', key: 'day' },
|
||||
];
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
refresh();
|
||||
</script>
|
||||
Reference in New Issue
Block a user