Last update

upload project
This commit is contained in:
Sultan A 2025-07-07 12:04:05 +07:00
parent b507cd40d6
commit 4a67610f55
73 changed files with 13061 additions and 187 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,27 @@
// Copyright 2015 The Chromium Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
data_hki18.xlsx Normal file

Binary file not shown.

BIN
data_hki19.xlsx Normal file

Binary file not shown.

BIN
data_hki20.xlsx Normal file

Binary file not shown.

BIN
data_hki21.xlsx Normal file

Binary file not shown.

BIN
data_hki22.xlsx Normal file

Binary file not shown.

BIN
data_penelitian10.xlsx Normal file

Binary file not shown.

BIN
data_penelitian11.xlsx Normal file

Binary file not shown.

BIN
data_penelitian12.xlsx Normal file

Binary file not shown.

BIN
data_penelitian13.xlsx Normal file

Binary file not shown.

BIN
data_penelitian14.xlsx Normal file

Binary file not shown.

BIN
data_penelitian15.xlsx Normal file

Binary file not shown.

BIN
data_penelitian16.xlsx Normal file

Binary file not shown.

BIN
data_penelitian17.xlsx Normal file

Binary file not shown.

BIN
data_penelitian18.xlsx Normal file

Binary file not shown.

BIN
data_penelitian19.xlsx Normal file

Binary file not shown.

BIN
data_penelitian6.xlsx Normal file

Binary file not shown.

BIN
data_penelitian7.xlsx Normal file

Binary file not shown.

BIN
data_penelitian8.xlsx Normal file

Binary file not shown.

BIN
data_penelitian9.xlsx Normal file

Binary file not shown.

BIN
data_pengabdian4.xlsx Normal file

Binary file not shown.

BIN
data_pengabdian5.xlsx Normal file

Binary file not shown.

BIN
data_scopus5.xlsx Normal file

Binary file not shown.

BIN
data_scopus6.xlsx Normal file

Binary file not shown.

BIN
data_scopus7.xlsx Normal file

Binary file not shown.

View File

@ -0,0 +1,49 @@
Panduan
Dashboard
Publikasi
Dosen
PANDUAN PENGGUNAAN
DASHBOARD PUBLIKASI DOSEN
1. Pilih Menu “Dashboard”
2. Pengguna dapat memuat ulang grafik visualisasi
data hasil scraping yang telah dikirim melewati
halaman “Scraper”.
3. Pengguna dapat mengolah data sesuai kebutuhan, seperti
mengunduh hasil data yang telah dimanipulasi dengan
tombol ⬇ hijau, memuat ulang grafik visualisasi tertentu
dengan tombol <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>berwarna biru, Serta memanipulasi data
dengan tombol/link “Edit Data Grafik”
4. Saat Pengguna menekan tombol/link “Edit Data
Grafik” pada suatu grafik maka akan memunculkan
tabel yang memungkinkan pengguna memanipulasi
data seperti menambahkan baris baru, menghapus
data tertentu, mengubah isi data tersebut sesuai
kebutuhan.
5. Pengguna dapat meninjau total data secara keseluruhan
data yang terhubung dengan grafik.
6. Pengguna dapat menyimpan perubahan / hasil
manipulasi data dengan menekan tombol biru “Simpan
Perubahan”.
7. Saat Pengguna menggulirkan halaman dashboard
ke bawah maka akan muncul grafik yang meninjau
data dari pie, untuk menampilkan grafik pie tersebut
maka pengguna perlu memilih date agar
menampilkan data publikasi sesuai tahun
8. Pengguna dapat meninjau hasil grafik pie berdasarkan
tahun yang telah dipilih untuk mendapatkan
insight(wawasan) dari kontribusi data publikasi dosen
seperti data Hak kaya Intelektual, Pengabdian
Masyarakat, dan Penelitian.

View File

@ -0,0 +1,79 @@
Panduan
Scraping Data
Publikasi
Dosen
1. Panduan Penggunaan Scraper untuk Mengambil
Data.
2. Panduan Pembersihan Data Hasil Scraping.
PANDUAN PENGGUNAAN
SCRAPER UNTUK MENGAMBIL
DATA
1. Pilih Menu “Scraper”
2. Pengguna dapat memulai proses scraping pada
tombol - tombol berdasarkan tujuan dan kebutuhan
dari sumber portal SINTA dan Google Scholar.
3. Pengguna memerlukan atau menyimpan
ID dari sebuah afiliasi/institusi pendidikan
untuk bisa melakukan scraping.
4. Setelah ID afiliasi/institusi pendidikan telah
disimpan maka pengguna perlu mengisinya di
dalam kolom teks ini.
Tampilan halaman “Form Input/edit Pasien Rawat Inap”
5. Pengguna bisa memulai scraping data
dengan menekan tombol “Scrape”
6. Pengguna perlu menunggu proses scraping
hingga Chrome driver memulai proses
scraping
7. Chrome Driver akan muncul pada layar setelah
proses scraping telah dieksekusi. Pengguna
diharapkan mengisi akun/kredensial dan login ke
Portal SINTA
6. Sistem Scraper akan mulai bekerja dan terus
melakukan scraping data hingga selesai pada
halaman terakhir.
9. Setelah proses scraping selesai maka file tersebut
telah tersimpan di folder dan siap untuk digunakan
sesuai kebutuhan pengguna
10. Pengguna dapat mengelola data hasil
scraping tersebut untuk dibersihkan dan
diproses sesuai kebutuhan.
PANDUAN PEMBERSIHAN DATA
HASIL SCRAPING
1. Pilih Menu “Scraper”
2. Pengguna dapat membersihkan data dari file excel hasil
scraping dengan drag-and-drop(tarik dan lepas) file
tersebut ke kotak upload tersebut atau bisa diupload
manual
3. Pengguna diharapkan menunggu proses
pembersihan file hasil scraping oleh sistem.
4. Pengguna bisa memilih opsi yang bertujuan
di-visualisasikan ke grafik pada halaman dashboard
5. Pengguna bisa langsung menuju Dashboard
untuk melihat hasil file excel dari scraping yang
telah dibersihkan ke dashboard dengan
menekan tombol ini.
6. File yang telah dibersihkan dapat terlihat di
dashboard dan Pengguna dapat mengolah data
tersebut untuk kebutuhan selanjutnya

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

View File

@ -1,6 +1,6 @@
<template>
<div class="fixed inset-0 bg-black bg-opacity-50 z-50 flex justify-center items-center">
<div class="bg-gray-800 border-2 border-gray-600 rounded-lg w-11/12 lg:w-4/5 p-6 relative">
<div class="bg-gray-800 border-2 border-gray-600 rounded-lg w-[98vw] max-w-[1600px] p-6 relative">
<button @click="$emit('close')" class="absolute top-2 right-2 text-white text-lg">&times;</button>
<div class="mb-4">
<slot name="header" />

View File

@ -1,16 +1,39 @@
<template>
<div class="fixed inset-0 w-full h-full flex bg-update-bg bg-cover bg-center">
<!-- Sidebar -->
<div class="absolute left-5 top-5 h-1/4 bg-gray-700 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
<nav class="flex flex-col space-y-6 text-center mt-4">
<a href="/dashboard" class="text-white hover:text-yellow-400 transition">
<LayoutDashboard class="w-8 h-8" />
</a>
<a href="/scraper" class="text-white hover:text-blue-400 transition">
<Bot class="w-8 h-8" />
</a>
<div class="fixed inset-0 w-full h-full flex bg-gradient-to-br from-white via-white/70 to-gray-300 backdrop-blur-lg">
<div class="absolute left-5 top-5 h-1/2 bg-gray-500 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
<nav class="flex flex-col space-y-6 mt-4">
<!-- Sidebar Item Component -->
<div class="relative group flex justify-center">
<a href="/dashboard" class="text-white hover:text-yellow-400 transition flex items-center justify-center w-10 h-10">
<LayoutDashboard class="w-6 h-6" />
</a>
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
Dashboard
</div>
</div>
<div class="relative group flex justify-center">
<a href="/scraper" class="text-white hover:text-blue-400 transition flex items-center justify-center w-10 h-10">
<Bot class="w-6 h-6" />
</a>
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
Scraper
</div>
</div>
<div class="relative group flex justify-center">
<button @click="openHelpModal" class="text-white hover:text-green-400 transition flex items-center justify-center w-10 h-10">
<HelpCircle class="w-6 h-6" />
</button>
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
Panduan
</div>
</div>
</nav>
</div>
<!-- Main Content -->
<div class="flex-1 ml-20 overflow-y-auto p-4">
@ -19,33 +42,90 @@
<h3 class="text-xl font-bold text-white">Edit Data {{ modalTitle }}</h3>
</template>
<template #body>
<div class="overflow-x-auto">
<table class="min-w-full text-sm text-white table-auto">
<thead>
<div class="overflow-x-auto max-h-[65vh] overflow-y-auto">
<div class="flex items-center justify-between mb-3 px-2">
<input
v-model="searchQuery"
type="text"
placeholder="Cari data..."
class="w-full md:w-1/3 px-4 py-2 rounded bg-gray-700 text-white placeholder-gray-400 border border-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-400"
/>
</div>
<table class="min-w-[150%] text-sm text-white table-auto">
<thead class="sticky top-0 bg-gray-800 z-10">
<tr>
<th v-for="(header, index) in tableHeaders" :key="index" class="px-4 py-2 border-b border-gray-500">{{ header }}</th>
<th class="px-4 py-2 border-b border-gray-500">Aksi</th>
<th class="px-4 py-2 border-b border-gray-500">
<input type="checkbox" @change="toggleSelectAll($event)" class="accent-gray-300" />
</th>
<th v-for="(header, index) in tableHeaders" :key="index" class="px-4 py-2 border-b border-gray-500 min-w-[12rem]">
{{ header }}
</th>
<th class="px-4 py-2 border-b border-gray-500 min-w-[8rem]">Aksi</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in editableData" :key="rowIndex">
<td v-for="(header, colIndex) in tableHeaders" :key="colIndex" class="px-4 py-2 border-b border-gray-600">
<input v-model="editableData[rowIndex][header]" class="bg-gray-700 text-white px-2 py-1 w-full rounded" />
<tr v-for="(index, i) in filteredIndexes" :key="i">
<td class="px-4 py-2 border-b border-gray-600 text-center">
<input
type="checkbox"
:value="index"
v-model="selectedRows"
class="accent-gray-300"
/>
</td>
<td
v-for="(header, colIndex) in tableHeaders"
:key="colIndex"
class="px-4 py-2 border-b border-gray-600 min-w-[12rem]"
>
<input
v-model="editableData[index][header]"
class="bg-gray-500 text-white px-2 py-3 w-full rounded"
/>
</td>
<td class="px-4 py-2 border-b border-gray-600 text-center">
<button @click="removeRow(rowIndex)" class="text-red-500 hover:underline">Hapus</button>
<button @click="removeRow(index)" class="text-red-500 hover:underline">Hapus</button>
</td>
</tr>
</tbody>
</table>
<button @click="addRow" class="mt-4 bg-green-500 hover:bg-green-400 text-white px-4 py-2 rounded">Tambah Baris</button>
</div>
</template>
<template #footer>
<button @click="saveChanges" class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded">Simpan Perubahan</button>
<div class="flex justify-end gap-3 pt-4">
<div class="flex flex-wrap gap-4 justify-between items-center text-white text-sm px-4 pb-4">
<div class="flex items-center gap-2 bg-gray-700 rounded-lg px-4 py-2 shadow-sm border border-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-yellow-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h4l3 9 4-18 3 9h4" />
</svg>
<span class="font-medium">Total Baris:</span>
<span class="font-semibold text-white">{{ editableData.length }}</span>
</div>
<div class="flex items-center gap-2 bg-gray-700 rounded-lg px-4 py-2 shadow-sm border border-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.1 0-2 .9-2 2s.9 2 2 2m0 4v.01M12 12v-2m0 10c4.42 0 8-1.79 8-4V8c0-2.21-3.58-4-8-4S4 5.79 4 8v8c0 2.21 3.58 4 8 4z" />
</svg>
<span class="font-medium">Total Dana:</span>
<span class="font-semibold text-white">{{ formattedTotalDana }}</span>
</div>
</div>
<button @click="addRow" class="bg-green-500 hover:bg-green-400 text-white px-4 py-2 rounded">
Tambah Baris
</button>
<button @click="removeSelectedRows" class="bg-red-600 hover:bg-red-500 text-white px-4 py-2 rounded" :disabled="selectedRows.length === 0">
Hapus Terpilih ({{ selectedRows.length }})
</button>
<button @click="saveChanges" class="bg-blue-600 hover:bg-blue-500 text-white px-4 py-2 rounded">
Simpan Perubahan
</button>
</div>
</template>
</Modal>
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
</Modal>
<div class="w-full bg-gray-500 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
<h2 class="text-white text-xl font-bold mb-6">DASHBOARD</h2>
<div class="flex items-center gap-4 mb-10">
<button @click="refreshData" class="text-white bg-green-600 hover:bg-green-500 px-4 py-2 rounded-2xl">
@ -78,7 +158,7 @@
🔄
</button>
</div>
<button @click="openModal('pengabdian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
<button @click="openModal('pengabdian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
<div
@dragover.prevent
@drop.prevent="handleDrop"
@ -110,7 +190,7 @@
🔄
</button>
</div>
<button @click="openModal('hki')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
<button @click="openModal('hki')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
<div
@dragover.prevent
@drop.prevent="handleDropHKI"
@ -142,7 +222,7 @@
🔄
</button>
</div>
<button @click="openModal('penelitian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
<button @click="openModal('penelitian')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
<div
@dragover.prevent
@drop.prevent="handleDropPenelitian"
@ -174,7 +254,7 @@
🔄
</button>
</div>
<button @click="openModal('scopus')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
<button @click="openModal('scopus')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
<div
@dragover.prevent
@drop.prevent="handleDropScopus"
@ -204,7 +284,7 @@
🔄
</button>
</div>
<button @click="openModal('scholar')" class="text-sm text-yellow-400 hover:underline mb-10">Edit</button>
<button @click="openModal('scholar')" class="text-sm text-yellow-400 hover:underline mb-10">Edit Data Grafik</button>
<div
@dragover.prevent
@drop.prevent="handleDropScholar"
@ -219,27 +299,54 @@
</div>
<div class="bg-gray-800 p-4 rounded-lg text-white w-full h-[500px] col-span-1 md:col-span-2 mt-10">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Kontribusi Luaran Publikasi Dosen per Tahun</h3>
<select v-model="selectedYear" class="bg-gray-700 text-white p-2 rounded">
<option v-for="year in availableYears" :key="year" :value="year">{{ year }}</option>
</select>
<h3 class="text-lg font-semibold">Kontribusi Data Luaran Publikasi Dosen per Tahun </h3>
<div class="relative group inline-block">
<select v-model="selectedYear" class="bg-gray-500 text-white p-2 rounded">
<option v-for="year in availableYears" :key="year" :value="year">{{ year }}</option>
</select>
<div
class="absolute bottom-full mb-1 left-1/2 -translate-x-1/2 bg-black text-white text-xs px-2 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
Pilih Tahun
</div>
</div>
</div>
<div class="w-80 h-80 mx-auto">
<canvas id="luaranPieChart" class="w-80 h-80" width="320" height="320"></canvas>
</div>
</div>
</div>
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
<div class="w-full bg-gray-500 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
</div>
</div>
<div
v-if="showHelpModal"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60"
>
<div class="bg-white rounded-lg overflow-hidden w-[85vw] h-[90vh] shadow-2xl relative">
<button
@click="showHelpModal = false"
class="absolute top-2 right-2 bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-sm z-10"
>
</button>
<iframe
src="/panduan/Panduan-Dashboard.pdf"
type="application/pdf"
width="100%"
height="100%"
class="rounded-b-lg"
></iframe>
</div>
</div>
</div>
</template>
<script setup>
import Modal from '@/components/Modal.vue'
import axios from 'axios'
import { LayoutDashboard, Bot, Settings, LogOut } from "lucide-vue-next"
import { LayoutDashboard, Bot, HelpCircle, Settings, LogOut } from "lucide-vue-next"
import { computed, defineComponent, h } from 'vue'
import { ref } from 'vue'
import * as XLSX from 'xlsx'
@ -252,6 +359,8 @@ import {
import { onMounted, watch} from 'vue'
import Chart from 'chart.js/auto';
onMounted(() => {
fetchCleanedFile('pengabdian', 'pengabdian_cleaned.xlsx', pengabdian)
fetchCleanedFile('penelitian', 'penelitian_cleaned.xlsx', penelitian)
@ -273,6 +382,36 @@ function refreshData() {
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)
const selectedRows = ref([]);
const searchQuery = ref('');
const filteredEditableData = computed(() => {
if (!searchQuery.value) return editableData.value;
return editableData.value.filter((row) =>
Object.values(row).some((value) =>
String(value).toLowerCase().includes(searchQuery.value.toLowerCase())
)
);
});
const filteredIndexes = computed(() => {
if (!searchQuery.value) return editableData.value.map((_, idx) => idx);
return editableData.value
.map((row, idx) => ({ row, idx }))
.filter(({ row }) =>
Object.values(row).some((value) =>
String(value).toLowerCase().includes(searchQuery.value.toLowerCase())
)
)
.map(({ idx }) => idx);
});
const showHelpModal = ref(false);
function openHelpModal() {
showHelpModal.value = true;
}
const pengabdian = ref([])
const hki = ref([])
const penelitian = ref([])
@ -281,6 +420,27 @@ const scholar = ref([])
const isModalOpen = ref(false)
const modalTitle = ref('')
const editableData = ref([])
const totalDana = computed(() => {
let total = 0;
editableData.value.forEach((row) => {
const value = row['Jumlah Dana'] || row['Dana'] || '';
const cleanValue = parseInt(
String(value).replace(/[^\d]/g, '') // Hilangkan Rp, titik, koma
);
if (!isNaN(cleanValue)) total += cleanValue;
});
return total;
});
const formattedTotalDana = computed(() => {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
minimumFractionDigits: 0,
}).format(totalDana.value);
});
const tableHeaders = ref([])
const currentTargetArray = ref(null)
@ -288,6 +448,8 @@ const selectedYear = ref(new Date().getFullYear());
const availableYears = ref([]);
let pieChartInstance = null;
function clearCache() {
pengabdian.value = []
hki.value = []
@ -311,6 +473,8 @@ function clearCache() {
console.log("Cache data cleared")
}
function refreshSingleData(type) {
switch(type) {
case 'pengabdian':
@ -331,6 +495,22 @@ function refreshSingleData(type) {
}
}
function removeSelectedRows() {
const rowsToRemove = [...selectedRows.value].sort((a, b) => b - a);
rowsToRemove.forEach(index => {
editableData.value.splice(index, 1);
});
selectedRows.value = [];
}
function toggleSelectAll(event) {
if (event.target.checked) {
selectedRows.value = editableData.value.map((_, index) => index);
} else {
selectedRows.value = [];
}
}
function getYearsFromData() {
const allYears = new Set();
@ -365,7 +545,7 @@ function renderPieChart() {
data: {
labels: ['HKI', 'Pengabdian', 'Penelitian'],
datasets: [{
label: 'Jumlah Luaran',
label: 'Jumlah Data Luaran',
data: [hkiCount, pengabdianCount, penelitianCount],
backgroundColor: ['#38bdf8', '#facc15', '#34d399'],
borderWidth: 1
@ -456,22 +636,24 @@ async function saveChanges() {
async function fetchCleanedFile(folder, filename, targetArray) {
try {
const response = await axios.get(`http://localhost:8000/cleaned-files/${folder}/${filename}`, {
const timestamp = new Date().getTime(); // Bypass cache
const response = await axios.get(`http://localhost:8000/cleaned-files/${folder}/${filename}?t=${timestamp}`, {
responseType: 'arraybuffer'
})
});
const data = new Uint8Array(response.data)
const workbook = XLSX.read(data, { type: 'array' })
const sheet = workbook.Sheets[workbook.SheetNames[0]]
const json = XLSX.utils.sheet_to_json(sheet)
const data = new Uint8Array(response.data);
const workbook = XLSX.read(data, { type: 'array' });
const sheet = workbook.Sheets[workbook.SheetNames[0]];
const json = XLSX.utils.sheet_to_json(sheet);
targetArray.value = json
targetArray.value = json;
} catch (error) {
console.error(`Gagal mengambil file cleaned (${folder}/${filename}):`, error)
console.error(`Gagal mengambil file cleaned (${folder}/${filename}):`, error);
}
}
function handleDrop(e) {
const file = e.dataTransfer.files[0]
if (file && file.type === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
@ -653,18 +835,14 @@ const BarChart = defineComponent({
</script>
<style scoped>
.font-pixel {
font-family: 'Press Start 2P', cursive;
}
.pixel-border {
image-rendering: pixelated;
position: relative;
border-width: 4px;
border-style: solid;
border-radius: 8px;
box-shadow:
inset -2px -2px 0px rgba(0, 0, 0, 0.8),
inset 2px 2px 0px rgba(255, 255, 255, 0.2);
border-radius: 0.75rem;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.2);
}
</style>

View File

@ -1,78 +1,236 @@
<template>
<div class="fixed inset-0 w-full h-full flex bg-update-bg bg-cover bg-center">
<div class="absolute left-5 top-5 h-1/4 bg-gray-700 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
<nav class="flex flex-col space-y-6 text-center mt-4">
<a href="/dashboard" class="text-white hover:text-yellow-400 transition">
<LayoutDashboard class="w-8 h-8" />
</a>
<a href="/scraper" class="text-white hover:text-blue-400 transition">
<Bot class="w-8 h-8" />
</a>
<template>
<div class="fixed inset-0 w-full h-full flex bg-gradient-to-br from-white via-white/60 to-gray-300 backdrop-blur-lg">
<div class="absolute left-5 top-5 h-1/2 bg-gray-500 bg-opacity-90 border-r-4 border-gray-500 pixel-border shadow-lg w-16 flex flex-col items-center p-4">
<nav class="flex flex-col space-y-6 mt-4">
<!-- Sidebar Item Component -->
<div class="relative group flex justify-center">
<a href="/dashboard" class="text-white hover:text-yellow-400 transition flex items-center justify-center w-10 h-10">
<LayoutDashboard class="w-6 h-6" />
</a>
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
Dashboard
</div>
</div>
<div class="relative group flex justify-center">
<a href="/scraper" class="text-white hover:text-blue-400 transition flex items-center justify-center w-10 h-10">
<Bot class="w-6 h-6" />
</a>
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
Scraper
</div>
</div>
<div class="relative group flex justify-center">
<button @click="openHelpModal" class="text-white hover:text-green-400 transition flex items-center justify-center w-10 h-10">
<HelpCircle class="w-6 h-6" />
</button>
<div class="absolute left-14 top-1/2 -translate-y-1/2 bg-black text-white text-xs px-3 py-1 rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-nowrap z-50">
Panduan
</div>
</div>
</nav>
</div>
<div class="flex flex-col w-full max-w-full gap-6 ml-10 p-4">
<!-- Scraping Section -->
<div class="w-full bg-gray-700 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
<h2 class="text-center text-xl font-bold font-pixel text-white mb-4"></h2>
<div class="w-full bg-gray-500 bg-opacity-90 border-4 border-gray-500 pixel-border flex flex-col items-center p-6">
<h2 class="text-center text-xl font-bold text-white mb-4">Silahkan melakukan scraping data data luaran sesuai tujuan dan kebutuhan anda</h2>
<div class="flex flex-col md:flex-row gap-4">
<button @click="startScrapeScopus" class="bg-blue-600 hover:bg-blue-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Scopus
</button>
<button @click="startScrapeHki" class="bg-green-600 hover:bg-green-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape HKI
</button>
<button @click="startScrapeGoogleScholar" class="bg-purple-600 hover:bg-purple-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Google Scholar
</button>
<button @click="startScrapePenelitian" class="bg-red-600 hover:bg-red-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Penelitian
</button>
<button @click="startScrapePengabdian" class="bg-yellow-600 hover:bg-yellow-700 text-white font-pixel px-4 py-2 rounded shadow">
Scrape Pengabdian
</button>
<!-- Scopus -->
<div class="relative group">
<button @click="showAffiliationModalScopus = true" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded shadow">
Scrape Scopus
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
Tombol untuk melakukan scraping data Scopus dosen dari portal SINTA
</div>
</div>
<!-- <a v-if="latestFile" :href="`http://localhost:8000/scopus/download`" target="_blank" class="bg-green-600 hover:bg-green-700 text-white font-pixel px-4 py-2 rounded shadow">
Download Data
</a>
<router-link v-if="latestFile" to="/data-cleaning" class="bg-purple-600 hover:bg-purple-700 text-white font-pixel px-4 py-2 rounded shadow">
Open Cleaning
</router-link> -->
<!-- HKI -->
<div class="relative group">
<button @click="showAffiliationModalHki = true" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded shadow">
Scrape HKI
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
Tombol untuk melakukan scraping data Hak Kaya Intelektual dosen dari portal SINTA
</div>
</div>
<!-- Google Scholar -->
<div class="relative group">
<button @click="startScrapeGoogleScholar" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded shadow">
Scrape Google Scholar
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
Tombol untuk melakukan scraping data luaran/publikasi dosen dari portal Google Scholar (Masih dalam lingkup Politeknik Negeri Jember Jurusan Teknologi Informasi)
</div>
</div>
<!-- Google Scholar lain-->
<!-- <div class="relative group">
<button @click="showModalGoogleScholar = true" class="bg-teal-600 hover:bg-teal-700 text-white px-4 py-2 rounded shadow">
Scrape Google Scholar
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
Tombol untuk melakukan scraping data luaran/publikasi dosen dari portal Google Scholar
</div>
</div> -->
<!-- Penelitian -->
<div class="relative group">
<button @click="showAffiliationModal = true" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded shadow">
Scrape Penelitian
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
Tombol untuk melakukan scraping data Penelitian dosen dari portal SINTA
</div>
</div>
<!-- Pengabdian -->
<div class="relative group">
<button @click="showAffiliationModalPengabdian = true" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded shadow">
Scrape Pengabdian
</button>
<div class="absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-black text-white text-xs px-3 py-1 rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 whitespace-normal w-64 text-center z-50">
Tombol untuk melakukan scraping data Pengabdian pada Masyarakat oleh dari portal SINTA
</div>
</div>
</div>
<div class="mt-6 text-sm font-pixel">
<div class="mt-6 text-sm">
<div v-if="loading" class="text-yellow-300 animate-pulse">🔄 Sedang memproses ...</div>
<div v-if="statusMessage" class="text-green-400">{{ statusMessage }}</div>
<div v-if="errorMessage" class="text-red-400">{{ errorMessage }}</div>
</div>
</div>
<div
class="w-full p-6 bg-gray-800 border-4 border-dashed border-gray-600 rounded-lg text-center text-white font-pixel"
class="w-full p-6 bg-gray-500 border-4 border-dashed border-gray-400 rounded-lg text-center text-white"
@dragover.prevent
@drop.prevent="handleDrop"
>
<p>📂 Tarik dan lepas file Excel di sini atau klik untuk upload</p>
<p>📂 Tarik dan lepas atau klik untuk upload file Excel hasil scraping data di sini untuk dibersihkan</p>
<input type="file" accept=".xlsx" class="hidden" ref="fileInput" @change="handleFileUpload" />
<button @click="$refs.fileInput.click()" class="mt-4 bg-blue-700 hover:bg-blue-800 px-4 py-2 rounded">
Upload Manual
Upload Manual File Excel
</button>
</div>
<!-- Modal Konfirmasi -->
<div v-if="showModal" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-700 rounded-lg shadow-lg w-96 p-6">
<div class="bg-gray-800 rounded-lg shadow-lg w-96 p-6">
<h3 class="text-lg font-bold mb-4">Pilih Kategori Grafik</h3>
<p class="mb-4 text-sm text-gray-700">File berhasil dibersihkan. Masukkan sebagai grafik apa?</p>
<p class="mb-4 text-sm text-white">File hasil scraping berhasil dibersihkan. Anda ingin memasukkan file sebagai grafik apa?</p>
<div class="grid grid-cols-2 gap-4 mb-6">
<button v-for="option in chartOptions" :key="option" @click="submitCleanedFile(option)"
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded font-pixel text-sm">
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm">
{{ option }}
</button>
</div>
<button @click="showModal = false" class="text-gray-500 hover:text-red-500 text-sm">Batal</button>
</div>
</div>
<!-- Modal Sukses Redirect -->
<div v-if="successModal" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-900 rounded-lg shadow-lg w-96 p-6 text-center text-white">
<h3 class="text-lg font-bold mb-4">🎉 File Berhasil Dikirim</h3>
<p class="mb-4 text-sm">File berhasil ditambahkan ke grafik. Lanjut ke dashboard untuk melihat visualisasi?</p>
<div class="flex justify-center gap-4">
<a href="/dashboard" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-white text-sm">Ke Dashboard</a>
<button @click="successModal = false" class="text-gray-400 hover:text-red-500 text-sm">Tutup</button>
</div>
</div>
</div>
<div v-if="showAffiliationModal" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
<input type="number" v-model="tempAffiliationId" placeholder="Contoh: 384"
class="w-full text-black px-2 py-1 rounded border mb-4" />
<div class="flex justify-end space-x-2">
<button @click="confirmScrapePenelitian"
class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded">Scrape</button>
<button @click="showAffiliationModal = false"
class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
</div>
</div>
</div>
<!-- Modal Pengabdian -->
<div v-if="showAffiliationModalPengabdian" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
<input type="number" v-model="tempAffiliationIdPengabdian" placeholder="Contoh: 447" class="w-full text-black px-2 py-1 rounded border mb-4" />
<div class="flex justify-end space-x-2">
<button @click="confirmScrapePengabdian" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded">Scrape</button>
<button @click="showAffiliationModalPengabdian = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
</div>
</div>
</div>
<div v-if="showAffiliationModalScopus" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
<input type="number" v-model="tempAffiliationIdScopus" placeholder="Contoh: 447" class="w-full text-black px-2 py-1 rounded border mb-4" />
<div class="flex justify-end space-x-2">
<button @click="confirmScrapeScopus" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">Scrape</button>
<button @click="showAffiliationModalScopus = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
</div>
</div>
</div>
<div v-if="showModalGoogleScholar" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-800 rounded-lg shadow-lg w-1/2 p-6 text-white">
<h3 class="text-lg font-bold mb-4">link</h3>
<input
type="text"
v-model="tempLinkOneScholar"
placeholder="contoh: https://scholar.google.com/citations?hl=id&user=syL17R9KKKs"
class="w-full text-black px-2 py-1 rounded border mb-4"
/>
<div class="flex justify-end space-x-2">
<button @click="confirmScrapeOneScholar" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">Scrape</button>
<button @click="showModalGoogleScholar = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
</div>
</div>
</div>
<!-- Modal HKI -->
<div v-if="showAffiliationModalHki" class="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50">
<div class="bg-gray-800 rounded-lg shadow-lg w-80 p-6 text-white">
<h3 class="text-lg font-bold mb-4">Masukkan ID Afiliasi untuk scraping</h3>
<input type="number" v-model="tempAffiliationIdHki" placeholder="Contoh: 447" class="w-full text-black px-2 py-1 rounded border mb-4" />
<div class="flex justify-end space-x-2">
<button @click="confirmScrapeHki" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded">Scrape</button>
<button @click="showAffiliationModalHki = false" class="bg-gray-600 hover:bg-gray-500 text-white px-4 py-2 rounded">Batal</button>
</div>
</div>
</div>
</div>
<div
v-if="showHelpModal"
class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-60"
>
<div class="bg-white rounded-lg overflow-hidden w-[85vw] h-[90vh] shadow-2xl relative">
<button
@click="showHelpModal = false"
class="absolute top-2 right-2 bg-red-600 hover:bg-red-700 text-white px-2 py-1 rounded text-sm z-10"
>
</button>
<iframe
src="/panduan/Panduan-Scraper.pdf"
type="application/pdf"
width="100%"
height="100%"
class="rounded-b-lg"
></iframe>
</div>
</div>
</div>
</template>
@ -80,7 +238,9 @@
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { LayoutDashboard, Bot, Settings, LogOut } from "lucide-vue-next"
import { LayoutDashboard, Bot, HelpCircle, Settings, LogOut } from "lucide-vue-next"
const successModal = ref(false);
const loading = ref(false)
const statusMessage = ref("")
@ -90,6 +250,33 @@
const showModal = ref(false)
const cleanedFile = ref(null)
const chartOptions = ["HKI", "Penelitian", "Pengabdian", "Scopus", "Scholar"]
const showAffiliationModal = ref(false) // Penelitian
const tempAffiliationId = ref(null) // Penelitian
const affiliationId = ref(null) // Penelitian
const showAffiliationModalPengabdian = ref(false)
const tempAffiliationIdPengabdian = ref(null)
const affiliationIdPengabdian = ref(null)
const showAffiliationModalHki = ref(false)
const tempAffiliationIdHki = ref(null)
const affiliationIdHki = ref(null)
const showAffiliationModalScopus = ref(false)
const tempAffiliationIdScopus = ref(null)
const affiliationIdScopus = ref(null)
const showModalGoogleScholar = ref(false)
const tempLinkOneScholar = ref(null)
const linkOneScholar = ref(null)
const showHelpModal = ref(false);
function openHelpModal() {
showHelpModal.value = true;
}
const selectChart = (option) => {
selectedChart.value = option
@ -117,18 +304,30 @@
statusMessage.value = ""
errorMessage.value = ""
try {
await axios.get(`http://localhost:8000/scrape/scopus`)
statusMessage.value = "✅ Scraping selesai!"
await fetchLatestData()
await axios.get(`http://localhost:8000/scrape/scopus`, {
params: { affiliation_id: affiliationIdScopus.value }
})
statusMessage.value = "✅ Scraping Scopus selesai!"
await fetchLatestData('scopus')
if (latestFile.value) {
triggerDownload(latestFile.value)
}
} catch (err) {
errorMessage.value = "❌ Gagal scraping: " + err.message
errorMessage.value = "❌ Gagal scraping Scopus: " + err.message
} finally {
loading.value = false
}
}
const confirmScrapeScopus = async () => {
if (!tempAffiliationIdScopus.value || isNaN(tempAffiliationIdScopus.value)) {
alert("❗ Mohon masukkan Affiliation ID yang valid.")
return
}
affiliationIdScopus.value = tempAffiliationIdScopus.value
showAffiliationModalScopus.value = false
await startScrapeScopus()
}
const startScrapeGoogleScholar = async () => {
loading.value = true
@ -149,15 +348,52 @@
loading.value = false
}
}
const startScrapeOneGoogleScholar = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
const response = await axios.get(`http://localhost:8000/scrape/onescholar`, {
params: { link: linkOneScholar.value }
})
statusMessage.value = response.data.message || "✅ Scraping One Scholar selesai!"
await fetchLatestData()
if (latestFile.value) {
triggerDownload(latestFile.value)
}
} catch (err) {
errorMessage.value = "❌ Gagal scraping One Scholar: " + (err.response?.data?.error || err.message)
} finally {
loading.value = false
}
}
const confirmScrapeOneScholar = async () => {
if (
!tempLinkOneScholar.value ||
!tempLinkOneScholar.value.startsWith("https://scholar.google.com/citations")
) {
alert("❗ Mohon masukkan link Google Scholar dosen yang valid.")
return
}
linkOneScholar.value = tempLinkOneScholar.value
showModalGoogleScholar.value = false
await startScrapeOneGoogleScholar()
}
const startScrapeHki = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
await axios.get(`http://localhost:8000/scrape/hki`)
await axios.get(`http://localhost:8000/scrape/hki`, {
params: { affiliation_id: affiliationIdHki.value }
})
statusMessage.value = "✅ Scraping HKI selesai!"
await fetchLatestData()
await fetchLatestData('hki')
if (latestFile.value) {
triggerDownload(latestFile.value)
}
@ -167,13 +403,26 @@
loading.value = false
}
}
const confirmScrapeHki = async () => {
if (!tempAffiliationIdHki.value || isNaN(tempAffiliationIdHki.value)) {
alert("❗ Mohon masukkan Affiliation ID yang valid.")
return
}
affiliationIdHki.value = tempAffiliationIdHki.value
showAffiliationModalHki.value = false
await startScrapeHki()
}
const startScrapePenelitian = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
console.log("🔍 ID yang dikirim:", affiliationId.value) // Tambahkan ini
try {
await axios.get(`http://localhost:8000/scrape/penelitian`)
await axios.get(`http://localhost:8000/scrape/penelitian`, {
params: { affiliation_id: affiliationId.value }
})
statusMessage.value = "✅ Scraping Penelitian selesai!"
await fetchLatestData()
if (latestFile.value) {
@ -185,13 +434,24 @@
loading.value = false
}
}
const confirmScrapePenelitian = async () => {
if (!tempAffiliationId.value || isNaN(tempAffiliationId.value)) {
alert("❗ Mohon masukkan Affiliation ID yang valid.")
return
}
affiliationId.value = tempAffiliationId.value
showAffiliationModal.value = false
await startScrapePenelitian()
}
const startScrapePengabdian = async () => {
loading.value = true
statusMessage.value = ""
errorMessage.value = ""
try {
await axios.get(`http://localhost:8000/scrape/pengabdian`) // Ganti dengan endpoint scraping Pengabdian
await axios.get(`http://localhost:8000/scrape/pengabdian`, {
params: { affiliation_id: affiliationIdPengabdian.value }
})
statusMessage.value = "✅ Scraping Pengabdian selesai!"
await fetchLatestData('pengabdian')
if (latestFile.value) {
@ -203,6 +463,16 @@
loading.value = false
}
}
const confirmScrapePengabdian = async () => {
if (!tempAffiliationIdPengabdian.value || isNaN(tempAffiliationIdPengabdian.value)) {
alert("❗ Mohon masukkan Affiliation ID yang valid.")
return
}
affiliationIdPengabdian.value = tempAffiliationIdPengabdian.value
showAffiliationModalPengabdian.value = false
await startScrapePengabdian()
}
const fetchLatestData = async () => {
try {
@ -270,7 +540,7 @@
}
const submitCleanedFile = async (chartTypeRaw) => {
const chartType = chartTypeRaw.toLowerCase()
const chartType = chartTypeRaw.toLowerCase();
if (!cleanedFile.value) return;
const formData = new FormData();
@ -281,29 +551,29 @@
statusMessage.value = `✅ File berhasil dikirim ke grafik ${chartTypeRaw}`;
showModal.value = false;
cleanedFile.value = null;
// Munculkan modal sukses
successModal.value = true;
} catch (err) {
console.error(err);
errorMessage.value = `❌ Gagal mengirim ke grafik ${chartTypeRaw}: ` + (err.response?.data?.error || err.message);
}
};
onMounted(fetchLatestData)
</script>
<style scoped>
.font-pixel {
font-family: 'Press Start 2P', cursive;
}
.pixel-border {
image-rendering: pixelated;
position: relative;
border-width: 4px;
border-style: solid;
border-radius: 8px;
box-shadow:
inset -2px -2px 0px rgba(0, 0, 0, 0.8),
inset 2px 2px 0px rgba(255, 255, 255, 0.2);
border-radius: 0.75rem;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.2);
}
</style>

View File

@ -13,6 +13,7 @@ export default {
'login-bg': "url('/bgta_n.png')",
'dashboard-bg': "url('/bgpixel3.png')",
'update-bg': "url('/update_bg.png')",
'whites-bg': "url('/whitebg1.png')",
},
},
},

53
main.py
View File

@ -4,8 +4,9 @@ from fastapi.responses import FileResponse, JSONResponse
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
from fastapi.responses import StreamingResponse
from fastapi import Query, HTTPException
from pydantic import BaseModel
from typing import List, Dict
from typing import List, Dict, Optional
import subprocess
import glob
import os
@ -29,7 +30,7 @@ app.add_middleware(
)
DATA_FOLDER = "D:\\lecturertask"
BASE_FILENAME = "data_scopus"
# BASE_FILENAME = "data_scopus"
FILE_EXT = ".xlsx"
BASE_DIR = "D:/lecturertask/download_files_scraper"
@ -126,9 +127,11 @@ def download_file(file_type: str):
def get_cleaned_file(folder: str, filename: str):
file_path = os.path.join(BASE_DIR, folder, filename)
if os.path.exists(file_path):
return FileResponse(file_path, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
response = FileResponse(file_path, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
response.headers["Cache-Control"] = "no-store"
return response
return {"error": "File not found"}
@app.get("/list-files/{category}")
def list_files(category: str):
folder_path = os.path.join(BASE_FOLDER, category.lower())
@ -244,47 +247,65 @@ async def upload_excel(file: UploadFile = File(...)):
def scrape_scholar():
return scrape_data("scraper_scholar.py", "Scraping Scholar selesai.")
@app.get("/scrape/onescholar")
def scrape_one_scholar(link: str):
return scrape_data("scraper_onescholar.py", "Scraping One Scholar selesai.", extra_args=[link])
@app.get("/scholar/download")
def download_latest_scholar():
return download_file("data_scholar*.xlsx")
@app.get("/scrape/scopus")
def scrape_scopus():
return scrape_data("scraper_scopus.py", "Scraping Scopus selesai.")
def scrape_scopus(affiliation_id: Optional[int] = Query(None)):
if not affiliation_id:
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
return scrape_data("scraper_scopus.py", "Scraping Scopus selesai.", extra_args=[str(affiliation_id)])
@app.get("/scopus/download")
def download_latest_file():
return download_file(f"{BASE_FILENAME}*{FILE_EXT}")
@app.get("/scrape/pengabdian")
def scrape_pengabdian():
return scrape_data("scraper_pengabdian.py", "Scraping Pengabdian selesai.")
def scrape_pengabdian(affiliation_id: Optional[int] = Query(None)):
if not affiliation_id:
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
return scrape_data("scraper_pengabdian.py", "Scraping Pengabdian selesai.", extra_args=[str(affiliation_id)])
@app.get("/pengabdian/download")
def download_latest_pengabdian():
return download_file("data_pengabdian*.xlsx")
@app.get("/scrape/hki")
def scrape_hki():
return scrape_data("scraper_HKI.py", "Scraping HKI selesai.")
def scrape_hki(affiliation_id: Optional[int] = Query(None)):
if not affiliation_id:
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
return scrape_data("scraper_HKI.py", "Scraping HKI selesai.", extra_args=[str(affiliation_id)])
@app.get("/hki/download")
def download_latest_hki():
return download_file("data_hki*.xlsx")
@app.get("/scrape/penelitian")
def scrape_penelitian():
return scrape_data("scraper_penelitian.py", "Scraping Penelitian selesai.")
def scrape_penelitian(affiliation_id: Optional[int] = Query(None)):
print("📩 Affiliation ID diterima di FastAPI:", affiliation_id)
if not affiliation_id:
return JSONResponse(content={"error": "affiliation_id kosong"}, status_code=400)
return scrape_data("scraper_penelitian.py", "Scraping Penelitian selesai.", extra_args=[str(affiliation_id)])
@app.get("/penelitian/download")
def download_latest_penelitian():
return download_file("data_penelitian*.xlsx")
# Generic function to scrape data
def scrape_data(script_name: str, success_message: str):
def scrape_data(script_name: str, success_message: str, extra_args: list = None):
try:
args = ["python", f"D:\\lecturertask\\scraper\\{script_name}"]
if extra_args:
args.extend(extra_args)
result = subprocess.run(
["python", f"D:\\lecturertask\\scraper\\{script_name}"],
args,
capture_output=True, text=True
)
if result.returncode == 0:

View File

@ -1,5 +1,6 @@
import os
import time
import sys
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
@ -23,6 +24,12 @@ chrome_options.add_argument("--window-size=1920x1080")
service = Service(chrome_driver_path)
browser = webdriver.Chrome(service=service, options=chrome_options)
# --- BACA AFFILIATION DARI ARGUMEN ---
if len(sys.argv) > 1:
affiliation_id = sys.argv[1]
else:
affiliation_id = "447"
all_hki = []
try:
@ -42,7 +49,7 @@ try:
else:
print("\n[WARNING] Belum login setelah waktu tunggu, lanjut scraping saja...")
base_url = "https://sinta.kemdikbud.go.id/affiliations/profile/447/?view=iprs"
base_url = f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}/?view=iprs"
page = 1
while True:
print(f"\nScraping halaman {page}...")

View File

@ -0,0 +1,117 @@
# === scraper_onescholar.py ===
import sys
import os
import time
import random
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Validasi argumen
if len(sys.argv) < 2:
raise ValueError("Link profil Google Scholar harus disediakan sebagai argumen.")
scholar_link = sys.argv[1]
print(f"[INFO] Link Google Scholar: {scholar_link}")
# Konfigurasi browser
CHROME_DRIVER_PATH = "D:\\lecturertask\\chromedriver.exe"
chrome_options = Options()
chrome_options.add_argument("--start-maximized")
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, seperti Gecko) Chrome/110.0.0.0 Safari/537.36")
service = Service(CHROME_DRIVER_PATH)
browser = webdriver.Chrome(service=service, options=chrome_options)
hasil_scraping = []
browser.get("https://scholar.google.com/")
time.sleep(1)
def scrape_teks(xpath, default="-"):
try:
return WebDriverWait(browser, 5).until(
EC.presence_of_element_located((By.XPATH, xpath))
).text.strip()
except:
return default
def klik_tampilkan_lagi():
while True:
try:
tombol = WebDriverWait(browser, 2).until(
EC.element_to_be_clickable((By.ID, "gsc_bpf_more"))
)
tombol.click()
print("Memuat lebih banyak...")
time.sleep(random.uniform(1.2, 2))
except:
break
try:
print(f"\nMembuka profil: {scholar_link}")
browser.get(scholar_link)
WebDriverWait(browser, 6).until(EC.presence_of_element_located((By.ID, "gsc_a_t")))
klik_tampilkan_lagi()
elements = browser.find_elements(By.CSS_SELECTOR, "tr.gsc_a_tr td.gsc_a_t a.gsc_a_at")
penelitian_links = [el.get_attribute("href") for el in elements]
print(f"Total publikasi: {len(penelitian_links)}")
for link in penelitian_links:
try:
browser.get(link)
WebDriverWait(browser, 5).until(EC.presence_of_element_located((By.ID, "gsc_oci_title")))
judul = scrape_teks("//div[@id='gsc_oci_title']")
pengarang = scrape_teks("//div[@class='gsc_oci_field' and text()='Pengarang']/following-sibling::div")
tahun = scrape_teks("//div[@class='gsc_oci_field' and text()='Tanggal terbit']/following-sibling::div")
jurnal = scrape_teks("//div[@class='gsc_oci_field' and text()='Jurnal']/following-sibling::div")
jilid = scrape_teks("//div[@class='gsc_oci_field' and text()='Jilid']/following-sibling::div")
terbitan = scrape_teks("//div[@class='gsc_oci_field' and text()='Terbitan']/following-sibling::div")
halaman = scrape_teks("//div[@class='gsc_oci_field' and text()='Halaman']/following-sibling::div")
penerbit = scrape_teks("//div[@class='gsc_oci_field' and text()='Penerbit']/following-sibling::div")
kutipan = scrape_teks("//div[@class='gsc_oci_field' and text()='Total kutipan']/following-sibling::div")
hasil_scraping.append([
scholar_link, judul, pengarang, tahun, jurnal,
jilid, terbitan, halaman, penerbit, kutipan
])
print(f"{judul}")
except Exception as e:
print(f"Gagal ambil detail publikasi: {e}")
except KeyboardInterrupt:
print("\nScraping dihentikan oleh pengguna.")
finally:
folder_path = "D:\\lecturertask"
os.makedirs(folder_path, exist_ok=True)
base_filename = "data_scholar"
file_ext = ".xlsx"
existing_files = [f for f in os.listdir(folder_path) if f.startswith(base_filename) and f.endswith(file_ext)]
max_num = max([int(f.replace(base_filename, "").replace(file_ext, "")) for f in existing_files if f.replace(base_filename, "").replace(file_ext, "").isdigit()] + [0])
new_filename = f"{base_filename}{max_num + 1}{file_ext}"
save_path = os.path.join(folder_path, new_filename)
if hasil_scraping:
df = pd.DataFrame(hasil_scraping, columns=[
"Link", "Judul", "Pengarang", "Tahun", "Jurnal",
"Jilid", "Terbitan", "Halaman", "Penerbit", "Total Kutipan"
])
df.to_excel(save_path, index=False, engine="openpyxl")
print(f"\nScraping selesai/dihentikan! Total {len(hasil_scraping)} data disimpan di '{save_path}'")
else:
print("\nScraping gagal/tidak mendapatkan data. File tidak dibuat.")
browser.quit()

View File

@ -1,5 +1,6 @@
import os
import time
import sys
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
@ -24,6 +25,10 @@ service = Service(chrome_driver_path)
browser = webdriver.Chrome(service=service, options=chrome_options)
all_research = []
if len(sys.argv) < 2:
raise ValueError("Affiliation ID harus disediakan sebagai argumen.")
affiliation_id = sys.argv[1]
try:
browser.get("https://sinta.kemdikbud.go.id/logins")
@ -42,7 +47,8 @@ try:
else:
print("\n[WARNING] Belum login setelah 3 menit, lanjut scraping saja...")
browser.get("https://sinta.kemdikbud.go.id/affiliations/profile/447/?view=researches")
url = f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}/?view=researches"
browser.get(url)
time.sleep(3)
page = 1

View File

@ -1,5 +1,6 @@
import os
import time
import sys
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
@ -24,6 +25,13 @@ chrome_options.add_argument("--window-size=1920x1080")
service = Service(chrome_driver_path)
browser = webdriver.Chrome(service=service, options=chrome_options)
# --- BACA AFFILIATION DARI ARGUMEN ---
if len(sys.argv) < 2:
raise ValueError("Affiliation ID harus disediakan sebagai argumen.")
affiliation_id = sys.argv[1]
print(f"[INFO] Menggunakan Affiliation ID: {affiliation_id}")
# --- LOGIN ---
browser.get("https://sinta.kemdikbud.go.id/logins")
browser.implicitly_wait(10)
@ -42,10 +50,12 @@ else:
print("\n[WARNING] Belum login setelah 3 menit, lanjut scraping saja...")
# --- SCRAPING PENGABDIAN ---
browser.get("https://sinta.kemdikbud.go.id/affiliations/profile/447/?view=services")
browser.get(f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}/?view=services")
time.sleep(3)
all_services = []
page = 1
while True:

View File

@ -1,11 +1,20 @@
import sys
import os
import time
import random
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
import time, random
import pandas as pd
import os
# Ambil Affiliation ID dari argumen
if len(sys.argv) < 2:
raise ValueError("Affiliation ID harus disediakan sebagai argumen.")
affiliation_id = sys.argv[1]
print(f"[INFO] Menggunakan Affiliation ID: {affiliation_id}")
# Konfigurasi Chrome
chrome_driver_path = "D:\\lecturertask\\chromedriver.exe"
chrome_options = Options()
chrome_options.add_argument("--start-maximized")
@ -15,87 +24,88 @@ chrome_options.add_argument("--no-sandbox")
service = Service(chrome_driver_path)
browser = webdriver.Chrome(service=service, options=chrome_options)
# Login manual
browser.get("https://sinta.kemdikbud.go.id/logins")
print("[INFO] Silakan login di browser yang terbuka...")
login_wait_timeout = 180
elapsed = 0
while elapsed < login_wait_timeout:
if "profile" in browser.current_url:
print("\n[SUCCESS] Login sukses, lanjut scraping...")
break
else:
print(f"[WAIT] Menunggu login... ({login_wait_timeout - elapsed} detik tersisa)", end='\r')
time.sleep(2)
elapsed += 2
print(f"[WAIT] Menunggu login... ({login_wait_timeout - elapsed} detik tersisa)", end='\r')
time.sleep(2)
elapsed += 2
else:
print("\n[WARNING] Belum login setelah 3 menit, lanjut scraping saja...")
base_url = "https://sinta.kemdikbud.go.id/affiliations/profile/447?page={}&view=scopus"
# Scraping Scopus
base_url = f"https://sinta.kemdikbud.go.id/affiliations/profile/{affiliation_id}?page={{}}&view=scopus"
all_scopus = []
page = 1
while True:
browser.get(base_url.format(page))
time.sleep(random.uniform(3, 5))
print(f"Scraping halaman {page}...")
try:
while True:
browser.get(base_url.format(page))
time.sleep(random.uniform(3, 5))
print(f"Scraping halaman {page}...")
items = browser.find_elements(By.CSS_SELECTOR, "div.ar-list-item.mb-5")
if not items:
print("Tidak ada data lagi. Selesai.")
break
items = browser.find_elements(By.CSS_SELECTOR, "div.ar-list-item.mb-5")
if not items:
print("Tidak ada data lagi. Selesai.")
break
for item in items:
try:
title = item.find_element(By.CSS_SELECTOR, "div.ar-title a").text.strip()
quartile = item.find_element(By.CSS_SELECTOR, "a.ar-quartile").text.strip()
journal = item.find_element(By.CSS_SELECTOR, "a.ar-pub").text.strip()
for item in items:
try:
title = item.find_element(By.CSS_SELECTOR, "div.ar-title a").text.strip()
quartile = item.find_element(By.CSS_SELECTOR, "a.ar-quartile").text.strip()
journal = item.find_element(By.CSS_SELECTOR, "a.ar-pub").text.strip()
metas = item.find_elements(By.CSS_SELECTOR, "div.ar-meta")
creator = "-"
for meta in metas:
anchor_tags = meta.find_elements(By.TAG_NAME, "a")
for a in anchor_tags:
if "Creator :" in a.text:
creator = a.text.replace("Creator :", "").strip()
break
if creator != "-":
break
metas = item.find_elements(By.CSS_SELECTOR, "div.ar-meta")
creator = "-"
for meta in metas:
anchor_tags = meta.find_elements(By.TAG_NAME, "a")
for a in anchor_tags:
if "Creator :" in a.text:
creator = a.text.replace("Creator :", "").strip()
break
year = item.find_element(By.CSS_SELECTOR, "a.ar-year").text.strip()
cited = item.find_element(By.CSS_SELECTOR, "a.ar-cited").text.strip()
year = item.find_element(By.CSS_SELECTOR, "a.ar-year").text.strip()
cited = item.find_element(By.CSS_SELECTOR, "a.ar-cited").text.strip()
all_scopus.append([title, quartile, journal, creator, year, cited])
print(f"{title} | {creator} ({year})")
except Exception as e:
print(f"Error mengambil data item: {e}")
all_scopus.append([title, quartile, journal, creator, year, cited])
print(f"{title} | {creator} ({year})")
except Exception as e:
print(f"Error mengambil data item: {e}")
page += 1
time.sleep(random.uniform(2.5, 5))
page += 1
time.sleep(random.uniform(2.5, 5))
folder_path = "D:\\lecturertask"
base_filename = "data_scopus"
file_ext = ".xlsx"
except KeyboardInterrupt:
print("\n[INTERRUPTED] Scraping dihentikan oleh pengguna (Ctrl+C atau tutup Chrome).")
existing_files = [f for f in os.listdir(folder_path) if f.startswith(base_filename) and f.endswith(file_ext)]
finally:
print("\n[MENYIMPAN DATA] Menyimpan hasil sementara ke Excel...")
folder_path = "D:\\lecturertask"
base_filename = "data_scopus"
file_ext = ".xlsx"
existing_files = [f for f in os.listdir(folder_path) if f.startswith(base_filename) and f.endswith(file_ext)]
max_num = max([int(f.replace(base_filename, "").replace(file_ext, "")) for f in existing_files if f.replace(base_filename, "").replace(file_ext, "").isdigit()] + [0])
next_num = max_num + 1
new_filename = f"{base_filename}{next_num}{file_ext}"
save_path = os.path.join(folder_path, new_filename)
if all_scopus:
df = pd.DataFrame(all_scopus, columns=["Judul", "Quartile", "Jurnal", "Creator", "Tahun", "Sitasi"])
df.to_excel(save_path, index=False, engine="openpyxl")
print(f"[SAVED] Data disimpan di '{save_path}' ({len(all_scopus)} item)")
else:
print("[INFO] Tidak ada data untuk disimpan.")
max_num = 0
for filename in existing_files:
try:
num = int(filename.replace(base_filename, "").replace(file_ext, ""))
if num > max_num:
max_num = num
browser.quit()
except:
pass
next_num = max_num + 1
new_filename = f"{base_filename}{next_num}{file_ext}"
save_path = os.path.join(folder_path, new_filename)
df = pd.DataFrame(all_scopus, columns=["Judul", "Quartile", "Jurnal", "Creator", "Tahun", "Sitasi"])
df.to_excel(save_path, index=False, engine="openpyxl")
print(f"\n[SUCCESS] Scraping selesai! {len(all_scopus)} data berhasil disimpan di '{save_path}'")
browser.quit()