first commit
This commit is contained in:
0
app/assets/builds/.keep
Normal file
0
app/assets/builds/.keep
Normal file
0
app/assets/images/.keep
Normal file
0
app/assets/images/.keep
Normal file
184
app/assets/stylesheets/application.tailwind.css
Normal file
184
app/assets/stylesheets/application.tailwind.css
Normal file
@@ -0,0 +1,184 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--color-default: rgb(226, 232, 240);
|
||||
--color-defaultSlate: rgb(100, 116, 139);
|
||||
--color-defaultSlateDark: rgb(71, 85, 105);
|
||||
--color-primary: rgb(66, 165, 245);
|
||||
--color-notice: rgb(19, 198, 154);
|
||||
--color-warning: rgb(255, 202, 40);
|
||||
--color-accept: rgb(121, 134, 203);
|
||||
--color-danger: rgb(239, 83, 80);
|
||||
--color-tableBorder: rgb(203, 213, 225);
|
||||
|
||||
--base-font-size: 0.85rem;
|
||||
--text-xs: calc(var(--base-font-size) * 0.75);
|
||||
--text-xs--line-height: calc(1 / var(--text-xs));
|
||||
--text-sm: calc(var(--base-font-size) * 0.875);
|
||||
--text-sm--line-height: calc(1.25 / var(--text-sm));
|
||||
--text-base: calc(var(--base-font-size) * 1);
|
||||
--text-base--line-height: calc(1.5 / var(--text-base));
|
||||
--text-lg: calc(var(--base-font-size) * 1.125);
|
||||
--text-lg--line-height: calc(1.75 / var(--text-lg));
|
||||
--text-xl: calc(var(--base-font-size) * 1.25);
|
||||
--text-xl--line-height: calc(1.75 / var(--text-xl));
|
||||
--text-2xl: calc(var(--base-font-size) * 1.5);
|
||||
--text-2xl--line-height: calc(2 / var(--text-2xl));
|
||||
--text-3xl: calc(var(--base-font-size) * 1.875);
|
||||
--text-3xl--line-height: calc(2.25 / var(--text-3xl));
|
||||
--text-4xl: calc(var(--base-font-size) * 2.25);
|
||||
--text-4xl--line-height: calc(2.5 / var(--text-4xl));
|
||||
--text-5xl: calc(var(--base-font-size) * 3);
|
||||
--text-5xl--line-height: 1;
|
||||
--text-6xl: calc(var(--base-font-size) * 3.75);
|
||||
--text-6xl--line-height: 1;
|
||||
--text-7xl: calc(var(--base-font-size) * 4.5);
|
||||
--text-7xl--line-height: 1;
|
||||
--text-8xl: calc(var(--base-font-size) * 6);
|
||||
--text-8xl--line-height: 1;
|
||||
--text-9xl: calc(var(--base-font-size) * 8);
|
||||
--text-9xl--line-height: 1;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply text-sm text-defaultSlateDark
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply border-tableBorder
|
||||
}
|
||||
|
||||
a.disabled {
|
||||
@apply pointer-events-none cursor-not-allowed opacity-50
|
||||
}
|
||||
|
||||
.disabled {
|
||||
@apply pointer-events-none cursor-not-allowed opacity-50
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply justify-center rounded-xs bg-defaultSlate px-2 py-0.5 text-sm font-semibold leading-6 text-white
|
||||
shadow-xs hover:opacity-80 cursor-pointer
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white border border-tableBorder rounded-xs shadow-xs
|
||||
}
|
||||
|
||||
/*Form*/
|
||||
.label-style {
|
||||
@apply block my-1 font-bold text-defaultSlateDark
|
||||
}
|
||||
|
||||
.select-style {
|
||||
@apply block w-full h-8 px-3 py-1.5 border border-defaultSlate/50 rounded-xs
|
||||
}
|
||||
|
||||
.input-style {
|
||||
@apply block w-full h-8 px-3 py-1.5 border border-defaultSlate/50 rounded-xs
|
||||
}
|
||||
|
||||
.textarea-style {
|
||||
@apply block w-full pl-4 flex-1 py-1.5 ring-1 ring-inset ring-defaultSlate/50 rounded-xs
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.page-title {
|
||||
@apply text-xl font-bold;
|
||||
}
|
||||
|
||||
.search-title {
|
||||
@apply text-base font-bold;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
@apply text-center text-3xl font-bold tracking-tight text-defaultSlateDark
|
||||
}
|
||||
|
||||
.field {
|
||||
@apply w-full
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
@apply inline-flex items-center rounded-xs px-2.5 py-1.5 text-sm font-semibold hover:opacity-75 disabled:cursor-not-allowed disabled:opacity-30 disabled:hover:bg-white
|
||||
}
|
||||
|
||||
/*Menu*/
|
||||
.menu-group {
|
||||
@apply py-2 px-4
|
||||
}
|
||||
|
||||
.menu-group-title {
|
||||
@apply py-1 text-sm text-defaultSlateDark font-medium
|
||||
}
|
||||
|
||||
.menu-group-items {
|
||||
@apply p-1
|
||||
}
|
||||
|
||||
.menu-group-items-title {
|
||||
@apply text-base font-bold
|
||||
}
|
||||
|
||||
.copyright {
|
||||
@apply text-defaultSlateDark text-center
|
||||
}
|
||||
|
||||
/*Pagy*/
|
||||
.pagy {
|
||||
@apply flex items-center justify-center space-x-1 font-semibold text-sm text-defaultSlate;
|
||||
}
|
||||
|
||||
.pagy a:not(.gap) {
|
||||
@apply block rounded-xs px-2 py-1 border border-defaultSlate/50;
|
||||
}
|
||||
|
||||
.pagy a:not(.gap):hover {
|
||||
@apply bg-slate-300
|
||||
}
|
||||
|
||||
.pagy a:not(.gap):not([href]) {
|
||||
@apply text-defaultSlate/50 border border-defaultSlate/25 cursor-default
|
||||
}
|
||||
|
||||
.pagy a:not(.gap).current {
|
||||
@apply text-white bg-defaultSlate
|
||||
}
|
||||
|
||||
.pagy a.gap {
|
||||
@apply inline-block my-auto
|
||||
}
|
||||
|
||||
.pagy label {
|
||||
@apply inline-block whitespace-nowrap bg-default rounded-xs px-3 py-0.5
|
||||
}
|
||||
|
||||
.pagy label input {
|
||||
@apply bg-default border-none rounded-xs
|
||||
}
|
||||
|
||||
/*Base Table*/
|
||||
.base-table {
|
||||
@apply min-w-full divide-y divide-tableBorder whitespace-nowrap
|
||||
}
|
||||
|
||||
.base-table th {
|
||||
@apply bg-default px-2 py-2 text-left font-semibold text-defaultSlateDark lg:table-cell
|
||||
}
|
||||
|
||||
.base-table td {
|
||||
@apply border-t border-tableBorder px-2 py-1 text-defaultSlate lg:table-cell whitespace-nowrap
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.page-title {
|
||||
@apply text-3xl font-bold;
|
||||
}
|
||||
|
||||
.search-title {
|
||||
@apply text-lg font-bold;
|
||||
}
|
||||
}
|
||||
4
app/controllers/application_controller.rb
Normal file
4
app/controllers/application_controller.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
# Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has.
|
||||
allow_browser versions: :modern
|
||||
end
|
||||
0
app/controllers/concerns/.keep
Normal file
0
app/controllers/concerns/.keep
Normal file
26
app/controllers/modbus_controller.rb
Normal file
26
app/controllers/modbus_controller.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
class ModbusController < ApplicationController
|
||||
def index
|
||||
@schedule = Schedule.all
|
||||
end
|
||||
|
||||
def schedule_edit
|
||||
@schedule = Schedule.all
|
||||
end
|
||||
|
||||
def schedule_edit_update
|
||||
error_hours = []
|
||||
|
||||
params[:schedule].each do |id, attributes|
|
||||
schedule = Schedule.find_by(id: id)
|
||||
unless schedule.update(temperature: attributes[:temperature])
|
||||
error_hours << "#{schedule.hour}시"
|
||||
end
|
||||
end
|
||||
|
||||
if error_hours.any?
|
||||
redirect_to modbus_index_path, alert: "#{error_hours.join(', ')}의 온도 업데이트에 실패하였습니다."
|
||||
else
|
||||
redirect_to modbus_index_path, notice: "스케줄이 성공적으로 업데이트되었습니다."
|
||||
end
|
||||
end
|
||||
end
|
||||
2
app/helpers/application_helper.rb
Normal file
2
app/helpers/application_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module ApplicationHelper
|
||||
end
|
||||
2
app/helpers/modbus_helper.rb
Normal file
2
app/helpers/modbus_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
||||
module ModbusHelper
|
||||
end
|
||||
3
app/javascript/application.js
Normal file
3
app/javascript/application.js
Normal file
@@ -0,0 +1,3 @@
|
||||
// Entry point for the build script in your package.json
|
||||
import "@hotwired/turbo-rails"
|
||||
import "./controllers"
|
||||
9
app/javascript/controllers/application.js
Normal file
9
app/javascript/controllers/application.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Application } from "@hotwired/stimulus"
|
||||
|
||||
const application = Application.start()
|
||||
|
||||
// Configure Stimulus development experience
|
||||
application.debug = false
|
||||
window.Stimulus = application
|
||||
|
||||
export { application }
|
||||
7
app/javascript/controllers/hello_controller.js
Normal file
7
app/javascript/controllers/hello_controller.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
this.element.textContent = "Hello World!"
|
||||
}
|
||||
}
|
||||
8
app/javascript/controllers/index.js
Normal file
8
app/javascript/controllers/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// This file is auto-generated by ./bin/rails stimulus:manifest:update
|
||||
// Run that command whenever you add a new controller or create them with
|
||||
// ./bin/rails generate stimulus controllerName
|
||||
|
||||
import { application } from "./application"
|
||||
|
||||
import HelloController from "./hello_controller"
|
||||
application.register("hello", HelloController)
|
||||
7
app/jobs/application_job.rb
Normal file
7
app/jobs/application_job.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class ApplicationJob < ActiveJob::Base
|
||||
# Automatically retry jobs that encountered a deadlock
|
||||
# retry_on ActiveRecord::Deadlocked
|
||||
|
||||
# Most jobs are safe to ignore if the underlying records are no longer available
|
||||
# discard_on ActiveJob::DeserializationError
|
||||
end
|
||||
4
app/mailers/application_mailer.rb
Normal file
4
app/mailers/application_mailer.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: "from@example.com"
|
||||
layout "mailer"
|
||||
end
|
||||
3
app/models/application_record.rb
Normal file
3
app/models/application_record.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
primary_abstract_class
|
||||
end
|
||||
0
app/models/concerns/.keep
Normal file
0
app/models/concerns/.keep
Normal file
6
app/models/schedule.rb
Normal file
6
app/models/schedule.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class Schedule < ApplicationRecord
|
||||
validates :temperature,
|
||||
presence: true,
|
||||
numericality: true,
|
||||
format: { with: /\A\d+(\.\d)?\z/ }
|
||||
end
|
||||
8
app/services/modbus/commands/bit_coil.rb
Normal file
8
app/services/modbus/commands/bit_coil.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "bit_coil", start_address: 0, end_address: 15).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def bit_coil(slave)
|
||||
read_bit(slave, :read_coils)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/bit_input.rb
Normal file
8
app/services/modbus/commands/bit_input.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "bit_input", start_address: 0, end_address: 15).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def bit_input(slave)
|
||||
read_bit(slave, :discrete_inputs)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/word_holding_float32.rb
Normal file
8
app/services/modbus/commands/word_holding_float32.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "word_holding_float32", start_address: 0, end_address: 15).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def word_holding_float32(slave)
|
||||
read_float(slave, :holding_registers)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/word_holding_int16.rb
Normal file
8
app/services/modbus/commands/word_holding_int16.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "word_holding_int16", start_address: 16, end_address: 31).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def word_holding_int16(slave)
|
||||
read_int(slave, 16, :holding_registers)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/word_input_float32.rb
Normal file
8
app/services/modbus/commands/word_input_float32.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "word_input_float32", start_address: 0, end_address: 15).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def word_input_float32(slave)
|
||||
read_float(slave, :input_registers)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/word_input_int16.rb
Normal file
8
app/services/modbus/commands/word_input_int16.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "word_input_int16", start_address: 0, end_address: 15).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def word_input_int16(slave)
|
||||
read_int(slave, 16, :input_registers)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/write_bit.rb
Normal file
8
app/services/modbus/commands/write_bit.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "write_bit", start_address: 0, value: 1).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def write_bit(slave)
|
||||
write_coil(slave)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/write_bit_toggle.rb
Normal file
8
app/services/modbus/commands/write_bit_toggle.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "write_bit_toggle", start_address: 0).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def write_bit_toggle(slave)
|
||||
write_toggle_coil(slave)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/write_float32.rb
Normal file
8
app/services/modbus/commands/write_float32.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "float32", start_address: 0, value: 123.45).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def write_float32(slave)
|
||||
write_float(slave)
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/services/modbus/commands/write_int16.rb
Normal file
8
app/services/modbus/commands/write_int16.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# Modbus::Service.new(plc_id: 1, type: "int16", start_address: 0, value: 123).execute
|
||||
module Modbus
|
||||
module Commands
|
||||
def write_int16(slave)
|
||||
write_int(slave, 16)
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/services/modbus/operations.rb
Normal file
93
app/services/modbus/operations.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# app/lib/modbus/operations.rb
|
||||
module Modbus
|
||||
module Operations
|
||||
# write method
|
||||
def write_toggle_coil(slave)
|
||||
with_timeout do
|
||||
target_value = slave.read_coils(@start_address, 1).first == 0 ? 1 : 0
|
||||
slave.coils[@start_address] = (target_value == 1)
|
||||
loop do
|
||||
break if slave.read_coils(@start_address, 1).first == target_value
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_coil(slave)
|
||||
with_timeout do
|
||||
slave.coils[@start_address] = @value
|
||||
loop do
|
||||
break if slave.read_coils(@start_address, 1).first == @value
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_int(slave, bits)
|
||||
with_timeout do
|
||||
regs = slave.holding_registers
|
||||
if bits == 16
|
||||
value = @value.present? ? @value.to_i : 123
|
||||
regs[@start_address] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_float(slave)
|
||||
with_timeout do
|
||||
regs = slave.holding_registers
|
||||
value = @value.present? ? @value.to_f : 123.45
|
||||
regs[@start_address], regs[@start_address + 1] = [ value ].pack("f").unpack("v*")
|
||||
end
|
||||
end
|
||||
|
||||
# read method
|
||||
def read_bit(slave, type)
|
||||
with_timeout do
|
||||
value = case type
|
||||
when :read_coils
|
||||
slave.read_coils(@start_address, @end_address - @start_address + 1)
|
||||
when :discrete_inputs
|
||||
slave.discrete_inputs[@start_address..@end_address]
|
||||
else
|
||||
# type code here
|
||||
end
|
||||
Rails.logger.info "[#{Time.current}] Read Bit (#{type}): #{value}"
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def read_int(slave, bits, type)
|
||||
with_timeout do
|
||||
result = slave.send(type)
|
||||
values = (@start_address..@end_address).map { |i| result[i] }
|
||||
Rails.logger.info "[#{Time.current}] Read Float#{bits} (#{type}): #{values}"
|
||||
values
|
||||
end
|
||||
end
|
||||
|
||||
def read_float(slave, type)
|
||||
with_timeout do
|
||||
result = slave.send(type)
|
||||
values = []
|
||||
(@start_address..@end_address).step(2) do |i|
|
||||
raw = [ result[i], result[i + 1] ]
|
||||
packed = raw.pack("v*")
|
||||
float = packed.unpack("f")[0].round(2)
|
||||
values << float
|
||||
end
|
||||
Rails.logger.info "[#{Time.current}] Read Float32 (#{type}): #{values.inspect}"
|
||||
values
|
||||
end
|
||||
end
|
||||
|
||||
def with_timeout
|
||||
seconds = ENV.fetch("OPERATION_TIME_OUT", 1).to_i
|
||||
Timeout.timeout(seconds) do
|
||||
yield
|
||||
end
|
||||
rescue Timeout::Error
|
||||
Rails.logger.error "[#{Time.current}] TimeOut (#{seconds}s)"
|
||||
end
|
||||
end
|
||||
end
|
||||
46
app/services/modbus/polling_service.rb
Normal file
46
app/services/modbus/polling_service.rb
Normal file
@@ -0,0 +1,46 @@
|
||||
module Modbus
|
||||
class PollingService
|
||||
class << self
|
||||
def start
|
||||
return if @thread&.alive?
|
||||
|
||||
@thread = Thread.new do
|
||||
puts "[#{Time.current}] Modbus polling service 시작됨"
|
||||
last_logged_hour = nil
|
||||
loop do
|
||||
begin
|
||||
now = Time.now
|
||||
current_hour = now.hour
|
||||
|
||||
if current_hour != last_logged_hour && now.min == 0
|
||||
schedule = Schedule.find_by(hour: current_hour)
|
||||
|
||||
Modbus::Service.new(
|
||||
type: "write_int16",
|
||||
start_address: 2,
|
||||
end_address: 2,
|
||||
value: schedule.temperature * 10
|
||||
).execute
|
||||
puts "[Schedule] #{current_hour}:00 -> Target temp: #{schedule.temperature}°C"
|
||||
last_logged_hour = current_hour
|
||||
end
|
||||
rescue StandardError => e
|
||||
error_message = "[#{Time.current}] 오류: #{e.message}"
|
||||
puts error_message
|
||||
ensure
|
||||
sleep 0.1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
if @thread
|
||||
@thread.exit
|
||||
@thread = nil
|
||||
puts "[#{Time.now}] Modbus polling service 중지됨"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/services/modbus/service.rb
Normal file
38
app/services/modbus/service.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
require_relative "operations"
|
||||
|
||||
Dir[File.join(__dir__.to_s, "commands", "**", "*.rb")].each do |file|
|
||||
load file
|
||||
end
|
||||
|
||||
class Modbus::Service
|
||||
include Modbus::Operations
|
||||
include Modbus::Commands
|
||||
|
||||
def initialize(type:, start_address:, end_address: nil, value: nil)
|
||||
@type = type
|
||||
@start_address = start_address.to_i
|
||||
@end_address = end_address&.to_i
|
||||
@value = value
|
||||
@slave_id = 1
|
||||
@plc_host = "rubyon.co.kr"
|
||||
@plc_port = 502
|
||||
end
|
||||
|
||||
def execute
|
||||
ModBus::TCPClient.new(@plc_host, @plc_port) do |client|
|
||||
client.with_slave(@slave_id) do |slave|
|
||||
method_name = @type.strip
|
||||
# Commands 모듈에 정의된 메서드만 허용
|
||||
if Modbus::Commands.instance_methods(false).include?(method_name.to_sym)
|
||||
return send(method_name, slave)
|
||||
else
|
||||
available_commands = Modbus::Commands.instance_methods(false).sort.map(&:to_s)
|
||||
command_list = available_commands.join("\r\n")
|
||||
raise ArgumentError, "지원되지 않는 type: #{@type}\r\n사용 가능한 명령어 목록:\r\n#{command_list}"
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "[#{Time.current}] Service 오류: #{e.message}"
|
||||
end
|
||||
end
|
||||
30
app/views/layouts/application.html.erb
Normal file
30
app/views/layouts/application.html.erb
Normal file
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title><%= content_for(:title) || "Farmitry Hvac" %></title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<%= csrf_meta_tags %>
|
||||
<%= csp_meta_tag %>
|
||||
|
||||
<%= yield :head %>
|
||||
|
||||
<%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
|
||||
<%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>
|
||||
|
||||
<link rel="icon" href="/icon.png" type="image/png">
|
||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/icon.png">
|
||||
|
||||
<%# Includes all stylesheet files in app/assets/stylesheets %>
|
||||
<%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
|
||||
<%= javascript_include_tag "application", "data-turbo-track": "reload", type: "module" %>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="m-4">
|
||||
<%= yield %>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
13
app/views/layouts/mailer.html.erb
Normal file
13
app/views/layouts/mailer.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<style>
|
||||
/* Email styles need to be inline */
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<%= yield %>
|
||||
</body>
|
||||
</html>
|
||||
1
app/views/layouts/mailer.text.erb
Normal file
1
app/views/layouts/mailer.text.erb
Normal file
@@ -0,0 +1 @@
|
||||
<%= yield %>
|
||||
18
app/views/modbus/index.html.erb
Normal file
18
app/views/modbus/index.html.erb
Normal file
@@ -0,0 +1,18 @@
|
||||
<% if flash[:notice] %>
|
||||
<div class="mb-4 rounded bg-green-100 px-4 py-2 text-green-800 border border-green-300">
|
||||
<%= flash[:notice] %>
|
||||
</div>
|
||||
<% elsif flash[:alert] %>
|
||||
<div class="mb-4 rounded bg-red-100 px-4 py-2 text-red-800 border border-red-300">
|
||||
<%= flash[:alert] %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% @schedule.each do |s| %>
|
||||
<div class="flex justify-items-center gap-x-2">
|
||||
<div><%= s.hour %>시</div>
|
||||
<div><%= s.temperature %> °C</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= link_to "수정", "/modbus/schedule_edit", class: "btn btn-primary" %>
|
||||
14
app/views/modbus/schedule_edit.html.erb
Normal file
14
app/views/modbus/schedule_edit.html.erb
Normal file
@@ -0,0 +1,14 @@
|
||||
<%= form_with url: "/modbus/schedule_edit_update", method: :post do %>
|
||||
<% @schedule.each do |s| %>
|
||||
<div class="flex justify-items-center gap-x-2 pb-2">
|
||||
<div><%= s.hour %>시</div>
|
||||
<div>
|
||||
<%= text_field_tag "schedule[#{s.id}][temperature]", s.temperature, class: "border px-2 py-1 rounded" %> °C
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pt-4">
|
||||
<%= submit_tag "업데이트", class: "bg-blue-500 text-white px-4 py-2 rounded" %>
|
||||
</div>
|
||||
<% end %>
|
||||
22
app/views/pwa/manifest.json.erb
Normal file
22
app/views/pwa/manifest.json.erb
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "FarmitryHvac",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
},
|
||||
{
|
||||
"src": "/icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"description": "FarmitryHvac.",
|
||||
"theme_color": "red",
|
||||
"background_color": "red"
|
||||
}
|
||||
26
app/views/pwa/service-worker.js
Normal file
26
app/views/pwa/service-worker.js
Normal file
@@ -0,0 +1,26 @@
|
||||
// Add a service worker for processing Web Push notifications:
|
||||
//
|
||||
// self.addEventListener("push", async (event) => {
|
||||
// const { title, options } = await event.data.json()
|
||||
// event.waitUntil(self.registration.showNotification(title, options))
|
||||
// })
|
||||
//
|
||||
// self.addEventListener("notificationclick", function(event) {
|
||||
// event.notification.close()
|
||||
// event.waitUntil(
|
||||
// clients.matchAll({ type: "window" }).then((clientList) => {
|
||||
// for (let i = 0; i < clientList.length; i++) {
|
||||
// let client = clientList[i]
|
||||
// let clientPath = (new URL(client.url)).pathname
|
||||
//
|
||||
// if (clientPath == event.notification.data.path && "focus" in client) {
|
||||
// return client.focus()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (clients.openWindow) {
|
||||
// return clients.openWindow(event.notification.data.path)
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// })
|
||||
Reference in New Issue
Block a user