init
Some checks failed
Build And Test / build-and-push (push) Failing after 53s

This commit is contained in:
2024-12-13 17:12:03 +09:00
parent a27be94b19
commit ea11832a53
695 changed files with 71766 additions and 1 deletions

0
butler/__init__.py Normal file
View File

5
butler/admin.py Normal file
View File

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import IPManagementRecord, NoticeBoard
admin.site.register(IPManagementRecord)
admin.site.register(NoticeBoard)

6
butler/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class ButlerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'butler'

View File

@ -0,0 +1,31 @@
# Generated by Django 4.2.14 on 2024-11-05 14:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='IPManagementRecord',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('network_nm', models.CharField(max_length=100)),
('ip_addrs', models.CharField(max_length=50)),
('svr_nm', models.CharField(max_length=100)),
('desc', models.TextField(blank=True, null=True)),
('remark', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 4.2.14 on 2024-11-10 01:17
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('butler', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='NoticeBoard',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=100)),
('contents', models.TextField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('author', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

35
butler/models.py Normal file
View File

@ -0,0 +1,35 @@
from django.db import models
from django.conf import settings
from django.urls import reverse
class IPManagementRecord(models.Model):
network_nm = models.CharField(max_length=100) # NETWORK_NM
ip_addrs = models.CharField(max_length=50) # IP_ADDRS
svr_nm = models.CharField(max_length=100) # SVR_NM
desc = models.TextField(blank=True, null=True) # DESC
remark = models.TextField(blank=True, null=True) # REMARK
created_at = models.DateTimeField(auto_now_add=True) # 생성 시간 자동 기록
updated_at = models.DateTimeField(auto_now=True) # 수정 시간 자동 기록
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)
def __str__(self):
return f'{self.network_nm} {self.ip_addrs} {self.svr_nm} {self.desc}'
class NoticeBoard(models.Model):
title = models.CharField(max_length=100)
contents = models.TextField(blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True) # 생성 시간 자동 기록
updated_at = models.DateTimeField(auto_now=True) # 수정 시간 자동 기록
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)
def __str__(self):
return f'{self.title} {self.author} {self.created_at}'
def get_absolute_url(self):
return reverse('butler:notice_detail', args=[str(self.pk)])
# <a href="{% url 'butler:notice_detail' record.pk %}">{{ record.title }}</a>

View File

@ -0,0 +1,177 @@
{% extends "components/base.html" %}
{% block title %}IP Management{% endblock %}
{% block main_area %}
<h2 class="fw-bold pt-3 pb-2">IP 관리 대장</h2>
{% if not request.user.is_authenticated %}
<p class="text-danger">비로그인 익명사용자로 접근 중입니다.
<br>로그인시 로그인 사용자가 등록한 데이터만 조회됩니다.</p>
{% endif %}
<!-- IP 레코드 목록 -->
<form id="recordForm" method="post" action="">
{% csrf_token %}
<table class="table table-striped table-hover table-bordered">
<thead class="table-dark">
<tr>
<th scope="col">Select</th>
<th scope="col">Network Name</th>
<th scope="col">IP Address</th>
<th scope="col">Server Name</th>
<th scope="col">Description</th>
<th scope="col">Remark</th>
<th scope="col">Author</th>
<th scope="col">Updated At</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr data-record-id="{{ record.id }}">
<td class="text-center">
<input type="checkbox" name="selected_records" value="{{ record.id }}" class="form-check-input record-checkbox">
</td>
<td>{{ record.network_nm }}</td>
<td>{{ record.ip_addrs }}</td>
<td>{{ record.svr_nm }}</td>
<td>{{ record.desc }}</td>
<td>{{ record.remark }}</td>
<td>{{ record.author }}</td>
<td>{{ record.updated_at|date:"Y-m-d H:i" }}</td>
</tr>
<!-- 수정 모달 -->
<div class="modal fade" id="editDataModal-{{ record.id }}" tabindex="-1" aria-labelledby="editDataModalLabel-{{ record.id }}" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-warning text-dark">
<h5 class="modal-title" id="editDataModalLabel-{{ record.id }}">Edit IP Record</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- 데이터 수정 폼 -->
<form id="editDataForm-{{ record.id }}" method="post" action="{% url 'butler:ip_mgmt_edit' record.id %}">
{% csrf_token %}
<div class="mb-3">
<label for="networkNm-{{ record.id }}" class="form-label">Network Name</label>
<input type="text" class="form-control" id="networkNm-{{ record.id }}" name="network_nm" value="{{ record.network_nm }}" required="required">
</div>
<div class="mb-3">
<label for="ipAddrs-{{ record.id }}" class="form-label">IP Address</label>
<input type="text" class="form-control" id="ipAddrs-{{ record.id }}" name="ip_addrs" value="{{ record.ip_addrs }}" required="required">
</div>
<div class="mb-3">
<label for="svrNm-{{ record.id }}" class="form-label">Server Name</label>
<input type="text" class="form-control" id="svrNm-{{ record.id }}" name="svr_nm" value="{{ record.svr_nm }}" required="required">
</div>
<div class="mb-3">
<label for="desc-{{ record.id }}" class="form-label">Description</label>
<textarea class="form-control" id="desc-{{ record.id }}" name="desc">{{ record.desc }}</textarea>
</div>
<div class="mb-3">
<label for="remark-{{ record.id }}" class="form-label">Remark</label>
<textarea class="form-control" id="remark-{{ record.id }}" name="remark">{{ record.remark }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Save Changes</button>
</form>
</div>
</div>
</div>
</div>
{% empty %}
<tr>
<td colspan="9" class="text-center">No records found.</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if request.user.is_authenticated %}
<!-- 버튼 컨테이너 -->
<div class="d-flex justify-content-between align-items-center mb-3">
<!-- 데이터 등록 버튼 -->
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addDataModal">
<i class="bi bi-plus-lg"></i>
Add New IP Record
</button>
<!-- 전체 삭제 버튼 -->
<button type="submit" formaction="{% url 'butler:ip_mgmt_delete' %}" class="btn btn-danger">
<i class="bi bi-trash"></i>
Delete Selected
</button>
<!-- 수정 버튼 -->
<button type="button" id="editSelectedButton" class="btn btn-warning" disabled="disabled">
<i class="bi bi-pencil-square"></i>
Edit Selected
</button>
</div>
{% endif %}
</form>
<!-- 데이터 등록 모달 -->
<div class="modal fade" id="addDataModal" tabindex="-1" aria-labelledby="addDataModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-success text-white">
<h5 class="modal-title" id="addDataModalLabel">Add New IP Record</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- 데이터 등록 폼 -->
<form id="addDataForm" method="post" action="{% url 'butler:ip_mgmt_add' %}">
{% csrf_token %}
<div class="mb-3">
<label for="networkNm" class="form-label">Network Name</label>
<input type="text" class="form-control" id="networkNm" name="network_nm" required="required">
</div>
<div class="mb-3">
<label for="ipAddrs" class="form-label">IP Address</label>
<input type="text" class="form-control" id="ipAddrs" name="ip_addrs" required="required">
</div>
<div class="mb-3">
<label for="svrNm" class="form-label">Server Name</label>
<input type="text" class="form-control" id="svrNm" name="svr_nm" required="required">
</div>
<div class="mb-3">
<label for="desc" class="form-label">Description</label>
<textarea class="form-control" id="desc" name="desc"></textarea>
</div>
<div class="mb-3">
<label for="remark" class="form-label">Remark</label>
<textarea class="form-control" id="remark" name="remark"></textarea>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const editButton = document.getElementById('editSelectedButton');
const checkboxes = document.querySelectorAll('.record-checkbox');
// Event listener to enable/disable the edit button
document.addEventListener('change', function () {
const selected = [...checkboxes].filter(checkbox => checkbox.checked);
if (selected.length === 1) {
editButton.disabled = false;
editButton.dataset.recordId = selected[0].value;
} else {
editButton.disabled = true;
delete editButton.dataset.recordId;
}
});
// Event listener to open the edit modal
editButton.addEventListener('click', function () {
const recordId = editButton.dataset.recordId;
if (recordId) {
const modal = new bootstrap.Modal(document.getElementById(`editDataModal-${recordId}`));
modal.show();
}
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends "components/base.html" %}
{% block title %}Landing Page{% endblock %}
{% block main_area %}
<article class="pt-3">
<h2 class="fw-bold pb-2">Welcome!</h2>
<h3>IT Infra 및 DevOps 자원관리 도구</h3>
* IP리스트 관리기능 제공 <br>
* Public NHN Cloud API 기능 일부 제공
<p>계속 기능 구현 중 입니다.</p>
</article>
{% endblock %}

View File

@ -0,0 +1,35 @@
{% extends "components/base.html" %}
{% block title %}Notice{% endblock %}
{% block main_area %}
<h2 class="fw-bold pt-3 pb-2">공지사항</h2>
<!-- 공지사항 목록 -->
{% if records %}
<table class="table table-bordered table-hover">
<thead class="table-dark">
<tr>
<th scope="col">글번호</th>
<th scope="col">제목</th>
<th scope="col">작성자</th>
<th scope="col">작성일</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr>
<td>{{ record.pk }}</td>
<td>
<a href="{{ record.get_absolute_url }}">{{ record.title }}</a>
</td>
<td>{{ record.author }}</td>
<td>{{ record.created_at|date:"Y-m-d H:i" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="text-muted">현재 공지사항이 없습니다.</p>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends "components/base.html" %}
{% block title %}Notice Detail{% endblock %}
{% block main_area %}
<h2 class="fw-bold pt-3 pb-2">{{ notice.title }}</h2>
<hr>
<div>
{{ notice.contents|linebreaks }}
</div>
<hr>
<p class="text-muted">작성자:
{{ notice.author }}</p>
<p class="text-muted">작성일:
{{ notice.created_at|date:"Y-m-d H:i" }}</p>
<p class="text-muted">수정일:
{{ notice.updated_at|date:"Y-m-d H:i" }}</p>
<a href="{% url 'butler:notice' %}" class="btn btn-secondary mt-3">Back to Notices</a>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends "components/base.html" %}
{% block title %}Privacy Policy{% endblock %}
{% block main_area %}
<article class="pt-3">
<h1>Privacy Policy</h1>
<div class="content">
{{ content|safe }}
</div>
</article>
{% endblock %}

View File

@ -0,0 +1,99 @@
{% extends "components/base.html" %}
{% block title %}Landing Page{% endblock %}
{% block main_area %}
<article class="my-3" id="typography">
<div class="bd-heading sticky-xl-top align-self-start mt-5 mb-3 mt-xl-0 mb-xl-2">
<h3>Typography</h3>
<a class="d-flex align-items-center" href="/docs/5.1/content/typography/">Documentation</a>
</div>
<div>
<div class="bd-example">
<p class="display-1">Display 1</p>
<p class="display-2">Display 2</p>
<p class="display-3">Display 3</p>
<p class="display-4">Display 4</p>
<p class="display-5">Display 5</p>
<p class="display-6">Display 6</p>
</div>
<div class="bd-example">
<p class="h1">Heading 1</p>
<p class="h2">Heading 2</p>
<p class="h3">Heading 3</p>
<p class="h4">Heading 4</p>
<p class="h5">Heading 5</p>
<p class="h6">Heading 6</p>
</div>
<div class="bd-example">
<p class="lead">
This is a lead paragraph. It stands out from regular paragraphs.
</p>
</div>
<div class="bd-example">
<p>You can use the mark tag to
<mark>highlight</mark>
text.</p>
<p>
<del>This line of text is meant to be treated as deleted text.</del>
</p>
<p>
<s>This line of text is meant to be treated as no longer accurate.</s>
</p>
<p>
<ins>This line of text is meant to be treated as an addition to the document.</ins>
</p>
<p>
<u>This line of text will render as underlined.</u>
</p>
<p>
<small>This line of text is meant to be treated as fine print.</small>
</p>
<p>
<strong>This line rendered as bold text.</strong>
</p>
<p>
<em>This line rendered as italicized text.</em>
</p>
</div>
<div class="bd-example">
<blockquote class="blockquote">
<p>A well-known quote, contained in a blockquote element.</p>
<footer class="blockquote-footer">Someone famous in
<cite title="Source Title">Source Title</cite>
</footer>
</blockquote>
</div>
<div class="bd-example">
<ul class="list-unstyled">
<li>This is a list.</li>
<li>It appears completely unstyled.</li>
<li>Structurally, it's still a list.</li>
<li>However, this style only applies to immediate child elements.</li>
<li>Nested lists:
<ul>
<li>are unaffected by this style</li>
<li>will still show a bullet</li>
<li>and have appropriate left margin</li>
</ul>
</li>
<li>This may still come in handy in some situations.</li>
</ul>
</div>
<div class="bd-example">
<ul class="list-inline">
<li class="list-inline-item">This is a list item.</li>
<li class="list-inline-item">And another one.</li>
<li class="list-inline-item">But they're displayed inline.</li>
</ul>
</div>
</div>
</article>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends "components/base.html" %}
{% block title %}Landing Page{% endblock %}
{% block main_area %}
<article class="pt-3">
<h2 class="fw-bold pb-2">test</h2>
<!-- Grafana IFrame -->
<div class="ratio ratio-16x9">
<iframe
src="https://grafana.nativedeck.com/d/PwMJtdvnz/1-k8s-for-prometheus-dashboard-20211010-en?orgId=2&from=1731468637504&to=1731470437504&kiosk"
width="100%"
height="800"
frameborder="0"
allowfullscreen
></iframe>
</div>
</article>
{% endblock %}

3
butler/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

16
butler/urls.py Normal file
View File

@ -0,0 +1,16 @@
from django.urls import path
from . import views
app_name = 'butler'
urlpatterns = [
path('', views.hello_view, name='landing'), # 루트 경로에서 hello_view 호출
path('notice', views.notice_view, name='notice'),
path('notice/<int:pk>/', views.notice_detail_view, name='notice_detail'),
path('ip_mgmt', views.ip_mgmt_view, name='ip_mgmt'),
path('ip-mgmt/add/', views.add_ip_record, name='ip_mgmt_add'), # 데이터 추가 경로
path('ip-mgmt/delete/', views.delete_ip_records, name='ip_mgmt_delete'), # 삭제 URL 추가
path('ip-mgmt/edit/<int:pk>/', views.edit_ip_record, name='ip_mgmt_edit'), # 수정 URL 추가
path('privacy/', views.privacy_view, name='privacy'),
path('test/', views.test_view, name='test'),
]

118
butler/views.py Normal file
View File

@ -0,0 +1,118 @@
from django.shortcuts import render, redirect, get_object_or_404
from pathlib import Path
import markdown
import os
from .models import NoticeBoard, IPManagementRecord
def hello_view(request):
return render(
request,
"butler/landing.html",
)
# --- notice ---
def notice_view(request):
records = NoticeBoard.objects.all()
return render(request, "butler/notice.html", {"records": records})
def notice_detail_view(request, pk):
try:
notice = NoticeBoard.objects.get(pk=pk)
except NoticeBoard.DoesNotExist:
return render(request, "404.html") # 선택적으로 에러 처리
return render(request, "butler/notice_detail.html", {"notice": notice})
# --- ip management ---
def ip_mgmt_view(request):
# records = IPManagementRecord.objects.all()
if request.user.is_authenticated:
records = IPManagementRecord.objects.filter(author=request.user).order_by(
"ip_addrs"
)
else:
# records = IPManagementRecord.objects.none()
records = IPManagementRecord.objects.all().order_by("ip_addrs")
return render(request, "butler/ip_mgmt.html", {"records": records})
def add_ip_record(request):
if request.method == "POST":
network_nm = request.POST.get("network_nm")
ip_addrs = request.POST.get("ip_addrs")
svr_nm = request.POST.get("svr_nm")
desc = request.POST.get("desc")
remark = request.POST.get("remark")
# 작성자 (author)는 로그인된 사용자로 설정
author = request.user
# 데이터 저장
IPManagementRecord.objects.create(
network_nm=network_nm,
ip_addrs=ip_addrs,
svr_nm=svr_nm,
desc=desc,
remark=remark,
author=author,
)
return redirect("/ip_mgmt")
def delete_ip_records(request):
print(f"삭제동작")
if request.method == "POST":
selected_ids = request.POST.getlist("selected_records")
if selected_ids:
IPManagementRecord.objects.filter(id__in=selected_ids).delete()
return redirect("/ip_mgmt")
def edit_ip_record(request, pk):
print(f"수정동작")
record = get_object_or_404(IPManagementRecord, pk=pk)
if request.method == "POST":
# 디버깅 메시지 추가
# print(f"체크: {request.POST}")
record.network_nm = request.POST.get("network_nm")
record.ip_addrs = request.POST.get("ip_addrs")
record.svr_nm = request.POST.get("svr_nm")
record.desc = request.POST.get("desc")
record.remark = request.POST.get("remark")
record.save()
return redirect("/ip_mgmt")
return render(request, "butler/ip_mgmt.html", {"record": record})
# --- privacy
def privacy_view(request):
# 'docs/privacy.md' 파일을 읽기
file_path = os.path.join("docs", "docs_md_files/privacy.md")
with open(file_path, "r", encoding="utf-8") as file:
text = file.read()
file_path = Path('docs/docs_md_files/privacy.md')
with file_path.open('r', encoding='utf-8') as file:
text = file.read()
# Markdown을 HTML로 변환
# html_content = markdown.markdown(text)
# Markdown을 HTML로 변환, tables 확장 활성화
html_content = markdown.markdown(
text,
extensions=[
"tables", # 테이블 지원 확장
],
)
# 변환된 HTML을 템플릿에 전달
return render(request, "butler/privacy.html", {"content": html_content})
def test_view(request):
return render(
request,
"butler/test.html",
)